@cjhyy/code-shell-core 0.8.20 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/dist/automation/desktop-authority-client.d.ts +9 -0
  2. package/dist/automation/desktop-authority-client.js +47 -0
  3. package/dist/automation/scheduler.d.ts +8 -0
  4. package/dist/automation/scheduler.js +37 -1
  5. package/dist/automation/store.js +14 -1
  6. package/dist/capabilities/index.d.ts +1 -0
  7. package/dist/cli/agent-server-stdio.js +4 -1
  8. package/dist/engine/engine-workspace-authority.d.ts +50 -0
  9. package/dist/engine/engine-workspace-authority.js +167 -0
  10. package/dist/engine/engine.d.ts +7 -10
  11. package/dist/engine/engine.js +87 -101
  12. package/dist/engine/input-attachments.d.ts +1 -0
  13. package/dist/engine/input-attachments.js +6 -2
  14. package/dist/engine/run-environment.d.ts +8 -3
  15. package/dist/engine/run-environment.js +33 -8
  16. package/dist/engine/run-image-input.d.ts +1 -0
  17. package/dist/engine/run-image-input.js +1 -0
  18. package/dist/engine/run-session-open.d.ts +2 -1
  19. package/dist/engine/run-session-open.js +6 -0
  20. package/dist/engine/run-setup.d.ts +1 -0
  21. package/dist/engine/run-setup.js +2 -1
  22. package/dist/engine/run-tooling.d.ts +2 -0
  23. package/dist/engine/run-tooling.js +3 -0
  24. package/dist/engine/run-types.d.ts +2 -0
  25. package/dist/engine/run-workspace.d.ts +6 -1
  26. package/dist/engine/run-workspace.js +47 -0
  27. package/dist/engine/subagent-spawner.d.ts +1 -0
  28. package/dist/engine/subagent-spawner.js +1 -0
  29. package/dist/engine/types.d.ts +2 -0
  30. package/dist/index.d.ts +3 -3
  31. package/dist/index.extension.d.ts +1 -1
  32. package/dist/index.internal.d.ts +2 -2
  33. package/dist/index.internal.js +1 -0
  34. package/dist/index.js +1 -1
  35. package/dist/plugins/pluginAutomationTemplates.d.ts +2 -0
  36. package/dist/plugins/pluginAutomationTemplates.js +4 -0
  37. package/dist/prompt/composer.d.ts +4 -1
  38. package/dist/prompt/composer.js +11 -3
  39. package/dist/protocol/background-result-wakeup.d.ts +18 -0
  40. package/dist/protocol/background-result-wakeup.js +101 -0
  41. package/dist/protocol/chat-session-manager.d.ts +42 -1
  42. package/dist/protocol/chat-session-manager.js +167 -4
  43. package/dist/protocol/chat-session.d.ts +9 -1
  44. package/dist/protocol/chat-session.js +20 -2
  45. package/dist/protocol/mobile-remote-types.d.ts +15 -0
  46. package/dist/protocol/server.d.ts +5 -6
  47. package/dist/protocol/server.js +62 -128
  48. package/dist/protocol/session-workspace-rpc.d.ts +23 -0
  49. package/dist/protocol/session-workspace-rpc.js +171 -0
  50. package/dist/protocol/types.d.ts +45 -1
  51. package/dist/protocol/types.js +4 -0
  52. package/dist/session/session-manager.d.ts +18 -0
  53. package/dist/session/session-manager.js +87 -0
  54. package/dist/settings/manager.d.ts +7 -0
  55. package/dist/settings/manager.js +64 -3
  56. package/dist/tool-system/builtin/agent-notifications.d.ts +8 -0
  57. package/dist/tool-system/builtin/agent-notifications.js +19 -0
  58. package/dist/tool-system/builtin/config.d.ts +11 -0
  59. package/dist/tool-system/builtin/config.js +55 -10
  60. package/dist/tool-system/builtin/cron.d.ts +11 -0
  61. package/dist/tool-system/builtin/cron.js +27 -2
  62. package/dist/tool-system/builtin/edit.js +5 -3
  63. package/dist/tool-system/builtin/view-image.js +8 -2
  64. package/dist/tool-system/builtin/write.js +5 -3
  65. package/dist/tool-system/context.d.ts +4 -1
  66. package/dist/tool-system/executor.js +1 -1
  67. package/dist/tool-system/path-policy.d.ts +11 -3
  68. package/dist/tool-system/path-policy.js +56 -36
  69. package/dist/types.d.ts +6 -0
  70. package/dist/workspace/canonical-key.d.ts +7 -0
  71. package/dist/workspace/canonical-key.js +39 -0
  72. package/dist/workspace/workspace-context.d.ts +35 -0
  73. package/dist/workspace/workspace-context.js +128 -0
  74. package/package.json +1 -1
