Skip to content
Merged
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
9 changes: 7 additions & 2 deletions packages/coding-agent/CHANGELOG.md

Large diffs are not rendered by default.

10 changes: 10 additions & 0 deletions packages/coding-agent/scripts/generate-sdk-operation-inventory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,16 @@ const LOCKED_EXCLUSIONS: Readonly<Record<string, string>> = {
"agent_session:setForcedToolChoice": "internal accessor/plumbing, not a user-facing control seam",
"agent_session:getActiveSkillState": "internal accessor/plumbing, not a user-facing control seam",
"agent_session:getActiveSkillPhase": "internal accessor/plumbing, not a user-facing control seam",
"agent_session:getEffectiveActiveWorkflowSkillState":
"internal restored-or-live workflow guard for session rescope, not a user-facing SDK control seam",
"agent_session:replaceOwnedMcpManager":
"internal cwd-rebinding helper for owned MCP authority after move_session, not a user-facing SDK control seam",
"agent_session:replaceNamedCustomTools":
"internal cwd-rebinding helper for named custom tools after move_session, not a user-facing SDK control seam",
"agent_session:replaceSkills":
"internal cwd-rebinding helper for session skills after move_session, not a user-facing SDK control seam",
"agent_session:retireWorkspaceTreeForRescope":
"internal cwd-rebinding helper that retires the cached workspace tree after move_session, not a user-facing SDK control seam",
"agent_session:getDeepInterviewAskStage":
"internal AskTool schema-selection accessor, not a user-facing SDK control seam",
"agent_session:peekQueueInvoker": "internal accessor/plumbing, not a user-facing control seam",
Expand Down
1 change: 1 addition & 0 deletions packages/coding-agent/scripts/generate-tool-catalog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ function makeSession(cwd: string): any {
hasEditTool: true,
taskDepth: 0,
currentAgentType: "executor",
rescopeSessionCwd: async () => ({ from: cwd, to: cwd }),
getSessionFile: () => null,
getSessionSpawns: () => null,
getSessionId: () => "catalog",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1153,13 +1153,15 @@ export class CommandController {
}

try {
await this.ctx.sessionManager.flush();
await this.ctx.sessionManager.moveTo(resolvedPath);
setProjectDir(resolvedPath);
clearClaudePluginRootsCache(); // re-warms preloadedPluginRoots with new project dir (async)
resetCapabilities();
await this.ctx.refreshSlashCommandState(resolvedPath);
await this.ctx.session.refreshSshTool({ activateIfAvailable: true });
await this.ctx.sessionManager.runExclusiveCwdTransition(async () => {
await this.ctx.sessionManager.flush();
await this.ctx.sessionManager.moveTo(resolvedPath);
setProjectDir(resolvedPath);
clearClaudePluginRootsCache();
resetCapabilities();
await this.ctx.refreshSlashCommandState(resolvedPath);
await this.ctx.session.refreshSshTool({ activateIfAvailable: true });
});

this.ctx.statusLine.invalidate();
this.ctx.updateEditorTopBorder();
Expand Down
15 changes: 15 additions & 0 deletions packages/coding-agent/src/prompts/tools/move-session.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
Rescope the session to a narrower working directory.

Use this only when the session's working directory is a broad launcher root (for example a
multi-repo workspace like `~/Projects`) and the task has clearly converged on one subdirectory
or repository: after this call, every later turn resolves relative paths and the bash default
cwd from the new directory, and project-scoped plugins/capabilities reload for it.

- `path` must be an existing directory; relative paths resolve against the current session cwd.
The canonical target must be strictly inside the current session directory — moves to a
parent, a sibling project, or an unrelated absolute path are refused.
- A session can be moved this way at most once, and never while another move is running; a
rejected call does not consume the move. Use it once the target repo is identified — not
speculatively — because the session file and caches move with the session.
- This tool is unavailable in subagent sessions and restricted profiles; ask the top-level
session to rescope instead.
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ export type OptionalRuntimeServicesOverrides = Partial<OptionalRuntimeServices>;

/** Context needed by services whose identity is scoped to the session cwd. */
export interface OptionalRuntimeServicesContext {
cwd?: string;
/** Session cwd. Pass a getter when the session can rescope (`move_session`). */
cwd?: string | (() => string);
}

/**
Expand All @@ -32,7 +33,7 @@ export function createOptionalRuntimeServices(
overrides: OptionalRuntimeServicesOverrides = {},
context: OptionalRuntimeServicesContext = {},
): OptionalRuntimeServices {
const cwd = context.cwd ?? process.cwd();
const cwd = context.cwd ?? (() => process.cwd());
return {
memoryBackend: overrides.memoryBackend ?? createMemoryBackendService(settings),
workspaceTree: overrides.workspaceTree ?? createWorkspaceTreeService(settings, cwd),
Expand Down
14 changes: 11 additions & 3 deletions packages/coding-agent/src/runtime/workspace-tree-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,24 @@ export interface WorkspaceTreeRuntime {
* Build the workspace-tree service without importing the native scanner until
* the service is activated. The scan itself remains the single authority for
* both eager startup and the lazy first-turn barrier.
*
* `cwd` is resolved per scan rather than captured once: a session that rescopes
* (`move_session`, `/move`) must have its refreshes re-root at the new cwd,
* otherwise every later tree describes the abandoned launcher root.
*/
export function createWorkspaceTreeService(settings: Settings, cwd: string): LazyService<WorkspaceTreeRuntime> {
export function createWorkspaceTreeService(
settings: Settings,
cwd: string | (() => string),
): LazyService<WorkspaceTreeRuntime> {
const resolveCwd = typeof cwd === "function" ? cwd : () => cwd;
return createLazyService({
id: "workspaceTree",
enabled: () => settings.get("workspaceTree.mode") === "eager" || settings.get("workspaceTree.mode") === "lazy",
initialize: async ({ signal }) => {
const scan = async (): Promise<WorkspaceTree> => {
if (signal.aborted) throw new Error("Workspace-tree scan was aborted before it started.");
const { buildWorkspaceTree } = await import("../workspace-tree");
const tree = await buildWorkspaceTree(cwd, { timeoutMs: WORKSPACE_TREE_SCAN_TIMEOUT_MS });
const tree = await buildWorkspaceTree(resolveCwd(), { timeoutMs: WORKSPACE_TREE_SCAN_TIMEOUT_MS });
if (signal.aborted) throw new Error("Workspace-tree scan was aborted before it completed.");
return tree;
};
Expand All @@ -34,7 +42,7 @@ export function createWorkspaceTreeService(settings: Settings, cwd: string): Laz
snapshot,
refresh: async () => {
const { buildWorkspaceTree } = await import("../workspace-tree");
return buildWorkspaceTree(cwd, { timeoutMs: WORKSPACE_TREE_SCAN_TIMEOUT_MS });
return buildWorkspaceTree(resolveCwd(), { timeoutMs: WORKSPACE_TREE_SCAN_TIMEOUT_MS });
},
},
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2446,6 +2446,39 @@
"testIds": "not_applicable"
}
},
{
"sourceId": "agent_session:getEffectiveActiveWorkflowSkillState",
"sourceFile": "packages/coding-agent/src/session/agent-session.ts",
"sourceKind": "agent_session",
"decision": "exclude",
"rationale": "internal restored-or-live workflow guard for session rescope, not a user-facing SDK control seam",
"exclusionMetadata": {
"adapterMappings": "not_applicable",
"testIds": "not_applicable"
}
},
{
"sourceId": "agent_session:replaceOwnedMcpManager",
"sourceFile": "packages/coding-agent/src/session/agent-session.ts",
"sourceKind": "agent_session",
"decision": "exclude",
"rationale": "internal cwd-rebinding helper for owned MCP authority after move_session, not a user-facing SDK control seam",
"exclusionMetadata": {
"adapterMappings": "not_applicable",
"testIds": "not_applicable"
}
},
{
"sourceId": "agent_session:replaceNamedCustomTools",
"sourceFile": "packages/coding-agent/src/session/agent-session.ts",
"sourceKind": "agent_session",
"decision": "exclude",
"rationale": "internal cwd-rebinding helper for named custom tools after move_session, not a user-facing SDK control seam",
"exclusionMetadata": {
"adapterMappings": "not_applicable",
"testIds": "not_applicable"
}
},
{
"sourceId": "agent_session:getActiveSkillPhase",
"sourceFile": "packages/coding-agent/src/session/agent-session.ts",
Expand Down Expand Up @@ -3693,6 +3726,28 @@
"testIds": "not_applicable"
}
},
{
"sourceId": "agent_session:replaceSkills",
"sourceFile": "packages/coding-agent/src/session/agent-session.ts",
"sourceKind": "agent_session",
"decision": "exclude",
"rationale": "internal cwd-rebinding helper for session skills after move_session, not a user-facing SDK control seam",
"exclusionMetadata": {
"adapterMappings": "not_applicable",
"testIds": "not_applicable"
}
},
{
"sourceId": "agent_session:retireWorkspaceTreeForRescope",
"sourceFile": "packages/coding-agent/src/session/agent-session.ts",
"sourceKind": "agent_session",
"decision": "exclude",
"rationale": "internal cwd-rebinding helper that retires the cached workspace tree after move_session, not a user-facing SDK control seam",
"exclusionMetadata": {
"adapterMappings": "not_applicable",
"testIds": "not_applicable"
}
},
{
"sourceId": "agent_session:getTodoPhases",
"sourceFile": "packages/coding-agent/src/session/agent-session.ts",
Expand Down
Loading
Loading