@myagentroam/agent 0.9.64 → 0.9.66
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/cli/main.js +13 -9
- package/dist/model/openai-responses.js +26 -5
- package/dist/prompts/output.d.ts +1 -0
- package/dist/prompts/output.js +1 -0
- package/dist/runtime/context-gc-runtime.js +47 -10
- package/dist/runtime/context-gc.d.ts +8 -1
- package/dist/runtime/context-gc.js +170 -16
- package/dist/runtime/context-projection.js +3 -1
- package/dist/sdk/agent.js +111 -39
- package/dist/sdk/types.d.ts +1 -0
- package/dist/session/context-gc-checkpoint.d.ts +9 -1
- package/dist/session/context-gc-checkpoint.js +130 -12
- package/dist/session/context-gc-replacements.d.ts +6 -3
- package/dist/session/context-gc-replacements.js +75 -15
- package/dist/session/context-gc-subagent.d.ts +12 -0
- package/dist/session/context-gc-subagent.js +79 -7
- package/dist/session/jsonl-store.d.ts +15 -0
- package/dist/session/jsonl-store.js +15 -0
- package/dist/subagent/scheduler.d.ts +14 -1
- package/dist/subagent/scheduler.js +46 -3
- package/dist/subagent/session-controller.d.ts +4 -1
- package/dist/subagent/session-controller.js +118 -22
- package/dist/tools/agent-wait.js +12 -8
- package/dist/tools/execution-limits.d.ts +1 -0
- package/dist/tools/execution-limits.js +1 -0
- package/dist/tools/view-image.js +1 -1
- package/package.json +1 -1
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
2
|
const RAW_MODEL_CONTEXT_PREVIEW_CHARACTERS = 256;
|
|
3
|
+
const EXEC_INPUT_COMPLETE_CHARACTERS = 512;
|
|
4
|
+
const EXEC_INPUT_PREVIEW_CHARACTERS = 256;
|
|
5
|
+
const EXEC_OUTPUT_PREVIEW_CHARACTERS = 512;
|
|
3
6
|
export function contextGcPatchResultFromCompletedPayload(payload) {
|
|
4
7
|
if (!isRecord(payload) || payload.type !== 'tool.completed')
|
|
5
8
|
return undefined;
|
|
@@ -43,39 +46,55 @@ export function contextGcPatchResultFromCompletedPayload(payload) {
|
|
|
43
46
|
}
|
|
44
47
|
};
|
|
45
48
|
}
|
|
46
|
-
export function contextGcApplyPatchInputReplacement(arguments_, result, evidenceSequence) {
|
|
49
|
+
export function contextGcApplyPatchInputReplacement(arguments_, result, evidenceSequence, formatVersion = 2) {
|
|
47
50
|
const original = typeof arguments_ === 'string' ? arguments_ : stableJson(arguments_);
|
|
48
51
|
const summary = result.files
|
|
49
52
|
.map((file) => `${file.operation} ${file.path}${file.targetPath ? ` -> ${file.targetPath}` : ''} (+${file.addedLines}/-${file.deletedLines})`)
|
|
50
53
|
.join('; ');
|
|
51
|
-
return `<context_gc kind="successful_apply_patch_input" originalBytes="${Buffer.byteLength(original)}" originalSha256="${sha256(original)}" evidenceSequence="${evidenceSequence}">Applied successfully: ${escapeXml(summary)}. Totals: ${result.totals.files} files, +${result.totals.addedLines}/-${result.totals.deletedLines}. The original patch body was reclaimed.</context_gc>`;
|
|
54
|
+
return `<context_gc kind="successful_apply_patch_input"${formatVersion === 2 ? ' formatVersion="2"' : ''} originalBytes="${Buffer.byteLength(original)}"${formatVersion === 1 ? ` originalSha256="${sha256(original)}"` : ''}${formatVersion === 1 ? ` evidenceSequence="${evidenceSequence}"` : ''}>Applied successfully: ${escapeXml(summary)}. Totals: ${result.totals.files} files, +${result.totals.addedLines}/-${result.totals.deletedLines}. The original patch body was reclaimed.</context_gc>`;
|
|
52
55
|
}
|
|
53
56
|
export function contextGcStaleReadReplacement(input) {
|
|
54
|
-
|
|
57
|
+
const formatVersion = input.formatVersion ?? 2;
|
|
58
|
+
return `<context_gc kind="stale_read" path="${escapeXml(input.path)}"${formatVersion === 2 ? ' formatVersion="2"' : ''} originalBytes="${Buffer.byteLength(input.content)}"${formatVersion === 1 ? ` originalSha256="${sha256(input.content)}"` : ''}${formatVersion === 1
|
|
59
|
+
? ` supersededByCallId="${escapeXml(input.supersededByCallId)}" evidenceSequence="${input.evidenceSequence}"`
|
|
60
|
+
: ''} operation="${input.operation}">This read predates a successful apply_patch. Re-read the path before relying on its contents.</context_gc>`;
|
|
55
61
|
}
|
|
56
|
-
export function
|
|
62
|
+
export function contextGcWebOutputReplacement(toolName, originalContent, truncated) {
|
|
63
|
+
const preview = unicodeHeadTail(originalContent, EXEC_OUTPUT_PREVIEW_CHARACTERS);
|
|
64
|
+
return JSON.stringify({
|
|
65
|
+
toolName,
|
|
66
|
+
originalBytes: Buffer.byteLength(originalContent),
|
|
67
|
+
lines: lineCount(originalContent),
|
|
68
|
+
truncated,
|
|
69
|
+
preview,
|
|
70
|
+
previewComplete: preview === originalContent,
|
|
71
|
+
contextGc: 'Detailed web tool output was reclaimed after six successful rollouts. An incomplete preview is not the complete search result or page, and GC did not access the network.'
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
export function contextGcExecInputReplacement(arguments_, formatVersion = 3) {
|
|
57
75
|
if (!isRecord(arguments_))
|
|
58
|
-
return `<context_gc kind="terminal_exec_input" originalSha256="${sha256(stableJson(arguments_))}" />`;
|
|
76
|
+
return `<context_gc kind="terminal_exec_input"${formatVersion === 3 ? ' formatVersion="3"' : ''} originalBytes="${Buffer.byteLength(stableJson(arguments_))}"${formatVersion === 3 ? '' : ` originalSha256="${sha256(stableJson(arguments_))}"`} />`;
|
|
59
77
|
const action = typeof arguments_.action === 'string' ? arguments_.action : 'unknown';
|
|
60
78
|
const original = stableJson(arguments_);
|
|
61
79
|
const retained = {
|
|
62
80
|
action,
|
|
63
81
|
contextGc: {
|
|
64
82
|
kind: 'terminal_exec_input',
|
|
83
|
+
...(formatVersion >= 2 ? { formatVersion } : {}),
|
|
65
84
|
originalBytes: Buffer.byteLength(original),
|
|
66
|
-
originalSha256: sha256(original)
|
|
85
|
+
...(formatVersion <= 2 ? { originalSha256: sha256(original) } : {})
|
|
67
86
|
}
|
|
68
87
|
};
|
|
69
88
|
for (const key of ['cwd', 'processId', 'timeoutMs', 'yieldMs'])
|
|
70
89
|
if (arguments_[key] !== undefined)
|
|
71
90
|
retained[key] = arguments_[key];
|
|
72
91
|
if (typeof arguments_.command === 'string')
|
|
73
|
-
retained
|
|
92
|
+
retainExecText(retained, 'command', arguments_.command, formatVersion);
|
|
74
93
|
if (typeof arguments_.data === 'string')
|
|
75
|
-
retained
|
|
94
|
+
retainExecText(retained, 'data', arguments_.data, formatVersion);
|
|
76
95
|
return retained;
|
|
77
96
|
}
|
|
78
|
-
export function contextGcExecOutputReplacement(result) {
|
|
97
|
+
export function contextGcExecOutputReplacement(result, formatVersion = 3) {
|
|
79
98
|
if (result.source === 'MODEL_CONTEXT_TRUNCATED')
|
|
80
99
|
return JSON.stringify({
|
|
81
100
|
status: result.status,
|
|
@@ -88,12 +107,15 @@ export function contextGcExecOutputReplacement(result) {
|
|
|
88
107
|
stderrDroppedBytes: result.stderrDroppedBytes,
|
|
89
108
|
modelContextTruncated: true,
|
|
90
109
|
modelContextBytes: Buffer.byteLength(result.modelContextContent),
|
|
91
|
-
modelContextSha256: sha256(result.modelContextContent),
|
|
92
110
|
modelContextPreview: unicodeHeadTail(result.modelContextContent, RAW_MODEL_CONTEXT_PREVIEW_CHARACTERS),
|
|
111
|
+
...(formatVersion >= 2 ? { contextGcFormatVersion: formatVersion } : {}),
|
|
112
|
+
...(formatVersion <= 2 ? { modelContextSha256: sha256(result.modelContextContent) } : {}),
|
|
93
113
|
contextGc: 'The model-visible terminal result was truncated after execution and is no longer valid JSON. Detailed terminal input/output was reclaimed after the process reached a stable terminal state. Re-run the command if full output is required.'
|
|
94
114
|
});
|
|
95
115
|
const stdout = result.stdout;
|
|
96
116
|
const stderr = result.stderr;
|
|
117
|
+
const stdoutPreview = unicodeHeadTail(stdout, EXEC_OUTPUT_PREVIEW_CHARACTERS);
|
|
118
|
+
const stderrPreview = unicodeHeadTail(stderr, EXEC_OUTPUT_PREVIEW_CHARACTERS);
|
|
97
119
|
return JSON.stringify({
|
|
98
120
|
status: result.status,
|
|
99
121
|
...(result.processId === undefined ? {} : { processId: result.processId }),
|
|
@@ -103,11 +125,32 @@ export function contextGcExecOutputReplacement(result) {
|
|
|
103
125
|
stderrBytes: nonnegativeNumber(result.stderrBytes) ?? Buffer.byteLength(stderr),
|
|
104
126
|
stdoutDroppedBytes: nonnegativeNumber(result.stdoutDroppedBytes) ?? 0,
|
|
105
127
|
stderrDroppedBytes: nonnegativeNumber(result.stderrDroppedBytes) ?? 0,
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
128
|
+
...(formatVersion <= 2
|
|
129
|
+
? {
|
|
130
|
+
stdoutSha256: sha256(stdout),
|
|
131
|
+
stderrSha256: sha256(stderr)
|
|
132
|
+
}
|
|
133
|
+
: {}),
|
|
134
|
+
stdoutPreview,
|
|
135
|
+
stderrPreview,
|
|
136
|
+
...(formatVersion >= 2
|
|
137
|
+
? {
|
|
138
|
+
stdoutLines: lineCount(stdout),
|
|
139
|
+
stderrLines: lineCount(stderr),
|
|
140
|
+
stdoutPreviewComplete: result.truncated === false &&
|
|
141
|
+
result.stdoutDroppedBytes === 0 &&
|
|
142
|
+
result.stdoutBytes === Buffer.byteLength(stdout) &&
|
|
143
|
+
stdoutPreview === stdout,
|
|
144
|
+
stderrPreviewComplete: result.truncated === false &&
|
|
145
|
+
result.stderrDroppedBytes === 0 &&
|
|
146
|
+
result.stderrBytes === Buffer.byteLength(stderr) &&
|
|
147
|
+
stderrPreview === stderr,
|
|
148
|
+
contextGcFormatVersion: formatVersion
|
|
149
|
+
}
|
|
150
|
+
: {}),
|
|
151
|
+
contextGc: formatVersion >= 2
|
|
152
|
+
? 'Detailed terminal input/output was reclaimed after the process reached a stable terminal state. An incomplete preview is not the complete result. Re-run the command if full output is required.'
|
|
153
|
+
: 'Detailed terminal input/output was reclaimed after the process reached a stable terminal state. Re-run the command if full output is required.'
|
|
111
154
|
});
|
|
112
155
|
}
|
|
113
156
|
export function normalizeContextGcExecResult(modelContent, completedPayload) {
|
|
@@ -192,6 +235,23 @@ function unicodeHeadTail(value, count) {
|
|
|
192
235
|
.slice(-count)
|
|
193
236
|
.join('')}`;
|
|
194
237
|
}
|
|
238
|
+
function retainExecText(retained, key, value, formatVersion) {
|
|
239
|
+
if (formatVersion === 1) {
|
|
240
|
+
retained[`${key}Prefix`] = [...value].slice(0, EXEC_INPUT_PREVIEW_CHARACTERS).join('');
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
if ([...value].length <= EXEC_INPUT_COMPLETE_CHARACTERS) {
|
|
244
|
+
retained[key] = value;
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
retained[`${key}Preview`] = unicodeHeadTail(value, EXEC_INPUT_PREVIEW_CHARACTERS);
|
|
248
|
+
retained[`${key}Bytes`] = Buffer.byteLength(value);
|
|
249
|
+
if (formatVersion === 2)
|
|
250
|
+
retained[`${key}Sha256`] = sha256(value);
|
|
251
|
+
}
|
|
252
|
+
function lineCount(value) {
|
|
253
|
+
return value.length === 0 ? 0 : value.split('\n').length;
|
|
254
|
+
}
|
|
195
255
|
function nonnegativeNumber(value) {
|
|
196
256
|
return typeof value === 'number' && Number.isFinite(value) && value >= 0 ? value : undefined;
|
|
197
257
|
}
|
|
@@ -14,6 +14,16 @@ export interface ContextGcSubagentSnapshot {
|
|
|
14
14
|
readonly timestamp: string;
|
|
15
15
|
readonly truncated?: boolean;
|
|
16
16
|
};
|
|
17
|
+
readonly latestActivity?: {
|
|
18
|
+
readonly kind: 'message';
|
|
19
|
+
readonly timestamp: string;
|
|
20
|
+
readonly phase: 'commentary' | 'final_answer';
|
|
21
|
+
} | {
|
|
22
|
+
readonly kind: 'tool';
|
|
23
|
+
readonly timestamp: string;
|
|
24
|
+
readonly toolName: string;
|
|
25
|
+
readonly status: 'started' | 'completed' | 'failed';
|
|
26
|
+
};
|
|
17
27
|
readonly summary?: string;
|
|
18
28
|
readonly changedFiles?: readonly string[];
|
|
19
29
|
readonly verification?: readonly string[];
|
|
@@ -34,10 +44,12 @@ export declare function contextGcSubagentSnapshotTurnId(snapshot: ContextGcSubag
|
|
|
34
44
|
export declare function contextGcSubagentNeedsRetainedPriorFinal(snapshot: ContextGcSubagentSnapshot): boolean;
|
|
35
45
|
export declare function contextGcSubagentPriorFinalIsRetained(source: ContextGcSubagentSnapshot, retained: ContextGcSubagentSnapshot | undefined): boolean;
|
|
36
46
|
export declare function contextGcSubagentSnapshotIsSuperseded(source: ContextGcSubagentSnapshot, terminal: ContextGcSubagentSnapshot, retainedPriorFinal?: ContextGcSubagentSnapshot): boolean;
|
|
47
|
+
export declare function contextGcSubagentSnapshotIsPreciselyCovered(source: ContextGcSubagentSnapshot, retained: ContextGcSubagentSnapshot): boolean;
|
|
37
48
|
export declare function contextGcSubagentOutputReplacement(input: {
|
|
38
49
|
readonly sourceContent: string;
|
|
39
50
|
readonly source: ContextGcSubagentSnapshot;
|
|
40
51
|
readonly terminal: ContextGcSubagentSnapshot;
|
|
41
52
|
readonly terminalCallId: string;
|
|
42
53
|
readonly evidenceSequence: number;
|
|
54
|
+
readonly formatVersion?: 1 | 2;
|
|
43
55
|
}): string;
|
|
@@ -67,8 +67,32 @@ export function contextGcSubagentSnapshotIsSuperseded(source, terminal, retained
|
|
|
67
67
|
stringArrayIsCovered(source.verification, terminal.verification) &&
|
|
68
68
|
evidenceIsCovered(source.evidence, terminal.evidence));
|
|
69
69
|
}
|
|
70
|
+
export function contextGcSubagentSnapshotIsPreciselyCovered(source, retained) {
|
|
71
|
+
if (retained.status !== 'cancelled' &&
|
|
72
|
+
retained.status !== 'failed' &&
|
|
73
|
+
retained.status !== 'interrupted')
|
|
74
|
+
return false;
|
|
75
|
+
if (source.agentId !== retained.agentId ||
|
|
76
|
+
source.description !== retained.description ||
|
|
77
|
+
contextGcSubagentSnapshotTurnId(source) !== contextGcSubagentSnapshotTurnId(retained) ||
|
|
78
|
+
!optionalStringMatches(source.modelId, retained.modelId) ||
|
|
79
|
+
!optionalStringMatches(source.modelName, retained.modelName) ||
|
|
80
|
+
!optionalStringMatches(source.reasoningEffort, retained.reasoningEffort) ||
|
|
81
|
+
!optionalStringMatches(source.summary, retained.summary) ||
|
|
82
|
+
!optionalStringMatches(source.errorCode, retained.errorCode) ||
|
|
83
|
+
!optionalStringMatches(source.errorMessage, retained.errorMessage) ||
|
|
84
|
+
!latestMessageIsCovered(source.latestMessage, retained.latestMessage) ||
|
|
85
|
+
!latestActivityIsCovered(source.latestActivity, retained.latestActivity) ||
|
|
86
|
+
!receiptIsCovered(source, retained) ||
|
|
87
|
+
!stringArrayIsCovered(source.changedFiles, retained.changedFiles) ||
|
|
88
|
+
!stringArrayIsCovered(source.verification, retained.verification) ||
|
|
89
|
+
!evidenceIsCovered(source.evidence, retained.evidence))
|
|
90
|
+
return false;
|
|
91
|
+
return (source.status === retained.status || source.status === 'queued' || source.status === 'running');
|
|
92
|
+
}
|
|
70
93
|
export function contextGcSubagentOutputReplacement(input) {
|
|
71
94
|
const latestMessage = input.source.latestMessage;
|
|
95
|
+
const formatVersion = input.formatVersion ?? 2;
|
|
72
96
|
return JSON.stringify({
|
|
73
97
|
agentId: input.source.agentId,
|
|
74
98
|
description: input.source.description,
|
|
@@ -87,26 +111,37 @@ export function contextGcSubagentOutputReplacement(input) {
|
|
|
87
111
|
? {}
|
|
88
112
|
: {
|
|
89
113
|
latestMessage: {
|
|
90
|
-
itemId: latestMessage.itemId,
|
|
91
114
|
turnId: latestMessage.turnId,
|
|
92
115
|
phase: latestMessage.phase,
|
|
93
116
|
timestamp: latestMessage.timestamp,
|
|
117
|
+
...(formatVersion === 1 ? { itemId: latestMessage.itemId } : {}),
|
|
94
118
|
...(latestMessage.truncated === undefined
|
|
95
119
|
? {}
|
|
96
120
|
: { truncated: latestMessage.truncated }),
|
|
97
121
|
textBytes: Buffer.byteLength(latestMessage.text),
|
|
98
|
-
textSha256: sha256(latestMessage.text)
|
|
122
|
+
...(formatVersion === 1 ? { textSha256: sha256(latestMessage.text) } : {})
|
|
99
123
|
}
|
|
100
124
|
}),
|
|
125
|
+
...(input.source.latestActivity === undefined
|
|
126
|
+
? {}
|
|
127
|
+
: { latestActivity: input.source.latestActivity }),
|
|
101
128
|
contextGc: {
|
|
102
129
|
kind: 'superseded_subagent_snapshot',
|
|
130
|
+
...(formatVersion === 2 ? { formatVersion } : {}),
|
|
103
131
|
originalBytes: Buffer.byteLength(input.sourceContent),
|
|
104
|
-
originalSha256: sha256(input.sourceContent),
|
|
132
|
+
...(formatVersion === 1 ? { originalSha256: sha256(input.sourceContent) } : {}),
|
|
105
133
|
terminalStatus: input.terminal.status,
|
|
106
|
-
terminalTurnId: contextGcCompletedSubagentTurnId(input.terminal)
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
134
|
+
terminalTurnId: contextGcCompletedSubagentTurnId(input.terminal) ??
|
|
135
|
+
contextGcSubagentSnapshotTurnId(input.terminal),
|
|
136
|
+
...(formatVersion === 1
|
|
137
|
+
? {
|
|
138
|
+
terminalCallId: input.terminalCallId,
|
|
139
|
+
evidenceSequence: input.evidenceSequence
|
|
140
|
+
}
|
|
141
|
+
: {}),
|
|
142
|
+
note: input.terminal.status === 'completed'
|
|
143
|
+
? 'This intermediate child-agent snapshot was superseded by retained completed evidence; complete Turn results remain in retained completed snapshots.'
|
|
144
|
+
: 'This duplicate child-agent snapshot was superseded by a retained non-completed terminal snapshot. Only fields exactly covered by the retained snapshot were reclaimed.'
|
|
110
145
|
}
|
|
111
146
|
});
|
|
112
147
|
}
|
|
@@ -128,6 +163,7 @@ function parseContextGcSubagentSnapshot(value) {
|
|
|
128
163
|
'modelName',
|
|
129
164
|
'reasoningEffort',
|
|
130
165
|
'latestMessage',
|
|
166
|
+
'latestActivity',
|
|
131
167
|
'summary',
|
|
132
168
|
'changedFiles',
|
|
133
169
|
'verification',
|
|
@@ -169,6 +205,9 @@ function parseContextGcSubagentSnapshot(value) {
|
|
|
169
205
|
const latestMessage = parseContextGcSubagentLatestMessage(parsed.latestMessage);
|
|
170
206
|
if (parsed.latestMessage !== undefined && !latestMessage)
|
|
171
207
|
return undefined;
|
|
208
|
+
const latestActivity = parseContextGcSubagentLatestActivity(parsed.latestActivity);
|
|
209
|
+
if (parsed.latestActivity !== undefined && !latestActivity)
|
|
210
|
+
return undefined;
|
|
172
211
|
return {
|
|
173
212
|
agentId: parsed.agentId,
|
|
174
213
|
description: parsed.description,
|
|
@@ -179,6 +218,7 @@ function parseContextGcSubagentSnapshot(value) {
|
|
|
179
218
|
? { reasoningEffort: parsed.reasoningEffort }
|
|
180
219
|
: {}),
|
|
181
220
|
...(latestMessage ? { latestMessage } : {}),
|
|
221
|
+
...(latestActivity ? { latestActivity } : {}),
|
|
182
222
|
...(typeof parsed.summary === 'string' ? { summary: parsed.summary } : {}),
|
|
183
223
|
...(Array.isArray(parsed.changedFiles) ? { changedFiles: [...parsed.changedFiles] } : {}),
|
|
184
224
|
...(Array.isArray(parsed.verification) ? { verification: [...parsed.verification] } : {}),
|
|
@@ -215,6 +255,25 @@ function parseContextGcSubagentLatestMessage(value) {
|
|
|
215
255
|
...(typeof value.truncated === 'boolean' ? { truncated: value.truncated } : {})
|
|
216
256
|
};
|
|
217
257
|
}
|
|
258
|
+
function parseContextGcSubagentLatestActivity(value) {
|
|
259
|
+
if (!isRecord(value) || !isNonemptyString(value.timestamp))
|
|
260
|
+
return undefined;
|
|
261
|
+
if (value.kind === 'message' &&
|
|
262
|
+
Object.keys(value).every((key) => ['kind', 'timestamp', 'phase'].includes(key)) &&
|
|
263
|
+
(value.phase === 'commentary' || value.phase === 'final_answer'))
|
|
264
|
+
return { kind: 'message', timestamp: value.timestamp, phase: value.phase };
|
|
265
|
+
if (value.kind === 'tool' &&
|
|
266
|
+
Object.keys(value).every((key) => ['kind', 'timestamp', 'toolName', 'status'].includes(key)) &&
|
|
267
|
+
isNonemptyString(value.toolName) &&
|
|
268
|
+
(value.status === 'started' || value.status === 'completed' || value.status === 'failed'))
|
|
269
|
+
return {
|
|
270
|
+
kind: 'tool',
|
|
271
|
+
timestamp: value.timestamp,
|
|
272
|
+
toolName: value.toolName,
|
|
273
|
+
status: value.status
|
|
274
|
+
};
|
|
275
|
+
return undefined;
|
|
276
|
+
}
|
|
218
277
|
function isOptionalSubagentEvidence(value) {
|
|
219
278
|
return (value === undefined ||
|
|
220
279
|
(Array.isArray(value) &&
|
|
@@ -236,6 +295,19 @@ function isOptionalNonemptyString(value) {
|
|
|
236
295
|
function optionalStringMatches(source, terminal) {
|
|
237
296
|
return source === undefined || source === terminal;
|
|
238
297
|
}
|
|
298
|
+
function latestMessageIsCovered(source, retained) {
|
|
299
|
+
return (source === undefined || (retained !== undefined && stableJson(source) === stableJson(retained)));
|
|
300
|
+
}
|
|
301
|
+
function latestActivityIsCovered(source, retained) {
|
|
302
|
+
return (source === undefined || (retained !== undefined && stableJson(source) === stableJson(retained)));
|
|
303
|
+
}
|
|
304
|
+
function receiptIsCovered(source, retained) {
|
|
305
|
+
if (source.messageId === undefined)
|
|
306
|
+
return true;
|
|
307
|
+
return (source.messageId === retained.messageId &&
|
|
308
|
+
source.turnId === retained.turnId &&
|
|
309
|
+
source.deliveryStatus === retained.deliveryStatus);
|
|
310
|
+
}
|
|
239
311
|
function stringArrayIsCovered(source, terminal) {
|
|
240
312
|
return source === undefined || source.every((item) => terminal?.includes(item) === true);
|
|
241
313
|
}
|
|
@@ -184,6 +184,21 @@ export declare class JsonlSessionStore {
|
|
|
184
184
|
source: SessionSource;
|
|
185
185
|
}[];
|
|
186
186
|
}>;
|
|
187
|
+
listSubagentChildren(parentSessionId: string): Promise<{
|
|
188
|
+
id: string;
|
|
189
|
+
title: string;
|
|
190
|
+
workspace: string;
|
|
191
|
+
createdAt: string;
|
|
192
|
+
lastActivityAt: string;
|
|
193
|
+
source: {
|
|
194
|
+
readonly type: "subagent";
|
|
195
|
+
readonly parentSessionId: string;
|
|
196
|
+
readonly rootSessionId: string;
|
|
197
|
+
readonly parentExecutionId: string;
|
|
198
|
+
readonly role: "general-purpose" | "explore";
|
|
199
|
+
readonly description: string;
|
|
200
|
+
};
|
|
201
|
+
}[]>;
|
|
187
202
|
sessionTree(sessionId: string): Promise<readonly string[]>;
|
|
188
203
|
delete(sessionId: string): Promise<{
|
|
189
204
|
readonly deleted: boolean;
|
|
@@ -841,6 +841,21 @@ export class JsonlSessionStore {
|
|
|
841
841
|
: undefined;
|
|
842
842
|
return { sessions, ...(next ? { nextCursor: next } : {}) };
|
|
843
843
|
}
|
|
844
|
+
async listSubagentChildren(parentSessionId) {
|
|
845
|
+
this.#assertOpen();
|
|
846
|
+
await this.#refreshIndex();
|
|
847
|
+
return this.#shared.index.sessions
|
|
848
|
+
.map((session) => ({ session, source: normalizeSessionSource(session.source) }))
|
|
849
|
+
.filter((item) => item.source.type === 'subagent' && item.source.parentSessionId === parentSessionId)
|
|
850
|
+
.map(({ session, source }) => ({
|
|
851
|
+
id: session.id,
|
|
852
|
+
title: session.title,
|
|
853
|
+
workspace: session.workspace,
|
|
854
|
+
createdAt: session.createdAt,
|
|
855
|
+
lastActivityAt: session.lastActivityAt,
|
|
856
|
+
source
|
|
857
|
+
}));
|
|
858
|
+
}
|
|
844
859
|
async sessionTree(sessionId) {
|
|
845
860
|
this.#assertOpen();
|
|
846
861
|
await this.#refreshIndex();
|
|
@@ -26,6 +26,16 @@ export interface SubagentLatestMessage {
|
|
|
26
26
|
timestamp: string;
|
|
27
27
|
truncated: boolean;
|
|
28
28
|
}
|
|
29
|
+
export type SubagentLatestActivity = {
|
|
30
|
+
kind: 'message';
|
|
31
|
+
timestamp: string;
|
|
32
|
+
phase: 'commentary' | 'final_answer';
|
|
33
|
+
} | {
|
|
34
|
+
kind: 'tool';
|
|
35
|
+
timestamp: string;
|
|
36
|
+
toolName: string;
|
|
37
|
+
status: 'started' | 'completed' | 'failed';
|
|
38
|
+
};
|
|
29
39
|
export interface SubagentTaskSnapshot extends Record<string, unknown> {
|
|
30
40
|
agentId: string;
|
|
31
41
|
description: string;
|
|
@@ -34,6 +44,7 @@ export interface SubagentTaskSnapshot extends Record<string, unknown> {
|
|
|
34
44
|
modelName?: string;
|
|
35
45
|
reasoningEffort?: MarAgentReasoningEffort;
|
|
36
46
|
latestMessage?: SubagentLatestMessage;
|
|
47
|
+
latestActivity?: SubagentLatestActivity;
|
|
37
48
|
summary?: string;
|
|
38
49
|
changedFiles?: string[];
|
|
39
50
|
verification?: string[];
|
|
@@ -64,6 +75,7 @@ export interface SubagentController {
|
|
|
64
75
|
beginExecution?(runner: (task: SubagentTaskInput, signal: AbortSignal) => Promise<SubagentTaskResult>): void;
|
|
65
76
|
run(input: SubagentTaskInput, options?: SubagentRunOptions): Promise<SubagentTaskSnapshot>;
|
|
66
77
|
output(agentId: string, options?: SubagentOutputOptions): Promise<SubagentTaskSnapshot>;
|
|
78
|
+
outputAny(options?: SubagentOutputOptions): Promise<SubagentTaskSnapshot>;
|
|
67
79
|
message(agentId: string, message: string, options?: SubagentMessageOptions): Promise<SubagentTaskSnapshot>;
|
|
68
80
|
cancel(agentId: string): Promise<SubagentTaskSnapshot>;
|
|
69
81
|
listSessionResources(): readonly {
|
|
@@ -86,11 +98,12 @@ export declare class SubagentScheduler implements SubagentController {
|
|
|
86
98
|
beginExecution(runner: (task: SubagentTaskInput, signal: AbortSignal) => Promise<SubagentTaskResult>): void;
|
|
87
99
|
run(input: SubagentTaskInput, options?: SubagentRunOptions): Promise<SubagentTaskSnapshot>;
|
|
88
100
|
output(agentId: string, options?: SubagentOutputOptions): Promise<SubagentTaskSnapshot>;
|
|
101
|
+
outputAny(options?: SubagentOutputOptions): Promise<SubagentTaskSnapshot>;
|
|
89
102
|
cancel(agentId: string): Promise<SubagentTaskSnapshot>;
|
|
90
103
|
message(_agentId: string, _message: string): Promise<SubagentTaskSnapshot>;
|
|
91
104
|
listSessionResources(): {
|
|
92
105
|
agentId: string;
|
|
93
|
-
status: "completed" | "cancelled" | "running" | "
|
|
106
|
+
status: "completed" | "cancelled" | "running" | "failed" | "queued";
|
|
94
107
|
description: string;
|
|
95
108
|
}[];
|
|
96
109
|
dispose(): Promise<void>;
|
|
@@ -6,6 +6,7 @@ export class SubagentScheduler {
|
|
|
6
6
|
limits;
|
|
7
7
|
#tasks = new Map();
|
|
8
8
|
#queue = [];
|
|
9
|
+
#completionWaiters = new Set();
|
|
9
10
|
#running = 0;
|
|
10
11
|
#disposed = false;
|
|
11
12
|
#createdInExecution = 0;
|
|
@@ -92,9 +93,26 @@ export class SubagentScheduler {
|
|
|
92
93
|
? undefined
|
|
93
94
|
: Math.max(options.waitMs, TOOL_EXECUTION_LIMITS.agentWaitWaitFloorMs);
|
|
94
95
|
if (waitMs !== undefined && !['completed', 'failed', 'cancelled'].includes(task.status))
|
|
95
|
-
await waitForCompletion(task
|
|
96
|
+
await this.#waitForCompletion(task, waitMs, options.signal);
|
|
96
97
|
return this.#public(task);
|
|
97
98
|
}
|
|
99
|
+
async outputAny(options = {}) {
|
|
100
|
+
options.signal?.throwIfAborted();
|
|
101
|
+
validateOutputOptions(options);
|
|
102
|
+
const tasks = [...this.#tasks.values()];
|
|
103
|
+
if (tasks.length === 0)
|
|
104
|
+
throw new MarAgentError('MAR_AGENT_SUBAGENT_NOT_FOUND', 'Subagent task was not found.');
|
|
105
|
+
const live = tasks.filter((task) => task.status === 'queued' || task.status === 'running');
|
|
106
|
+
if (live.length === 0 || options.waitMs === 0)
|
|
107
|
+
return this.#public(live.at(-1) ?? tasks.at(-1));
|
|
108
|
+
const waitMs = options.waitMs === undefined
|
|
109
|
+
? undefined
|
|
110
|
+
: Math.max(options.waitMs, TOOL_EXECUTION_LIMITS.agentWaitWaitFloorMs);
|
|
111
|
+
if (waitMs !== undefined)
|
|
112
|
+
await this.#waitForAnyCompletion(live, waitMs, options.signal);
|
|
113
|
+
const completed = live.find((task) => task.status !== 'queued' && task.status !== 'running');
|
|
114
|
+
return this.#public(completed ?? live.at(-1));
|
|
115
|
+
}
|
|
98
116
|
async cancel(agentId) {
|
|
99
117
|
const task = this.#tasks.get(agentId);
|
|
100
118
|
if (!task)
|
|
@@ -115,6 +133,7 @@ export class SubagentScheduler {
|
|
|
115
133
|
this.#queue.splice(index, 1);
|
|
116
134
|
task.controller.abort(reason);
|
|
117
135
|
task.resolve();
|
|
136
|
+
this.#notifyCompletion(task);
|
|
118
137
|
this.#drain();
|
|
119
138
|
}
|
|
120
139
|
else if (task.status === 'running')
|
|
@@ -145,6 +164,7 @@ export class SubagentScheduler {
|
|
|
145
164
|
.finally(() => {
|
|
146
165
|
this.#running--;
|
|
147
166
|
task.resolve();
|
|
167
|
+
this.#notifyCompletion(task);
|
|
148
168
|
this.#drain();
|
|
149
169
|
});
|
|
150
170
|
}
|
|
@@ -175,6 +195,7 @@ export class SubagentScheduler {
|
|
|
175
195
|
task.status = 'cancelled';
|
|
176
196
|
task.controller.abort();
|
|
177
197
|
task.resolve();
|
|
198
|
+
this.#notifyCompletion(task);
|
|
178
199
|
}
|
|
179
200
|
else if (task.status === 'running')
|
|
180
201
|
task.controller.abort();
|
|
@@ -183,6 +204,16 @@ export class SubagentScheduler {
|
|
|
183
204
|
this.#queue.length = 0;
|
|
184
205
|
this.#tasks.clear();
|
|
185
206
|
}
|
|
207
|
+
#waitForAnyCompletion(candidates, waitMs, signal) {
|
|
208
|
+
return waitForCompletionNotification(this.#completionWaiters, (task) => candidates.includes(task), waitMs, signal);
|
|
209
|
+
}
|
|
210
|
+
#waitForCompletion(task, waitMs, signal) {
|
|
211
|
+
return waitForCompletionNotification(this.#completionWaiters, (completed) => completed === task, waitMs, signal);
|
|
212
|
+
}
|
|
213
|
+
#notifyCompletion(task) {
|
|
214
|
+
for (const waiter of this.#completionWaiters)
|
|
215
|
+
waiter(task);
|
|
216
|
+
}
|
|
186
217
|
}
|
|
187
218
|
function normalizeResult(result) {
|
|
188
219
|
return {
|
|
@@ -217,7 +248,7 @@ function truncateUtf8(value, maximumBytes) {
|
|
|
217
248
|
end--;
|
|
218
249
|
return bytes.subarray(0, end).toString('utf8');
|
|
219
250
|
}
|
|
220
|
-
function
|
|
251
|
+
function waitForCompletionNotification(waiters, accepts, waitMs, signal) {
|
|
221
252
|
return new Promise((resolve, reject) => {
|
|
222
253
|
let settled = false;
|
|
223
254
|
const finish = (error) => {
|
|
@@ -226,20 +257,32 @@ function waitForCompletion(completion, waitMs, signal) {
|
|
|
226
257
|
settled = true;
|
|
227
258
|
clearTimeout(timer);
|
|
228
259
|
signal?.removeEventListener('abort', abort);
|
|
260
|
+
waiters.delete(notify);
|
|
229
261
|
if (error === undefined)
|
|
230
262
|
resolve();
|
|
231
263
|
else
|
|
232
264
|
reject(error);
|
|
233
265
|
};
|
|
266
|
+
const notify = (value) => {
|
|
267
|
+
if (accepts(value))
|
|
268
|
+
finish();
|
|
269
|
+
};
|
|
234
270
|
const abort = () => finish(signal?.reason ?? new MarAgentError('MAR_AGENT_EXECUTION_CANCELLED', 'Execution cancelled.'));
|
|
235
271
|
const timer = setTimeout(finish, waitMs);
|
|
236
272
|
timer.unref();
|
|
273
|
+
waiters.add(notify);
|
|
237
274
|
signal?.addEventListener('abort', abort, { once: true });
|
|
238
|
-
void completion.then(() => finish());
|
|
239
275
|
if (signal?.aborted)
|
|
240
276
|
abort();
|
|
241
277
|
});
|
|
242
278
|
}
|
|
279
|
+
function validateOutputOptions(options) {
|
|
280
|
+
if (options.waitMs !== undefined &&
|
|
281
|
+
(!Number.isInteger(options.waitMs) ||
|
|
282
|
+
options.waitMs < TOOL_EXECUTION_LIMITS.agentWaitMinWaitMs ||
|
|
283
|
+
options.waitMs > TOOL_EXECUTION_LIMITS.agentWaitMaxWaitMs))
|
|
284
|
+
throw new MarAgentError('MAR_AGENT_TOOL_INPUT_INVALID', 'Task wait is invalid.');
|
|
285
|
+
}
|
|
243
286
|
function linkAbort(signal, abort) {
|
|
244
287
|
const listener = () => abort(signal.reason);
|
|
245
288
|
signal.addEventListener('abort', listener, { once: true });
|
|
@@ -42,6 +42,7 @@ export interface SubagentParentExecution {
|
|
|
42
42
|
}
|
|
43
43
|
export interface PersistedSubagentSession {
|
|
44
44
|
readonly id: string;
|
|
45
|
+
readonly createdAt: string;
|
|
45
46
|
readonly source: Extract<SessionSource, {
|
|
46
47
|
type: 'subagent';
|
|
47
48
|
}>;
|
|
@@ -57,6 +58,7 @@ export interface SubagentSessionControllerBackend {
|
|
|
57
58
|
readonly id: string;
|
|
58
59
|
}>;
|
|
59
60
|
deleteSession(sessionId: string): Promise<void>;
|
|
61
|
+
/** Returns children in durable creation order. */
|
|
60
62
|
listChildren(parentSessionId: string): Promise<readonly PersistedSubagentSession[]>;
|
|
61
63
|
loadChild(parentSessionId: string, sessionId: string): Promise<PersistedSubagentSession | undefined>;
|
|
62
64
|
}
|
|
@@ -83,11 +85,12 @@ export declare class SubagentSessionController implements SubagentController {
|
|
|
83
85
|
endParentExecution(executionId: string): void;
|
|
84
86
|
run(input: SubagentTaskInput, options?: SubagentRunOptions): Promise<SubagentTaskSnapshot>;
|
|
85
87
|
output(agentId: string, options?: SubagentOutputOptions): Promise<SubagentTaskSnapshot>;
|
|
88
|
+
outputAny(options?: SubagentOutputOptions): Promise<SubagentTaskSnapshot>;
|
|
86
89
|
message(agentId: string, message: string, options?: SubagentMessageOptions): Promise<SubagentTaskSnapshot>;
|
|
87
90
|
cancel(agentId: string): Promise<SubagentTaskSnapshot>;
|
|
88
91
|
listSessionResources(): {
|
|
89
92
|
agentId: string;
|
|
90
|
-
status: "completed" | "cancelled" | "running" | "
|
|
93
|
+
status: "completed" | "cancelled" | "running" | "failed" | "queued" | "interrupted";
|
|
91
94
|
description: string;
|
|
92
95
|
}[];
|
|
93
96
|
dispose(): Promise<void>;
|