@@ -1,4 +1,6 @@
1
1
  import { resolveRunProfileState } from "./run-setup.js";
2
+ import { canonicalKey } from "../workspace/canonical-key.js";
3
+ import { legacySingleRootWorkspace, validateWorkspaceContext, workspacePrimaryRoot, } from "../workspace/workspace-context.js";
2
4
  /**
3
5
  * Resolve the working directory for a run. Precedence for legacy sessions:
4
6
  * options.cwd > resumed session's state.cwd > config.cwd > process.cwd()
@@ -93,6 +95,37 @@ export async function resolveRunWorkspace(args) {
93
95
  configCwd: args.configCwd,
94
96
  processCwd: args.processCwd,
95
97
  });
98
+ const rawWorkspaceContext = options?.workspaceContext ?? args.configWorkspaceContext;
99
+ let workspaceContext;
100
+ try {
101
+ workspaceContext = rawWorkspaceContext
102
+ ? validateWorkspaceContext(rawWorkspaceContext)
103
+ : legacySingleRootWorkspace(cwd);
104
+ }
105
+ catch (error) {
106
+ return workspaceError(options?.sessionId, error instanceof Error ? error.message : String(error));
107
+ }
108
+ const authoritativeWorkspaceContext = rawWorkspaceContext !== undefined;
109
+ const primary = workspacePrimaryRoot(workspaceContext);
110
+ if (canonicalKey(primary.path) !== canonicalKey(cwd)) {
111
+ return workspaceError(options?.sessionId, `WorkspaceContext primary does not match effective cwd: ${primary.path} != ${cwd}`);
112
+ }
113
+ if (workspaceResume?.ok &&
114
+ workspaceResume.reason !== "legacy" &&
115
+ canonicalKey(workspaceResume.workspace.root) !== canonicalKey(primary.path)) {
116
+ return workspaceError(options?.sessionId, "WorkspaceContext primary does not match SessionWorkspace");
117
+ }
118
+ const binding = options?.sessionId
119
+ ? args.sessionManager.readSessionProjectBinding(options.sessionId)
120
+ : undefined;
121
+ if (binding && !authoritativeWorkspaceContext) {
122
+ return workspaceError(options?.sessionId, "bound Session requires an authoritative WorkspaceContext");
123
+ }
124
+ if (binding &&
125
+ (binding.projectId !== workspaceContext.projectId ||
126
+ binding.mainRootId !== workspaceContext.sessionMainRootId)) {
127
+ return workspaceError(options?.sessionId, "WorkspaceContext does not match persisted project binding");
128
+ }
96
129
  const profileState = profile?.disableWorkspaceProfile
97
130
  ? {
98
131
  workspaceProfile: undefined,
@@ -114,7 +147,21 @@ export async function resolveRunWorkspace(args) {
114
147
  runPermissionMode,
115
148
  runPlanMode,
116
149
  cwd,
150
+ workspaceContext,
151
+ authoritativeWorkspaceContext,
117
152
  profileState,
118
153
  },
119
154
  };
120
155
  }
156
+ function workspaceError(sessionId, message) {
157
+ return {
158
+ ok: false,
159
+ result: {
160
+ text: `ERROR: ${message}`,
161
+ reason: "completed",
162
+ sessionId: sessionId ?? "workspace-invalid",
163
+ turnCount: 0,
164
+ usage: { promptTokens: 0, completionTokens: 0, totalTokens: 0 },
165
+ },
166
+ };
167
+ }
@@ -21,6 +21,7 @@ export interface CreateSubAgentSpawnerDeps {
21
21
  parentSandbox: SandboxConfig;
22
22
  presetName: AgentPresetName;
23
23
  cwd: string;
24
+ workspaceContext?: import("../workspace/workspace-context.js").WorkspaceContext;
24
25
  permissionMode: NonNullable<EngineConfig["permissionMode"]>;
25
26
  modelPool?: ModelPool;
26
27
  appendParentSubagent: (agentId: string, description: string) => void;
@@ -233,6 +233,7 @@ export function createSubAgentSpawner(deps) {
233
233
  retryMaxAttempts: 2,
234
234
  },
235
235
  cwd: deps.cwd,
236
+ workspaceContext: deps.workspaceContext ?? deps.parentConfig.workspaceContext,
236
237
  permissionMode: deps.permissionMode,
237
238
  preset: deps.presetName,
238
239
  enabledBuiltinTools: scope.enabled,
@@ -33,6 +33,8 @@ export interface EngineConfig {
33
33
  */
