@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.
- package/dist/host/local-agent-host.d.ts +5 -0
- package/dist/host/local-agent-host.js +2 -1
- package/dist/prompts/index.d.ts +2 -1
- package/dist/prompts/index.js +2 -2
- package/dist/prompts/resources.d.ts +1 -0
- package/dist/prompts/resources.js +8 -2
- package/dist/prompts/workflow.d.ts +1 -1
- package/dist/prompts/workflow.js +7 -2
- package/dist/runtime/context-gc.js +10 -6
- package/dist/runtime/context-projection.js +1 -1
- package/dist/sdk/agent.js +163 -88
- package/dist/sdk/types.d.ts +17 -2
- package/dist/session/context-gc-checkpoint.d.ts +1 -1
- package/dist/session/context-gc-checkpoint.js +11 -5
- package/dist/session/context-gc-replacements.d.ts +2 -0
- package/dist/session/context-gc-replacements.js +18 -0
- package/dist/session/jsonl-store.d.ts +5 -2
- package/dist/session/jsonl-store.js +105 -12
- package/dist/subagent/session-controller.d.ts +2 -0
- package/dist/subagent/session-controller.js +23 -6
- package/dist/tools/callers.d.ts +2 -0
- package/dist/tools/callers.js +7 -1
- package/dist/tools/code-mode/cells.d.ts +20 -0
- package/dist/tools/code-mode/cells.js +250 -0
- package/dist/tools/code-mode/dispatch.d.ts +21 -0
- package/dist/tools/code-mode/dispatch.js +53 -0
- package/dist/tools/code-mode/engine.d.ts +1 -0
- package/dist/tools/code-mode/quickjs-engine.js +4 -0
- package/dist/tools/code-mode/worker.js +1 -0
- package/dist/tools/code-mode.d.ts +15 -4
- package/dist/tools/code-mode.js +83 -17
- package/dist/tools/execution-limits.d.ts +5 -0
- package/dist/tools/execution-limits.js +5 -0
- package/dist/tools/local-registry.d.ts +5 -0
- package/dist/tools/local-registry.js +5 -1
- package/dist/tools/registry.js +2 -1
- package/dist/tools/scheduler.d.ts +2 -0
- package/dist/tools/scheduler.js +11 -0
- package/dist/tools/tool-search.js +12 -6
- package/package.json +1 -1
package/dist/sdk/types.d.ts
CHANGED
|
@@ -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
|
-
|
|
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' &&
|
|
270
|
-
|
|
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 =
|
|
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
|
-
/**
|
|
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
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
#
|
|
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.#
|
|
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.#
|
|
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.#
|
|
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
|
-
|
|
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
|
|
611
|
+
return execution;
|
|
595
612
|
}
|
|
596
613
|
#receiptSnapshot(state) {
|
|
597
614
|
return this.#snapshot(state, {
|
package/dist/tools/callers.d.ts
CHANGED
|
@@ -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;
|
package/dist/tools/callers.js
CHANGED
|
@@ -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' &&
|
|
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 {};
|