apex-code 0.0.5 → 0.1.0

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 (257) hide show
  1. package/CHANGELOG.md +40 -1
  2. package/README.md +7 -8
  3. package/dist/cli/agent-lifecycle.d.ts +24 -0
  4. package/dist/cli/agent-lifecycle.d.ts.map +1 -0
  5. package/dist/cli/agent-lifecycle.js +126 -0
  6. package/dist/cli/agent-lifecycle.js.map +1 -0
  7. package/dist/cli/args.d.ts +13 -12
  8. package/dist/cli/args.d.ts.map +1 -1
  9. package/dist/cli/args.js +80 -30
  10. package/dist/cli/args.js.map +1 -1
  11. package/dist/cli.d.ts.map +1 -1
  12. package/dist/cli.js +4 -130
  13. package/dist/cli.js.map +1 -1
  14. package/dist/config.d.ts +1 -1
  15. package/dist/config.d.ts.map +1 -1
  16. package/dist/config.js +1 -1
  17. package/dist/config.js.map +1 -1
  18. package/dist/core/agent-session-services.d.ts.map +1 -1
  19. package/dist/core/agent-session-services.js +0 -8
  20. package/dist/core/agent-session-services.js.map +1 -1
  21. package/dist/core/agent-session.d.ts +160 -0
  22. package/dist/core/agent-session.d.ts.map +1 -1
  23. package/dist/core/agent-session.js +265 -0
  24. package/dist/core/agent-session.js.map +1 -1
  25. package/dist/core/delegation/agents.d.ts.map +1 -1
  26. package/dist/core/delegation/agents.js +2 -1
  27. package/dist/core/delegation/agents.js.map +1 -1
  28. package/dist/core/delegation/runtime.d.ts +674 -1
  29. package/dist/core/delegation/runtime.d.ts.map +1 -1
  30. package/dist/core/delegation/runtime.js +1338 -66
  31. package/dist/core/delegation/runtime.js.map +1 -1
  32. package/dist/core/extensions/loader.d.ts.map +1 -1
  33. package/dist/core/extensions/loader.js +3 -4
  34. package/dist/core/extensions/loader.js.map +1 -1
  35. package/dist/core/extensions/source-runtime.d.ts +4 -5
  36. package/dist/core/extensions/source-runtime.d.ts.map +1 -1
  37. package/dist/core/extensions/source-runtime.js +4 -5
  38. package/dist/core/extensions/source-runtime.js.map +1 -1
  39. package/dist/core/formatter-lifecycle.d.ts +2 -1
  40. package/dist/core/formatter-lifecycle.d.ts.map +1 -1
  41. package/dist/core/formatter-lifecycle.js +2 -1
  42. package/dist/core/formatter-lifecycle.js.map +1 -1
  43. package/dist/core/hooks/types.d.ts +2 -3
  44. package/dist/core/hooks/types.d.ts.map +1 -1
  45. package/dist/core/hooks/types.js +2 -3
  46. package/dist/core/hooks/types.js.map +1 -1
  47. package/dist/core/http-idle-timeout.d.ts +3 -5
  48. package/dist/core/http-idle-timeout.d.ts.map +1 -1
  49. package/dist/core/http-idle-timeout.js +3 -5
  50. package/dist/core/http-idle-timeout.js.map +1 -1
  51. package/dist/core/mcp/config.d.ts +18 -1
  52. package/dist/core/mcp/config.d.ts.map +1 -1
  53. package/dist/core/mcp/config.js +24 -7
  54. package/dist/core/mcp/config.js.map +1 -1
  55. package/dist/core/mcp/connector.d.ts +2 -3
  56. package/dist/core/mcp/connector.d.ts.map +1 -1
  57. package/dist/core/mcp/connector.js.map +1 -1
  58. package/dist/core/mcp/oauth/authorize.d.ts +2 -0
  59. package/dist/core/mcp/oauth/authorize.d.ts.map +1 -1
  60. package/dist/core/mcp/oauth/authorize.js +5 -2
  61. package/dist/core/mcp/oauth/authorize.js.map +1 -1
  62. package/dist/core/mcp/oauth/mcp-token.d.ts +4 -6
  63. package/dist/core/mcp/oauth/mcp-token.d.ts.map +1 -1
  64. package/dist/core/mcp/oauth/mcp-token.js +5 -7
  65. package/dist/core/mcp/oauth/mcp-token.js.map +1 -1
  66. package/dist/core/mcp/runtime.d.ts +1 -0
  67. package/dist/core/mcp/runtime.d.ts.map +1 -1
  68. package/dist/core/mcp/runtime.js +2 -1
  69. package/dist/core/mcp/runtime.js.map +1 -1
  70. package/dist/core/package-manager.d.ts.map +1 -1
  71. package/dist/core/package-manager.js +0 -15
  72. package/dist/core/package-manager.js.map +1 -1
  73. package/dist/core/permissions/store.d.ts +6 -11
  74. package/dist/core/permissions/store.d.ts.map +1 -1
  75. package/dist/core/permissions/store.js +23 -51
  76. package/dist/core/permissions/store.js.map +1 -1
  77. package/dist/core/project-resources.d.ts +62 -0
  78. package/dist/core/project-resources.d.ts.map +1 -0
  79. package/dist/core/project-resources.js +42 -0
  80. package/dist/core/project-resources.js.map +1 -0
  81. package/dist/core/sdk.d.ts +62 -12
  82. package/dist/core/sdk.d.ts.map +1 -1
  83. package/dist/core/sdk.js +313 -44
  84. package/dist/core/sdk.js.map +1 -1
  85. package/dist/core/session-share.d.ts +6 -8
  86. package/dist/core/session-share.d.ts.map +1 -1
  87. package/dist/core/session-share.js +9 -12
  88. package/dist/core/session-share.js.map +1 -1
  89. package/dist/core/settings-manager.d.ts +0 -21
  90. package/dist/core/settings-manager.d.ts.map +1 -1
  91. package/dist/core/settings-manager.js +4 -12
  92. package/dist/core/settings-manager.js.map +1 -1
  93. package/dist/core/tools/bash.d.ts.map +1 -1
  94. package/dist/core/tools/bash.js +0 -47
  95. package/dist/core/tools/bash.js.map +1 -1
  96. package/dist/core/tools/delegate.d.ts +8 -0
  97. package/dist/core/tools/delegate.d.ts.map +1 -1
  98. package/dist/core/tools/delegate.js +33 -3
  99. package/dist/core/tools/delegate.js.map +1 -1
  100. package/dist/core/tools/web-fetch.d.ts.map +1 -1
  101. package/dist/core/tools/web-fetch.js +4 -8
  102. package/dist/core/tools/web-fetch.js.map +1 -1
  103. package/dist/core/tools/web-search-exa.d.ts +3 -6
  104. package/dist/core/tools/web-search-exa.d.ts.map +1 -1
  105. package/dist/core/tools/web-search-exa.js +5 -8
  106. package/dist/core/tools/web-search-exa.js.map +1 -1
  107. package/dist/core/trust-manager.d.ts +6 -5
  108. package/dist/core/trust-manager.d.ts.map +1 -1
  109. package/dist/core/trust-manager.js +42 -19
  110. package/dist/core/trust-manager.js.map +1 -1
  111. package/dist/core/web-search-provider.d.ts +0 -10
  112. package/dist/core/web-search-provider.d.ts.map +1 -1
  113. package/dist/core/web-search-provider.js +2 -16
  114. package/dist/core/web-search-provider.js.map +1 -1
  115. package/dist/core/workspace/git-observer.d.ts +16 -0
  116. package/dist/core/workspace/git-observer.d.ts.map +1 -1
  117. package/dist/core/workspace/git-observer.js +8 -1
  118. package/dist/core/workspace/git-observer.js.map +1 -1
  119. package/dist/core/workspace/git-worktree-owner.d.ts +89 -0
  120. package/dist/core/workspace/git-worktree-owner.d.ts.map +1 -0
  121. package/dist/core/workspace/git-worktree-owner.js +240 -0
  122. package/dist/core/workspace/git-worktree-owner.js.map +1 -0
  123. package/dist/main.d.ts +0 -1
  124. package/dist/main.d.ts.map +1 -1
  125. package/dist/main.js +22 -3
  126. package/dist/main.js.map +1 -1
  127. package/dist/modes/acp/server.d.ts +43 -1
  128. package/dist/modes/acp/server.d.ts.map +1 -1
  129. package/dist/modes/acp/server.js +78 -0
  130. package/dist/modes/acp/server.js.map +1 -1
  131. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  132. package/dist/modes/interactive/components/settings-selector.js +1 -1
  133. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  134. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  135. package/dist/modes/interactive/interactive-mode.js +0 -29
  136. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  137. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  138. package/dist/modes/rpc/rpc-mode.js +38 -0
  139. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  140. package/dist/modes/rpc/rpc-types.d.ts +110 -0
  141. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  142. package/dist/modes/rpc/rpc-types.js.map +1 -1
  143. package/dist/utils/tools-manager.d.ts +0 -25
  144. package/dist/utils/tools-manager.d.ts.map +1 -1
  145. package/dist/utils/tools-manager.js +3 -52
  146. package/dist/utils/tools-manager.js.map +1 -1
  147. package/dist/utils/version-check.d.ts +0 -2
  148. package/dist/utils/version-check.d.ts.map +1 -1
  149. package/dist/utils/version-check.js +0 -2
  150. package/dist/utils/version-check.js.map +1 -1
  151. package/docs/settings.md +2 -7
  152. package/npm-shrinkwrap.json +5 -5
  153. package/package.json +2 -2
  154. package/dist/core/sandbox/bwrap-arguments.d.ts +0 -41
  155. package/dist/core/sandbox/bwrap-arguments.d.ts.map +0 -1
  156. package/dist/core/sandbox/bwrap-arguments.js +0 -136
  157. package/dist/core/sandbox/bwrap-arguments.js.map +0 -1
  158. package/dist/core/sandbox/child-entry.d.ts +0 -2
  159. package/dist/core/sandbox/child-entry.d.ts.map +0 -1
  160. package/dist/core/sandbox/child-entry.js +0 -24
  161. package/dist/core/sandbox/child-entry.js.map +0 -1
  162. package/dist/core/sandbox/cli-launch.d.ts +0 -162
  163. package/dist/core/sandbox/cli-launch.d.ts.map +0 -1
  164. package/dist/core/sandbox/cli-launch.js +0 -366
  165. package/dist/core/sandbox/cli-launch.js.map +0 -1
  166. package/dist/core/sandbox/cli-supervisor.d.ts +0 -31
  167. package/dist/core/sandbox/cli-supervisor.d.ts.map +0 -1
  168. package/dist/core/sandbox/cli-supervisor.js +0 -147
  169. package/dist/core/sandbox/cli-supervisor.js.map +0 -1
  170. package/dist/core/sandbox/default-hosts.d.ts +0 -28
  171. package/dist/core/sandbox/default-hosts.d.ts.map +0 -1
  172. package/dist/core/sandbox/default-hosts.js +0 -69
  173. package/dist/core/sandbox/default-hosts.js.map +0 -1
  174. package/dist/core/sandbox/full-access.d.ts +0 -28
  175. package/dist/core/sandbox/full-access.d.ts.map +0 -1
  176. package/dist/core/sandbox/full-access.js +0 -63
  177. package/dist/core/sandbox/full-access.js.map +0 -1
  178. package/dist/core/sandbox/git-identity.d.ts +0 -57
  179. package/dist/core/sandbox/git-identity.d.ts.map +0 -1
  180. package/dist/core/sandbox/git-identity.js +0 -78
  181. package/dist/core/sandbox/git-identity.js.map +0 -1
  182. package/dist/core/sandbox/host-approval.d.ts +0 -42
  183. package/dist/core/sandbox/host-approval.d.ts.map +0 -1
  184. package/dist/core/sandbox/host-approval.js +0 -99
  185. package/dist/core/sandbox/host-approval.js.map +0 -1
  186. package/dist/core/sandbox/linux-backend.d.ts +0 -48
  187. package/dist/core/sandbox/linux-backend.d.ts.map +0 -1
  188. package/dist/core/sandbox/linux-backend.js +0 -328
  189. package/dist/core/sandbox/linux-backend.js.map +0 -1
  190. package/dist/core/sandbox/macos-backend.d.ts +0 -30
  191. package/dist/core/sandbox/macos-backend.d.ts.map +0 -1
  192. package/dist/core/sandbox/macos-backend.js +0 -385
  193. package/dist/core/sandbox/macos-backend.js.map +0 -1
  194. package/dist/core/sandbox/network-proxy.d.ts +0 -38
  195. package/dist/core/sandbox/network-proxy.d.ts.map +0 -1
  196. package/dist/core/sandbox/network-proxy.js +0 -153
  197. package/dist/core/sandbox/network-proxy.js.map +0 -1
  198. package/dist/core/sandbox/network-refusal.d.ts +0 -24
  199. package/dist/core/sandbox/network-refusal.d.ts.map +0 -1
  200. package/dist/core/sandbox/network-refusal.js +0 -72
  201. package/dist/core/sandbox/network-refusal.js.map +0 -1
  202. package/dist/core/sandbox/policy.d.ts +0 -40
  203. package/dist/core/sandbox/policy.d.ts.map +0 -1
  204. package/dist/core/sandbox/policy.js +0 -55
  205. package/dist/core/sandbox/policy.js.map +0 -1
  206. package/dist/core/sandbox/profiles.d.ts +0 -25
  207. package/dist/core/sandbox/profiles.d.ts.map +0 -1
  208. package/dist/core/sandbox/profiles.js +0 -23
  209. package/dist/core/sandbox/profiles.js.map +0 -1
  210. package/dist/core/sandbox/rpc/command-client.d.ts +0 -28
  211. package/dist/core/sandbox/rpc/command-client.d.ts.map +0 -1
  212. package/dist/core/sandbox/rpc/command-client.js +0 -86
  213. package/dist/core/sandbox/rpc/command-client.js.map +0 -1
  214. package/dist/core/sandbox/rpc/command-proxy.d.ts +0 -48
  215. package/dist/core/sandbox/rpc/command-proxy.d.ts.map +0 -1
  216. package/dist/core/sandbox/rpc/command-proxy.js +0 -120
  217. package/dist/core/sandbox/rpc/command-proxy.js.map +0 -1
  218. package/dist/core/sandbox/rpc/credential-client.d.ts +0 -20
  219. package/dist/core/sandbox/rpc/credential-client.d.ts.map +0 -1
  220. package/dist/core/sandbox/rpc/credential-client.js +0 -170
  221. package/dist/core/sandbox/rpc/credential-client.js.map +0 -1
  222. package/dist/core/sandbox/rpc/credential-proxy.d.ts +0 -44
  223. package/dist/core/sandbox/rpc/credential-proxy.d.ts.map +0 -1
  224. package/dist/core/sandbox/rpc/credential-proxy.js +0 -287
  225. package/dist/core/sandbox/rpc/credential-proxy.js.map +0 -1
  226. package/dist/core/sandbox/rpc/framing.d.ts +0 -35
  227. package/dist/core/sandbox/rpc/framing.d.ts.map +0 -1
  228. package/dist/core/sandbox/rpc/framing.js +0 -114
  229. package/dist/core/sandbox/rpc/framing.js.map +0 -1
  230. package/dist/core/sandbox/rpc/git-credential-helper.d.ts +0 -47
  231. package/dist/core/sandbox/rpc/git-credential-helper.d.ts.map +0 -1
  232. package/dist/core/sandbox/rpc/git-credential-helper.js +0 -222
  233. package/dist/core/sandbox/rpc/git-credential-helper.js.map +0 -1
  234. package/dist/core/sandbox/rpc/git-credential-proxy.d.ts +0 -66
  235. package/dist/core/sandbox/rpc/git-credential-proxy.d.ts.map +0 -1
  236. package/dist/core/sandbox/rpc/git-credential-proxy.js +0 -209
  237. package/dist/core/sandbox/rpc/git-credential-proxy.js.map +0 -1
  238. package/dist/core/sandbox/supervisor-temp.d.ts +0 -18
  239. package/dist/core/sandbox/supervisor-temp.d.ts.map +0 -1
  240. package/dist/core/sandbox/supervisor-temp.js +0 -21
  241. package/dist/core/sandbox/supervisor-temp.js.map +0 -1
  242. package/dist/core/sandbox/supervisor.d.ts +0 -62
  243. package/dist/core/sandbox/supervisor.d.ts.map +0 -1
  244. package/dist/core/sandbox/supervisor.js +0 -32
  245. package/dist/core/sandbox/supervisor.js.map +0 -1
  246. package/dist/core/sandbox/terminal-handoff.d.ts +0 -68
  247. package/dist/core/sandbox/terminal-handoff.d.ts.map +0 -1
  248. package/dist/core/sandbox/terminal-handoff.js +0 -221
  249. package/dist/core/sandbox/terminal-handoff.js.map +0 -1
  250. package/dist/core/sandbox/terminal-size.d.ts +0 -53
  251. package/dist/core/sandbox/terminal-size.d.ts.map +0 -1
  252. package/dist/core/sandbox/terminal-size.js +0 -126
  253. package/dist/core/sandbox/terminal-size.js.map +0 -1
  254. package/dist/core/sandbox/violations.d.ts +0 -23
  255. package/dist/core/sandbox/violations.d.ts.map +0 -1
  256. package/dist/core/sandbox/violations.js +0 -30
  257. package/dist/core/sandbox/violations.js.map +0 -1
