@myagentroam/agent 0.9.136 → 0.9.138

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 (40) hide show
  1. package/dist/host/local-agent-host.d.ts +5 -0
  2. package/dist/host/local-agent-host.js +2 -1
  3. package/dist/prompts/index.d.ts +2 -1
  4. package/dist/prompts/index.js +2 -2
  5. package/dist/prompts/resources.d.ts +1 -0
  6. package/dist/prompts/resources.js +8 -2
  7. package/dist/prompts/workflow.d.ts +1 -1
  8. package/dist/prompts/workflow.js +7 -2
  9. package/dist/runtime/context-gc.js +10 -6
  10. package/dist/runtime/context-projection.js +1 -1
  11. package/dist/sdk/agent.js +163 -88
  12. package/dist/sdk/types.d.ts +17 -2
  13. package/dist/session/context-gc-checkpoint.d.ts +1 -1
  14. package/dist/session/context-gc-checkpoint.js +11 -5
  15. package/dist/session/context-gc-replacements.d.ts +2 -0
  16. package/dist/session/context-gc-replacements.js +18 -0
  17. package/dist/session/jsonl-store.d.ts +5 -2
  18. package/dist/session/jsonl-store.js +105 -12
  19. package/dist/subagent/session-controller.d.ts +2 -0
  20. package/dist/subagent/session-controller.js +23 -6
  21. package/dist/tools/callers.d.ts +2 -0
  22. package/dist/tools/callers.js +7 -1
  23. package/dist/tools/code-mode/cells.d.ts +20 -0
  24. package/dist/tools/code-mode/cells.js +250 -0
  25. package/dist/tools/code-mode/dispatch.d.ts +21 -0
  26. package/dist/tools/code-mode/dispatch.js +53 -0
  27. package/dist/tools/code-mode/engine.d.ts +1 -0
  28. package/dist/tools/code-mode/quickjs-engine.js +4 -0
  29. package/dist/tools/code-mode/worker.js +1 -0
  30. package/dist/tools/code-mode.d.ts +15 -4
  31. package/dist/tools/code-mode.js +83 -17
  32. package/dist/tools/execution-limits.d.ts +5 -0
  33. package/dist/tools/execution-limits.js +5 -0
  34. package/dist/tools/local-registry.d.ts +5 -0
  35. package/dist/tools/local-registry.js +5 -1
  36. package/dist/tools/registry.js +2 -1
  37. package/dist/tools/scheduler.d.ts +2 -0
  38. package/dist/tools/scheduler.js +11 -0
  39. package/dist/tools/tool-search.js +12 -6
  40. package/package.json +1 -1
@@ -22,6 +22,8 @@ export interface ToolExecutionContext {
22
22
  readonly data: Buffer;
23
23
  }[];
24
24
  codeMode?: {
25
+ retainSession?(): Promise<() => Promise<void>>;
26
+ onSettled?(result: CodeCellEventData): Promise<void>;
25
27
  supportsImages?: boolean;
26
28
  tools: readonly import('../model/contracts.js').ClientToolDefinition[];
27
29
  state?: readonly CodeModeStateEntry[];
@@ -92,7 +94,17 @@ export interface ToolExecutionResult {
92
94
  }[];
93
95
  artifacts?: readonly MarAgentToolArtifact[];
94
96
  }