34
34
  clientDefaults?: ClientDefaults;
35
35
  cwd?: string;
36
+ /** Trusted host-provided root authorization for runs created by this Engine. */
37
+ workspaceContext?: import("../workspace/workspace-context.js").WorkspaceContext;
36
38
  maxTurns?: number;
37
39
  /**
38
40
  * Override the goal-mode consecutive-stop-block cap (TODO 3.1). Falls back to
package/dist/index.d.ts CHANGED
@@ -3,8 +3,8 @@
3
3
  *
4
4
  * Public API exports.
5
5
  */
6
- export declare const VERSION = "0.8.20";
7
- export type { Message, ContentBlock, ToolDefinition, ToolCall, ToolResult, RegisteredTool, TranscriptEvent, TranscriptEventType, SessionState, SessionKind, SessionWorkspace, SessionForkLineage, ContextUsageAnchor, SessionStatus, TokenUsage, CompiledInput, PermissionDecision, PermissionMode, PermissionRule, TurnPhase, TurnResult, TerminalReason, TurnCompletionKind, StreamEvent, StreamCallback, LLMConfig, ClientDefaults, LLMResponse, Settings, MCPServerConfig, } from "./types.js";
6
+ export declare const VERSION = "0.9.1";
7
+ export type { Message, ContentBlock, ToolDefinition, ToolCall, ToolResult, RegisteredTool, TranscriptEvent, TranscriptEventType, SessionState, SessionKind, SessionWorkspace, SessionProjectBinding, SessionForkLineage, ContextUsageAnchor, SessionStatus, TokenUsage, CompiledInput, PermissionDecision, PermissionMode, PermissionRule, TurnPhase, TurnResult, TerminalReason, TurnCompletionKind, StreamEvent, StreamCallback, LLMConfig, ClientDefaults, LLMResponse, Settings, MCPServerConfig, } from "./types.js";
8
8
  export type { GoalConfig, GoalLifecycleConfig, GoalLifecyclePhase, GoalLifecycleTerminalReason, GoalLifecycleV1, } from "./goal/lifecycle.js";
9
9
  export { FrameworkError, LLMError, LLMRateLimitError, ContextLimitError, ToolError, ToolNotFoundError, ToolExecutionError, ToolTimeoutError, PermissionDeniedError, SessionError, TranscriptError, ConfigError, CompositionError, SandboxUnavailableError, } from "./exceptions.js";
10
10
  export { compileComposition, toCompositionSnapshot, computeCompositionDigest, compositionToolCatalog, resolvePresetFromComposition, } from "./composition/index.js";
@@ -62,7 +62,7 @@ export { AgentClient, type BackgroundAgentCompletedHandler } from "./protocol/cl
62
62
  export { createInProcessTransport, StdioTransport, type Transport } from "./protocol/transport.js";
63
63
  export { SocketTransport, listenTcp, type TcpListenResult } from "./protocol/tcp-transport.js";
64
64
  export { createServer, createClient, type CreateServerOptions, type CreateClientOptions, type ServerHandle, } from "./protocol/factories.js";
65
- export { Methods, ErrorCodes, type RpcMessage, type RunResult, type ForkSessionParams, type ForkSessionResult as ProtocolForkSessionResult, } from "./protocol/types.js";
65
+ export { Methods, ErrorCodes, type RpcMessage, type RunResult, type ForkSessionParams, type ForkSessionResult as ProtocolForkSessionResult, type MigrateSessionMainRootResult, } from "./protocol/types.js";
66
66
  export type * from "./protocol/mobile-remote-types.js";