package/dist/core/sdk.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { join } from "node:path";
2
2
  import { clampThinkingLevel, streamSimple } from "@earendil-works/pi-ai/compat";
3
- import { Agent, setDefaultStreamFn } from "apex-code-agent-core";
3
+ import { Agent, createRunBudgetController, setDefaultStreamFn, } from "apex-code-agent-core";
4
4
  import { getAgentDir } from "../config.js";
5
5
  import { resolvePath } from "../utils/paths.js";
6
6
  import { AgentSession } from "./agent-session.js";
@@ -8,6 +8,7 @@ import { formatNoModelsAvailableMessage } from "./auth-guidance.js";
8
8
  import { AuthStorage } from "./auth-storage.js";
9
9
  import { DEFAULT_THINKING_LEVEL } from "./defaults.js";
10
10
  import { createAgentDefinitionResolver } from "./delegation/agents.js";
11
+ import { ChildRunRegistry, } from "./delegation/runtime.js";
11
12
  import { loadHookRuntime } from "./hooks/loader.js";
12
13
  import { createSessionMcpConnector } from "./mcp/connector.js";
13
14
  import { createMcpRuntime } from "./mcp/runtime.js";
@@ -17,19 +18,16 @@ import { ModelRuntime } from "./model-runtime.js";
17
18
  import { DerivedPermissionRuleStore } from "./permissions/store.js";
