Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/app/root/e2e/helpers/sessionHelpers/seeders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ import {
} from "@src/store/session/planApprovalAtom";
import {
type Session,
registerCreatedSession,
sessionsAtom,
upsertSession,
} from "@src/store/session/sessionAtom";
import { updateShellProcessAtom } from "@src/store/session/shellProcessAtom";
import { updateSubagentJobAtom } from "@src/store/session/subagentJobAtom";
Expand Down Expand Up @@ -187,7 +187,7 @@ export function createSessionSeederHelpers(store: E2EStore) {
category: existing?.category ?? "rust_agent",
is_active: true,
};
upsertSession(session);
registerCreatedSession(session);
return { ok: true, sessionId: input.sessionId };
} catch (err) {
return asError(err);
Expand Down Expand Up @@ -235,7 +235,7 @@ export function createSessionSeederHelpers(store: E2EStore) {
model: "composer-2",
is_active: true,
};
upsertSession(session);
registerCreatedSession(session);
store.set(stationModeAtom, "my-station");
store.set(chatPanelMaximizedAtom, true);
store.set(chatWidthAtom, 560);
Expand Down
13 changes: 11 additions & 2 deletions src/app/root/e2e/helpers/sessions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,10 @@ import {
} from "@src/store/session/planApprovalAtom";
import { sessionsAtom } from "@src/store/session/sessionAtom/atoms";
import { loadSessions } from "@src/store/session/sessionAtom/loaders";
import { upsertSession } from "@src/store/session/sessionAtom/mutations";
import {
registerCreatedSession,
upsertSession,
} from "@src/store/session/sessionAtom/mutations";
import { sessionPaginationAtom } from "@src/store/session/sessionAtom/paginationAtoms";
import type { Session } from "@src/store/session/sessionAtom/types";
import {
Expand Down Expand Up @@ -417,6 +420,12 @@ export function createSessionHelpers(store: E2EStore) {
? result.session_id
: null;
if (sessionId) {
const launchedSession = await rpc.agentSession.getSession({
sessionId,
});
if (launchedSession) {
registerCreatedSession(toStoreSession(launchedSession));
}
store.set(activeSessionIdAtom, sessionId);
store.set(workstationActiveSessionIdAtom, sessionId);
await waitForSessionSurface(sessionId);
Expand Down Expand Up @@ -745,7 +754,7 @@ export function createSessionHelpers(store: E2EStore) {
name: input.name ?? input.userInput ?? input.sessionId,
category: input.category ?? "cli_agent",
});
upsertSession(session);
registerCreatedSession(session);
await saveEvents(
input.sessionId,
input.events as unknown as SessionEvent[]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -522,6 +522,26 @@ describe("launchPayload", () => {
expect(source).toContain("void emitOpenWorkspace(");
expect(source).not.toContain("await emitOpenWorkspace(");
});

it("registers a launched session with the Sidebar roster before navigation", () => {
const launchHookPath = fileURLToPath(
new URL(
"../useSessionCreator/useSessionLaunch/index.tsx",
import.meta.url
)
);
const source = readFileSync(launchHookPath, "utf8");
const registrationIndex = source.indexOf(
"registerCreatedSession(launchedSession);"
);
const navigationIndex = source.indexOf(
"navigateToLaunchedSession(result.sessionId, sessionUsesHostedKey);"
);

expect(registrationIndex).toBeGreaterThan(-1);
expect(navigationIndex).toBeGreaterThan(registrationIndex);
expect(source).not.toContain("syncSidebarSessionRoster(launchedSession)");
});
});

function baseLaunchOptions(): Parameters<typeof buildSessionLaunchPayload>[0] {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,12 @@ import {
activeSessionIdAtom,
dispatchCategoryAtom,
loadSidebarSessions,
registerCreatedSession,
selectedAgentDefinitionIdAtom,
selectedAgentOrgIdAtom,
sessionCreatorDraftAtom,
sessionSourceAtom,
sessionTargetKindAtom,
upsertSession,
workstationActiveSessionIdAtom,
} from "@src/store/session";
import { lastUserMessageAtom } from "@src/store/session/cliSessionStatusAtom";
Expand Down Expand Up @@ -230,16 +230,15 @@ export function useSessionLaunch(
clearImages?.();
}

upsertSession(
buildSessionFromLaunchResult({
agentExecMode,
effectiveSource,
isBackgroundLaunch,
launchCliAgentType: launchParams.platform,
launchOrgContext: resolvedWorkItemContext ?? undefined,
result,
})
);
const launchedSession = buildSessionFromLaunchResult({
agentExecMode,
effectiveSource,
isBackgroundLaunch,
launchCliAgentType: launchParams.platform,
launchOrgContext: resolvedWorkItemContext ?? undefined,
result,
});
registerCreatedSession(launchedSession);
void autoTagLaunchedSessionToActiveCloudOrg({
sessionId: result.sessionId,
repoPath: effectiveSource?.repoPath ?? null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ import type { WorkspaceSnapshot } from "@src/services/context/workspaceSnapshot"
import {
SESSION_TARGET_KIND,
type Session,
type SessionStatus,
type SessionTargetKind,
buildCreatedSessionRecord,
} from "@src/store/session";
import type {
SessionLaunchOrgContext,
Expand Down Expand Up @@ -319,48 +319,15 @@ export function buildSessionFromLaunchResult(options: {
result,
} = options;

return {
session_id: result.sessionId,
status: result.status as SessionStatus,
created_at: result.createdAt,
updated_at: result.createdAt,
user_input: result.userInput || result.name,
repo_name: effectiveSource?.repoName ?? "",
name: result.name,
branch:
result.worktreeBranch || result.branch || effectiveSource?.branch || "",
is_active: !isBackgroundLaunch,
category: result.category as
| typeof DISPATCH_CATEGORY.RUST_AGENT
| typeof DISPATCH_CATEGORY.CLI_AGENT,
model: result.model ?? undefined,
cliAgentType: result.cliAgentType ?? launchCliAgentType ?? undefined,
return buildCreatedSessionRecord({
result,
repoName: effectiveSource?.repoName,
fallbackRepoPath: effectiveSource?.repoPath,
fallbackBranch: effectiveSource?.branch,
fallbackCliAgentType: launchCliAgentType ?? undefined,
isActive: !isBackgroundLaunch,
agentExecMode,
...(result.agentOrgId
? { agentIconId: AGENT_ORG_ICON_ID, agentOrgId: result.agentOrgId }
: {}),
...(result.accountId ? { accountId: result.accountId } : {}),
...((result.orgId ?? launchOrgContext?.orgId)
? { orgId: result.orgId ?? launchOrgContext?.orgId }
: {}),
...((result.projectId ?? launchOrgContext?.projectId)
? { projectId: result.projectId ?? launchOrgContext?.projectId }
: {}),
...((result.projectName ?? launchOrgContext?.projectName)
? { projectName: result.projectName ?? launchOrgContext?.projectName }
: {}),
...((result.projectSlug ?? launchOrgContext?.projectSlug)
? { projectSlug: result.projectSlug ?? launchOrgContext?.projectSlug }
: {}),
...((result.workItemId ?? launchOrgContext?.workItemId)
? { workItemId: result.workItemId ?? launchOrgContext?.workItemId }
: {}),
...((result.agentRole ?? launchOrgContext?.agentRole)
? { agentRole: result.agentRole ?? launchOrgContext?.agentRole }
: {}),
...(result.background ? { background: true } : {}),
...(result.worktreePath ? { worktreePath: result.worktreePath } : {}),
...(result.worktreeBranch ? { worktreeBranch: result.worktreeBranch } : {}),
...(result.workspacePath ? { repoPath: result.workspacePath } : {}),
};
agentIconId: result.agentOrgId ? AGENT_ORG_ICON_ID : undefined,
context: launchOrgContext,
});
}
86 changes: 86 additions & 0 deletions src/engines/SessionCore/services/SessionService.create.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { beforeEach, describe, expect, it, vi } from "vitest";

import type { SessionLaunchResult } from "@src/api/tauri/agent/session";

import { SessionService } from "./SessionService";

const mocks = vi.hoisted(() => ({
sessionLaunch: vi.fn(),
registerCreatedSession: vi.fn(),
}));

vi.mock("@src/api/tauri/agent", async (importOriginal) => ({
...(await importOriginal<typeof import("@src/api/tauri/agent")>()),
sessionLaunch: mocks.sessionLaunch,
}));

vi.mock("@src/store/session", async (importOriginal) => ({
...(await importOriginal<typeof import("@src/store/session")>()),
registerCreatedSession: mocks.registerCreatedSession,
}));

vi.mock("@src/services/context/collectors", () => ({
collectAdeContext: () => undefined,
}));

function launchResult(
overrides: Partial<SessionLaunchResult> = {}
): SessionLaunchResult {
return {
sessionId: "sdeagent-service-created",
category: "rust_agent",
name: "Service-created session",
status: "running",
createdAt: "2026-08-05T12:00:00.000Z",
userInput: "Do the work",
background: false,
...overrides,
};
}

describe("SessionService.create", () => {
beforeEach(() => {
mocks.sessionLaunch.mockReset();
mocks.registerCreatedSession.mockReset();
});

it("registers the created entity and its Sidebar projection before returning", async () => {
mocks.sessionLaunch.mockResolvedValue(
launchResult({
workspacePath: "/workspace/repo",
workItemId: "ORG-42",
})
);

await expect(
SessionService.create({
task: "Do the work",
repoPath: "/workspace/repo",
model: "gpt-5.6",
mode: "build",
agentDefinitionId: "builtin:sde",
workItemId: "ORG-42",
})
).resolves.toEqual({ sessionId: "sdeagent-service-created" });

expect(mocks.registerCreatedSession).toHaveBeenCalledOnce();
expect(mocks.registerCreatedSession).toHaveBeenCalledWith(
expect.objectContaining({
session_id: "sdeagent-service-created",
repoPath: "/workspace/repo",
agentDefinitionId: "builtin:sde",
agentExecMode: "build",
workItemId: "ORG-42",
})
);
});

it("does not register anything when the launch boundary fails", async () => {
mocks.sessionLaunch.mockRejectedValue(new Error("launch failed"));

await expect(
SessionService.create({ task: "Do the work" })
).rejects.toThrow("Failed to create session: launch failed");
expect(mocks.registerCreatedSession).not.toHaveBeenCalled();
});
});
16 changes: 16 additions & 0 deletions src/engines/SessionCore/services/SessionService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,10 @@ import {
type Session,
type SessionStatus,
activeSessionIdAtom,
buildCreatedSessionRecord,
loadSessions,
markSessionActive,
registerCreatedSession,
sessionsAtom,
workstationActiveSessionIdAtom,
} from "@src/store/session";
Expand Down Expand Up @@ -185,6 +187,20 @@ export const SessionService = {
const result = await sessionLaunch(
launchParams as Parameters<typeof sessionLaunch>[0]
);
registerCreatedSession(
buildCreatedSessionRecord({
result,
fallbackRepoPath: params.projectRepoPath || params.repoPath,
agentExecMode: params.mode,
agentDefinitionId: params.agentDefinitionId,
parentSessionId: params.parentSessionId,
context: {
projectSlug: params.projectSlug,
workItemId: params.workItemId,
agentRole: params.agentRole,
},
})
);
logger.info(
`Created and started ${category} session: ${result.sessionId}`
);
Expand Down
4 changes: 2 additions & 2 deletions src/features/TeamCollaboration/engine/collabSessionFork.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { loadSharedLocalKeys } from "@src/hooks/keyVault/sharedLocalKeyStore";
import { COLLAB_SESSION_ACCESS_MODE } from "@src/store/collaboration/types";
import { lastModelPairMapAtom } from "@src/store/session/creatorDefaultModelAtom";
import { sessionsAtom } from "@src/store/session/sessionAtom/atoms";
import { upsertSession } from "@src/store/session/sessionAtom/mutations";
import { registerCreatedSession } from "@src/store/session/sessionAtom/mutations";
import { persistSessions } from "@src/store/session/sessionAtom/persistence";
import type {
Session,
Expand Down Expand Up @@ -342,7 +342,7 @@ export async function forkSession(
remoteSession.forkedFrom?.rootSessionId ?? remoteSession.sourceSessionId,
};
const name = buildForkedSessionName(remoteSession.title);
upsertSession({
registerCreatedSession({
session_id: localSessionId,
status: "completed",
created_at: now,
Expand Down
3 changes: 2 additions & 1 deletion src/features/TeamCollaboration/engine/collabSessionImport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { sessionsAtom } from "@src/store/session/sessionAtom/atoms";
import { recordGuestImportedSession } from "@src/store/session/sessionAtom/guestImportRegistry";
import {
applyImportedSessionTimestamps,
registerCreatedSession,
upsertSession,
} from "@src/store/session/sessionAtom/mutations";
import { persistSessions } from "@src/store/session/sessionAtom/persistence";
Expand Down Expand Up @@ -1004,7 +1005,7 @@ async function importRemoteSessionInner(
throwIfAborted(options.signal);
// No await after the final abort check: the session row, guest registry
// and persisted list commit synchronously as one local critical section.
upsertSession(importedRow);
registerCreatedSession(importedRow);
// Re-import of an existing copy: upsertSession pins timestamps against
// careless reconcile writes, but this row's clock belongs to the source.
applyImportedSessionTimestamps(localSessionId, {
Expand Down
Loading
Loading