@gajae-code/coding-agent 0.5.1 → 0.5.3

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 (165) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/README.md +1 -1
  3. package/dist/types/async/job-manager.d.ts +6 -0
  4. package/dist/types/cli/setup-cli.d.ts +8 -1
  5. package/dist/types/commands/setup.d.ts +7 -0
  6. package/dist/types/config/file-lock.d.ts +24 -2
  7. package/dist/types/config/model-registry.d.ts +4 -0
  8. package/dist/types/config/models-config-schema.d.ts +5 -0
  9. package/dist/types/config/settings-schema.d.ts +62 -0
  10. package/dist/types/dap/client.d.ts +2 -1
  11. package/dist/types/edit/read-file.d.ts +6 -0
  12. package/dist/types/eval/js/context-manager.d.ts +3 -0
  13. package/dist/types/eval/js/executor.d.ts +1 -0
  14. package/dist/types/exec/bash-executor.d.ts +2 -0
  15. package/dist/types/gjc-runtime/state-writer.d.ts +64 -2
  16. package/dist/types/gjc-runtime/tmux-sessions.d.ts +7 -1
  17. package/dist/types/gjc-runtime/ultragoal-guard.d.ts +10 -0
  18. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +29 -0
  19. package/dist/types/lsp/types.d.ts +2 -0
  20. package/dist/types/modes/bridge/bridge-mode.d.ts +1 -0
  21. package/dist/types/modes/components/model-selector.d.ts +2 -0
  22. package/dist/types/modes/components/oauth-selector.d.ts +1 -0
  23. package/dist/types/modes/components/provider-onboarding-selector.d.ts +1 -1
  24. package/dist/types/modes/components/runtime-mcp-add-wizard.d.ts +1 -0
  25. package/dist/types/modes/components/tool-execution.d.ts +1 -0
  26. package/dist/types/modes/interactive-mode.d.ts +1 -1
  27. package/dist/types/modes/rpc/rpc-mode.d.ts +56 -1
  28. package/dist/types/modes/shared/agent-wire/unattended-session.d.ts +10 -0
  29. package/dist/types/modes/theme/defaults/index.d.ts +302 -0
  30. package/dist/types/modes/theme/theme.d.ts +1 -0
  31. package/dist/types/modes/types.d.ts +1 -1
  32. package/dist/types/runtime/process-lifecycle.d.ts +108 -0
  33. package/dist/types/runtime-mcp/transports/stdio.d.ts +1 -0
  34. package/dist/types/runtime-mcp/types.d.ts +2 -0
  35. package/dist/types/session/agent-session.d.ts +17 -1
  36. package/dist/types/session/artifacts.d.ts +4 -1
  37. package/dist/types/session/history-storage.d.ts +2 -2
  38. package/dist/types/session/session-manager.d.ts +10 -1
  39. package/dist/types/session/streaming-output.d.ts +5 -0
  40. package/dist/types/setup/credential-import.d.ts +79 -0
  41. package/dist/types/slash-commands/helpers/fast-status-report.d.ts +76 -0
  42. package/dist/types/task/executor.d.ts +1 -0
  43. package/dist/types/task/render.d.ts +1 -1
  44. package/dist/types/tools/bash.d.ts +1 -0
  45. package/dist/types/tools/browser/tab-supervisor.d.ts +9 -0
  46. package/dist/types/tools/sqlite-reader.d.ts +2 -1
  47. package/dist/types/tools/subagent-render.d.ts +7 -1
  48. package/dist/types/tools/subagent.d.ts +21 -0
  49. package/dist/types/tools/ultragoal-ask-guard.d.ts +5 -0
  50. package/dist/types/web/search/index.d.ts +4 -4
  51. package/dist/types/web/search/provider.d.ts +16 -20
  52. package/dist/types/web/search/providers/base.d.ts +2 -1
  53. package/dist/types/web/search/providers/openai-compatible.d.ts +9 -0
  54. package/dist/types/web/search/types.d.ts +14 -2
  55. package/package.json +7 -7
  56. package/scripts/build-binary.ts +7 -0
  57. package/src/async/job-manager.ts +153 -39
  58. package/src/cli/args.ts +2 -0
  59. package/src/cli/fast-help.ts +2 -0
  60. package/src/cli/setup-cli.ts +138 -3
  61. package/src/commands/setup.ts +5 -1
  62. package/src/commands/ultragoal.ts +3 -1
  63. package/src/config/file-lock-gc.ts +14 -2
  64. package/src/config/file-lock.ts +63 -13
  65. package/src/config/model-profile-activation.ts +15 -3
  66. package/src/config/model-profiles.ts +15 -15
  67. package/src/config/model-registry.ts +21 -1
  68. package/src/config/models-config-schema.ts +1 -0
  69. package/src/config/settings-schema.ts +62 -0
  70. package/src/dap/client.ts +105 -64
  71. package/src/dap/session.ts +44 -7
  72. package/src/defaults/gjc/skills/ultragoal/SKILL.md +30 -8
  73. package/src/edit/read-file.ts +19 -1
  74. package/src/eval/js/context-manager.ts +228 -65
  75. package/src/eval/js/executor.ts +2 -0
  76. package/src/eval/js/index.ts +1 -0
  77. package/src/eval/js/worker-core.ts +10 -6
  78. package/src/eval/py/executor.ts +68 -19
  79. package/src/eval/py/kernel.ts +46 -22
  80. package/src/eval/py/runner.py +68 -14
  81. package/src/exec/bash-executor.ts +49 -13
  82. package/src/gjc-runtime/deep-interview-recorder.ts +40 -0
  83. package/src/gjc-runtime/launch-tmux.ts +3 -4
  84. package/src/gjc-runtime/ralplan-runtime.ts +174 -12
  85. package/src/gjc-runtime/state-runtime.ts +2 -1
  86. package/src/gjc-runtime/state-writer.ts +254 -7
  87. package/src/gjc-runtime/tmux-gc.ts +88 -38
  88. package/src/gjc-runtime/tmux-sessions.ts +44 -6
  89. package/src/gjc-runtime/ultragoal-guard.ts +155 -0
  90. package/src/gjc-runtime/ultragoal-runtime.ts +1227 -31
  91. package/src/gjc-runtime/workflow-manifest.generated.json +44 -0
  92. package/src/gjc-runtime/workflow-manifest.ts +12 -0
  93. package/src/harness-control-plane/owner.ts +3 -2
  94. package/src/harness-control-plane/rpc-adapter.ts +1 -1
  95. package/src/hooks/skill-state.ts +121 -2
  96. package/src/internal-urls/artifact-protocol.ts +10 -1
  97. package/src/internal-urls/docs-index.generated.ts +14 -10
  98. package/src/lsp/client.ts +64 -26
  99. package/src/lsp/defaults.json +1 -0
  100. package/src/lsp/index.ts +2 -1
  101. package/src/lsp/lspmux.ts +33 -9
  102. package/src/lsp/types.ts +2 -0
  103. package/src/main.ts +14 -4
  104. package/src/modes/acp/acp-agent.ts +4 -2
  105. package/src/modes/bridge/bridge-mode.ts +23 -1
  106. package/src/modes/components/assistant-message.ts +10 -2
  107. package/src/modes/components/bash-execution.ts +5 -1
  108. package/src/modes/components/eval-execution.ts +5 -1
  109. package/src/modes/components/history-search.ts +5 -2
  110. package/src/modes/components/model-selector.ts +60 -2
  111. package/src/modes/components/oauth-selector.ts +5 -0
  112. package/src/modes/components/provider-onboarding-selector.ts +6 -1
  113. package/src/modes/components/runtime-mcp-add-wizard.ts +58 -7
  114. package/src/modes/components/skill-message.ts +24 -16
  115. package/src/modes/components/tool-execution.ts +6 -0
  116. package/src/modes/controllers/extension-ui-controller.ts +33 -6
  117. package/src/modes/controllers/input-controller.ts +5 -0
  118. package/src/modes/controllers/selector-controller.ts +86 -2
  119. package/src/modes/interactive-mode.ts +11 -1
  120. package/src/modes/rpc/rpc-mode.ts +132 -18
  121. package/src/modes/shared/agent-wire/command-dispatch.ts +5 -2
  122. package/src/modes/shared/agent-wire/host-tool-bridge.ts +3 -0
  123. package/src/modes/shared/agent-wire/unattended-session.ts +16 -1
  124. package/src/modes/theme/defaults/claude-code.json +100 -0
  125. package/src/modes/theme/defaults/codex.json +100 -0
  126. package/src/modes/theme/defaults/index.ts +6 -0
  127. package/src/modes/theme/defaults/opencode.json +102 -0
  128. package/src/modes/theme/theme.ts +2 -2
  129. package/src/modes/types.ts +1 -1
  130. package/src/modes/utils/ui-helpers.ts +5 -2
  131. package/src/prompts/agents/executor.md +5 -2
  132. package/src/runtime/process-lifecycle.ts +400 -0
  133. package/src/runtime-mcp/manager.ts +164 -50
  134. package/src/runtime-mcp/transports/http.ts +12 -11
  135. package/src/runtime-mcp/transports/stdio.ts +64 -38
  136. package/src/runtime-mcp/types.ts +3 -0
  137. package/src/sdk.ts +39 -1
  138. package/src/session/agent-session.ts +190 -33
  139. package/src/session/artifacts.ts +17 -2
  140. package/src/session/blob-store.ts +36 -2
  141. package/src/session/history-storage.ts +32 -11
  142. package/src/session/session-manager.ts +99 -31
  143. package/src/session/streaming-output.ts +54 -3
  144. package/src/setup/credential-import.ts +429 -0
  145. package/src/skill-state/deep-interview-mutation-guard.ts +2 -1
  146. package/src/slash-commands/builtin-registry.ts +30 -3
  147. package/src/slash-commands/helpers/fast-status-report.ts +111 -0
  148. package/src/task/executor.ts +7 -1
  149. package/src/task/render.ts +18 -7
  150. package/src/tools/archive-reader.ts +10 -1
  151. package/src/tools/ask.ts +4 -2
  152. package/src/tools/bash.ts +11 -4
  153. package/src/tools/browser/tab-supervisor.ts +22 -0
  154. package/src/tools/browser.ts +38 -4
  155. package/src/tools/cron.ts +1 -1
  156. package/src/tools/read.ts +11 -12
  157. package/src/tools/sqlite-reader.ts +19 -5
  158. package/src/tools/subagent-render.ts +119 -29
  159. package/src/tools/subagent.ts +147 -7
  160. package/src/tools/ultragoal-ask-guard.ts +39 -0
  161. package/src/web/search/index.ts +25 -25
  162. package/src/web/search/provider.ts +178 -87
  163. package/src/web/search/providers/base.ts +2 -1
  164. package/src/web/search/providers/openai-compatible.ts +151 -0
  165. package/src/web/search/types.ts +47 -22