67
67
  export { Transcript } from "./session/transcript.js";
68
68
  export { SessionManager, sessionMainRoot, codeShellHome, sessionsRoot, buildForkState, buildForkTranscript, type ForkSessionOptions, type ForkSessionResult, } from "./session/session-manager.js";
@@ -26,7 +26,7 @@ export type { ExposureRationale } from "./tool-system/external-tool-exposure.js"
26
26
  export { webSearchTool } from "./tool-system/builtin/web-search.js";
27
27
  export { webFetchTool } from "./tool-system/builtin/web-fetch.js";
28
28
  export { extractJSON, extractJSONArray } from "./utils/json.js";
29
- export type { ExtensionQueryHandler, ExtensionTool, } from "./tool-system/capability-module.js";
29
+ export type { ExtensionQueryHandler, ExtensionTool } from "./tool-system/capability-module.js";
30
30
  export type { SessionWorkspace } from "./types.js";
31
31
  export type { CapabilityArtifactDetector, CapabilityDynamicContextProvider, CapabilityToolServiceHost, } from "./capabilities/index.js";
32
32
  export type { AgentModule, AgentEngineContributions, AgentProtocolContributions, AgentModuleToolContribution, ResolvedComposition, } from "./composition/types.js";
@@ -7,6 +7,7 @@
7
7
  */
8
8
  export { getGraphemeSegmenter, firstGrapheme, lastGrapheme, getWordSegmenter, getRelativeTimeFormat, getTimeZone, getSystemLocaleLanguage, } from "./utils/intl.js";
9
9
  export { env } from "./utils/env.js";
10
+ export { canonicalKey, canonicalPath, computeWorkspaceRootsDigest, createWorkspaceContext, legacySingleRootWorkspace, validateWorkspaceContext, workspacePrimaryRoot, type ProjectId, type ProjectRootContext, type ProjectRootId, type WorkspaceContext, type WorkspaceContextInput, } from "./workspace/workspace-context.js";
10
11
  export { default as sliceAnsi } from "./utils/sliceAnsi.js";
11
12
  export { execFileNoThrow } from "./utils/execFileNoThrow.js";
12
13
  export { findExecutable, resolveExecutable, setGitPathOverride, resolveGit, isGitAvailable, resolveGitPath, } from "./utils/exec.js";
@@ -27,7 +28,6 @@ export { getInteractiveApprovalBackend } from "./tool-system/permission.js";
27
28
  export { defaultSandboxConfig, type SandboxConfig } from "./tool-system/sandbox/index.js";
28
29
  export { buildNotificationMessage, buildNotificationSummary, notificationQueue, agentNotificationBus, notificationItemToStreamEvent, type NotificationItem, } from "./tool-system/builtin/agent-notifications.js";
29
30
  export { backgroundJobRegistry } from "./tool-system/builtin/background-jobs.js";
30
- export type { BackgroundAgentCompletedEvent } from "./types.js";
31
31
  export { startAutomation, type StartAutomationDeps, type AutomationHandle, CronScheduler, cronScheduler, type CronExecutionOutcome, type CronJob, type CronJobLifecycleEvent, type CronPermissionLevel, type CronTemplateSource, type CreateJobOptions, type UpdateJobPatch, CronStore, defaultCronStorePath, bindCronToEngine, bindCronToRunManager, type CronRunner, type CronRunRequest, type CronRunResult, type RunSubmitter, isCronExpression, parseCronExpression, nextCronTime, validateSchedule, type ParsedCron, resolveWritePolicy, wrapUntrustedInput, type WritePolicy, runWriteJobInWorktree, type WriteJobGitOps, type RunWriteJobInput, type RunWriteJobResult, } from "./automation/index.js";
32
32
  export { asyncAgentRegistry, type AsyncAgentEntry } from "./tool-system/builtin/agent-registry.js";
33
33
  export { backgroundShellManager, BackgroundShellManager, type BgShell, type BgShellStatus, } from "./runtime/background-shell.js";
@@ -46,7 +46,7 @@ export { capabilitiesFor, type Capability } from "./llm/capabilities/index.js";
46
46
  export { reasoningControlFor, type ReasoningControl, } from "./llm/capabilities/reasoning-control.js";