97
+ export interface CodeCellEventData {
98
+ cellId: string;
99
+ status: 'running' | 'completed' | 'failed' | 'cancelled';
100
+ cached?: boolean;
101
+ }
95
102
  export interface HostSessionResourceSnapshot {
103
+ codeCells?: readonly {
104
+ cellId: string;
105
+ status: 'running' | 'completed' | 'failed' | 'cancelled';
106
+ executionId: string;
107
+ }[];
96
108
  idleTtlMs?: number;
97
109
  processes: readonly {
98
110
  processId: string;
@@ -143,7 +155,7 @@ export type MarAgentEvent = MarAgentEventBase & ({
143
155
  modelName: string;
144
156
  } | {
145
157
  type: 'model.performance';
146
- ttftMs: number;
158
+ latencyMs: number;
147
159
  outputTokens?: number | undefined;
148
160
  outputDurationMs?: number | undefined;
149
161
  tokensPerSecond?: number | undefined;
@@ -186,19 +198,22 @@ export type MarAgentEvent = MarAgentEventBase & ({
186
198
  type: 'tool.started';
187
199
  toolName: string;
188
200
  callId: string;
201
+ parentCallId?: string;
189
202
  input?: unknown;
190
203
  } | {
191
204
  type: 'tool.completed';
192
205
  toolName: string;
193
206
  callId: string;
207
+ parentCallId?: string;
194
208
  summary: string;
195
209
  truncated?: boolean;
196
210
  artifacts?: readonly MarAgentToolArtifact[];
197
- data?: import('../tools/exec.js').ExecResult | Omit<import('../tools/exec.js').ExecResult, 'stdout' | 'stderr'> | import('../tools/apply-patch.js').ApplyPatchResult | import('../tools/read.js').ReadEventData | import('../tools/view-image.js').ViewImageResult | import('../subagent/scheduler.js').SubagentTaskSnapshot;
211
+ data?: CodeCellEventData | import('../tools/exec.js').ExecResult | Omit<import('../tools/exec.js').ExecResult, 'stdout' | 'stderr'> | import('../tools/apply-patch.js').ApplyPatchResult | import('../tools/read.js').ReadEventData | import('../tools/view-image.js').ViewImageResult | import('../subagent/scheduler.js').SubagentTaskSnapshot;
198
212
  } | {
199
213
  type: 'tool.failed';
200
214
  toolName: string;
201
215
  callId: string;
216
+ parentCallId?: string;
202
217
  code: string;
203
218
  message?: string;
204
219
  } | {
@@ -50,7 +50,7 @@ export type ContextGcReplacement = {
50
50
  readonly evidenceSequences: readonly number[];
51
51
  } | {
52
52
  readonly kind: 'TOOL_CALL_INPUT';
53
- readonly toolName: 'apply_patch' | 'exec' | 'code_mode';
53
+ readonly toolName: 'apply_patch' | 'exec' | 'code_mode' | 'code_exec';
54
54
  readonly callId: string;
55
55
  readonly target: ContextGcTarget;
56
56
  readonly replacementArguments: unknown;
@@ -2,7 +2,7 @@ import { createHash } from 'node:crypto';
2
2
  import { posix, win32 } from 'node:path';
3
3
  import { isDeepStrictEqual } from 'node:util';
4
4
  import { parseReadContextOutput } from '../tools/read-context.js';
5
- import { contextGcApplyPatchInputReplacement, contextGcCodeModeInputReplacement, contextGcCodeModeOutputReplacement, contextGcExecInputReplacement, contextGcExecOutputReplacement, contextGcPatchResultFromCompletedPayload, contextGcStaleReadReplacement, contextGcWebOutputReplacement, normalizeContextGcCodeModeInput, normalizeContextGcExecResult } from './context-gc-replacements.js';
5
+ import { codeCellCompletionSequence, contextGcApplyPatchInputReplacement, contextGcCodeModeInputReplacement, contextGcCodeModeOutputReplacement, contextGcExecInputReplacement, contextGcExecOutputReplacement, contextGcPatchResultFromCompletedPayload, contextGcStaleReadReplacement, contextGcWebOutputReplacement, normalizeContextGcCodeModeInput, normalizeContextGcExecResult } from './context-gc-replacements.js';
6
6
  import { contextGcSubagentNeedsRetainedPriorFinal, contextGcSubagentOutputReplacement, contextGcSubagentSnapshotIsPreciselyCovered, contextGcSubagentSnapshotIsSuperseded, normalizeContextGcSubagentSnapshot } from './context-gc-subagent.js';
7
7
  export const CONTEXT_GC_CHECKPOINT_VERSION = 1;
8
8
  export const CONTEXT_GC_MAX_TARGETS = 4_096;
@@ -266,17 +266,22 @@ function replacementMatchesRecords(replacement, records, recordsBySequence, work
266
266
  result !== undefined &&
267
267
  isDeepStrictEqual(replacement.replacementArguments, contextGcApplyPatchInputReplacement(source.payload.arguments, result, replacement.evidenceSequence, applyPatchReplacementFormatVersion(replacement.replacementArguments))));
268
268
  }
269
- if (replacement.kind === 'TOOL_CALL_INPUT' && replacement.toolName === 'code_mode') {
270
- const evidence = successfulToolEvidence(records, recordsBySequence, replacement.evidenceSequence, 'code_mode');
269
+ if (replacement.kind === 'TOOL_CALL_INPUT' &&
270
+ (replacement.toolName === 'code_mode' || replacement.toolName === 'code_exec')) {
271
+ const evidence = successfulToolEvidence(records, recordsBySequence, replacement.evidenceSequence, replacement.toolName);
271
272
  const input = normalizeContextGcCodeModeInput(source.payload.arguments);
272
273
  return (evidence?.callId === replacement.callId &&
274
+ codeCellCompletionSequence(records, evidence.completed) !== undefined &&
273
275
  evidence.call.sequence === replacement.target.sequence &&
274
276
  input !== undefined &&
275
277
  isDeepStrictEqual(replacement.replacementArguments, contextGcCodeModeInputReplacement(input)));
276
278
  }
277
279
  if (replacement.kind === 'CODE_MODE_OUTPUT') {
278
- const evidence = successfulToolEvidence(records, recordsBySequence, replacement.evidenceSequence, 'code_mode');
280
+ const evidence = ['code_mode', 'code_exec', 'code_exec_wait']
281
+ .map((name) => successfulToolEvidence(records, recordsBySequence, replacement.evidenceSequence, name))
282
+ .find((item) => item !== undefined);
279
283
  return (evidence?.callId === replacement.callId &&
284
+ codeCellCompletionSequence(records, evidence.completed) !== undefined &&
280
285
  evidence.result.sequence === replacement.target.sequence &&
281
286
  isRecord(evidence.result.payload) &&
282
287
  typeof evidence.result.payload.content === 'string' &&
@@ -626,7 +631,8 @@ function isContextGcReplacement(value) {
626
631
  value.target.role === 'tool_call' &&
627
632
  (value.toolName === 'apply_patch' ||
628
633
  value.toolName === 'exec' ||
629
- value.toolName === 'code_mode') &&
634
+ value.toolName === 'code_mode' ||
635
+ value.toolName === 'code_exec') &&
630
636
  value.target.toolName === value.toolName &&
631
637
  Array.isArray(value.providerAliasTargets) &&
632
638
  value.providerAliasTargets.every((target) => isContextGcTarget(target) &&
@@ -60,3 +60,5 @@ export declare function contextGcExecInputReplacement(arguments_: unknown, forma
60
60
  export declare function contextGcExecOutputReplacement(result: ContextGcExecResult, formatVersion?: 1 | 2 | 3): string;
61
61
  export declare function normalizeContextGcExecResult(modelContent: unknown, completedPayload: unknown): ContextGcExecResult | undefined;
62
62
  export declare function parseContextGcExecResult(value: unknown): ContextGcExecResult | undefined;
63
+ /** A returned tool wait is not proof that its retained JavaScript cell finished. */
64
+ export declare function codeCellCompletionSequence(records: readonly import('./jsonl-store.js').SessionRecord[], completed: import('./jsonl-store.js').SessionRecord): number | undefined;
@@ -314,3 +314,21 @@ function isNonemptyString(value) {
314
314
  function isNonnegativeInteger(value) {
315
315
  return Number.isSafeInteger(value) && Number(value) >= 0;
316
316
  }
317
+ /** A returned tool wait is not proof that its retained JavaScript cell finished. */
318
+ export function codeCellCompletionSequence(records, completed) {
319
+ if (!isRecord(completed.payload))
320
+ return undefined;
321
+ if (completed.payload.toolName === 'code_mode')
322
+ return completed.sequence;
323
+ const data = completed.payload.data;
324
+ if (!isRecord(data) || typeof data.cellId !== 'string')
325
+ return undefined;
326
+ if (data.status === 'completed')
327
+ return completed.sequence;
328
+ if (data.status !== 'running')
329
+ return undefined;
330
+ return records.findLast((record) => record.type === 'code.cell.completed' &&
331
+ isRecord(record.payload) &&
332
+ record.payload.cellId === data.cellId &&
333
+ record.payload.status === 'completed')?.sequence;
334
+ }
@@ -151,6 +151,7 @@ export declare class JsonlSessionStore {
151
151
  commitCodeModeState(sessionId: string, mutations: unknown, input: {
152
152
  readonly executionId: string;
153
153
  readonly turnId: string;
154
+ readonly expectedState?: readonly CodeModeStateEntry[];
154
155
  }): Promise<CodeModeStateEntry[]>;
155
156
  readExecutionState(sessionId: string): Promise<SessionExecutionState>;
156
157
  readContext(sessionId: string): Promise<SessionRecord[]>;
@@ -175,8 +176,10 @@ export declare class JsonlSessionStore {
175
176
  executionId?: string;
176
177
  turnId?: string;
177
178
  }): Promise<SessionRecord>;
178
- /** Recover files under the sole writer lock; flush dirty index state before releasing it. */
179
- acquireSessionLease(sessionId: string): Promise<() => Promise<void>>;
179
+ /** A warm cell retains physical writer ownership between Executions of the same Agent. */
180
+ acquireSessionLease(sessionId: string, owner?: object): Promise<() => Promise<void>>;
181
+ hasRetainedSessionLease(sessionId: string, owner: object): boolean;
182
+ retainSessionLease(sessionId: string, owner: object): Promise<() => Promise<void>>;
180
183
  list(input?: SessionListInput): Promise<{
181
184
  nextCursor?: string;
182
185
  sessions: {
@@ -157,6 +157,9 @@ export class JsonlSessionStore {
157
157
  indexDirty: false,
158
158
  dirtySessionVersions: new Map(),
159
159
  pendingPublishIds: new Set(),
160
+ codeStateWrites: new Map(),
161
+ recordWrites: new Map(),
162
+ writerLeases: new Map(),
160
163
  segments: new Map(),
161
164
  executionModelReads: new Map()
162
165
  };
@@ -897,6 +900,22 @@ export class JsonlSessionStore {
897
900
  };
898
901
  }
899
902
  async append(sessionId, input) {
903
+ return this.#serializeRecordOperation(sessionId, () => this.#appendRecord(sessionId, input));
904
+ }
905
+ async #serializeRecordOperation(sessionId, action) {
906
+ const operation = (this.#shared.recordWrites.get(sessionId) ?? Promise.resolve())
907
+ .catch(() => undefined)
908
+ .then(action);
909
+ this.#shared.recordWrites.set(sessionId, operation);
910
+ try {
911
+ return await operation;
912
+ }
913
+ finally {
914
+ if (this.#shared.recordWrites.get(sessionId) === operation)
915
+ this.#shared.recordWrites.delete(sessionId);
916
+ }
917
+ }
918
+ async #appendRecord(sessionId, input) {
900
919
  this.#assertOpen();
901
920
  const meta = this.#shared.index.sessions.find((item) => item.id === sessionId) ??
902
921
  this.#shared.pendingSessions.get(sessionId);
@@ -974,6 +993,9 @@ export class JsonlSessionStore {
974
993
  return record;
975
994
  }
976
995
  async read(sessionId) {
996
+ return this.#serializeRecordOperation(sessionId, () => this.#readRecords(sessionId));
997
+ }
998
+ async #readRecords(sessionId) {
977
999
  this.#assertOpen();
978
1000
  try {
979
1001
  const records = [];
@@ -991,17 +1013,32 @@ export class JsonlSessionStore {
991
1013
  return restoreCodeModeState(await this.read(sessionId));
992
1014
  }
993
1015
  async commitCodeModeState(sessionId, mutations, input) {
994
- const parsed = parseCodeModeStateMutations(mutations);
995
- if (parsed.length === 0)
996
- return this.readCodeModeState(sessionId);
997
- const state = applyCodeModeStateMutations(await this.readCodeModeState(sessionId), parsed);
998
- await this.append(sessionId, {
999
- type: 'code.state.updated',
1000
- payload: { mutations: parsed },
1001
- executionId: input.executionId,
1002
- turnId: input.turnId
1016
+ const operation = (this.#shared.codeStateWrites.get(sessionId) ?? Promise.resolve())
1017
+ .catch(() => undefined)
1018
+ .then(async () => {
1019
+ const parsed = parseCodeModeStateMutations(mutations);
1020
+ if (parsed.length === 0)
1021
+ return this.readCodeModeState(sessionId);
1022
+ const current = await this.readCodeModeState(sessionId);
1023
+ if (input.expectedState && !isDeepStrictEqual(current, input.expectedState))
1024
+ throw new MarAgentError('MAR_AGENT_CODE_STATE_CONFLICT', 'Session state changed while this cell was running.');
1025
+ const state = applyCodeModeStateMutations(current, parsed);
1026
+ await this.append(sessionId, {
1027
+ type: 'code.state.updated',
1028
+ payload: { mutations: parsed },
1029
+ executionId: input.executionId,
1030
+ turnId: input.turnId
1031
+ });
1032
+ return state;
1003
1033
  });
1004
- return state;
1034
+ this.#shared.codeStateWrites.set(sessionId, operation);
1035
+ try {
1036
+ return await operation;
1037
+ }
1038
+ finally {
1039
+ if (this.#shared.codeStateWrites.get(sessionId) === operation)
1040
+ this.#shared.codeStateWrites.delete(sessionId);
1041
+ }
1005
1042
  }
1006
1043
  async readExecutionState(sessionId) {
1007
1044
  this.#assertOpen();
@@ -1155,8 +1192,64 @@ export class JsonlSessionStore {
1155
1192
  ...(input.turnId ? { turnId: input.turnId } : {})
1156
1193
  });
1157
1194
  }
1195
+ /** A warm cell retains physical writer ownership between Executions of the same Agent. */
1196
+ async acquireSessionLease(sessionId, owner) {
1197
+ return this.#serializeRecordOperation(sessionId, async () => {
1198
+ this.#assertOpen();
1199
+ let lease = this.#shared.writerLeases.get(sessionId);
1200
+ if (lease) {
1201
+ if (lease.executing || owner === undefined || lease.owner !== owner)
1202
+ throw new MarAgentError('MAR_AGENT_SESSION_LOCKED', 'Session already has a writer.');
1203
+ lease.executing = true;
1204
+ }
1205
+ else {
1206
+ const release = await this.#acquireSessionLease(sessionId);
1207
+ lease = { owner, executing: true, retained: 0, release };
1208
+ this.#shared.writerLeases.set(sessionId, lease);
1209
+ }
1210
+ const current = lease;
1211
+ let released = false;
1212
+ return () => this.#serializeRecordOperation(sessionId, async () => {
1213
+ if (released)
1214
+ return;
1215
+ released = true;
1216
+ current.executing = false;
1217
+ await this.#releaseUnusedWriter(sessionId, current);
1218
+ });
1219
+ });
1220
+ }
1221
+ hasRetainedSessionLease(sessionId, owner) {
1222
+ const lease = this.#shared.writerLeases.get(sessionId);
1223
+ return lease?.owner === owner && lease.retained > 0;
1224
+ }
1225
+ async retainSessionLease(sessionId, owner) {
1226
+ return this.#serializeRecordOperation(sessionId, async () => {
1227
+ const lease = this.#shared.writerLeases.get(sessionId);
1228
+ if (!lease || !lease.executing || lease.owner !== owner)
1229
+ throw new MarAgentError('MAR_AGENT_SESSION_LOCKED', 'Retained work requires its originating Session writer.');
1230
+ lease.retained++;
1231
+ let released = false;
1232
+ return () => this.#serializeRecordOperation(sessionId, async () => {
1233
+ if (released)
1234
+ return;
1235
+ released = true;
1236
+ lease.retained--;
1237
+ await this.#releaseUnusedWriter(sessionId, lease);
1238
+ });
1239
+ });
1240
+ }
1241
+ async #releaseUnusedWriter(sessionId, lease) {
1242
+ if (lease.executing || lease.retained)
1243
+ return;
1244
+ try {
1245
+ await lease.release();
1246
+ }
1247
+ finally {
1248
+ this.#shared.writerLeases.delete(sessionId);
1249
+ }
1250
+ }
1158
1251
  /** Recover files under the sole writer lock; flush dirty index state before releasing it. */
1159
- async acquireSessionLease(sessionId) {
1252
+ async #acquireSessionLease(sessionId) {
1160
1253
  this.#assertOpen();
1161
1254
  await this.#refreshIndex();
1162
1255
  if (!this.#shared.index.sessions.some((item) => item.id === sessionId))
@@ -1509,7 +1602,7 @@ export class JsonlSessionStore {
1509
1602
  const latestPath = paths.at(-1);
1510
1603
  await repairSegmentTail(latestPath);
1511
1604
  const records = await readRecords(latestPath);
1512
- await recoverPendingInputImages(this.sessionDirectory(sessionId), () => this.read(sessionId));
1605
+ await recoverPendingInputImages(this.sessionDirectory(sessionId), () => this.#readRecords(sessionId));
1513
1606
  const last = records.at(-1);
1514
1607
  const match = /events-(\d{6})\.jsonl$/.exec(latestPath);
1515
1608
  const segment = Number(match?.[1] ?? 1);
@@ -83,6 +83,8 @@ export declare class SubagentSessionController implements SubagentController {
83
83
  };
84
84
  }): Promise<SubagentSessionController>;
85
85
  beginParentExecution(execution: SubagentParentExecution): Promise<void>;
86
+ /** A retained cell must not borrow a later model Execution's identity or task budget. */
87
+ captureExecution(): SubagentController;
86
88
  endParentExecution(executionId: string): void;
87
89
  run(input: SubagentTaskInput, options?: SubagentRunOptions): Promise<SubagentTaskSnapshot>;
88
90
  output(agentId: string, options?: SubagentOutputOptions): Promise<SubagentTaskSnapshot>;
@@ -1,3 +1,4 @@
1
+ import { AsyncLocalStorage } from 'node:async_hooks';
1
2
  import { randomUUID } from 'node:crypto';
2
3
  import { MarAgentError, describeMarAgentError } from '../error.js';
3
4
  import { TOOL_EXECUTION_LIMITS } from '../tools/execution-limits.js';
@@ -14,7 +15,8 @@ export class SubagentSessionController {
14
15
  #resourceRevision = 0;
15
16
  #running = 0;
16
17
  #disposed = false;
17
- #createdInExecution = 0;
18
+ #creationBudget = { count: 0 };
19
+ #retainedExecution = new AsyncLocalStorage();
18
20
  #execution;
19
21
  constructor(parentSessionId, rootSessionId, backend, limits = { maxTasks: 8, maxConcurrent: 4 }) {
20
22
  this.parentSessionId = parentSessionId;
@@ -36,10 +38,23 @@ export class SubagentSessionController {
36
38
  if (this.#disposed)
37
39
  throw new MarAgentError('MAR_AGENT_SUBAGENT_LIMIT', 'Subagent controller is disposed.');
38
40
  this.#execution = execution;
39
- this.#createdInExecution = 0;
41
+ this.#creationBudget = { count: 0 };
40
42
  for (const state of this.#agents.values())
41
43
  await this.#publishTerminal(state);
42
44
  }
45
+ /** A retained cell must not borrow a later model Execution's identity or task budget. */
46
+ captureExecution() {
47
+ const context = { execution: this.#requireExecution(), budget: this.#creationBudget };
48
+ return {
49
+ run: (input, options) => this.#retainedExecution.run(context, () => this.run(input, options)),
50
+ message: (id, message, options) => this.#retainedExecution.run(context, () => this.message(id, message, options)),
51
+ output: (id, options) => this.output(id, options),
52
+ outputAny: (options) => this.outputAny(options),
53
+ cancel: (id) => this.cancel(id),
54
+ listSessionResources: () => this.listSessionResources(),
55
+ dispose: () => this.dispose()
56
+ };
57
+ }
43
58
  endParentExecution(executionId) {
44
59
  if (this.#execution?.executionId === executionId)
45
60
  this.#execution = undefined;
@@ -48,7 +63,8 @@ export class SubagentSessionController {
48
63
  options.signal?.throwIfAborted();
49
64
  const execution = this.#requireExecution();
50
65
  validateTaskInput(input);
51
- if (this.#createdInExecution >= this.limits.maxTasks)
66
+ if ((this.#retainedExecution.getStore()?.budget ?? this.#creationBudget).count >=
67
+ this.limits.maxTasks)
52
68
  throw new MarAgentError('MAR_AGENT_SUBAGENT_LIMIT', 'Subagent task limit was reached.');
53
69
  const source = {
54
70
  type: 'subagent',
@@ -85,7 +101,7 @@ export class SubagentSessionController {
85
101
  terminalObserved: false,
86
102
  resourceRevision: ++this.#resourceRevision
87
103
  };
88
- this.#createdInExecution++;
104
+ (this.#retainedExecution.getStore()?.budget ?? this.#creationBudget).count++;
89
105
  this.#agents.set(state.agentId, state);
90
106
  const knownModel = execution.models.get(input.modelId ?? execution.modelId);
91
107
  if (knownModel) {
@@ -589,9 +605,10 @@ export class SubagentSessionController {
589
605
  #requireExecution() {
590
606
  if (this.#disposed)
591
607
  throw new MarAgentError('MAR_AGENT_DISPOSED', 'Subagent controller is disposed.');
592
- if (!this.#execution)
608
+ const execution = this.#retainedExecution.getStore()?.execution ?? this.#execution;
609
+ if (!execution)
593
610
  throw new MarAgentError('MAR_AGENT_SUBAGENT_FAILED', 'Subagent control requires an active parent execution.');
594
- return this.#execution;
611
+ return execution;
595
612
  }
596
613
  #receiptSnapshot(state) {
597
614
  return this.#snapshot(state, {
@@ -2,3 +2,5 @@ import type { ClientToolDefinition, ToolCaller } from '../model/contracts.js';
2
2
  /** Normalize trusted Host policy independently of discovery, Agent scope and parallel safety. */
3
3
  export declare function toolCallers(tool: ClientToolDefinition): readonly ToolCaller[];
4
4
  export declare function allowsToolCaller(tool: ClientToolDefinition, caller: ToolCaller): boolean;
5
+ /** Code Mode Only changes model exposure, never the Host's underlying caller permissions. */
6
+ export declare function allowsDirectTool(tool: ClientToolDefinition, codeModeEnabled: boolean): boolean;
@@ -12,7 +12,13 @@ export function toolCallers(tool) {
12
12
  }
13
13
  export function allowsToolCaller(tool, caller) {
14
14
  // Control tools cannot be widened by a Host override or historical definition.
15
- if (caller === 'programmatic' && ['code_mode', 'tool_search', 'question'].includes(tool.name))
15
+ if (caller === 'programmatic' &&
16
+ ['code_exec', 'code_exec_wait', 'code_mode', 'tool_search', 'question'].includes(tool.name))
16
17
  return false;
17
18
  return toolCallers(tool).includes(caller);
18
19
  }
20
+ /** Code Mode Only changes model exposure, never the Host's underlying caller permissions. */
21
+ export function allowsDirectTool(tool, codeModeEnabled) {
22
+ return (allowsToolCaller(tool, 'direct') &&
23
+ (!codeModeEnabled || !allowsToolCaller(tool, 'programmatic')));
24
+ }
@@ -0,0 +1,20 @@
1
+ import type { ToolExecutionContext, ToolExecutionResult } from '../../sdk/types.js';
2
+ export interface CellOutput {
3
+ text(value: string): void;
4
+ image(value: NonNullable<ToolExecutionResult['images']>[number]): void;
5
+ yield(): void;
6
+ }
7
+ type Status = 'running' | 'completed' | 'failed' | 'cancelled';
8
+ /** Session-owned execution, separate from the cancellable model-tool wait. */
9
+ export declare class CodeCells {
10
+ #private;
11
+ start(value: unknown, context: ToolExecutionContext, run: (code: string, context: ToolExecutionContext, output: CellOutput) => Promise<ToolExecutionResult>): Promise<ToolExecutionResult>;
12
+ wait(value: unknown, context: ToolExecutionContext): Promise<ToolExecutionResult>;
13
+ snapshot(sessionId: string): {
14
+ cellId: string;
15
+ status: Status;
16
+ executionId: string;
17
+ }[];
18
+ dispose(sessionId?: string): Promise<void>;
19
+ }
20
+ export {};