@myagentroam/agent 0.9.66 → 0.9.68
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/prompts/subagent.d.ts +1 -1
- package/dist/prompts/subagent.js +1 -1
- package/dist/sdk/agent.js +5 -0
- package/dist/session/context-gc-subagent.d.ts +1 -0
- package/dist/session/context-gc-subagent.js +13 -1
- package/dist/subagent/scheduler.d.ts +98 -2
- package/dist/subagent/scheduler.js +47 -27
- package/dist/subagent/session-controller.js +179 -36
- package/dist/tools/agent-wait.js +15 -4
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const subagentPrompt = "# Subagents\nUse your own direct tools by default. Delegate only when a bounded task can proceed independently and parallelism has real value; do not delegate ordinary local search, a known-file read, a single verification command, a small edit, or sequential debugging. Give each child a self-contained objective, relevant paths and constraints, expected evidence, and a clear read/write boundary. Subagents cannot recurse and share the real filesystem; do not assign conflicting writes or assume isolation. Track background agent IDs, wait or cancel them, inspect their actual workspace effects, and verify every claim before incorporating it. The parent retains responsibility for the final result.";
|
|
1
|
+
export declare const subagentPrompt = "# Subagents\nUse your own direct tools by default. Delegate only when a bounded task can proceed independently and parallelism has real value; do not delegate ordinary local search, a known-file read, a single verification command, a small edit, or sequential debugging. Give each child a self-contained objective, relevant paths and constraints, expected evidence, and a clear read/write boundary. Subagents cannot recurse and share the real filesystem; do not assign conflicting writes or assume isolation. Track background agent IDs, wait or cancel them, inspect their actual workspace effects, and verify every claim before incorporating it. Continue meaningful non-overlapping work after starting a background child. Wait for completion only when the next critical-path action is blocked on the final result, and then prefer one long wait measured in minutes rather than repeated short waits. Wait for a public message only when a fresh progress update would change the next decision; do not use message waits as heartbeat checks or request progress merely to confirm that a child is still running. The parent retains responsibility for the final result.";
|
|
2
2
|
export interface SubagentModelOption {
|
|
3
3
|
readonly id: string;
|
|
4
4
|
readonly name: string;
|
package/dist/prompts/subagent.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export const subagentPrompt = '# Subagents\nUse your own direct tools by default. Delegate only when a bounded task can proceed independently and parallelism has real value; do not delegate ordinary local search, a known-file read, a single verification command, a small edit, or sequential debugging. Give each child a self-contained objective, relevant paths and constraints, expected evidence, and a clear read/write boundary. Subagents cannot recurse and share the real filesystem; do not assign conflicting writes or assume isolation. Track background agent IDs, wait or cancel them, inspect their actual workspace effects, and verify every claim before incorporating it. The parent retains responsibility for the final result.';
|
|
1
|
+
export const subagentPrompt = '# Subagents\nUse your own direct tools by default. Delegate only when a bounded task can proceed independently and parallelism has real value; do not delegate ordinary local search, a known-file read, a single verification command, a small edit, or sequential debugging. Give each child a self-contained objective, relevant paths and constraints, expected evidence, and a clear read/write boundary. Subagents cannot recurse and share the real filesystem; do not assign conflicting writes or assume isolation. Track background agent IDs, wait or cancel them, inspect their actual workspace effects, and verify every claim before incorporating it. Continue meaningful non-overlapping work after starting a background child. Wait for completion only when the next critical-path action is blocked on the final result, and then prefer one long wait measured in minutes rather than repeated short waits. Wait for a public message only when a fresh progress update would change the next decision; do not use message waits as heartbeat checks or request progress merely to confirm that a child is still running. The parent retains responsibility for the final result.';
|
|
2
2
|
export function currentAgentModelPrompt(model) {
|
|
3
3
|
if (!model)
|
|
4
4
|
return '';
|
package/dist/sdk/agent.js
CHANGED
|
@@ -1841,6 +1841,11 @@ function isSubagentTaskSnapshot(value) {
|
|
|
1841
1841
|
(value.reasoningEffort === undefined ||
|
|
1842
1842
|
(typeof value.reasoningEffort === 'string' &&
|
|
1843
1843
|
['low', 'medium', 'high', 'xhigh', 'max', 'ultra'].includes(value.reasoningEffort))) &&
|
|
1844
|
+
(value.waitOutcome === undefined ||
|
|
1845
|
+
value.waitOutcome === 'snapshot' ||
|
|
1846
|
+
value.waitOutcome === 'message' ||
|
|
1847
|
+
value.waitOutcome === 'completion' ||
|
|
1848
|
+
value.waitOutcome === 'timeout') &&
|
|
1844
1849
|
(value.latestMessage === undefined ||
|
|
1845
1850
|
(isRecord(value.latestMessage) &&
|
|
1846
1851
|
typeof value.latestMessage.itemId === 'string' &&
|
|
@@ -37,6 +37,7 @@ export interface ContextGcSubagentSnapshot {
|
|
|
37
37
|
readonly messageId?: string;
|
|
38
38
|
readonly turnId?: string;
|
|
39
39
|
readonly deliveryStatus?: 'accepted' | 'applied' | 'cancelled' | 'failed';
|
|
40
|
+
readonly waitOutcome?: 'snapshot' | 'message' | 'completion' | 'timeout';
|
|
40
41
|
}
|
|
41
42
|
export declare function normalizeContextGcSubagentSnapshot(modelContent: unknown, completedPayload: unknown, toolName: ContextGcSubagentToolName): ContextGcSubagentSnapshot | undefined;
|
|
42
43
|
export declare function contextGcCompletedSubagentTurnId(snapshot: ContextGcSubagentSnapshot): string | undefined;
|
|
@@ -107,6 +107,7 @@ export function contextGcSubagentOutputReplacement(input) {
|
|
|
107
107
|
...(input.source.deliveryStatus === undefined
|
|
108
108
|
? {}
|
|
109
109
|
: { deliveryStatus: input.source.deliveryStatus }),
|
|
110
|
+
...(input.source.waitOutcome === undefined ? {} : { waitOutcome: input.source.waitOutcome }),
|
|
110
111
|
...(latestMessage === undefined
|
|
111
112
|
? {}
|
|
112
113
|
: {
|
|
@@ -172,7 +173,8 @@ function parseContextGcSubagentSnapshot(value) {
|
|
|
172
173
|
'errorMessage',
|
|
173
174
|
'messageId',
|
|
174
175
|
'turnId',
|
|
175
|
-
'deliveryStatus'
|
|
176
|
+
'deliveryStatus',
|
|
177
|
+
'waitOutcome'
|
|
176
178
|
].includes(key)) ||
|
|
177
179
|
!isNonemptyString(parsed.agentId) ||
|
|
178
180
|
!isNonemptyString(parsed.description) ||
|
|
@@ -195,6 +197,11 @@ function parseContextGcSubagentSnapshot(value) {
|
|
|
195
197
|
parsed.deliveryStatus !== 'applied' &&
|
|
196
198
|
parsed.deliveryStatus !== 'cancelled' &&
|
|
197
199
|
parsed.deliveryStatus !== 'failed') ||
|
|
200
|
+
(parsed.waitOutcome !== undefined &&
|
|
201
|
+
parsed.waitOutcome !== 'snapshot' &&
|
|
202
|
+
parsed.waitOutcome !== 'message' &&
|
|
203
|
+
parsed.waitOutcome !== 'completion' &&
|
|
204
|
+
parsed.waitOutcome !== 'timeout') ||
|
|
198
205
|
!isOptionalStringArray(parsed.changedFiles) ||
|
|
199
206
|
!isOptionalStringArray(parsed.verification) ||
|
|
200
207
|
!isOptionalSubagentEvidence(parsed.evidence))
|
|
@@ -233,6 +240,11 @@ function parseContextGcSubagentSnapshot(value) {
|
|
|
233
240
|
? {
|
|
234
241
|
deliveryStatus: parsed.deliveryStatus
|
|
235
242
|
}
|
|
243
|
+
: {}),
|
|
244
|
+
...(typeof parsed.waitOutcome === 'string'
|
|
245
|
+
? {
|
|
246
|
+
waitOutcome: parsed.waitOutcome
|
|
247
|
+
}
|
|
236
248
|
: {})
|
|
237
249
|
};
|
|
238
250
|
}
|
|
@@ -58,7 +58,10 @@ export interface SubagentTaskSnapshot extends Record<string, unknown> {
|
|
|
58
58
|
messageId?: string;
|
|
59
59
|
turnId?: string;
|
|
60
60
|
deliveryStatus?: 'accepted' | 'applied' | 'cancelled' | 'failed';
|
|
61
|
+
waitOutcome?: SubagentWaitOutcome;
|
|
61
62
|
}
|
|
63
|
+
export type SubagentWaitFor = 'completion' | 'message';
|
|
64
|
+
export type SubagentWaitOutcome = 'snapshot' | 'message' | 'completion' | 'timeout';
|
|
62
65
|
export interface SubagentMessageOptions {
|
|
63
66
|
delivery?: 'append' | 'replace';
|
|
64
67
|
signal?: AbortSignal;
|
|
@@ -69,6 +72,7 @@ export interface SubagentRunOptions {
|
|
|
69
72
|
}
|
|
70
73
|
export interface SubagentOutputOptions {
|
|
71
74
|
waitMs?: number;
|
|
75
|
+
waitFor?: SubagentWaitFor;
|
|
72
76
|
signal?: AbortSignal;
|
|
73
77
|
}
|
|
74
78
|
export interface SubagentController {
|
|
@@ -97,8 +101,100 @@ export declare class SubagentScheduler implements SubagentController {
|
|
|
97
101
|
});
|
|
98
102
|
beginExecution(runner: (task: SubagentTaskInput, signal: AbortSignal) => Promise<SubagentTaskResult>): void;
|
|
99
103
|
run(input: SubagentTaskInput, options?: SubagentRunOptions): Promise<SubagentTaskSnapshot>;
|
|
100
|
-
output(agentId: string, options?: SubagentOutputOptions): Promise<
|
|
101
|
-
|
|
104
|
+
output(agentId: string, options?: SubagentOutputOptions): Promise<{
|
|
105
|
+
waitOutcome: "snapshot";
|
|
106
|
+
agentId: string;
|
|
107
|
+
description: string;
|
|
108
|
+
status: "queued" | "running" | "completed" | "failed" | "cancelled" | "interrupted";
|
|
109
|
+
modelId?: string;
|
|
110
|
+
modelName?: string;
|
|
111
|
+
reasoningEffort?: MarAgentReasoningEffort;
|
|
112
|
+
latestMessage?: SubagentLatestMessage;
|
|
113
|
+
latestActivity?: SubagentLatestActivity;
|
|
114
|
+
summary?: string;
|
|
115
|
+
changedFiles?: string[];
|
|
116
|
+
verification?: string[];
|
|
117
|
+
evidence?: Array<{
|
|
118
|
+
path: string;
|
|
119
|
+
startLine?: number;
|
|
120
|
+
endLine?: number;
|
|
121
|
+
}>;
|
|
122
|
+
errorCode?: string;
|
|
123
|
+
errorMessage?: string;
|
|
124
|
+
messageId?: string;
|
|
125
|
+
turnId?: string;
|
|
126
|
+
deliveryStatus?: "accepted" | "applied" | "cancelled" | "failed";
|
|
127
|
+
} | {
|
|
128
|
+
waitOutcome: "timeout" | "completion";
|
|
129
|
+
agentId: string;
|
|
130
|
+
description: string;
|
|
131
|
+
status: "queued" | "running" | "completed" | "failed" | "cancelled" | "interrupted";
|
|
132
|
+
modelId?: string;
|
|
133
|
+
modelName?: string;
|
|
134
|
+
reasoningEffort?: MarAgentReasoningEffort;
|
|
135
|
+
latestMessage?: SubagentLatestMessage;
|
|
136
|
+
latestActivity?: SubagentLatestActivity;
|
|
137
|
+
summary?: string;
|
|
138
|
+
changedFiles?: string[];
|
|
139
|
+
verification?: string[];
|
|
140
|
+
evidence?: Array<{
|
|
141
|
+
path: string;
|
|
142
|
+
startLine?: number;
|
|
143
|
+
endLine?: number;
|
|
144
|
+
}>;
|
|
145
|
+
errorCode?: string;
|
|
146
|
+
errorMessage?: string;
|
|
147
|
+
messageId?: string;
|
|
148
|
+
turnId?: string;
|
|
149
|
+
deliveryStatus?: "accepted" | "applied" | "cancelled" | "failed";
|
|
150
|
+
}>;
|
|
151
|
+
outputAny(options?: SubagentOutputOptions): Promise<{
|
|
152
|
+
waitOutcome: "snapshot";
|
|
153
|
+
agentId: string;
|
|
154
|
+
description: string;
|
|
155
|
+
status: "queued" | "running" | "completed" | "failed" | "cancelled" | "interrupted";
|
|
156
|
+
modelId?: string;
|
|
157
|
+
modelName?: string;
|
|
158
|
+
reasoningEffort?: MarAgentReasoningEffort;
|
|
159
|
+
latestMessage?: SubagentLatestMessage;
|
|
160
|
+
latestActivity?: SubagentLatestActivity;
|
|
161
|
+
summary?: string;
|
|
162
|
+
changedFiles?: string[];
|
|
163
|
+
verification?: string[];
|
|
164
|
+
evidence?: Array<{
|
|
165
|
+
path: string;
|
|
166
|
+
startLine?: number;
|
|
167
|
+
endLine?: number;
|
|
168
|
+
}>;
|
|
169
|
+
errorCode?: string;
|
|
170
|
+
errorMessage?: string;
|
|
171
|
+
messageId?: string;
|
|
172
|
+
turnId?: string;
|
|
173
|
+
deliveryStatus?: "accepted" | "applied" | "cancelled" | "failed";
|
|
174
|
+
} | {
|
|
175
|
+
waitOutcome: "timeout" | "completion";
|
|
176
|
+
agentId: string;
|
|
177
|
+
description: string;
|
|
178
|
+
status: "queued" | "running" | "completed" | "failed" | "cancelled" | "interrupted";
|
|
179
|
+
modelId?: string;
|
|
180
|
+
modelName?: string;
|
|
181
|
+
reasoningEffort?: MarAgentReasoningEffort;
|
|
182
|
+
latestMessage?: SubagentLatestMessage;
|
|
183
|
+
latestActivity?: SubagentLatestActivity;
|
|
184
|
+
summary?: string;
|
|
185
|
+
changedFiles?: string[];
|
|
186
|
+
verification?: string[];
|
|
187
|
+
evidence?: Array<{
|
|
188
|
+
path: string;
|
|
189
|
+
startLine?: number;
|
|
190
|
+
endLine?: number;
|
|
191
|
+
}>;
|
|
192
|
+
errorCode?: string;
|
|
193
|
+
errorMessage?: string;
|
|
194
|
+
messageId?: string;
|
|
195
|
+
turnId?: string;
|
|
196
|
+
deliveryStatus?: "accepted" | "applied" | "cancelled" | "failed";
|
|
197
|
+
}>;
|
|
102
198
|
cancel(agentId: string): Promise<SubagentTaskSnapshot>;
|
|
103
199
|
message(_agentId: string, _message: string): Promise<SubagentTaskSnapshot>;
|
|
104
200
|
listSessionResources(): {
|
|
@@ -81,37 +81,43 @@ export class SubagentScheduler {
|
|
|
81
81
|
}
|
|
82
82
|
async output(agentId, options = {}) {
|
|
83
83
|
options.signal?.throwIfAborted();
|
|
84
|
+
rejectUnsupportedMessageWait(options);
|
|
84
85
|
const task = this.#tasks.get(agentId);
|
|
85
86
|
if (!task)
|
|
86
87
|
throw new MarAgentError('MAR_AGENT_SUBAGENT_NOT_FOUND', 'Subagent task was not found.');
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
88
|
+
validateOutputOptions(options);
|
|
89
|
+
const terminal = ['completed', 'failed', 'cancelled'].includes(task.status);
|
|
90
|
+
if (options.waitMs === undefined || options.waitMs === 0)
|
|
91
|
+
return { ...this.#public(task), waitOutcome: 'snapshot' };
|
|
92
|
+
if (terminal)
|
|
93
|
+
return { ...this.#public(task), waitOutcome: 'completion' };
|
|
94
|
+
const waitMs = Math.max(options.waitMs, TOOL_EXECUTION_LIMITS.agentWaitWaitFloorMs);
|
|
95
|
+
const completed = await this.#waitForCompletion(task, waitMs, options.signal);
|
|
96
|
+
options.signal?.throwIfAborted();
|
|
97
|
+
return {
|
|
98
|
+
...this.#public(task),
|
|
99
|
+
waitOutcome: completed ? 'completion' : 'timeout'
|
|
100
|
+
};
|
|
98
101
|
}
|
|
99
102
|
async outputAny(options = {}) {
|
|
100
103
|
options.signal?.throwIfAborted();
|
|
101
104
|
validateOutputOptions(options);
|
|
105
|
+
rejectUnsupportedMessageWait(options);
|
|
102
106
|
const tasks = [...this.#tasks.values()];
|
|
103
107
|
if (tasks.length === 0)
|
|
104
108
|
throw new MarAgentError('MAR_AGENT_SUBAGENT_NOT_FOUND', 'Subagent task was not found.');
|
|
105
109
|
const live = tasks.filter((task) => task.status === 'queued' || task.status === 'running');
|
|
106
|
-
if (
|
|
107
|
-
return this.#public(live.at(-1) ?? tasks.at(-1));
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
110
|
+
if (options.waitMs === undefined || options.waitMs === 0)
|
|
111
|
+
return { ...this.#public(live.at(-1) ?? tasks.at(-1)), waitOutcome: 'snapshot' };
|
|
112
|
+
if (live.length === 0)
|
|
113
|
+
return { ...this.#public(tasks.at(-1)), waitOutcome: 'completion' };
|
|
114
|
+
const waitMs = Math.max(options.waitMs, TOOL_EXECUTION_LIMITS.agentWaitWaitFloorMs);
|
|
115
|
+
const completed = await this.#waitForAnyCompletion(live, waitMs, options.signal);
|
|
116
|
+
options.signal?.throwIfAborted();
|
|
117
|
+
return {
|
|
118
|
+
...this.#public(completed ?? live.at(-1)),
|
|
119
|
+
waitOutcome: completed ? 'completion' : 'timeout'
|
|
120
|
+
};
|
|
115
121
|
}
|
|
116
122
|
async cancel(agentId) {
|
|
117
123
|
const task = this.#tasks.get(agentId);
|
|
@@ -259,13 +265,20 @@ function waitForCompletionNotification(waiters, accepts, waitMs, signal) {
|
|
|
259
265
|
signal?.removeEventListener('abort', abort);
|
|
260
266
|
waiters.delete(notify);
|
|
261
267
|
if (error === undefined)
|
|
262
|
-
resolve();
|
|
268
|
+
resolve(undefined);
|
|
263
269
|
else
|
|
264
270
|
reject(error);
|
|
265
271
|
};
|
|
266
272
|
const notify = (value) => {
|
|
267
|
-
if (accepts(value))
|
|
268
|
-
|
|
273
|
+
if (accepts(value)) {
|
|
274
|
+
if (settled)
|
|
275
|
+
return;
|
|
276
|
+
settled = true;
|
|
277
|
+
clearTimeout(timer);
|
|
278
|
+
signal?.removeEventListener('abort', abort);
|
|
279
|
+
waiters.delete(notify);
|
|
280
|
+
resolve(value);
|
|
281
|
+
}
|
|
269
282
|
};
|
|
270
283
|
const abort = () => finish(signal?.reason ?? new MarAgentError('MAR_AGENT_EXECUTION_CANCELLED', 'Execution cancelled.'));
|
|
271
284
|
const timer = setTimeout(finish, waitMs);
|
|
@@ -277,12 +290,19 @@ function waitForCompletionNotification(waiters, accepts, waitMs, signal) {
|
|
|
277
290
|
});
|
|
278
291
|
}
|
|
279
292
|
function validateOutputOptions(options) {
|
|
280
|
-
if (options.
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
293
|
+
if ((options.waitFor !== undefined &&
|
|
294
|
+
options.waitFor !== 'completion' &&
|
|
295
|
+
options.waitFor !== 'message') ||
|
|
296
|
+
(options.waitMs !== undefined &&
|
|
297
|
+
(!Number.isInteger(options.waitMs) ||
|
|
298
|
+
options.waitMs < TOOL_EXECUTION_LIMITS.agentWaitMinWaitMs ||
|
|
299
|
+
options.waitMs > TOOL_EXECUTION_LIMITS.agentWaitMaxWaitMs)))
|
|
284
300
|
throw new MarAgentError('MAR_AGENT_TOOL_INPUT_INVALID', 'Task wait is invalid.');
|
|
285
301
|
}
|
|
302
|
+
function rejectUnsupportedMessageWait(options) {
|
|
303
|
+
if (options.waitFor === 'message')
|
|
304
|
+
throw new MarAgentError('MAR_AGENT_TOOL_NOT_AVAILABLE', 'Message waits require a session-backed subagent controller.');
|
|
305
|
+
}
|
|
286
306
|
function linkAbort(signal, abort) {
|
|
287
307
|
const listener = () => abort(signal.reason);
|
|
288
308
|
signal.addEventListener('abort', listener, { once: true });
|
|
@@ -9,6 +9,8 @@ export class SubagentSessionController {
|
|
|
9
9
|
#agents = new Map();
|
|
10
10
|
#queue = [];
|
|
11
11
|
#completionWaiters = new Set();
|
|
12
|
+
#activityWaiters = new Set();
|
|
13
|
+
#activityRevision = 0;
|
|
12
14
|
#running = 0;
|
|
13
15
|
#disposed = false;
|
|
14
16
|
#createdInExecution = 0;
|
|
@@ -69,6 +71,9 @@ export class SubagentSessionController {
|
|
|
69
71
|
description: source.description,
|
|
70
72
|
role: input.role,
|
|
71
73
|
status: 'queued',
|
|
74
|
+
messageRevision: 0,
|
|
75
|
+
observedMessageRevision: 0,
|
|
76
|
+
latestMessageActivityRevision: 0,
|
|
72
77
|
changedFiles: [],
|
|
73
78
|
verification: [],
|
|
74
79
|
evidence: [],
|
|
@@ -121,19 +126,42 @@ export class SubagentSessionController {
|
|
|
121
126
|
async output(agentId, options = {}) {
|
|
122
127
|
options.signal?.throwIfAborted();
|
|
123
128
|
const state = await this.#find(agentId);
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
(state
|
|
135
|
-
|
|
136
|
-
|
|
129
|
+
options.signal?.throwIfAborted();
|
|
130
|
+
validateOutputOptions(options);
|
|
131
|
+
if (options.waitMs === undefined || options.waitMs === 0)
|
|
132
|
+
return { ...this.#snapshot(state), waitOutcome: 'snapshot' };
|
|
133
|
+
if (isTerminalState(state)) {
|
|
134
|
+
this.#observeLatestMessage(state);
|
|
135
|
+
return { ...this.#snapshot(state), waitOutcome: 'completion' };
|
|
136
|
+
}
|
|
137
|
+
const waitFor = options.waitFor ?? 'completion';
|
|
138
|
+
if (waitFor === 'message' && this.#hasUnreadMessage(state)) {
|
|
139
|
+
this.#observeLatestMessage(state);
|
|
140
|
+
return { ...this.#snapshot(state), waitOutcome: 'message' };
|
|
141
|
+
}
|
|
142
|
+
const waitMs = Math.max(options.waitMs, TOOL_EXECUTION_LIMITS.agentWaitWaitFloorMs);
|
|
143
|
+
if (waitFor === 'completion') {
|
|
144
|
+
const completed = await this.#waitForCompletion(state, waitMs, options.signal);
|
|
145
|
+
options.signal?.throwIfAborted();
|
|
146
|
+
if (completed)
|
|
147
|
+
this.#observeLatestMessage(state);
|
|
148
|
+
return {
|
|
149
|
+
...this.#snapshot(state),
|
|
150
|
+
waitOutcome: completed ? 'completion' : 'timeout'
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
const activity = await this.#waitForMessageOrCompletion([state], waitMs, options.signal);
|
|
154
|
+
options.signal?.throwIfAborted();
|
|
155
|
+
if (activity)
|
|
156
|
+
this.#observeLatestMessage(activity.state);
|
|
157
|
+
return {
|
|
158
|
+
...this.#snapshot(activity?.state ?? state),
|
|
159
|
+
waitOutcome: activity?.kind === 'message'
|
|
160
|
+
? 'message'
|
|
161
|
+
: activity?.kind === 'completion'
|
|
162
|
+
? 'completion'
|
|
163
|
+
: 'timeout'
|
|
164
|
+
};
|
|
137
165
|
}
|
|
138
166
|
async outputAny(options = {}) {
|
|
139
167
|
options.signal?.throwIfAborted();
|
|
@@ -142,16 +170,47 @@ export class SubagentSessionController {
|
|
|
142
170
|
if (states.length === 0)
|
|
143
171
|
throw new MarAgentError('MAR_AGENT_SUBAGENT_NOT_FOUND', 'Subagent was not found.');
|
|
144
172
|
const live = states.filter((state) => state.status === 'queued' || state.status === 'running');
|
|
145
|
-
if (
|
|
146
|
-
return
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
if (
|
|
151
|
-
|
|
173
|
+
if (options.waitMs === undefined || options.waitMs === 0)
|
|
174
|
+
return {
|
|
175
|
+
...this.#snapshot(live.at(-1) ?? states.at(-1)),
|
|
176
|
+
waitOutcome: 'snapshot'
|
|
177
|
+
};
|
|
178
|
+
if (live.length === 0) {
|
|
179
|
+
const state = states.at(-1);
|
|
180
|
+
this.#observeLatestMessage(state);
|
|
181
|
+
return { ...this.#snapshot(state), waitOutcome: 'completion' };
|
|
152
182
|
}
|
|
153
|
-
const
|
|
154
|
-
|
|
183
|
+
const waitFor = options.waitFor ?? 'completion';
|
|
184
|
+
if (waitFor === 'message') {
|
|
185
|
+
const pending = this.#oldestUnreadMessage(live);
|
|
186
|
+
if (pending) {
|
|
187
|
+
this.#observeLatestMessage(pending);
|
|
188
|
+
return { ...this.#snapshot(pending), waitOutcome: 'message' };
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
const waitMs = Math.max(options.waitMs, TOOL_EXECUTION_LIMITS.agentWaitWaitFloorMs);
|
|
192
|
+
if (waitFor === 'message') {
|
|
193
|
+
const activity = await this.#waitForMessageOrCompletion(live, waitMs, options.signal);
|
|
194
|
+
options.signal?.throwIfAborted();
|
|
195
|
+
if (activity)
|
|
196
|
+
this.#observeLatestMessage(activity.state);
|
|
197
|
+
return {
|
|
198
|
+
...this.#snapshot(activity?.state ?? live.at(-1)),
|
|
199
|
+
waitOutcome: activity?.kind === 'message'
|
|
200
|
+
? 'message'
|
|
201
|
+
: activity?.kind === 'completion'
|
|
202
|
+
? 'completion'
|
|
203
|
+
: 'timeout'
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
const completed = await this.#waitForAnyCompletion(live, waitMs, options.signal);
|
|
207
|
+
options.signal?.throwIfAborted();
|
|
208
|
+
if (completed)
|
|
209
|
+
this.#observeLatestMessage(completed);
|
|
210
|
+
return {
|
|
211
|
+
...this.#snapshot(completed ?? live.at(-1)),
|
|
212
|
+
waitOutcome: completed ? 'completion' : 'timeout'
|
|
213
|
+
};
|
|
155
214
|
}
|
|
156
215
|
async message(agentId, message, options = {}) {
|
|
157
216
|
const execution = this.#requireExecution();
|
|
@@ -340,8 +399,13 @@ export class SubagentSessionController {
|
|
|
340
399
|
async #consumeTurn(state, turn) {
|
|
341
400
|
try {
|
|
342
401
|
const consume = (async () => {
|
|
343
|
-
for await (const event of turn.handle)
|
|
344
|
-
observeAgentEvent(state, event)
|
|
402
|
+
for await (const event of turn.handle) {
|
|
403
|
+
if (!observeAgentEvent(state, event))
|
|
404
|
+
continue;
|
|
405
|
+
state.messageRevision++;
|
|
406
|
+
state.latestMessageActivityRevision = ++this.#activityRevision;
|
|
407
|
+
this.#notifyActivity({ state, kind: 'message' });
|
|
408
|
+
}
|
|
345
409
|
})();
|
|
346
410
|
const result = await turn.handle.result();
|
|
347
411
|
await consume;
|
|
@@ -402,6 +466,32 @@ export class SubagentSessionController {
|
|
|
402
466
|
#notifyCompletion(state) {
|
|
403
467
|
for (const waiter of this.#completionWaiters)
|
|
404
468
|
waiter(state);
|
|
469
|
+
this.#notifyActivity({ state, kind: 'completion' });
|
|
470
|
+
}
|
|
471
|
+
#waitForMessageOrCompletion(candidates, waitMs, signal) {
|
|
472
|
+
return waitForNotification(this.#activityWaiters, (activity) => candidates.includes(activity.state), () => {
|
|
473
|
+
const pending = this.#oldestUnreadMessage(candidates);
|
|
474
|
+
if (pending)
|
|
475
|
+
return { state: pending, kind: 'message' };
|
|
476
|
+
const terminal = candidates.find(isTerminalState);
|
|
477
|
+
return terminal ? { state: terminal, kind: 'completion' } : undefined;
|
|
478
|
+
}, waitMs, signal);
|
|
479
|
+
}
|
|
480
|
+
#notifyActivity(activity) {
|
|
481
|
+
for (const waiter of this.#activityWaiters)
|
|
482
|
+
waiter(activity);
|
|
483
|
+
}
|
|
484
|
+
#hasUnreadMessage(state) {
|
|
485
|
+
return state.messageRevision > state.observedMessageRevision;
|
|
486
|
+
}
|
|
487
|
+
#oldestUnreadMessage(states) {
|
|
488
|
+
return states
|
|
489
|
+
.filter((state) => this.#hasUnreadMessage(state))
|
|
490
|
+
.sort((left, right) => left.latestMessageActivityRevision - right.latestMessageActivityRevision)
|
|
491
|
+
.at(0);
|
|
492
|
+
}
|
|
493
|
+
#observeLatestMessage(state) {
|
|
494
|
+
state.observedMessageRevision = state.messageRevision;
|
|
405
495
|
}
|
|
406
496
|
#closeReceipt(state) {
|
|
407
497
|
if (state.receipt?.deliveryStatus === 'accepted' && state.receipt.turnId === state.turn?.turnId)
|
|
@@ -571,6 +661,9 @@ function restoreAgentState(session) {
|
|
|
571
661
|
...(isReasoningEffort(header?.reasoningEffort)
|
|
572
662
|
? { reasoningEffort: header.reasoningEffort }
|
|
573
663
|
: {}),
|
|
664
|
+
messageRevision: 0,
|
|
665
|
+
observedMessageRevision: 0,
|
|
666
|
+
latestMessageActivityRevision: 0,
|
|
574
667
|
changedFiles: [],
|
|
575
668
|
verification: [],
|
|
576
669
|
evidence: [],
|
|
@@ -581,8 +674,10 @@ function restoreAgentState(session) {
|
|
|
581
674
|
!['message.completed', 'tool.started', 'tool.completed', 'tool.failed'].includes(eventType(record) ?? ''))
|
|
582
675
|
continue;
|
|
583
676
|
const event = record.payload;
|
|
584
|
-
observeAgentEvent(state, event)
|
|
677
|
+
if (observeAgentEvent(state, event))
|
|
678
|
+
state.messageRevision++;
|
|
585
679
|
}
|
|
680
|
+
state.observedMessageRevision = state.messageRevision;
|
|
586
681
|
if (state.status === 'completed' && state.latestMessage?.phase === 'final_answer')
|
|
587
682
|
state.summary = truncateUtf8(state.latestMessage.text, TOOL_EXECUTION_LIMITS.subagentSummaryMaxBytes);
|
|
588
683
|
if (state.status === 'failed' && terminal && isRecord(terminal.payload)) {
|
|
@@ -601,7 +696,7 @@ function observeAgentEvent(state, event) {
|
|
|
601
696
|
timestamp: event.timestamp,
|
|
602
697
|
phase: event.phase
|
|
603
698
|
};
|
|
604
|
-
return;
|
|
699
|
+
return true;
|
|
605
700
|
}
|
|
606
701
|
if (event.type === 'tool.started') {
|
|
607
702
|
state.latestActivity = {
|
|
@@ -610,7 +705,7 @@ function observeAgentEvent(state, event) {
|
|
|
610
705
|
toolName: event.toolName,
|
|
611
706
|
status: 'started'
|
|
612
707
|
};
|
|
613
|
-
return;
|
|
708
|
+
return false;
|
|
614
709
|
}
|
|
615
710
|
if (event.type === 'tool.failed') {
|
|
616
711
|
state.latestActivity = {
|
|
@@ -619,10 +714,10 @@ function observeAgentEvent(state, event) {
|
|
|
619
714
|
toolName: event.toolName,
|
|
620
715
|
status: 'failed'
|
|
621
716
|
};
|
|
622
|
-
return;
|
|
717
|
+
return false;
|
|
623
718
|
}
|
|
624
719
|
if (event.type !== 'tool.completed')
|
|
625
|
-
return;
|
|
720
|
+
return false;
|
|
626
721
|
state.latestActivity = {
|
|
627
722
|
kind: 'tool',
|
|
628
723
|
timestamp: event.timestamp,
|
|
@@ -664,6 +759,7 @@ function observeAgentEvent(state, event) {
|
|
|
664
759
|
else if (event.toolName === 'exec' &&
|
|
665
760
|
state.verification.length < TOOL_EXECUTION_LIMITS.subagentVerificationMaxItems)
|
|
666
761
|
state.verification.push(truncateUtf8(event.summary, 2_000));
|
|
762
|
+
return false;
|
|
667
763
|
}
|
|
668
764
|
function eventType(record) {
|
|
669
765
|
return record && isRecord(record.payload) && typeof record.payload.type === 'string'
|
|
@@ -738,13 +834,18 @@ function waitForCompletionNotification(waiters, accepts, waitMs, signal) {
|
|
|
738
834
|
signal?.removeEventListener('abort', abort);
|
|
739
835
|
waiters.delete(notify);
|
|
740
836
|
if (error === undefined)
|
|
741
|
-
resolve();
|
|
837
|
+
resolve(undefined);
|
|
742
838
|
else
|
|
743
839
|
reject(error);
|
|
744
840
|
};
|
|
745
841
|
const notify = (value) => {
|
|
746
|
-
if (accepts(value))
|
|
747
|
-
|
|
842
|
+
if (!accepts(value) || settled)
|
|
843
|
+
return;
|
|
844
|
+
settled = true;
|
|
845
|
+
clearTimeout(timer);
|
|
846
|
+
signal?.removeEventListener('abort', abort);
|
|
847
|
+
waiters.delete(notify);
|
|
848
|
+
resolve(value);
|
|
748
849
|
};
|
|
749
850
|
const abort = () => finish(signal?.reason ?? new MarAgentError('MAR_AGENT_EXECUTION_CANCELLED', 'Execution cancelled.'));
|
|
750
851
|
const timer = setTimeout(finish, waitMs);
|
|
@@ -755,13 +856,55 @@ function waitForCompletionNotification(waiters, accepts, waitMs, signal) {
|
|
|
755
856
|
abort();
|
|
756
857
|
});
|
|
757
858
|
}
|
|
859
|
+
function waitForNotification(waiters, accepts, current, waitMs, signal) {
|
|
860
|
+
return new Promise((resolve, reject) => {
|
|
861
|
+
let settled = false;
|
|
862
|
+
const finish = (value, error) => {
|
|
863
|
+
if (settled)
|
|
864
|
+
return;
|
|
865
|
+
settled = true;
|
|
866
|
+
clearTimeout(timer);
|
|
867
|
+
signal?.removeEventListener('abort', abort);
|
|
868
|
+
waiters.delete(notify);
|
|
869
|
+
if (error === undefined)
|
|
870
|
+
resolve(value);
|
|
871
|
+
else
|
|
872
|
+
reject(error);
|
|
873
|
+
};
|
|
874
|
+
const notify = (value) => {
|
|
875
|
+
if (accepts(value))
|
|
876
|
+
finish(value);
|
|
877
|
+
};
|
|
878
|
+
const abort = () => finish(undefined, signal?.reason ?? new MarAgentError('MAR_AGENT_EXECUTION_CANCELLED', 'Execution cancelled.'));
|
|
879
|
+
const timer = setTimeout(() => finish(), waitMs);
|
|
880
|
+
timer.unref();
|
|
881
|
+
waiters.add(notify);
|
|
882
|
+
signal?.addEventListener('abort', abort, { once: true });
|
|
883
|
+
if (signal?.aborted)
|
|
884
|
+
abort();
|
|
885
|
+
else {
|
|
886
|
+
const pending = current();
|
|
887
|
+
if (pending !== undefined)
|
|
888
|
+
finish(pending);
|
|
889
|
+
}
|
|
890
|
+
});
|
|
891
|
+
}
|
|
758
892
|
function validateOutputOptions(options) {
|
|
759
|
-
if (options.
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
893
|
+
if ((options.waitFor !== undefined &&
|
|
894
|
+
options.waitFor !== 'completion' &&
|
|
895
|
+
options.waitFor !== 'message') ||
|
|
896
|
+
(options.waitMs !== undefined &&
|
|
897
|
+
(!Number.isInteger(options.waitMs) ||
|
|
898
|
+
options.waitMs < TOOL_EXECUTION_LIMITS.agentWaitMinWaitMs ||
|
|
899
|
+
options.waitMs > TOOL_EXECUTION_LIMITS.agentWaitMaxWaitMs)))
|
|
763
900
|
throw new MarAgentError('MAR_AGENT_TOOL_INPUT_INVALID', 'Task wait is invalid.');
|
|
764
901
|
}
|
|
902
|
+
function isTerminalState(state) {
|
|
903
|
+
return (state.status === 'completed' ||
|
|
904
|
+
state.status === 'failed' ||
|
|
905
|
+
state.status === 'cancelled' ||
|
|
906
|
+
state.status === 'interrupted');
|
|
907
|
+
}
|
|
765
908
|
function linkAbort(signal, abort) {
|
|
766
909
|
const listener = () => abort();
|
|
767
910
|
signal.addEventListener('abort', listener, { once: true });
|
package/dist/tools/agent-wait.js
CHANGED
|
@@ -3,11 +3,17 @@ import { TOOL_EXECUTION_LIMITS } from './execution-limits.js';
|
|
|
3
3
|
export const agentWaitToolDefinition = {
|
|
4
4
|
name: 'agent_wait',
|
|
5
5
|
parallelSafety: 'serial',
|
|
6
|
-
description: 'Read or wait a bounded time for a child agent result. Omit agentId to wait for whichever live child returns first, matching Codex-style untargeted waiting and avoiding UUID transcription; provide the exact agentId returned by agent_start only when observing a specific child. If no child is live, untargeted waiting reads the most recently created child. For an exec processId, use exec with action:"poll" instead. Omitting waitMs waits up to 30 seconds by default; waitMs=0 returns the current snapshot immediately. Positive waits range up to 5 minutes, and positive values below 10 seconds are raised to 10 seconds. Every result includes the real agentId and description. latestMessage is the most recent public assistant commentary/final message, never hidden reasoning or a tool result; latestActivity identifies whether the latest observed activity was a public message or a bounded tool started/completed/failed status.
|
|
6
|
+
description: 'Read or wait a bounded time for a child agent result. waitFor defaults to "completion"; use "message" only when a fresh public commentary/final message would change the next decision. Omit agentId to wait for whichever live child returns first, matching Codex-style untargeted waiting and avoiding UUID transcription; provide the exact agentId returned by agent_start only when observing a specific child. If no child is live, untargeted waiting reads the most recently created child. For an exec processId, use exec with action:"poll" instead. Omitting waitMs waits up to 30 seconds by default; waitMs=0 returns the current snapshot immediately. Positive waits range up to 5 minutes, and positive values below 10 seconds are raised to 10 seconds. When blocked on a live child result, prefer one 300000 ms completion wait rather than repeated short waits. Every result includes waitOutcome as snapshot, message, completion, or timeout plus the real agentId and description. latestMessage is the most recent public assistant commentary/final message, never hidden reasoning or a tool result; latestActivity identifies whether the latest observed activity was a public message or a bounded tool started/completed/failed status. A queued or running result is not completion. A message wait consumes only the latest unread public message in the current Session runtime; tool activity does not wake it, and parent cancellation preserves unread progress for a later Execution. Do not use message waits as heartbeat checks or request progress merely to confirm that a child is still running. Terminal results include bounded summary, changedFiles, verification, and read evidence with path/line ranges; use that evidence to avoid repeating the full exploration, while independently checking consequential edits and claims.',
|
|
7
7
|
inputSchema: {
|
|
8
8
|
type: 'object',
|
|
9
9
|
properties: {
|
|
10
10
|
agentId: { type: 'string' },
|
|
11
|
+
waitFor: {
|
|
12
|
+
type: 'string',
|
|
13
|
+
enum: ['completion', 'message'],
|
|
14
|
+
default: 'completion',
|
|
15
|
+
description: 'Wait for terminal completion or for the latest unread public message. Terminal state ends either wait.'
|
|
16
|
+
},
|
|
11
17
|
waitMs: {
|
|
12
18
|
type: 'integer',
|
|
13
19
|
minimum: TOOL_EXECUTION_LIMITS.agentWaitMinWaitMs,
|
|
@@ -26,6 +32,7 @@ export async function executeAgentWait(arguments_, context) {
|
|
|
26
32
|
throw new MarAgentError('MAR_AGENT_SUBAGENT_RECURSION_DISABLED', 'Subagents cannot recurse.');
|
|
27
33
|
const options = {
|
|
28
34
|
waitMs: value.waitMs ?? TOOL_EXECUTION_LIMITS.agentWaitDefaultWaitMs,
|
|
35
|
+
waitFor: value.waitFor ?? 'completion',
|
|
29
36
|
signal: context.signal
|
|
30
37
|
};
|
|
31
38
|
const task = value.agentId === undefined
|
|
@@ -37,18 +44,22 @@ function parseAgentWaitInput(value) {
|
|
|
37
44
|
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
38
45
|
return invalidAgentWait();
|
|
39
46
|
const input = value;
|
|
40
|
-
if (Object.keys(input).some((key) => !['agentId', 'waitMs'].includes(key)) ||
|
|
47
|
+
if (Object.keys(input).some((key) => !['agentId', 'waitFor', 'waitMs'].includes(key)) ||
|
|
41
48
|
(input.agentId !== undefined &&
|
|
42
|
-
input.agentId !== null &&
|
|
43
49
|
(typeof input.agentId !== 'string' || input.agentId.trim().length === 0)) ||
|
|
50
|
+
(input.waitFor !== undefined &&
|
|
51
|
+
input.waitFor !== 'completion' &&
|
|
52
|
+
input.waitFor !== 'message') ||
|
|
44
53
|
(input.waitMs !== undefined &&
|
|
45
|
-
input.waitMs !== null &&
|
|
46
54
|
(!Number.isInteger(input.waitMs) ||
|
|
47
55
|
input.waitMs < TOOL_EXECUTION_LIMITS.agentWaitMinWaitMs ||
|
|
48
56
|
input.waitMs > TOOL_EXECUTION_LIMITS.agentWaitMaxWaitMs)))
|
|
49
57
|
return invalidAgentWait();
|
|
50
58
|
return {
|
|
51
59
|
...(typeof input.agentId === 'string' ? { agentId: input.agentId.trim() } : {}),
|
|
60
|
+
...(input.waitFor === 'completion' || input.waitFor === 'message'
|
|
61
|
+
? { waitFor: input.waitFor }
|
|
62
|
+
: {}),
|
|
52
63
|
...(typeof input.waitMs === 'number' ? { waitMs: input.waitMs } : {})
|
|
53
64
|
};
|
|
54
65
|
}
|