47
47
  export { REASONING_EFFORTS, type ReasoningSetting } from "./llm/reasoning-setting.js";
48
48
  export { type ProviderConfig } from "./llm/provider-catalog.js";
49
- export type { ApprovalRequest, ApprovalResult, ApprovalScope, TaskInfo } from "./types.js";
49
+ export type { ApprovalRequest, ApprovalResult, ApprovalScope, BackgroundAgentCompletedEvent, TaskInfo, } from "./types.js";
50
50
  export { fileCache } from "./tool-system/builtin/file-cache.js";
51
51
  export { validateToolArgs } from "./tool-system/validation.js";
52
52
  export { createOffBackend } from "./tool-system/sandbox/off.js";
@@ -8,6 +8,7 @@
8
8
  // ─── Utils (shared primitives used by TUI) ───────────────────────
9
9
  export { getGraphemeSegmenter, firstGrapheme, lastGrapheme, getWordSegmenter, getRelativeTimeFormat, getTimeZone, getSystemLocaleLanguage, } from "./utils/intl.js";
10
10
  export { env } from "./utils/env.js";
11
+ export { canonicalKey, canonicalPath, computeWorkspaceRootsDigest, createWorkspaceContext, legacySingleRootWorkspace, validateWorkspaceContext, workspacePrimaryRoot, } from "./workspace/workspace-context.js";
11
12
  export { default as sliceAnsi } from "./utils/sliceAnsi.js";
12
13
  export { execFileNoThrow } from "./utils/execFileNoThrow.js";
13
14
  export { findExecutable, resolveExecutable, setGitPathOverride, resolveGit, isGitAvailable, resolveGitPath, } from "./utils/exec.js";
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Public API exports.
5
5
  */
6
- export const VERSION = "0.8.20";
6
+ export const VERSION = "0.9.1";
7
7
  // ─── Exceptions ──────────────────────────────────────────────────
8
8
  export { FrameworkError, LLMError, LLMRateLimitError, ContextLimitError, ToolError, ToolNotFoundError, ToolExecutionError, ToolTimeoutError, PermissionDeniedError, SessionError, TranscriptError, ConfigError, CompositionError, SandboxUnavailableError, } from "./exceptions.js";
9
9
  // ─── Composition (AgentModule / ResolvedComposition) ─────────────
