@myagentroam/agent 0.9.71 → 0.9.72
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/index.d.ts +1 -1
- package/dist/prompts/index.js +1 -1
- package/dist/prompts/workflow.js +2 -2
- package/dist/runtime/context-gc.js +2 -0
- package/dist/runtime/token-budget.d.ts +1 -0
- package/dist/runtime/token-budget.js +22 -4
- package/dist/sdk/agent.js +5 -2
- package/dist/session/context-gc-checkpoint.js +2 -1
- package/dist/subagent/session-controller.js +96 -26
- package/dist/tools/agent-message.js +1 -1
- package/dist/tools/agent-wait.js +1 -1
- package/dist/tools/local-registry.js +92 -31
- package/dist/tools/read-context.d.ts +1 -0
- package/dist/tools/read-context.js +6 -3
- package/dist/tools/read.js +1 -1
- package/package.json +1 -1
package/dist/prompts/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { ExecutionMode } from '../sdk/types.js';
|
|
|
2
2
|
import { type ExecutionBudgetPromptState } from './execution-budget.js';
|
|
3
3
|
import { type RetainedSessionResources } from './resources.js';
|
|
4
4
|
import { type CurrentAgentModel, type SubagentModelOption } from './subagent.js';
|
|
5
|
-
export declare const MAR_AGENT_PROMPT_VERSION = "1.
|
|
5
|
+
export declare const MAR_AGENT_PROMPT_VERSION = "1.42";
|
|
6
6
|
export declare function buildSystemPrompt(input: {
|
|
7
7
|
mode: ExecutionMode;
|
|
8
8
|
platform: string;
|
package/dist/prompts/index.js
CHANGED
|
@@ -6,7 +6,7 @@ import { outputStylePrompt } from './output.js';
|
|
|
6
6
|
import { retainedSessionResourcesPrompt } from './resources.js';
|
|
7
7
|
import { subagentModelOptionsPrompt, subagentPrompt, currentAgentModelPrompt } from './subagent.js';
|
|
8
8
|
import { interactionPrompt, longRunningPrompt, safetyPrompt, toolUsagePrompt, workflowPrompt } from './workflow.js';
|
|
9
|
-
export const MAR_AGENT_PROMPT_VERSION = '1.
|
|
9
|
+
export const MAR_AGENT_PROMPT_VERSION = '1.42';
|
|
10
10
|
export function buildSystemPrompt(input) {
|
|
11
11
|
const toolNames = new Set(input.tools);
|
|
12
12
|
const hasLongRunningCapability = [
|
package/dist/prompts/workflow.js
CHANGED
|
@@ -3,14 +3,14 @@ export function toolUsagePrompt(tools) {
|
|
|
3
3
|
const names = new Set(tools);
|
|
4
4
|
const clauses = [
|
|
5
5
|
`# Tools and execution\nAvailable client tools: ${tools.join(', ')}.`,
|
|
6
|
-
'Inspect before editing, do not manufacture file contents from memory, and treat tool success as evidence only for what its result actually proves. A model response may request multiple independent tools; the Runtime may parallelize calls declared safe while preserving serial barriers and model call order.'
|
|
6
|
+
'Inspect before editing, do not manufacture file contents from memory, and treat tool success as evidence only for what its result actually proves. Start exploration with the smallest evidence that can change the next decision: locate relevant symbols, headings, or files before reading bodies, and expand only when current evidence is insufficient. Do not request multiple broad outputs merely because tools can run in parallel. A model response may request multiple independent tools; the Runtime may parallelize calls declared safe while preserving serial barriers and model call order.'
|
|
7
7
|
];
|
|
8
8
|
if (names.has('skill'))
|
|
9
9
|
clauses.push('If the request names or clearly matches an available Skill description, call skill before generic tools and follow the loaded workflow; semantic matches do not require the user to know the Skill name. Do not load unrelated Skills or claim to use one without calling skill.');
|
|
10
10
|
if (names.has('exec'))
|
|
11
11
|
clauses.push('Use exec for platform-native commands, scripts, verification, and mechanical edits. Prefer rg or rg --files for search. Batch independent lookups and already-decided commands, preserving failure ordering. Use scripts for mechanical substitutions instead of reproducing long unchanged text in a patch; inspect actual effects afterward.');
|
|
12
12
|
if (names.has('read'))
|
|
13
|
-
clauses.push('Use read to batch known files and ranges. Read only what can affect the next decision,
|
|
13
|
+
clauses.push('Use read to batch known files and ranges. Batch only already-located, bounded ranges that are likely to fit together; for large files or documents, locate relevant sections before reading them. Read only what can affect the next decision, continue only relevant incomplete results using returned metadata, and avoid rereading unchanged content.');
|
|
14
14
|
if (names.has('apply_patch'))
|
|
15
15
|
clauses.push('Prefer apply_patch for precise local edits. After a conflict, use the reported evidence to refresh only the affected context before retrying.');
|
|
16
16
|
if (names.has('apply_patch') || names.has('exec'))
|
|
@@ -459,6 +459,8 @@ function collectStageACandidates(input, groups, existingReplacements, existingTa
|
|
|
459
459
|
if (!parsed)
|
|
460
460
|
continue;
|
|
461
461
|
const selected = parsed.items.flatMap((item) => {
|
|
462
|
+
if (item.pathTruncated)
|
|
463
|
+
return [];
|
|
462
464
|
const itemKey = `${read.result.record.recordId}:${item.index}`;
|
|
463
465
|
if (claimedReadItems.has(itemKey))
|
|
464
466
|
return [];
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export declare const MODEL_TOKEN_ESTIMATOR: "UTF8_BYTES_V1";
|
|
2
2
|
/** Local budget estimate only; provider usage remains authoritative. */
|
|
3
3
|
export declare function countModelTokens(value: string): number;
|
|
4
|
+
export declare function modelTokenBudgetBytes(maximumTokens: number): number;
|
|
4
5
|
export declare function truncateModelText(value: string, maximumTokens: number, omittedLabel?: string): {
|
|
5
6
|
content: string;
|
|
6
7
|
truncated: boolean;
|
|
@@ -4,12 +4,16 @@ const BYTES_PER_TOKEN = 4;
|
|
|
4
4
|
export function countModelTokens(value) {
|
|
5
5
|
return Math.ceil(Buffer.byteLength(value, 'utf8') / BYTES_PER_TOKEN);
|
|
6
6
|
}
|
|
7
|
+
export function modelTokenBudgetBytes(maximumTokens) {
|
|
8
|
+
return Math.max(0, Math.floor(maximumTokens)) * BYTES_PER_TOKEN;
|
|
9
|
+
}
|
|
7
10
|
export function truncateModelText(value, maximumTokens, omittedLabel = 'tool output') {
|
|
8
11
|
const tokens = countModelTokens(value);
|
|
9
12
|
if (tokens <= maximumTokens)
|
|
10
13
|
return { content: value, truncated: false, omittedTokens: 0 };
|
|
11
|
-
const maximumBytes =
|
|
14
|
+
const maximumBytes = modelTokenBudgetBytes(maximumTokens);
|
|
12
15
|
const bytes = Buffer.from(value, 'utf8');
|
|
16
|
+
const warning = `Warning: truncated output (original token count: ${tokens})\nTotal output lines: ${lineCount(value)}\n\n`;
|
|
13
17
|
let retainedBytes = Math.max(0, maximumBytes - 16 * BYTES_PER_TOKEN);
|
|
14
18
|
for (let attempt = 0; attempt < 8; attempt++) {
|
|
15
19
|
let head = Math.ceil(retainedBytes / 2);
|
|
@@ -21,10 +25,14 @@ export function truncateModelText(value, maximumTokens, omittedLabel = 'tool out
|
|
|
21
25
|
tail++;
|
|
22
26
|
const omittedTokens = Math.ceil((tail - head) / BYTES_PER_TOKEN);
|
|
23
27
|
const marker = `\n...[${omittedTokens} tokens of ${omittedLabel} omitted]...\n`;
|
|
24
|
-
const candidateBytes =
|
|
28
|
+
const candidateBytes = Buffer.byteLength(warning, 'utf8') +
|
|
29
|
+
head +
|
|
30
|
+
Buffer.byteLength(marker, 'utf8') +
|
|
31
|
+
bytes.length -
|
|
32
|
+
tail;
|
|
25
33
|
if (candidateBytes <= maximumBytes)
|
|
26
34
|
return {
|
|
27
|
-
content: `${bytes.toString('utf8', 0, head)}${marker}${bytes.toString('utf8', tail)}`,
|
|
35
|
+
content: `${warning}${bytes.toString('utf8', 0, head)}${marker}${bytes.toString('utf8', tail)}`,
|
|
28
36
|
truncated: true,
|
|
29
37
|
omittedTokens
|
|
30
38
|
};
|
|
@@ -33,12 +41,22 @@ export function truncateModelText(value, maximumTokens, omittedLabel = 'tool out
|
|
|
33
41
|
retainedBytes = Math.max(0, retainedBytes - (candidateBytes - maximumBytes));
|
|
34
42
|
}
|
|
35
43
|
const marker = `\n...[${tokens} tokens of ${omittedLabel} omitted]...\n`;
|
|
44
|
+
const fallback = `${warning}${marker}`;
|
|
36
45
|
return {
|
|
37
|
-
content: countModelTokens(
|
|
46
|
+
content: countModelTokens(fallback) <= maximumTokens ? fallback : '',
|
|
38
47
|
truncated: true,
|
|
39
48
|
omittedTokens: tokens
|
|
40
49
|
};
|
|
41
50
|
}
|
|
51
|
+
function lineCount(value) {
|
|
52
|
+
let lines = value.length === 0 ? 0 : 1;
|
|
53
|
+
for (let index = 0; index < value.length; index++)
|
|
54
|
+
if (value.charCodeAt(index) === 10)
|
|
55
|
+
lines++;
|
|
56
|
+
if (value.endsWith('\n'))
|
|
57
|
+
lines--;
|
|
58
|
+
return lines;
|
|
59
|
+
}
|
|
42
60
|
function isContinuationByte(byte) {
|
|
43
61
|
return byte !== undefined && (byte & 0xc0) === 0x80;
|
|
44
62
|
}
|
package/dist/sdk/agent.js
CHANGED
|
@@ -931,14 +931,17 @@ export async function createMarAgent(options) {
|
|
|
931
931
|
})
|
|
932
932
|
});
|
|
933
933
|
const modelOutput = boundedModelToolOutput(output.content);
|
|
934
|
+
const eventSummary = output.content.slice(0, AGENT_EXECUTION_POLICY.toolEventSummaryCharacters);
|
|
934
935
|
const structuredData = structuredToolEventData(call.name, output.data);
|
|
935
936
|
const artifacts = structuredToolArtifacts(call.name, output.artifacts);
|
|
936
937
|
await emit({
|
|
937
938
|
type: 'tool.completed',
|
|
938
939
|
toolName: call.name,
|
|
939
940
|
callId: call.callId,
|
|
940
|
-
summary:
|
|
941
|
-
truncated: output.truncated === true ||
|
|
941
|
+
summary: eventSummary,
|
|
942
|
+
truncated: output.truncated === true ||
|
|
943
|
+
modelOutput.truncated ||
|
|
944
|
+
eventSummary.length < output.content.length,
|
|
942
945
|
...(artifacts === undefined ? {} : { artifacts }),
|
|
943
946
|
...(structuredData === undefined ? {} : { data: structuredData })
|
|
944
947
|
});
|
|
@@ -243,7 +243,8 @@ function replacementMatchesRecords(replacement, records, recordsBySequence, work
|
|
|
243
243
|
return replacement.items.every((item) => {
|
|
244
244
|
const original = originalItems.get(item.index);
|
|
245
245
|
const evidence = successfulPatchEvidenceForPath(records, recordsBySequence, item.evidenceSequence, item.path, workspace, platform);
|
|
246
|
-
return (original?.
|
|
246
|
+
return (original?.pathTruncated === false &&
|
|
247
|
+
original?.path === item.path &&
|
|
247
248
|
evidence !== undefined &&
|
|
248
249
|
item.replacement ===
|
|
249
250
|
contextGcStaleReadReplacement({
|
|
@@ -77,7 +77,8 @@ export class SubagentSessionController {
|
|
|
77
77
|
changedFiles: [],
|
|
78
78
|
verification: [],
|
|
79
79
|
evidence: [],
|
|
80
|
-
terminalPending: false
|
|
80
|
+
terminalPending: false,
|
|
81
|
+
terminalObserved: false
|
|
81
82
|
};
|
|
82
83
|
this.#createdInExecution++;
|
|
83
84
|
this.#agents.set(state.agentId, state);
|
|
@@ -96,7 +97,7 @@ export class SubagentSessionController {
|
|
|
96
97
|
this.#setFailure(state, error);
|
|
97
98
|
}
|
|
98
99
|
try {
|
|
99
|
-
await options.onCreated?.(this.#
|
|
100
|
+
await options.onCreated?.(this.#receiptSnapshot(state));
|
|
100
101
|
if (this.#disposed)
|
|
101
102
|
throw new MarAgentError('MAR_AGENT_DISPOSED', 'Subagent controller is disposed.');
|
|
102
103
|
options.signal?.throwIfAborted();
|
|
@@ -106,6 +107,7 @@ export class SubagentSessionController {
|
|
|
106
107
|
}
|
|
107
108
|
if (state.status === 'failed') {
|
|
108
109
|
await this.#publishTerminal(state);
|
|
110
|
+
state.terminalObserved = true;
|
|
109
111
|
return this.#snapshot(state);
|
|
110
112
|
}
|
|
111
113
|
this.#queueTurn(state, input.prompt, execution.startTurn, []);
|
|
@@ -115,9 +117,13 @@ export class SubagentSessionController {
|
|
|
115
117
|
})
|
|
116
118
|
: undefined;
|
|
117
119
|
try {
|
|
118
|
-
if (
|
|
119
|
-
|
|
120
|
-
|
|
120
|
+
if (input.background)
|
|
121
|
+
return this.#receiptSnapshot(state);
|
|
122
|
+
await state.turn.completion;
|
|
123
|
+
const includeLatestMessage = this.#hasUnreadMessage(state);
|
|
124
|
+
const includeTerminalDetails = this.#observeTerminal(state);
|
|
125
|
+
this.#observeLatestMessage(state);
|
|
126
|
+
return this.#snapshot(state, { includeLatestMessage, includeTerminalDetails });
|
|
121
127
|
}
|
|
122
128
|
finally {
|
|
123
129
|
detachParentAbort?.();
|
|
@@ -129,10 +135,21 @@ export class SubagentSessionController {
|
|
|
129
135
|
options.signal?.throwIfAborted();
|
|
130
136
|
validateOutputOptions(options);
|
|
131
137
|
if (options.waitMs === undefined || options.waitMs === 0)
|
|
132
|
-
return {
|
|
138
|
+
return {
|
|
139
|
+
...this.#snapshot(state, {
|
|
140
|
+
includeLatestMessage: false,
|
|
141
|
+
includeTerminalDetails: false
|
|
142
|
+
}),
|
|
143
|
+
waitOutcome: 'snapshot'
|
|
144
|
+
};
|
|
133
145
|
if (isTerminalState(state)) {
|
|
146
|
+
const includeLatestMessage = this.#hasUnreadMessage(state);
|
|
147
|
+
const includeTerminalDetails = this.#observeTerminal(state);
|
|
134
148
|
this.#observeLatestMessage(state);
|
|
135
|
-
return {
|
|
149
|
+
return {
|
|
150
|
+
...this.#snapshot(state, { includeLatestMessage, includeTerminalDetails }),
|
|
151
|
+
waitOutcome: 'completion'
|
|
152
|
+
};
|
|
136
153
|
}
|
|
137
154
|
const waitFor = options.waitFor ?? 'completion';
|
|
138
155
|
if (waitFor === 'message' && this.#hasUnreadMessage(state)) {
|
|
@@ -143,19 +160,27 @@ export class SubagentSessionController {
|
|
|
143
160
|
if (waitFor === 'completion') {
|
|
144
161
|
const completed = await this.#waitForCompletion(state, waitMs, options.signal);
|
|
145
162
|
options.signal?.throwIfAborted();
|
|
163
|
+
const includeLatestMessage = completed ? this.#hasUnreadMessage(state) : false;
|
|
164
|
+
const includeTerminalDetails = completed ? this.#observeTerminal(state) : false;
|
|
146
165
|
if (completed)
|
|
147
166
|
this.#observeLatestMessage(state);
|
|
148
167
|
return {
|
|
149
|
-
...this.#snapshot(state),
|
|
168
|
+
...this.#snapshot(state, { includeLatestMessage, includeTerminalDetails }),
|
|
150
169
|
waitOutcome: completed ? 'completion' : 'timeout'
|
|
151
170
|
};
|
|
152
171
|
}
|
|
153
172
|
const activity = await this.#waitForMessageOrCompletion([state], waitMs, options.signal);
|
|
154
173
|
options.signal?.throwIfAborted();
|
|
174
|
+
const includeLatestMessage = activity?.kind === 'message' ||
|
|
175
|
+
(activity?.kind === 'completion' && this.#hasUnreadMessage(activity.state));
|
|
176
|
+
const includeTerminalDetails = activity?.kind === 'completion' ? this.#observeTerminal(activity.state) : false;
|
|
155
177
|
if (activity)
|
|
156
178
|
this.#observeLatestMessage(activity.state);
|
|
157
179
|
return {
|
|
158
|
-
...this.#snapshot(activity?.state ?? state
|
|
180
|
+
...this.#snapshot(activity?.state ?? state, {
|
|
181
|
+
includeLatestMessage,
|
|
182
|
+
includeTerminalDetails
|
|
183
|
+
}),
|
|
159
184
|
waitOutcome: activity?.kind === 'message'
|
|
160
185
|
? 'message'
|
|
161
186
|
: activity?.kind === 'completion'
|
|
@@ -172,13 +197,21 @@ export class SubagentSessionController {
|
|
|
172
197
|
const live = states.filter((state) => state.status === 'queued' || state.status === 'running');
|
|
173
198
|
if (options.waitMs === undefined || options.waitMs === 0)
|
|
174
199
|
return {
|
|
175
|
-
...this.#snapshot(live.at(-1) ?? states.at(-1)
|
|
200
|
+
...this.#snapshot(live.at(-1) ?? states.at(-1), {
|
|
201
|
+
includeLatestMessage: false,
|
|
202
|
+
includeTerminalDetails: false
|
|
203
|
+
}),
|
|
176
204
|
waitOutcome: 'snapshot'
|
|
177
205
|
};
|
|
178
206
|
if (live.length === 0) {
|
|
179
207
|
const state = states.at(-1);
|
|
208
|
+
const includeLatestMessage = this.#hasUnreadMessage(state);
|
|
209
|
+
const includeTerminalDetails = this.#observeTerminal(state);
|
|
180
210
|
this.#observeLatestMessage(state);
|
|
181
|
-
return {
|
|
211
|
+
return {
|
|
212
|
+
...this.#snapshot(state, { includeLatestMessage, includeTerminalDetails }),
|
|
213
|
+
waitOutcome: 'completion'
|
|
214
|
+
};
|
|
182
215
|
}
|
|
183
216
|
const waitFor = options.waitFor ?? 'completion';
|
|
184
217
|
if (waitFor === 'message') {
|
|
@@ -192,10 +225,16 @@ export class SubagentSessionController {
|
|
|
192
225
|
if (waitFor === 'message') {
|
|
193
226
|
const activity = await this.#waitForMessageOrCompletion(live, waitMs, options.signal);
|
|
194
227
|
options.signal?.throwIfAborted();
|
|
228
|
+
const includeLatestMessage = activity?.kind === 'message' ||
|
|
229
|
+
(activity?.kind === 'completion' && this.#hasUnreadMessage(activity.state));
|
|
230
|
+
const includeTerminalDetails = activity?.kind === 'completion' ? this.#observeTerminal(activity.state) : false;
|
|
195
231
|
if (activity)
|
|
196
232
|
this.#observeLatestMessage(activity.state);
|
|
197
233
|
return {
|
|
198
|
-
...this.#snapshot(activity?.state ?? live.at(-1)
|
|
234
|
+
...this.#snapshot(activity?.state ?? live.at(-1), {
|
|
235
|
+
includeLatestMessage,
|
|
236
|
+
includeTerminalDetails
|
|
237
|
+
}),
|
|
199
238
|
waitOutcome: activity?.kind === 'message'
|
|
200
239
|
? 'message'
|
|
201
240
|
: activity?.kind === 'completion'
|
|
@@ -205,10 +244,15 @@ export class SubagentSessionController {
|
|
|
205
244
|
}
|
|
206
245
|
const completed = await this.#waitForAnyCompletion(live, waitMs, options.signal);
|
|
207
246
|
options.signal?.throwIfAborted();
|
|
247
|
+
const includeLatestMessage = completed ? this.#hasUnreadMessage(completed) : false;
|
|
248
|
+
const includeTerminalDetails = completed ? this.#observeTerminal(completed) : false;
|
|
208
249
|
if (completed)
|
|
209
250
|
this.#observeLatestMessage(completed);
|
|
210
251
|
return {
|
|
211
|
-
...this.#snapshot(completed ?? live.at(-1)
|
|
252
|
+
...this.#snapshot(completed ?? live.at(-1), {
|
|
253
|
+
includeLatestMessage,
|
|
254
|
+
includeTerminalDetails
|
|
255
|
+
}),
|
|
212
256
|
waitOutcome: completed ? 'completion' : 'timeout'
|
|
213
257
|
};
|
|
214
258
|
}
|
|
@@ -250,14 +294,14 @@ export class SubagentSessionController {
|
|
|
250
294
|
state.turn === turn &&
|
|
251
295
|
turn.handle?.enqueueMessage(message, delivery)) {
|
|
252
296
|
state.receipt = mutableReceipt;
|
|
253
|
-
return this.#
|
|
297
|
+
return this.#messageReceiptSnapshot(state);
|
|
254
298
|
}
|
|
255
299
|
await turn.completion;
|
|
256
300
|
}
|
|
257
301
|
else if (state.status === 'queued' && state.turn) {
|
|
258
302
|
state.turn.pendingMessages.push({ text: message, delivery });
|
|
259
303
|
state.receipt = mutableReceipt;
|
|
260
|
-
return this.#
|
|
304
|
+
return this.#messageReceiptSnapshot(state);
|
|
261
305
|
}
|
|
262
306
|
this.#requireExecution();
|
|
263
307
|
options.signal?.throwIfAborted();
|
|
@@ -268,12 +312,16 @@ export class SubagentSessionController {
|
|
|
268
312
|
mutableReceipt.turnId = randomUUID();
|
|
269
313
|
state.receipt = mutableReceipt;
|
|
270
314
|
this.#queueTurn(state, message, execution.startTurn, [], mutableReceipt.turnId, delivery);
|
|
271
|
-
return this.#
|
|
315
|
+
return this.#messageReceiptSnapshot(state);
|
|
272
316
|
});
|
|
273
317
|
}
|
|
274
318
|
async cancel(agentId) {
|
|
275
319
|
const state = await this.#find(agentId);
|
|
276
|
-
|
|
320
|
+
await this.#control(state, () => this.#cancelState(state));
|
|
321
|
+
const includeLatestMessage = this.#hasUnreadMessage(state);
|
|
322
|
+
const includeTerminalDetails = this.#observeTerminal(state);
|
|
323
|
+
this.#observeLatestMessage(state);
|
|
324
|
+
return this.#snapshot(state, { includeLatestMessage, includeTerminalDetails });
|
|
277
325
|
}
|
|
278
326
|
#control(state, operation) {
|
|
279
327
|
const result = (state.controlTail ?? Promise.resolve()).then(operation);
|
|
@@ -304,7 +352,6 @@ export class SubagentSessionController {
|
|
|
304
352
|
}
|
|
305
353
|
await turn.completion;
|
|
306
354
|
}
|
|
307
|
-
return this.#snapshot(state);
|
|
308
355
|
}
|
|
309
356
|
listSessionResources() {
|
|
310
357
|
return [...this.#agents.values()].map((state) => ({
|
|
@@ -493,6 +540,12 @@ export class SubagentSessionController {
|
|
|
493
540
|
#observeLatestMessage(state) {
|
|
494
541
|
state.observedMessageRevision = state.messageRevision;
|
|
495
542
|
}
|
|
543
|
+
#observeTerminal(state) {
|
|
544
|
+
if (!isTerminalState(state) || state.terminalObserved)
|
|
545
|
+
return false;
|
|
546
|
+
state.terminalObserved = true;
|
|
547
|
+
return true;
|
|
548
|
+
}
|
|
496
549
|
#closeReceipt(state) {
|
|
497
550
|
if (state.receipt?.deliveryStatus === 'accepted' && state.receipt.turnId === state.turn?.turnId)
|
|
498
551
|
state.receipt.deliveryStatus = state.status === 'cancelled' ? 'cancelled' : 'failed';
|
|
@@ -527,12 +580,28 @@ export class SubagentSessionController {
|
|
|
527
580
|
throw new MarAgentError('MAR_AGENT_SUBAGENT_FAILED', 'Subagent control requires an active parent execution.');
|
|
528
581
|
return this.#execution;
|
|
529
582
|
}
|
|
530
|
-
#
|
|
583
|
+
#receiptSnapshot(state) {
|
|
584
|
+
return this.#snapshot(state, {
|
|
585
|
+
includeLatestMessage: false,
|
|
586
|
+
includeLatestActivity: false,
|
|
587
|
+
includeTerminalDetails: false
|
|
588
|
+
});
|
|
589
|
+
}
|
|
590
|
+
#messageReceiptSnapshot(state) {
|
|
591
|
+
// A follow-up starts a new observation boundary; messages from the preceding state
|
|
592
|
+
// must not be returned later as fresh progress for the newly requested work.
|
|
593
|
+
this.#observeLatestMessage(state);
|
|
594
|
+
return this.#receiptSnapshot(state);
|
|
595
|
+
}
|
|
596
|
+
#snapshot(state, options = {}) {
|
|
531
597
|
const terminal = state.status === 'completed' ||
|
|
532
598
|
state.status === 'failed' ||
|
|
533
599
|
state.status === 'cancelled' ||
|
|
534
600
|
state.status === 'interrupted';
|
|
535
|
-
const
|
|
601
|
+
const includeLatestMessage = options.includeLatestMessage ?? true;
|
|
602
|
+
const includeLatestActivity = options.includeLatestActivity ?? true;
|
|
603
|
+
const includeTerminalDetails = options.includeTerminalDetails ?? true;
|
|
604
|
+
const latestMessage = !includeLatestMessage || state.latestMessage === undefined
|
|
536
605
|
? undefined
|
|
537
606
|
: terminal
|
|
538
607
|
? { ...state.latestMessage }
|
|
@@ -546,17 +615,17 @@ export class SubagentSessionController {
|
|
|
546
615
|
...(state.modelName === undefined ? {} : { modelName: state.modelName }),
|
|
547
616
|
...(state.reasoningEffort === undefined ? {} : { reasoningEffort: state.reasoningEffort }),
|
|
548
617
|
...(latestMessage === undefined ? {} : { latestMessage }),
|
|
549
|
-
...(state.latestActivity === undefined
|
|
618
|
+
...(!includeLatestActivity || state.latestActivity === undefined
|
|
550
619
|
? {}
|
|
551
620
|
: { latestActivity: { ...state.latestActivity } }),
|
|
552
|
-
...(state.summary === undefined ? {} : { summary: state.summary }),
|
|
553
|
-
...(!terminal || state.changedFiles.length === 0
|
|
621
|
+
...(!includeTerminalDetails || state.summary === undefined ? {} : { summary: state.summary }),
|
|
622
|
+
...(!includeTerminalDetails || !terminal || state.changedFiles.length === 0
|
|
554
623
|
? {}
|
|
555
624
|
: { changedFiles: [...state.changedFiles] }),
|
|
556
|
-
...(!terminal || state.verification.length === 0
|
|
625
|
+
...(!includeTerminalDetails || !terminal || state.verification.length === 0
|
|
557
626
|
? {}
|
|
558
627
|
: { verification: [...state.verification] }),
|
|
559
|
-
...(!terminal || state.evidence.length === 0
|
|
628
|
+
...(!includeTerminalDetails || !terminal || state.evidence.length === 0
|
|
560
629
|
? {}
|
|
561
630
|
: { evidence: state.evidence.map((item) => ({ ...item })) }),
|
|
562
631
|
...(state.errorCode === undefined ? {} : { errorCode: state.errorCode }),
|
|
@@ -667,7 +736,8 @@ function restoreAgentState(session) {
|
|
|
667
736
|
changedFiles: [],
|
|
668
737
|
verification: [],
|
|
669
738
|
evidence: [],
|
|
670
|
-
terminalPending: false
|
|
739
|
+
terminalPending: false,
|
|
740
|
+
terminalObserved: false
|
|
671
741
|
};
|
|
672
742
|
for (const record of currentRecords) {
|
|
673
743
|
if (!isRecord(record.payload) ||
|
|
@@ -3,7 +3,7 @@ import { TOOL_EXECUTION_LIMITS } from './execution-limits.js';
|
|
|
3
3
|
export const agentMessageToolDefinition = {
|
|
4
4
|
name: 'agent_message',
|
|
5
5
|
parallelSafety: 'serial',
|
|
6
|
-
description: 'Send a real user message to an existing child agent. Default delivery=append inserts guidance into a running child turn at the next safe boundary after the current model response or complete tool batch. Explicit delivery=replace immediately requests cancellation, waits for the old turn to close, then starts a new turn in the same Session. When idle, either mode starts a new turn using persisted history. Returns messageId, turnId and
|
|
6
|
+
description: 'Send a real user message to an existing child agent. Default delivery=append inserts guidance into a running child turn at the next safe boundary after the current model response or complete tool batch. Explicit delivery=replace immediately requests cancellation, waits for the old turn to close, then starts a new turn in the same Session. When idle, either mode starts a new turn using persisted history. Returns messageId, turnId, deliveryStatus, current status, and model metadata without repeating the child’s previous latestMessage, latestActivity, summary, or verification evidence. accepted means queued in memory; applied means persisted in model context, not that the guidance has been executed. Use agent_wait for subsequent progress or completion. Does not wait for the new task to complete or roll back existing side effects.',
|
|
7
7
|
inputSchema: {
|
|
8
8
|
type: 'object',
|
|
9
9
|
properties: {
|
package/dist/tools/agent-wait.js
CHANGED
|
@@ -3,7 +3,7 @@ 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. 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.
|
|
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. A message or first terminal completion result includes the newly observed latest public commentary/final message; snapshot, timeout, and repeated terminal results omit previously observed message text. latestActivity can still identify a public message or 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. First 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: {
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { MarAgentError } from '../error.js';
|
|
2
|
+
import { AGENT_EXECUTION_POLICY } from '../runtime/execution-policy.js';
|
|
3
|
+
import { modelTokenBudgetBytes } from '../runtime/token-budget.js';
|
|
2
4
|
import { ApplyPatchTool } from './apply-patch.js';
|
|
3
5
|
import { ExecTool } from './exec.js';
|
|
4
6
|
import { CodeModeTool } from './code-mode.js';
|
|
5
|
-
import { TOOL_EXECUTION_LIMITS } from './execution-limits.js';
|
|
6
7
|
import { ReadTool } from './read.js';
|
|
7
8
|
import { WorkspacePathResolver } from './shared/path-resolver.js';
|
|
8
9
|
import { ViewImageTool } from './view-image.js';
|
|
@@ -188,46 +189,106 @@ async function fetchWebUrl(url, signal, allowUntrustedTls, redirects = 0) {
|
|
|
188
189
|
function escapeAttribute(value) {
|
|
189
190
|
return value.replaceAll('&', '&').replaceAll('"', '"').replaceAll('<', '<');
|
|
190
191
|
}
|
|
191
|
-
function
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
192
|
+
function escapeText(value) {
|
|
193
|
+
return value.replaceAll('&', '&').replaceAll('<', '<');
|
|
194
|
+
}
|
|
195
|
+
function boundedUtf8Prefix(value, maximumBytes) {
|
|
196
|
+
if (Buffer.byteLength(value) <= maximumBytes)
|
|
197
|
+
return { value, truncated: false };
|
|
198
|
+
const marker = '…';
|
|
199
|
+
const contentBudget = Math.max(0, maximumBytes - Buffer.byteLength(marker));
|
|
200
|
+
let result = '';
|
|
201
|
+
let bytes = 0;
|
|
202
|
+
for (const character of value) {
|
|
203
|
+
const characterBytes = Buffer.byteLength(character);
|
|
204
|
+
if (bytes + characterBytes > contentBudget)
|
|
205
|
+
break;
|
|
206
|
+
result += character;
|
|
207
|
+
bytes += characterBytes;
|
|
208
|
+
}
|
|
209
|
+
return { value: `${result}${marker}`, truncated: true };
|
|
210
|
+
}
|
|
211
|
+
function formatBatchReadResult(result, index, maximumBytes) {
|
|
212
|
+
const visiblePath = boundedUtf8Prefix(result.path, 128);
|
|
213
|
+
const path = escapeAttribute(visiblePath.value);
|
|
214
|
+
const pathTruncatedAttribute = visiblePath.truncated ? ' pathTruncated="true"' : '';
|
|
215
|
+
if ('error' in result) {
|
|
216
|
+
const message = boundedUtf8Prefix(result.error.message, 256);
|
|
217
|
+
const detailsTruncatedAttribute = message.truncated ? ' detailsTruncated="true"' : '';
|
|
218
|
+
const content = `<read_error index="${index}" path="${path}"${pathTruncatedAttribute} code="${escapeAttribute(result.error.code)}"${detailsTruncatedAttribute}>${escapeText(message.value)}</read_error>`;
|
|
219
|
+
if (Buffer.byteLength(content) <= maximumBytes)
|
|
220
|
+
return { content, previewComplete: true };
|
|
221
|
+
return {
|
|
222
|
+
content: `<read_error index="${index}" code="MAR_AGENT_TOOL_OUTPUT_LIMIT">Model-visible error metadata exceeds this item's output share.</read_error>`,
|
|
223
|
+
previewComplete: false
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
const lines = result.content ? result.content.split('\n') : [];
|
|
227
|
+
const render = (visibleLines) => {
|
|
228
|
+
const previewComplete = visibleLines === lines.length;
|
|
229
|
+
const endLine = visibleLines === 0 ? undefined : result.startLine + Math.max(0, visibleLines - 1);
|
|
230
|
+
const metadata = {
|
|
231
|
+
path: visiblePath.value,
|
|
232
|
+
...(visiblePath.truncated ? { pathTruncated: true } : {}),
|
|
233
|
+
encoding: result.encoding,
|
|
234
|
+
newline: result.newline,
|
|
235
|
+
startLine: result.startLine,
|
|
236
|
+
...(endLine === undefined ? {} : { endLine }),
|
|
237
|
+
eof: previewComplete ? result.eof : false,
|
|
238
|
+
truncated: previewComplete ? result.truncated : true,
|
|
239
|
+
...(previewComplete
|
|
240
|
+
? result.nextOffset === undefined
|
|
241
|
+
? {}
|
|
242
|
+
: { nextOffset: result.nextOffset }
|
|
243
|
+
: { nextOffset: result.startLine + visibleLines }),
|
|
244
|
+
previewComplete
|
|
245
|
+
};
|
|
246
|
+
return [
|
|
247
|
+
`<read_result index="${index}" path="${path}"${pathTruncatedAttribute}>`,
|
|
248
|
+
lines.slice(0, visibleLines).join('\n'),
|
|
249
|
+
`<read_metadata>${JSON.stringify(metadata)}</read_metadata>`,
|
|
250
|
+
'</read_result>'
|
|
251
|
+
]
|
|
252
|
+
.filter(Boolean)
|
|
253
|
+
.join('\n');
|
|
254
|
+
};
|
|
255
|
+
const full = render(lines.length);
|
|
256
|
+
if (Buffer.byteLength(full) <= maximumBytes)
|
|
257
|
+
return { content: full, previewComplete: true };
|
|
258
|
+
let low = 0;
|
|
259
|
+
let high = lines.length;
|
|
260
|
+
while (low < high) {
|
|
261
|
+
const middle = Math.ceil((low + high) / 2);
|
|
262
|
+
if (Buffer.byteLength(render(middle)) <= maximumBytes)
|
|
263
|
+
low = middle;
|
|
264
|
+
else
|
|
265
|
+
high = middle - 1;
|
|
266
|
+
}
|
|
267
|
+
const content = render(low);
|
|
268
|
+
if (Buffer.byteLength(content) <= maximumBytes)
|
|
269
|
+
return { content, previewComplete: low === lines.length };
|
|
270
|
+
return {
|
|
271
|
+
content: `<read_error index="${index}" code="MAR_AGENT_TOOL_OUTPUT_LIMIT">Model-visible read metadata exceeds this item's output share.</read_error>`,
|
|
272
|
+
previewComplete: false
|
|
204
273
|
};
|
|
205
|
-
return [
|
|
206
|
-
`<read_result index="${index}" path="${path}">`,
|
|
207
|
-
result.content,
|
|
208
|
-
`<read_metadata>${JSON.stringify(metadata)}</read_metadata>`,
|
|
209
|
-
'</read_result>'
|
|
210
|
-
]
|
|
211
|
-
.filter(Boolean)
|
|
212
|
-
.join('\n');
|
|
213
274
|
}
|
|
214
275
|
function formatBatchReadOutput(results) {
|
|
215
276
|
const blocks = [];
|
|
216
277
|
let bytes = 0;
|
|
217
278
|
let truncated = false;
|
|
279
|
+
const maximumBytes = modelTokenBudgetBytes(AGENT_EXECUTION_POLICY.modelToolOutputTokens);
|
|
218
280
|
for (const [index, result] of results.entries()) {
|
|
219
|
-
let block = formatBatchReadResult(result, index);
|
|
220
281
|
const separatorBytes = blocks.length === 0 ? 0 : 1;
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
const blockBytes = Buffer.byteLength(block);
|
|
227
|
-
if (bytes + separatorBytes + blockBytes > TOOL_EXECUTION_LIMITS.readMaxBatchBytes)
|
|
282
|
+
const remainingItems = results.length - index;
|
|
283
|
+
const itemBudget = Math.floor((maximumBytes - bytes - separatorBytes) / remainingItems);
|
|
284
|
+
const block = formatBatchReadResult(result, index, itemBudget);
|
|
285
|
+
const blockBytes = Buffer.byteLength(block.content);
|
|
286
|
+
if (bytes + separatorBytes + blockBytes > maximumBytes)
|
|
228
287
|
break;
|
|
229
|
-
blocks.push(block);
|
|
288
|
+
blocks.push(block.content);
|
|
230
289
|
bytes += separatorBytes + blockBytes;
|
|
290
|
+
if (!block.previewComplete)
|
|
291
|
+
truncated = true;
|
|
231
292
|
}
|
|
232
293
|
return { content: blocks.join('\n'), truncated };
|
|
233
294
|
}
|
|
@@ -5,7 +5,7 @@ export function parseReadContextOutput(value) {
|
|
|
5
5
|
let offset = 0;
|
|
6
6
|
while (offset < value.length) {
|
|
7
7
|
if (value.startsWith('<read_result ', offset)) {
|
|
8
|
-
const header = /^<read_result index="(\d+)" path="([^"]*)"
|
|
8
|
+
const header = /^<read_result index="(\d+)" path="([^"]*)"( pathTruncated="true")?>\n/u.exec(value.slice(offset));
|
|
9
9
|
if (!header)
|
|
10
10
|
return undefined;
|
|
11
11
|
const contentStart = offset + header[0].length;
|
|
@@ -27,15 +27,18 @@ export function parseReadContextOutput(value) {
|
|
|
27
27
|
}
|
|
28
28
|
const index = Number(header[1]);
|
|
29
29
|
const path = unescapeAttribute(header[2]);
|
|
30
|
+
const pathTruncated = header[3] !== undefined;
|
|
30
31
|
if (!Number.isSafeInteger(index) ||
|
|
31
32
|
index < 0 ||
|
|
32
33
|
!isRecord(metadata) ||
|
|
33
|
-
metadata.path !== path
|
|
34
|
+
metadata.path !== path ||
|
|
35
|
+
(metadata.pathTruncated === true) !== pathTruncated)
|
|
34
36
|
return undefined;
|
|
35
37
|
const blockEnd = metadataEnd + metadataEndMarker.length;
|
|
36
38
|
items.push({
|
|
37
39
|
index,
|
|
38
40
|
path,
|
|
41
|
+
pathTruncated,
|
|
39
42
|
content: value.slice(contentStart, metadataStart),
|
|
40
43
|
metadata,
|
|
41
44
|
contentStart,
|
|
@@ -44,7 +47,7 @@ export function parseReadContextOutput(value) {
|
|
|
44
47
|
offset = blockEnd;
|
|
45
48
|
}
|
|
46
49
|
else if (value.startsWith('<read_error ', offset)) {
|
|
47
|
-
const error = /^<read_error index="\d+" path="[^"]*" code="[^"]*"
|
|
50
|
+
const error = /^<read_error index="\d+" path="[^"]*"(?: pathTruncated="true")? code="[^"]*"(?: detailsTruncated="true")?>[^\n]*<\/read_error>/u.exec(value.slice(offset));
|
|
48
51
|
if (!error)
|
|
49
52
|
return undefined;
|
|
50
53
|
offset += error[0].length;
|
package/dist/tools/read.js
CHANGED
|
@@ -46,7 +46,7 @@ export const readToolDefinition = {
|
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
48
|
},
|
|
49
|
-
description: `Read one or more known text files/ranges. Strict UTF-8 with optional BOM is the default; BOM-declared UTF-16LE/UTF-16BE is supported. Invalid text returns an item error instead of replacement characters. Always pass reads; use one item for a single file and up to ${TOOL_EXECUTION_LIMITS.readMaxBatchItems} items for a batch. Results retain input order, use 1-based line numbers, and include <read_metadata> with path, encoding, newline, exact range, eof, truncation, and
|
|
49
|
+
description: `Read one or more known text files/ranges. Strict UTF-8 with optional BOM is the default; BOM-declared UTF-16LE/UTF-16BE is supported. Invalid text returns an item error instead of replacement characters. Always pass reads; use one item for a single file and up to ${TOOL_EXECUTION_LIMITS.readMaxBatchItems} items for a batch. Batch only already-located, bounded ranges whose combined content is likely to fit the model-visible output budget; for exploration, locate relevant files, symbols, or headings first, then read the smallest useful ranges. Results retain input order, use 1-based line numbers, and include <read_metadata> with path, encoding, newline, exact visible range, eof, truncation, nextOffset, and whether previewComplete. Items fail independently and run with bounded concurrency. Each item returns at most ${TOOL_EXECUTION_LIMITS.readMaxLines} lines/${TOOL_EXECUTION_LIMITS.readMaxBytes} bytes; the structured call shares a ${TOOL_EXECUTION_LIMITS.readMaxBatchBytes}-byte budget, while ordinary model-visible content fairly shares the common tool-output budget across all items. Structured data.results items provide text as decoded source for the returned range, preserving line endings and excluding BOM; content is numbered display text. Large files support bounded range reads. If previewComplete is false, continue only the still-relevant item from nextOffset. Relative paths use the workspace; absolute paths are allowed. Prefer exec with rg for search and read for known files.`,
|
|
50
50
|
inputSchema: {
|
|
51
51
|
type: 'object',
|
|
52
52
|
properties: {
|