@@ -5,7 +5,8 @@
5
5
  * Messages are newline-delimited JSON.
6
6
  */
7
7
 
8
- import { getProjectDir, ptree, readJsonl, Snowflake } from "@gajae-code/utils";
8
+ import { getProjectDir, readJsonl, Snowflake } from "@gajae-code/utils";
9
+ import { type OwnedProcess, spawnOwnedProcess } from "../../runtime/process-lifecycle";
9
10
  import type {
10
11
  JsonRpcError,
11
12
  JsonRpcMessage,
@@ -24,7 +25,7 @@ import { toJsonRpcError } from "../../runtime-mcp/types";
24
25
  const CLOSE_WAIT_MS = 1_000;
25
26
 
26
27
  export class StdioTransport implements MCPTransport {
27
- #process: ptree.ChildProcess<"pipe"> | null = null;
28
+ #process: OwnedProcess | null = null;
28
29
  #pendingRequests = new Map<
29
30
  string | number,
30
31
  {
@@ -34,6 +35,8 @@ export class StdioTransport implements MCPTransport {
34
35
  >();
35
36
  #connected = false;
36
37
  #readLoop: Promise<void> | null = null;
38
+ #stderrLoop: Promise<void> | null = null;
39
+ #closePromise: Promise<void> | null = null;
37
40
 
38
41
  onClose?: () => void;
39
42
  onError?: (error: Error) => void;
@@ -46,10 +49,17 @@ export class StdioTransport implements MCPTransport {
46
49
  return this.#connected;
47
50
  }
48
51
 
52
+ get closeBeforeReconnect(): true {
53
+ return true;
54
+ }
55
+
49
56
  /**
50
57
  * Start the subprocess and begin reading.
51
58
  */
52
59
  async connect(): Promise<void> {
60
+ if (this.#closePromise) {
61
+ throw new Error("Transport is closing");
62
+ }
53
63
  if (this.#connected) return;
54
64
 
55
65
  const args = this.config.args ?? [];
@@ -58,11 +68,12 @@ export class StdioTransport implements MCPTransport {
58
68
  ...this.config.env,
59
69
  };
60
70
 
61
- this.#process = ptree.spawn([this.config.command, ...args], {
71
+ this.#process = spawnOwnedProcess([this.config.command, ...args], {
62
72
  cwd: this.config.cwd ?? getProjectDir(),
63
73
  env,
64
74
  stdin: "pipe",
65
- stderr: "full",
75
+ gracefulMs: CLOSE_WAIT_MS,
76
+ name: `mcp-stdio:${this.config.command}`,
66
77
  });
67
78
 
68
79
  this.#connected = true;
@@ -71,13 +82,13 @@ export class StdioTransport implements MCPTransport {
71
82
  this.#readLoop = this.#startReadLoop();
72
83
 
73
84
  // Log stderr for debugging
74
- this.#startStderrLoop();
85
+ this.#stderrLoop = this.#startStderrLoop();
75
86
  }
76
87
 
77
88
  async #startReadLoop(): Promise<void> {
78
- if (!this.#process?.stdout) return;
89
+ if (!this.#process?.child.stdout) return;
79
90
  try {
80
- for await (const line of readJsonl(this.#process.stdout)) {
91
+ for await (const line of readJsonl(this.#process.child.stdout)) {
81
92
  if (!this.#connected) break;
82
93
  try {
83
94
  this.#handleMessage(line as JsonRpcMessage);
@@ -95,9 +106,9 @@ export class StdioTransport implements MCPTransport {
95
106
  }
96
107
 
97
108
  async #startStderrLoop(): Promise<void> {
98
- if (!this.#process?.stderr) return;
109
+ if (!this.#process?.child.stderr) return;
99
110
 
100
- const reader = this.#process.stderr.getReader();
111
+ const reader = this.#process.child.stderr.getReader();
101
112
  const decoder = new TextDecoder();
102
113
 
103
114
  try {
@@ -168,26 +179,23 @@ export class StdioTransport implements MCPTransport {
168
179
  }
169
180
  }
170
181
 
182
+ #getStdin(): Bun.FileSink | null {
183
+ const stdin = this.#process?.child.stdin;
184
+ return typeof stdin === "object" && stdin !== null ? stdin : null;
185
+ }
186
+
171
187
  #sendResponse(id: string | number, result?: unknown, error?: JsonRpcError): void {
172
- if (!this.#connected || !this.#process?.stdin) return;
188
+ const stdin = this.#getStdin();
189
+ if (!this.#connected || !stdin) return;
173
190
  const response = error
174
191
  ? { jsonrpc: "2.0" as const, id, error }
175
192
  : { jsonrpc: "2.0" as const, id, result: result ?? {} };
176
- this.#process.stdin.write(`${JSON.stringify(response)}\n`);
177
- this.#process.stdin.flush();
193
+ stdin.write(`${JSON.stringify(response)}\n`);
194
+ stdin.flush();
178
195
  }
179
196
 
180
197
  #handleClose(): void {
181
- if (!this.#connected) return;
182
- this.#connected = false;
183
-
184
- // Reject all pending requests
185
- for (const [, pending] of this.#pendingRequests) {
186
- pending.reject(new Error("Transport closed"));
187
- }
188
- this.#pendingRequests.clear();
189
-
190
- this.onClose?.();
198
+ void this.#closeInternal(true);
191
199
  }
192
200
 
193
201
  async request<T = unknown>(
@@ -195,7 +203,8 @@ export class StdioTransport implements MCPTransport {
195
203
  params?: Record<string, unknown>,
196
204
  options?: MCPRequestOptions,
197
205
  ): Promise<T> {
198
- if (!this.#connected || !this.#process?.stdin) {
206
+ const stdin = this.#getStdin();
207
+ if (!this.#connected || !stdin) {
199
208
  throw new Error("Transport not connected");
200
209
  }
201
210
 
@@ -261,8 +270,8 @@ export class StdioTransport implements MCPTransport {
261
270
  const message = `${JSON.stringify(request)}\n`;
262
271
  try {
263
272
  // Bun's FileSink has write() method directly
264
- this.#process.stdin.write(message);
265
- this.#process.stdin.flush();
273
+ stdin.write(message);
274
+ stdin.flush();
266
275
  } catch (error: unknown) {
267
276
  cleanup();
268
277
  reject(error instanceof Error ? error : new Error(String(error)));
@@ -272,7 +281,8 @@ export class StdioTransport implements MCPTransport {
272
281
  }
273
282
 
274
283
  async notify(method: string, params?: Record<string, unknown>): Promise<void> {
275
- if (!this.#connected || !this.#process?.stdin) {
284
+ const stdin = this.#getStdin();
285
+ if (!this.#connected || !stdin) {
276
286
  throw new Error("Transport not connected");
277
287
  }
278
288
 
@@ -284,35 +294,51 @@ export class StdioTransport implements MCPTransport {
284
294
 
285
295
  const message = `${JSON.stringify(notification)}\n`;
286
296
  // Bun's FileSink has write() method directly
287
- this.#process.stdin.write(message);
288
- this.#process.stdin.flush();
297
+ stdin.write(message);
298
+ stdin.flush();
289
299
  }
290
300
 
291
301
  async close(): Promise<void> {
292
- if (!this.#connected) return;
302
+ await this.#closeInternal(false);
303
+ }
304
+
305
+ #closeInternal(fromReadLoop: boolean): Promise<void> {
306
+ if (this.#closePromise) return this.#closePromise;
307
+ this.#closePromise = this.#finishClose(fromReadLoop).finally(() => {
308
+ this.#closePromise = null;
309
+ });
310
+ return this.#closePromise;
311
+ }
312
+
313
+ async #finishClose(fromReadLoop: boolean): Promise<void> {
314
+ const wasConnected = this.#connected;
293
315
  this.#connected = false;
294
316
 
295
- // Reject pending requests
296
317
  for (const [, pending] of this.#pendingRequests) {
297
318
  pending.reject(new Error("Transport closed"));
298
319
  }
299
320
  this.#pendingRequests.clear();
300
321
 
301
- // Terminate the subprocess tree and keep the handle until exit is observed.
322
+ const stdin = this.#getStdin();
302
323
  const process = this.#process;
324
+ this.#process = null;
303
325
  if (process) {
304
- process.kill();
305
- await Promise.race([process.exited.catch(() => {}), Bun.sleep(CLOSE_WAIT_MS)]);
306
- this.#process = null;
326
+ stdin?.end();
327
+ await process.dispose().catch(() => {});
328
+ await process.awaitExit({ timeoutMs: CLOSE_WAIT_MS }).catch(() => ({ exited: false, code: null }));
307
329
  }
308
330
 
309
- // Wait for read loop to finish
310
- if (this.#readLoop) {
331
+ if (!fromReadLoop && this.#readLoop) {
311
332
  await this.#readLoop.catch(() => {});
312
- this.#readLoop = null;
333
+ }
334
+ this.#readLoop = null;
335
+
336
+ if (this.#stderrLoop) {
337
+ await this.#stderrLoop.catch(() => {});
338
+ this.#stderrLoop = null;
313
339
  }
314
340
 
315
- this.onClose?.();
341
+ if (wasConnected) this.onClose?.();
316
342
  }
317
343
  }
318
344
 
@@ -225,6 +225,9 @@ export interface MCPTransport {
225
225
  /** Close the transport */
226
226
  close(): Promise<void>;
227
227
 
228
+ /** Whether close must finish before reconnect can safely spawn a replacement. */
229
+ readonly closeBeforeReconnect?: boolean;
230
+
228
231
  /** Whether the transport is connected */
229
232
  readonly connected: boolean;
230
233
 
package/src/sdk.ts CHANGED
@@ -52,6 +52,7 @@ import { resolveConfigValue } from "./config/resolve-config-value";
52
52
  import { getEmbeddedDefaultGjcSkills } from "./defaults/gjc-defaults";
53
53
  import { BUNDLED_GROK_BUILD_EXTENSION_ID, getBundledGrokBuildExtensionFactory } from "./defaults/gjc-grok-cli";
54
54
  import { initializeWithSettings } from "./discovery";
55
+ import { disposeAllVmContexts, disposeVmContextsByOwner } from "./eval/js/context-manager";
55
56
  import { disposeAllKernelSessions, disposeKernelSessionsByOwner } from "./eval/py/executor";
56
57
  import { TtsrManager } from "./export/ttsr";
57
58
  import type { CustomCommandsLoadResult, LoadedCustomCommand } from "./extensibility/custom-commands";
@@ -115,6 +116,7 @@ import {
115
116
  FindTool,
116
117
  getSearchTools,
117
118
  HIDDEN_TOOLS,
119
+ isConfigurableSearchProviderId,
118
120
  isSearchProviderPreference,
119
121
  type LspStartupServerInfo,
120
122
  loadSshTool,
@@ -124,6 +126,7 @@ import {
124
126
  SearchTool,
125
127
  setPreferredImageProvider,
126
128
  setPreferredSearchProvider,
129
+ setSearchFallbackProviders,
127
130
  type Tool,
128
131
  type ToolSession,
129
132
  WebSearchTool,
@@ -133,6 +136,7 @@ import {
133
136
  import { ToolContextStore } from "./tools/context";
134
137
  import { getImageGenTools } from "./tools/image-gen";
135
138
  import { wrapToolWithMetaNotice } from "./tools/output-meta";
139
+ import { guardToolForUltragoalAsk } from "./tools/ultragoal-ask-guard";
136
140
  import { EventBus } from "./utils/event-bus";
137
141
  import { buildNamedToolChoice, buildNamedToolChoiceResult } from "./utils/tool-choice";
138
142
  import { buildWorkspaceTree, type WorkspaceTree } from "./workspace-tree";
@@ -411,6 +415,7 @@ function getDefaultAgentDir(): string {
411
415
  */
412
416
  export async function discoverAuthStorage(agentDir: string = getDefaultAgentDir()): Promise<AuthStorage> {
413
417
  const brokerConfig = await resolveAuthBrokerConfig();
418
+ const credentialRankingMode = resolveCredentialRankingMode();
414
419
  if (brokerConfig) {
415
420
  const client = new AuthBrokerClient({ url: brokerConfig.url, token: brokerConfig.token });
416
421
  const initialResult = await client.fetchSnapshot();
@@ -421,6 +426,7 @@ export async function discoverAuthStorage(agentDir: string = getDefaultAgentDir(
421
426
  const storage = new AuthStorage(store, {
422
427
  configValueResolver: resolveConfigValue,
423
428
  sourceLabel: `broker ${brokerConfig.url}`,
429
+ credentialRankingMode,
424
430
  });
425
431
  await storage.reload();
426
432
  return storage;
@@ -429,11 +435,25 @@ export async function discoverAuthStorage(agentDir: string = getDefaultAgentDir(
429
435
  const storage = await AuthStorage.create(dbPath, {
430
436
  configValueResolver: resolveConfigValue,
431
437
  sourceLabel: `local ${dbPath}`,
438
+ credentialRankingMode,
432
439
  });
433
440
  await storage.reload();
434
441
  return storage;
435
442
  }
436
443
 
444
+ /**
445
+ * Opt-in multi-account credential ranking mode, read from the
446
+ * `GJC_CREDENTIAL_RANKING_MODE` env var. Unset/unknown → `undefined`, leaving
447
+ * {@link AuthStorage}'s default (`balanced`) untouched. `earliest-reset`
448
+ * switches to earliest-expiry-first selection so soon-to-reset tumbling-window
449
+ * quota is drained before it is lost.
450
+ */
451
+ function resolveCredentialRankingMode(): "balanced" | "earliest-reset" | undefined {
452
+ const raw = process.env.GJC_CREDENTIAL_RANKING_MODE?.trim();
453
+ if (raw === "balanced" || raw === "earliest-reset") return raw;
454
+ return undefined;
455
+ }
456
+
437
457
  /**
438
458
  * Discover extensions from cwd.
439
459
  */
@@ -567,6 +587,14 @@ function registerPythonCleanup(): void {
567
587
  postmortem.register("python-cleanup", disposeAllKernelSessions);
568
588
  }
569
589
 
590
+ let jsVmCleanupRegistered = false;
591
+
592
+ function registerJsVmCleanup(): void {
593
+ if (jsVmCleanupRegistered) return;
594
+ jsVmCleanupRegistered = true;
595
+ postmortem.register("js-vm-cleanup", disposeAllVmContexts);
596
+ }
597
+
570
598
  /**
571
599
  * Resolve whether to enable append-only context mode based on the setting and provider.
572
600
  *
@@ -803,6 +831,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
803
831
 
804
832
  registerSshCleanup();
805
833
  registerPythonCleanup();
834
+ registerJsVmCleanup();
806
835
 
807
836
  // Pin authStorage to modelRegistry.authStorage: ModelRegistry.getApiKey() routes refresh
808
837
  // failures through that instance, so any divergent storage handed to the bridge / mcpManager
@@ -865,6 +894,12 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
865
894
  if (typeof webSearchProvider === "string" && isSearchProviderPreference(webSearchProvider)) {
866
895
  setPreferredSearchProvider(webSearchProvider);
867
896
  }
897
+ const webSearchFallback = settings.get("web_search.fallback");
898
+ if (Array.isArray(webSearchFallback)) {
899
+ setSearchFallbackProviders(
900
+ webSearchFallback.filter(value => typeof value === "string" && isConfigurableSearchProviderId(value)),
901
+ );
902
+ }
868
903
 
869
904
  const imageProvider = settings.get("providers.image");
870
905
  if (
@@ -1806,7 +1841,9 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1806
1841
 
1807
1842
  const initialTools = initialToolNames
1808
1843
  .map(name => toolRegistry.get(name))
1809
- .filter((tool): tool is AgentTool => tool !== undefined);
1844
+ .filter((tool): tool is AgentTool => tool !== undefined)
1845
+ // AgentSession tool wrapping is not installed until after Agent construction.
1846
+ .map(tool => guardToolForUltragoalAsk(tool, () => sessionManager.getCwd()));
1810
1847
 
1811
1848
  const openaiWebsocketSetting = settings.get("providers.openaiWebsockets") ?? "off";
1812
1849
  const preferOpenAICodexWebsockets =
@@ -2189,6 +2226,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2189
2226
  } else {
2190
2227
  if (hasRegistered) agentRegistry.unregister(resolvedAgentId);
2191
2228
  await disposeKernelSessionsByOwner(evalKernelOwnerId);
2229
+ await disposeVmContextsByOwner(evalKernelOwnerId);
2192
2230
  }
2193
2231
  } catch (cleanupError) {
2194
2232
  logger.warn("Failed to clean up createAgentSession resources after startup error", {