@@ -6,6 +6,8 @@ export interface InstantiatePluginAutomationTemplateOptions {
6
6
  templateId: string;
7
7
  expectedRevision: string;
8
8
  cwd?: string;
9
+ projectId?: string;
10
+ rootId?: string;
9
11
  /** Effective project/global disable list computed by the host for this cwd. */
10
12
  disabledPluginNames: ReadonlySet<string>;
11
13
  maxJobs?: number;
@@ -38,6 +38,10 @@ export function instantiatePluginAutomationTemplate(options) {
38
38
  const template = contribution.template;
39
39
  return options.scheduler.create(template.title.default, template.schedule, template.prompt, {
40
40
  ...(template.workspace === "current" && options.cwd ? { cwd: options.cwd } : {}),
41
+ ...(template.workspace === "current" && options.projectId
42
+ ? { projectId: options.projectId }
43
+ : {}),
44
+ ...(template.workspace === "current" && options.rootId ? { rootId: options.rootId } : {}),
41
45
  ...(template.timezone ? { timezone: template.timezone } : {}),
42
46
  permissionLevel: template.permissionLevel,
43
47
  templateSource: {
@@ -10,8 +10,11 @@ import { type ScanOptions } from "./instruction-scanner.js";
10
10
  import { type AgentPreset } from "../preset/index.js";
11
11
  import type { BuiltinTool } from "../tool-system/builtin/index.js";
12
12
  import type { CapabilityDynamicContextProvider } from "../capabilities/index.js";
13
+ import { type WorkspaceContext } from "../workspace/workspace-context.js";
13
14
  export interface ComposerOptions {
14
15
  cwd: string;
16
+ /** Run workspace; omitted by legacy callers that only provide cwd. */
17
+ workspace?: WorkspaceContext;
15
18
  model: string;
16
19
  instructionOptions?: ScanOptions;
17
20
  /** Resolved preset — used to load section-based prompt. */
@@ -106,9 +109,9 @@ export interface ComposerOptions {
106
109
  disableSourcesContext?: boolean;
107
110
  }
108
111
  export declare class PromptComposer {
109
- private readonly options;
110
112
  private sectionCache;
111
113
  private cachedInstructions;
114
+ private readonly options;
112
115
  constructor(options: ComposerOptions);
113
116
  /**
114
117
  * Build the system prompt from sections.
@@ -11,12 +11,18 @@ import { MemoryManager } from "../session/memory.js";
11
11
  import { scanSkills } from "../skills/index.js";
12
12
  import { buildSkillListing } from "../tool-system/builtin/skill-prompt.js";
13
13
  import { resolveAgentPreset, buildPresetSystemPrompt } from "../preset/index.js";
14
+ import { legacySingleRootWorkspace, validateWorkspaceContext, } from "../workspace/workspace-context.js";
14
15
  export class PromptComposer {
15
- options;
16
16
  sectionCache = new SectionCache();
17
17
  cachedInstructions = null;
18
+ options;
18
19
  constructor(options) {
19
- this.options = options;
20
+ this.options = {
21
+ ...options,
22
+ workspace: options.workspace === undefined
23
+ ? legacySingleRootWorkspace(options.cwd)
24
+ : validateWorkspaceContext(options.workspace),
25
+ };
20
26
  }
21
27
  /**
22
28
  * Build the system prompt from sections.
@@ -58,7 +64,7 @@ export class PromptComposer {
58
64
  const preset = this.options.preset ?? resolveAgentPreset();
59
65
  const parts = await Promise.all((this.options.dynamicContextProviders ?? []).map(async (provider) => {
60
66
  try {
61
- return await provider({ cwd: this.options.cwd, preset });
67
+ return await provider({ cwd: this.options.cwd, workspace: this.options.workspace, preset });
62
68
  }
63
69
  catch {
64
70
  // A capability's optional context must not make a turn fail.
@@ -164,6 +170,8 @@ export class PromptComposer {
164
170
  const lines = [
165
171
  `You are an AI agent powered by ${this.options.model}.`,
166
172
  `Working directory: ${this.options.cwd}`,
173
+ `Workspace roots (${this.options.workspace.roots.length}):`,
174
+ ...this.options.workspace.roots.map((root) => `- [${root.role}] ${root.path} (rootId: ${root.id})`),
167
175
  `Platform: ${process.platform}`,
168
176
  `Shell: ${process.env.SHELL ?? "unknown"}`,
169
177
  ];
@@ -0,0 +1,18 @@
1
+ import type { StreamEvent } from "../types.js";
2
+ import type { ApprovalRouter } from "../tool-system/permission.js";
3
+ import type { ChatSession } from "./chat-session.js";
4
+ import type { ChatSessionManager } from "./chat-session-manager.js";
5
+ interface BackgroundResultWakeOptions {
6
+ sessionId: string;
7
+ manager: ChatSessionManager | null;
8
+ rehydrate(sessionId: string): Promise<ChatSession | null>;
9
+ approvalRouter: ApprovalRouter;
10
+ onStream(event: StreamEvent): void;
11
+ }
12
+ /**
13
+ * Drain pending background results into exactly one synthetic continuation.
14
+ * Busy sessions are awaited so a completion cannot fall into the gap between
15
+ * the notification bus callback and the interactive run-boundary re-check.
16
+ */
17
+ export declare function wakeSessionForBackgroundResults({ sessionId, manager, rehydrate, approvalRouter, onStream, }: BackgroundResultWakeOptions): Promise<boolean>;
18
+ export {};
@@ -0,0 +1,101 @@
1
+ import { logger } from "../logging/logger.js";
2
+ import { buildNotificationMessage, notificationQueue, } from "../tool-system/builtin/agent-notifications.js";
3
+ /**
4
+ * Drain pending background results into exactly one synthetic continuation.
5
+ * Busy sessions are awaited so a completion cannot fall into the gap between
6
+ * the notification bus callback and the interactive run-boundary re-check.
7
+ */
8
+ export async function wakeSessionForBackgroundResults({ sessionId, manager, rehydrate, approvalRouter, onStream, }) {
9
+ if (!manager) {
10
+ logger.debug("bg_wakeup.skipped", { sessionId, reason: "no_chat_manager" });
11
+ return false;
12
+ }
13
+ if (manager.isUnavailable(sessionId)) {
14
+ logger.debug("bg_wakeup.skipped", { sessionId, reason: "session_unavailable" });
15
+ return false;
16
+ }
17
+ let session = manager.get(sessionId) ?? (await rehydrate(sessionId));
18
+ if (!session) {
19
+ logger.debug("bg_wakeup.skipped", { sessionId, reason: "session_missing" });
20
+ return false;
21
+ }
22
+ while (session.isBusy()) {
23
+ logger.debug("bg_wakeup.waiting_for_idle", {
24
+ sessionId,
25
+ pendingCount: notificationQueue.getSnapshot(sessionId).length,
26
+ });
27
+ await session.settled;
28
+ if (manager.isUnavailable(sessionId)) {
29
+ logger.debug("bg_wakeup.skipped", {
30
+ sessionId,
31
+ reason: "session_became_unavailable",
32
+ });
33
+ return false;
34
+ }
35
+ const current = manager.get(sessionId);
36
+ if (!current) {
37
+ logger.debug("bg_wakeup.skipped", { sessionId, reason: "session_evicted_after_settle" });
38
+ return false;
39
+ }
40
+ session = current;
41
+ }
42
+ // Headless/automation runs are one-shot and have no continuation consumer.
43
+ if (session.engine.isHeadless()) {
44
+ logger.debug("bg_wakeup.skipped", { sessionId, reason: "headless" });
45
+ return false;
46
+ }
47
+ // A user Stop must win over a later background completion.
48
+ if (session.wasCancelledSinceLastTurn()) {
49
+ logger.debug("bg_wakeup.skipped", { sessionId, reason: "cancelled_since_last_turn" });
50
+ return false;
51
+ }
52
+ let runWorkspace = {};
53
+ try {
54
+ const resolver = session.engine.resolveSessionRunWorkspace;
55
+ runWorkspace = resolver?.call(session.engine, sessionId) ?? {};
56
+ }
57
+ catch (error) {
58
+ // Resolve before draining so an unavailable authoritative worktree context
59
+ // leaves the completion recoverable by a later user run.
60
+ logger.warn("bg_wakeup.workspace_unavailable", {
61
+ sessionId,
62
+ error: error instanceof Error ? error.message : String(error),
63
+ });
64
+ return false;
65
+ }
66
+ const pending = notificationQueue.drainAll(sessionId);
67
+ if (pending.length === 0) {
68
+ logger.debug("bg_wakeup.skipped", { sessionId, reason: "no_pending_results" });
69
+ return false;
70
+ }
71
+ const task = `<system-reminder>\n${buildNotificationMessage(pending)}\n</system-reminder>`;
72
+ try {
73
+ const result = await session.enqueueTurn(task, {
74
+ injected: true,
75
+ ...runWorkspace,
76
+ onStream,
77
+ approvalRouter,
78
+ });
79
+ if (result.turnCount === 0) {
80
+ const restored = notificationQueue.restoreResults(sessionId, pending);
81
+ logger.warn("bg_wakeup.turn_not_started", {
82
+ sessionId,
83
+ restored,
84
+ reason: result.reason,
85
+ text: result.text.slice(0, 500),
86
+ });
87
+ return false;
88
+ }
89
+ }
90
+ catch (error) {
91
+ const message = error instanceof Error ? error.message : String(error);
92
+ const restored = notificationQueue.restoreResults(sessionId, pending);
93
+ logger.warn("bg_wakeup.turn_failed", { sessionId, error: message, restored });
94
+ // A setup failure can occur before the turn loop emits its own terminal
95
+ // event. Emit an error so every renderer clears its busy state, but keep
96
+ // the result queued so a later user turn can consume it safely.
97
+ onStream({ type: "error", error: message || "background wakeup failed" });
98
+ return false;
99
+ }
100
+ return true;
101
+ }
@@ -1,9 +1,10 @@
1
1
  import { ChatSession } from "./chat-session.js";
2
2
  import type { SessionKind } from "../types.js";
3
+ import type { SessionProjectBinding, SessionWorkspace } from "../types.js";
3
4
  import type { Engine } from "../engine/engine.js";
4
5
  import type { EngineRuntime } from "../engine/runtime.js";
5
6
  import type { EngineConfig } from "../engine/types.js";
6
- export type EngineConfigSlice = Pick<EngineConfig, "permissionMode" | "preset" | "customSystemPrompt" | "appendSystemPrompt" | "goal" | "maxTurns" | "maxContextTokens" | "cwd" | "projectTrusted" | "sessionStorageDir">;
7
+ export type EngineConfigSlice = Pick<EngineConfig, "permissionMode" | "preset" | "customSystemPrompt" | "appendSystemPrompt" | "goal" | "maxTurns" | "maxContextTokens" | "cwd" | "workspaceContext" | "projectTrusted" | "sessionStorageDir">;
7
8
  /** Identity scope every ChatSessionManager belongs to when none is injected. */
8
9
  export declare const LOCAL_CHAT_IDENTITY = "local";
9
10
  export interface ChatSessionManagerOptions {
@@ -48,11 +49,30 @@ export interface LiveChatSessionSnapshot {
48
49
  }>;
49
50
  }
50
51
  export declare const CLOSED_CHAT_SESSION_TOMBSTONE_LIMIT = 4096;
52
+ export interface ResidentSessionMainRootMigration {
53
+ project: SessionProjectBinding;
54
+ mainRoot: string;
55
+ workspaceContext: import("../workspace/workspace-context.js").WorkspaceContext;
56
+ projectTrusted: boolean;
57
+ }
58
+ export type SessionMigrationOwnership = {
59
+ status: "resident";
60
+ session: ChatSession;
61
+ } | {
62
+ status: "not-resident";
63
+ ownershipToken: string;
64
+ } | {
65
+ status: "failed";
66
+ error: string;
67
+ };
51
68
  export declare class ChatSessionManager {
52
69
  private readonly sessions;
53
70
  private readonly closingSessions;
54
71
  private readonly closedSessions;
55
72
  private readonly sessionGeneration;
73
+ private readonly sessionSlices;
74
+ private readonly migrationClaims;
75
+ private readonly residentMigrations;
56
76
  readonly runtime: EngineRuntime;
57
77
  /** Identity scope this manager serves ("local" unless injected). */
58
78
  readonly identity: string;
@@ -76,6 +96,26 @@ export declare class ChatSessionManager {
76
96
  */
77
97
  forIdentity(identity: string): ChatSessionManager;
78
98
  getOrCreate(sessionId: string, slice: EngineConfigSlice): Promise<ChatSession>;
99
+ /**
100
+ * Prove whether this worker currently owns a resident Engine, or fence the
101
+ * Session so Main can perform one durable migration without a re-resume race.
102
+ */
103
+ beginSessionMigration(sessionId: string, ownershipToken: string): SessionMigrationOwnership;
104
+ /** Release only the exact claim minted for this Session. */
105
+ completeSessionMigration(sessionId: string, ownershipToken: string): boolean;
106
+ /**
107
+ * Rebuild an idle resident Engine against a new authoritative main root.
108
+ *
109
+ * Transaction order is intentional:
110
+ * 1. construct and restore the candidate while durable state still points at
111
+ * the old owner (factory/restore failure is therefore a no-op),
112
+ * 2. atomically commit durable state through the old owning Engine,
113
+ * 3. synchronously swap the Engine inside the existing ChatSession,
114
+ * 4. dispose the unreachable old Engine before reporting success.
115
+ *
116
+ * No await occurs between the idle check, durable commit and owner swap.
117
+ */
118
+ migrateResidentSessionMainRoot(sessionId: string, target: ResidentSessionMainRootMigration): Promise<SessionWorkspace>;
79
119
  /**
80
120
  * Cold-resume a persisted session using its own cwd. The first detached
81
121
  * Engine is only a storage probe; all actual work runs on the second Engine
@@ -121,6 +161,7 @@ export declare class ChatSessionManager {
121
161
  startIdleSweeper(intervalMs?: number): void;
122
162
  stopIdleSweeper(): void;
123
163
  private unregisterMcpOwner;
164
+ private disposeEngine;
124
165
  private engineSessionManager;
125
166
  }
126
167
  /**