18
19
  import { mergeProviderAttributionHeaders } from "./provider-attribution.js";
19
20
  import { DefaultResourceLoader } from "./resource-loader.js";
20
- import { SANDBOX_ENFORCEMENT_MARKER_VALUE, SANDBOX_ENFORCEMENT_MARKER_VARIABLE } from "./sandbox/cli-launch.js";
21
- import { createSandboxCredentialStore } from "./sandbox/rpc/credential-client.js";
22
21
  import { getDefaultSessionDir, SessionManager } from "./session-manager.js";
23
22
  import { SettingsManager } from "./settings-manager.js";
24
23
  import { time } from "./timings.js";
25
24
  import { createBackgroundShellRegistry } from "./tools/background-shell.js";
26
25
  import { createAllToolDefinitions, createBashTool, createCodingTools, createDelegateToolDefinition, createEditTool, createFindTool, createGrepTool, createLsTool, createPowerShellTool, createReadOnlyTools, createReadTool, createWriteTool, withFileMutationQueue, } from "./tools/index.js";
26
+ import { GitWorktreeWorkspaceOwner } from "./workspace/git-worktree-owner.js";
27
27
  // Preserve the pre-0.81 fallback for extensions that construct Agent instances
28
28
  // or invoke low-level agent loops without supplying streamFn. Agent core remains
29
29
  // provider-agnostic and does not import pi-ai/compat itself.
30
30
  setDefaultStreamFn(streamSimple);
31
- export const SDK_SANDBOX_ENFORCEMENT_MARKER_VARIABLE = SANDBOX_ENFORCEMENT_MARKER_VARIABLE;
32
- export const SDK_SANDBOX_ENFORCEMENT_MARKER_VALUE = SANDBOX_ENFORCEMENT_MARKER_VALUE;
33
31
  // Re-exports
34
32
  export * from "./agent-session-runtime.js";
35
33
  export { withFileMutationQueue,
@@ -91,16 +89,6 @@ function extractFinalAssistantText(session) {
91
89
  return "";
92
90
  }
93
91
  export async function createAgentSession(options = {}) {
94
- const sandboxContract = options.sandbox ?? "none";
95
- const markerPresent = process.env[SDK_SANDBOX_ENFORCEMENT_MARKER_VARIABLE] === SDK_SANDBOX_ENFORCEMENT_MARKER_VALUE;
96
- if (sandboxContract === "required" && !markerPresent) {
97
- throw new Error("SDK sandbox contract requires OS containment, but no enforcing supervisor marker is present.");
98
- }
99
- const sandboxDiagnostic = sandboxContract === "external"
100
- ? "Sandbox containment is external to the SDK; the SDK cannot verify OS containment."
101
- : sandboxContract === "none"
102
- ? "No OS containment is asserted by the SDK sandbox contract."
103
- : undefined;
104
92
  const cwd = resolvePath(options.cwd ?? options.sessionManager?.getCwd() ?? process.cwd());
105
93
  const agentDir = options.agentDir ? resolvePath(options.agentDir) : getDefaultAgentDir();
106
94
  let resourceLoader = options.resourceLoader;
@@ -110,6 +98,43 @@ export async function createAgentSession(options = {}) {
110
98
  const settingsManager = options.settingsManager ?? SettingsManager.create(cwd, agentDir);
111
99
  const sessionManager = options.sessionManager ?? SessionManager.create(cwd, getDefaultSessionDir(cwd, agentDir));
112
100
  const backgroundShellRegistry = options.backgroundShellRegistry ?? createBackgroundShellRegistry();
101
+ // One resolved per-run budget policy: the caller's override when supplied,
102
+ // else the settings policy. Children built below inherit this resolved value
103
+ // so a child run carries the same per-run policy as its parent without
104
+ // re-reading settings (spec 2026-09-09, "Shared budgets"). Per-run only: it
105
+ // never creates a shared ceiling.
106
+ const runBudget = options.runBudget ?? settingsManager.getRunBudget();
107
+ // The root aggregate budget (spec 2026-09-09, "Shared budgets"): created
108
+ // ONLY when the caller explicitly configures `aggregateBudget` -- never
109
+ // derived from the per-run policy. One controller per root tree: the
110
+ // parent's own Agent consumes it (composed under its per-prompt controller),
111
+ // and buildChildSession forwards THE SAME instance to every child regardless
112
+ // of depth, so a grandchild's consumption lands on the root ledger. Its wall
113
+ // time starts here, at root session construction, and no descendant can
114
+ // reset it: descendants only consume.
115
+ const aggregateBudgetController = options.aggregateBudget
116
+ ? createRunBudgetController(options.aggregateBudget)
117
+ : undefined;
118
+ // The shared ceiling this session consumes and hands to its delegated
119
+ // children: the root aggregate when configured, else a caller-supplied
120
+ // controller (which is how a delegated child receives its root's aggregate).
121
+ // With neither there is NO shared controller at all: every session keeps
122
+ // local-only budgets under its inherited per-run policy.
123
+ const sharedBudgetController = aggregateBudgetController ?? options.sharedBudgetController;
124
+ // The tree-facing usage snapshot: the aggregate controller's counters cover
125
+ // every accepted provider request, tool call, and maintenance request of the
126
+ // parent's own runs and of every descendant, so one read reports the tree.
127
+ const aggregateBudgetUsage = sharedBudgetController
128
+ ? () => {
129
+ const usage = sharedBudgetController.usage();
130
+ return {
131
+ providerRequests: usage.providerRequests,
132
+ toolCalls: usage.toolCalls,
133
+ maintenanceRequests: usage.maintenanceRequests,
134
+ startedAtMs: usage.startedAt,
135
+ };
136
+ }
137
+ : undefined;
113
138
  if (!resourceLoader) {
114
139
  resourceLoader = new DefaultResourceLoader({ cwd, agentDir, settingsManager });
115
140
  await resourceLoader.reload();
@@ -175,12 +200,11 @@ export async function createAgentSession(options = {}) {
175
200
  thinkingLevel = clampThinkingLevel(model, thinkingLevel);
176
201
  }
177
202
  // OAuth-capable MCP servers resolve tokens through the session's own credential
178
- // seam: direct and lock-serialized on the host, supervisor-mediated in a sandboxed
179
- // child, fail-closed when neither applies (spec 2026-09-01-mcp-oauth). Servers
180
- // without `auth: "oauth"` never touch it.
203
+ // seam: direct and lock-serialized, fail-closed when no store applies
204
+ // (spec 2026-09-01-mcp-oauth). Servers without `auth: "oauth"` never touch it.
181
205
  const mcpRuntime = createMcpRuntime(cwd, createSessionMcpConnector({
182
- credentials: options.mcpCredentials ?? createSandboxCredentialStore() ?? AuthStorage.create(authPath),
183
- }), { projectTrusted: settingsManager.isProjectTrusted() });
206
+ credentials: options.mcpCredentials ?? AuthStorage.create(authPath),
207
+ }), { projectTrusted: settingsManager.isProjectTrusted(), agentDir });
184
208
  // `web_search`, `lsp`, and `mcp` join the core four only when configured. All stay
185
209
  // registered either way, so an explicit `--tools` selection still reaches them and an
186
210
  // unconfigured call still explains itself. Activating an unconfigured tool would put a
@@ -235,10 +259,16 @@ export async function createAgentSession(options = {}) {
235
259
  thinkingLevel,
236
260
  tools: [],
237
261
  },
238
- // One normalized budget policy from settings; every execution mode (interactive,
239
- // print, JSON, RPC, ACP, SDK) runs through this session, so none counts on its
240
- // own (spec 2026-09-01-tool-reliability-and-execution-budgets.md).
241
- runBudget: settingsManager.getRunBudget(),
262
+ // The resolved budget policy from above (override or settings); every
263
+ // execution mode (interactive, print, JSON, RPC, ACP, SDK) runs through
264
+ // this session, so none counts on its own
265
+ // (spec 2026-09-01-tool-reliability-and-execution-budgets.md).
266
+ runBudget,
267
+ budgetScope: options.budgetScope ?? "prompt",
268
+ // The root aggregate when configured (the parent's own runs consume it,
269
+ // per-prompt local scope unchanged), else a caller-supplied shared
270
+ // controller, else nothing (spec 2026-09-09, "Shared budgets").
271
+ sharedBudgetController,
242
272
  convertToLlm: convertToLlmWithBlockImages,
243
273
  streamFn: async (model, context, options) => {
244
274
  const providerRetrySettings = settingsManager.getProviderRetrySettings();
@@ -313,11 +343,13 @@ export async function createAgentSession(options = {}) {
313
343
  // must read the *live* session (active tools can change after construction),
314
344
  // not a snapshot frozen at this point.
315
345
  const parentSessionRef = {};
346
+ const childRunRegistry = options.childRunRegistry ?? new ChildRunRegistry();
316
347
  const customTools = options.customTools ? [...options.customTools] : [];
317
348
  // A configured permission gate supplies the authority a child must derive, so
318
349
  // discovery is enabled by default in real sessions. Callers that need fixture
319
350
  // definitions may still provide an explicit resolver.
320
351
  const delegation = options.delegation ?? (options.permissionGate ? {} : undefined);
352
+ let delegationRuntime;
321
353
  if (delegation) {
322
354
  const parentPermissionGate = options.permissionGate;
323
355
  const resolveAgent = delegation.resolveAgent ??
@@ -326,8 +358,17 @@ export async function createAgentSession(options = {}) {
326
358
  agentDir,
327
359
  isProjectTrusted: () => settingsManager.isProjectTrusted(),
328
360
  });
329
- const delegationRuntime = {
361
+ let worktreeOwner;
362
+ // One read of the delegation bound for this runtime; the policy snapshot
363
+ // below reports the same value it enforces instead of re-reading settings.
364
+ const delegationMaxDepth = settingsManager.getDelegationMaxDepth();
365
+ delegationRuntime = {
366
+ childRunRegistry,
330
367
  resolveAgent,
368
+ // Concurrency cap (spec 2026-09-09, "Shared budgets"): the registry's
369
+ // admission path reads it from the runtime options. Undefined is
370
+ // unlimited.
371
+ maxConcurrentChildren: options.maxConcurrentChildren,
331
372
  getParentCapabilities: () => {
332
373
  const capabilities = new Set();
333
374
  for (const name of parentSessionRef.current?.getActiveToolNames() ?? []) {
@@ -356,9 +397,23 @@ export async function createAgentSession(options = {}) {
356
397
  // the (possibly delegated) session this createAgentSession call is
357
398
  // building, via the same sessionManager this closure already captures.
358
399
  getDelegationDepth: () => sessionManager.getDelegationDepth(),
359
- maxDelegationDepth: settingsManager.getDelegationMaxDepth(),
400
+ maxDelegationDepth: delegationMaxDepth,
360
401
  getParentSessionDir: () => sessionManager.getSessionDir(),
361
- buildChildSession: async ({ definition, toolNames, depth, sessionId, artifactDir }) => {
402
+ // Child-run records carry the parent session id so durable records and
403
+ // protocol payloads tie each child to this session's own identity.
404
+ getParentSessionId: () => sessionManager.getSessionId(),
405
+ // Worktree isolation (spec 2026-09-09, "Parallel work and ownership"):
406
+ // the production workspace owner, constructed on the first worktree-
407
+ // isolated delegation only. Shared-read delegations never read this
408
+ // property (the runtime consults the owner only for isolation
409
+ // "worktree"), so they never construct an owner and never invoke git.
410
+ get workspaceOwner() {
411
+ if (worktreeOwner === undefined) {
412
+ worktreeOwner = new GitWorktreeWorkspaceOwner(cwd);
413
+ }
414
+ return worktreeOwner;
415
+ },
416
+ buildChildSession: async ({ definition, toolNames, capabilities, depth, sessionId, artifactDir, workspace, reattachSessionPath, timeoutMs, }) => {
362
417
  if (!parentPermissionGate) {
363
418
  throw new Error("delegate requires a permission gate configured on the parent session (ADR 0008).");
364
419
  }
@@ -367,23 +422,77 @@ export async function createAgentSession(options = {}) {
367
422
  if (!childModel) {
368
423
  throw new Error(`Cannot delegate to "${definition.name}": no model is available for the child session.`);
369
424
  }
370
- const childSessionManager = artifactDir
371
- ? SessionManager.create(cwd, artifactDir, {
372
- id: sessionId,
373
- parentSession: sessionManager.getSessionId(),
374
- delegationDepth: depth,
375
- })
376
- : SessionManager.inMemory(cwd, {
377
- parentSession: sessionManager.getSessionId(),
378
- delegationDepth: depth,
379
- });
425
+ // The derived policy snapshot (spec 2026-09-09, "Derive, do not
426
+ // reconstruct"): the exact values this construction used -- the
427
+ // ceiling-checked tool allowlist, the admitted capability set from
428
+ // the runtime's single admission projection (never a second
429
+ // classification, ADR 0010), the delegation bound it enforces, the
430
+ // child's model, and the budget wiring every child is built with
431
+ // (session-scoped own controller; aggregate only when a shared ceiling
432
+ // was configured at the root). It rides on the returned handle so the
433
+ // registry persists it on the child-run record.
434
+ const policy = {
435
+ tools: [...toolNames],
436
+ capabilities: [...capabilities].sort(),
437
+ maxDelegationDepth: delegationMaxDepth,
438
+ model: childModel.id,
439
+ budgetScope: "session",
440
+ aggregateBudget: sharedBudgetController !== undefined,
441
+ };
442
+ // An isolated child runs inside the workspace owner's prepared root
443
+ // (its linked worktree); a shared child keeps this session's cwd.
444
+ const childCwd = workspace?.root ?? cwd;
445
+ // The one reattachment seam (restart reconstruction): when the request
446
+ // carries the existing child session path, that transcript is OPENED
447
+ // rather than created; identity (header id, parentSession, depth) comes
448
+ // from the file's own header. Every other input below is derived
449
+ // exactly as for a fresh delegation -- permission store, model, tools,
450
+ // budget -- so there is no second construction path.
451
+ const childSessionManager = reattachSessionPath
452
+ ? SessionManager.open(reattachSessionPath, artifactDir)
453
+ : artifactDir
454
+ ? SessionManager.create(childCwd, artifactDir, {
455
+ id: sessionId,
456
+ parentSession: sessionManager.getSessionId(),
457
+ delegationDepth: depth,
458
+ })
459
+ : SessionManager.inMemory(childCwd, {
460
+ parentSession: sessionManager.getSessionId(),
461
+ delegationDepth: depth,
462
+ });
380
463
  const derivedStore = new DerivedPermissionRuleStore({ parent: parentPermissionGate.store });
381
464
  const { session: childSession } = await createAgentSession({
382
- cwd,
465
+ cwd: childCwd,
383
466
  agentDir,
384
467
  model: childModel,
385
468
  modelRuntime,
386
469
  settingsManager,
470
+ // The parent's already-resolved per-run budget (override or settings),
471
+ // not a fresh settings read: every child run carries the same per-run
472
+ // policy as its parent (spec 2026-09-09, "Shared budgets").
473
+ //
474
+ // Session scope + the root aggregate: a resumed or follow-up child
475
+ // turn continues the same child budget (one controller per child
476
+ // Agent, created at its first turn), and every gate also answers
477
+ // to the tree's shared ceiling -- accepted consumption records to
478
+ // both the child's own controller and the aggregate. THE SAME
479
+ // aggregate instance reaches every descendant regardless of
480
+ // depth (a grandchild's parent forwards it onward), so there is
481
+ // exactly ONE aggregate per root tree and no child can reset it
482
+ // by resuming or spawning children (spec 2026-09-09, "Shared
483
+ // budgets"). Without an aggregate there is no shared controller:
484
+ // the child keeps local-only budgets under the inherited policy.
485
+ //
486
+ // A launch timeout (spec 2026-09-09, timeouts) overrides the
487
+ // child's OWN wall-time limit with the requested timeoutMs, so the
488
+ // existing AgentRunBudget wall-time gate enforces it mid-run; the
489
+ // parent policy's other limits still apply unchanged.
490
+ runBudget: timeoutMs === undefined ? runBudget : { ...runBudget, maxWallTimeMs: timeoutMs },
491
+ budgetScope: "session",
492
+ sharedBudgetController,
493
+ // The same per-parent admission cap applies to a child's own
494
+ // delegations (each runtime counts its direct children).
495
+ maxConcurrentChildren: options.maxConcurrentChildren,
387
496
  sessionManager: childSessionManager,
388
497
  resourceLoader,
389
498
  tools: toolNames,
@@ -409,27 +518,188 @@ export async function createAgentSession(options = {}) {
409
518
  // header on its next createAgentSession call) -- otherwise recursion
410
519
  // would be silently capped at one level regardless of maxDelegationDepth.
411
520
  delegation,
412
- sandbox: sandboxContract,
413
521
  });
414
522
  await childSession.bindExtensions({});
523
+ let status = "idle";
524
+ let runPromise;
525
+ let lastTurnResult;
526
+ // The captured structured terminal outcome of the run in flight
527
+ // (`agent_end`'s stopReason). Budget refusals emit agent_end without
528
+ // an assistant message, so this is the only way the settlement below
529
+ // can see and name them.
530
+ let runStopReason;
531
+ const ensureOpen = () => {
532
+ if (status === "closed")
533
+ throw new Error("Child session is closed.");
534
+ };
535
+ const start = (input) => {
536
+ ensureOpen();
537
+ if (runPromise)
538
+ throw new Error("Child session is already running.");
539
+ status = "running";
540
+ runStopReason = undefined;
541
+ // Capture the run's structured terminal outcome: a budget refusal
542
+ // emits agent_end without producing an assistant message, so the
543
+ // message-based checks below cannot see it.
544
+ const unsubscribeStop = childSession.subscribe((event) => {
545
+ if (event.type === "agent_end")
546
+ runStopReason = event.stopReason;
547
+ });
548
+ const pending = childSession
549
+ .prompt(input)
550
+ .then(() => {
551
+ unsubscribeStop();
552
+ if (status === "closed") {
553
+ lastTurnResult = { outcome: "failed", output: "" };
554
+ throw new Error("Child session is closed.");
555
+ }
556
+ if (status === "interrupted") {
557
+ lastTurnResult = { outcome: "interrupted", output: extractFinalAssistantText(childSession) };
558
+ throw new Error("Child session is interrupted.");
559
+ }
560
+ const lastAssistant = [...childSession.agent.state.messages]
561
+ .reverse()
562
+ .find((message) => message.role === "assistant");
563
+ if (lastAssistant?.role === "assistant" &&
564
+ (lastAssistant.stopReason === "aborted" || lastAssistant.stopReason === "error")) {
565
+ status = "interrupted";
566
+ lastTurnResult = {
567
+ outcome: lastAssistant.stopReason === "aborted" ? "interrupted" : "failed",
568
+ output: lastAssistant.errorMessage || extractFinalAssistantText(childSession),
569
+ };
570
+ throw new Error(lastAssistant.errorMessage || "Child session was interrupted.");
571
+ }
572
+ status = "idle";
573
+ lastTurnResult = { outcome: "completed", output: extractFinalAssistantText(childSession) };
574
+ }, (error) => {
575
+ unsubscribeStop();
576
+ if (status !== "closed")
577
+ status = "interrupted";
578
+ lastTurnResult =
579
+ status === "closed"
580
+ ? { outcome: "failed", output: "" }
581
+ : { outcome: "interrupted", output: extractFinalAssistantText(childSession) };
582
+ throw error;
583
+ })
584
+ .finally(() => {
585
+ runPromise = undefined;
586
+ });
587
+ runPromise = pending;
588
+ return pending;
589
+ };
590
+ const sendInput = (input) => {
591
+ ensureOpen();
592
+ if (runPromise) {
593
+ if (status !== "running")
594
+ throw new Error("Child session is still stopping.");
595
+ // A follow-up queued onto a live run settles through that run's
596
+ // own loop; only turns that settle here pass through the gate.
597
+ return childSession.followUp(input);
598
+ }
599
+ return start(input).then(() => requireVerifiedTurn());
600
+ };
601
+ const close = () => {
602
+ if (status === "closed")
603
+ return;
604
+ status = "closed";
605
+ childSession.dispose();
606
+ };
607
+ // Verification gate (spec 2026-09-09, "Verification at completion"):
608
+ // a child with configured verification policies may not settle a turn
609
+ // as successful unless the session's canonical tracker reports
610
+ // "verified". The tracker is the only verifier; no child-local one is
611
+ // introduced. With the "explicit" boundary the gate runs
612
+ // requestVerification once here; with the "post-turn" boundary the
613
+ // session already ran verification at its own turn boundary, so the
614
+ // gate only reads the status. Any other completion status --
615
+ // failed/unavailable/interrupted/continued-unverified -- fails the
616
+ // turn with the status named in the error.
617
+ const requireVerifiedTurn = async () => {
618
+ const policyIds = childSession.verificationPolicyIds();
619
+ if (policyIds.length === 0)
620
+ return;
621
+ const verificationStatus = childSession.verificationBoundary() === "explicit"
622
+ ? await childSession.requestVerification()
623
+ : childSession.verificationStatus();
624
+ if (verificationStatus === "verified")
625
+ return;
626
+ const message = `Child verification failed: ${verificationStatus} (policies: ${policyIds.join(", ")}).`;
627
+ lastTurnResult = { outcome: "failed", output: message };
628
+ throw new Error(message);
629
+ };
415
630
  return {
631
+ get status() {
632
+ return status;
633
+ },
634
+ latestResult: () => lastTurnResult,
635
+ usage: () => childSession.agent.budgetUsage(),
636
+ // The usage-rollup seam (spec 2026-09-09): the registry reads the
637
+ // child's own transcript file when one exists, and falls back to
638
+ // these in-memory entries only for a child that never persisted a
639
+ // transcript. Read-only; the child's SessionManager stays the owner.
640
+ sessionEntries: () => childSessionManager.getEntries(),
416
641
  async run(task) {
417
642
  const prompt = definition.systemPrompt
418
643
  ? `${definition.systemPrompt}\n\nTask: ${task}`
419
644
  : `Task: ${task}`;
420
- await childSession.prompt(prompt);
645
+ await start(prompt);
646
+ if (status === "closed" || status === "interrupted")
647
+ throw new Error(`Child session is ${status}.`);
648
+ // The launch timeout's settlement error path (spec 2026-09-09,
649
+ // timeouts): a run refused at the child's own wall-time gate never
650
+ // started meaningful work, so the delegation settles failed with
651
+ // the exhausted limit named, reusing the budget's existing
652
+ // exhaustedLimit vocabulary. Other budget refusals keep their
653
+ // existing semantics (the refusal is named in the child transcript
654
+ // and the prior result stands).
655
+ if (runStopReason?.kind === "budget-exhausted" && runStopReason.limit === "wall-time") {
656
+ const message = `Child run stopped before completing: the run's ${runStopReason.limit} budget was exhausted.`;
657
+ lastTurnResult = { outcome: "failed", output: message };
658
+ throw new Error(message);
659
+ }
660
+ // Delegated work cannot claim success when the child has a
661
+ // required verification boundary and verification did not pass.
662
+ // Use the session's canonical tracker rather than introducing
663
+ // a second child-only verifier.
664
+ await requireVerifiedTurn();
421
665
  return { output: extractFinalAssistantText(childSession) };
422
666
  },
423
- dispose() {
424
- childSession.dispose();
667
+ wait: async () => {
668
+ while (runPromise)
669
+ await runPromise;
670
+ },
671
+ interrupt: () => {
672
+ ensureOpen();
673
+ if (status === "running") {
674
+ status = "interrupted";
675
+ void childSession.abort().catch(() => undefined);
676
+ }
425
677
  },
678
+ close,
679
+ sendInput,
680
+ followUp: sendInput,
681
+ dispose: close,
682
+ // Construction linkage (spec 2026-09-09, policy/artifact
683
+ // linkage): the registry relays these onto the persisted child_run
684
+ // record and every status/wait payload derived from it.
685
+ policy,
426
686
  };
427
687
  },
428
688
  };
429
689
  customTools.push(createDelegateToolDefinition(delegationRuntime));
690
+ // Historical resume (restart reconstruction) reattaches a persisted child
691
+ // through THIS runtime's buildChildSession seam. Wiring it here -- not only
692
+ // at the first delegation -- is what makes resume work on a freshly
693
+ // reopened session before any new delegation has run.
694
+ childRunRegistry.setRuntimeOptions(delegationRuntime);
430
695
  }
431
696
  const session = new AgentSession({
697
+ childRunRegistry,
698
+ delegationRuntime,
432
699
  agent,
700
+ // Root aggregate usage (spec 2026-09-09, "Shared budgets"): undefined
701
+ // when the tree has no aggregate budget.
702
+ aggregateBudgetUsage,
433
703
  sessionManager,
434
704
  settingsManager,
435
705
  cwd,
@@ -466,9 +736,8 @@ export async function createAgentSession(options = {}) {
466
736
  return {
467
737
  session,
468
738
  extensionsResult,
739
+ delegationRuntime,
469
740
  modelFallbackMessage,
470
- sandboxContract,
471
- sandboxDiagnostic,
472
741
  };
473
742
  }
474
743
  //# sourceMappingURL=sdk.js.map