@myagentroam/agent 0.11.43 → 0.11.45
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/subagent/index.js +19 -0
- package/dist/runtime/event-projection.js +1 -0
- package/dist/runtime/events.d.ts +4 -0
- package/dist/sdk/session-execution.js +7 -0
- package/dist/tools/code-mode/cells.d.ts +1 -1
- package/dist/tools/code-mode/cells.js +35 -3
- package/dist/tools/code-mode/dispatch.js +25 -7
- package/dist/tools/code-mode/engine.d.ts +2 -0
- package/dist/tools/code-mode/index.js +37 -5
- package/dist/tools/code-mode/quickjs-engine.js +5 -1
- package/dist/tools/code-mode/text-output.d.ts +2 -1
- package/dist/tools/code-mode/text-output.js +15 -2
- package/dist/tools/contracts.d.ts +4 -1
- package/dist/tools/execution.js +8 -3
- package/dist/tools/output-projection.d.ts +7 -0
- package/dist/tools/output-projection.js +56 -5
- package/dist/tools/runtime.js +4 -1
- package/package.json +1 -1
|
@@ -7,6 +7,13 @@ import { registerQueryTools } from './query.js';
|
|
|
7
7
|
import { nativeSubagentService, SUBAGENT_NATIVE_SERVICE } from './native.js';
|
|
8
8
|
import { childResources } from './resources.js';
|
|
9
9
|
import { sharedSessionHistory } from './history.js';
|
|
10
|
+
const delegationPrompt = [
|
|
11
|
+
'# Delegation and coordination',
|
|
12
|
+
'Delegate bounded, self-contained tasks when independent progress or context savings materially helps. Keep simple or tightly coupled work local.',
|
|
13
|
+
'Give each child an objective, necessary context, expected output, and a read-only boundary or non-overlapping write scope.',
|
|
14
|
+
'While children run, continue meaningful non-overlapping work. Do not redo delegated work. Wait only when the next step needs the result.',
|
|
15
|
+
'Verify consequential findings and changes. Join or stop task-required child work before final delivery, and retain responsibility for the final result.'
|
|
16
|
+
].join('\n\n');
|
|
10
17
|
export { SUBAGENT_NATIVE_SERVICE } from './native.js';
|
|
11
18
|
/** Optional local consumer of Core Session lifecycle and observation APIs. */
|
|
12
19
|
export function createSubagentExtension(options) {
|
|
@@ -70,6 +77,18 @@ export function createSubagentExtension(options) {
|
|
|
70
77
|
registerControlTools(context, agent, observations, warmObservations, admission);
|
|
71
78
|
registerWait(context, agent, observations, history);
|
|
72
79
|
registerQueryTools(context, agent);
|
|
80
|
+
context.hooks.intercept('prompt.sections', (input, _call, next) => next({
|
|
81
|
+
...input,
|
|
82
|
+
sections: [
|
|
83
|
+
...input.sections,
|
|
84
|
+
{
|
|
85
|
+
id: 'subagent:delegation',
|
|
86
|
+
text: input.facts?.mode !== 'compact' && input.facts?.tools?.includes('agent_start')
|
|
87
|
+
? delegationPrompt
|
|
88
|
+
: null
|
|
89
|
+
}
|
|
90
|
+
]
|
|
91
|
+
}));
|
|
73
92
|
}
|
|
74
93
|
catch (error) {
|
|
75
94
|
disposed = true;
|
package/dist/runtime/events.d.ts
CHANGED
|
@@ -95,6 +95,7 @@ export type AgentEvent = AgentEventBase & ({
|
|
|
95
95
|
callId: string;
|
|
96
96
|
parentCallId?: string;
|
|
97
97
|
output: import('../tools/contracts.js').ExecOutputSnapshot;
|
|
98
|
+
outputTruncation?: 'HEAD' | 'SOURCE';
|
|
98
99
|
} | {
|
|
99
100
|
type: 'tool.completed';
|
|
100
101
|
resultExtensions?: readonly import('../tools/result-extension-contract.js').ToolResultExtension[];
|
|
@@ -103,6 +104,8 @@ export type AgentEvent = AgentEventBase & ({
|
|
|
103
104
|
parentCallId?: string;
|
|
104
105
|
summary: string;
|
|
105
106
|
truncated?: boolean;
|
|
107
|
+
summaryTruncated?: boolean;
|
|
108
|
+
outputTruncation?: 'HEAD' | 'SOURCE';
|
|
106
109
|
artifacts?: readonly AgentToolArtifact[];
|
|
107
110
|
data?: CodeCellEventData | import('../tools/contracts.js').ExecResult | Omit<import('../tools/contracts.js').ExecResult, 'stdout' | 'stderr'> | import('../tools/contracts.js').ApplyPatchResult | import('../tools/contracts.js').ReadEventData | import('../tools/contracts.js').ViewImageResult | import('../tools/contracts.js').SleepResult | Readonly<Record<string, unknown>> | import('./public-tool-metadata.js').PublicToolMetadata;
|
|
108
111
|
} | {
|
|
@@ -116,6 +119,7 @@ export type AgentEvent = AgentEventBase & ({
|
|
|
116
119
|
message?: string;
|
|
117
120
|
permissionStop?: import('../tools/permission-contract.js').PermissionStop;
|
|
118
121
|
truncated?: boolean;
|
|
122
|
+
outputTruncation?: 'HEAD' | 'SOURCE';
|
|
119
123
|
} | {
|
|
120
124
|
type: 'sleep.started';
|
|
121
125
|
callId: string;
|
|
@@ -241,6 +241,13 @@ export class SessionExecution {
|
|
|
241
241
|
const deliver = missed && full.output.displayOutput
|
|
242
242
|
? {
|
|
243
243
|
...full,
|
|
244
|
+
...(full.outputTruncation === 'SOURCE' ||
|
|
245
|
+
full.output.displayOutput.stdoutDroppedBytes > 0 ||
|
|
246
|
+
full.output.displayOutput.stderrDroppedBytes > 0
|
|
247
|
+
? {
|
|
248
|
+
outputTruncation: full.outputTruncation === 'SOURCE' ? 'SOURCE' : 'HEAD'
|
|
249
|
+
}
|
|
250
|
+
: {}),
|
|
244
251
|
output: {
|
|
245
252
|
...full.output,
|
|
246
253
|
mode: 'snapshot',
|
|
@@ -3,7 +3,7 @@ import type { ToolExecutionContext, ToolExecutionResult } from '../contracts.js'
|
|
|
3
3
|
import type { CodeObservation, CodeToolCallRecord } from '../../session/code-observation-contract.js';
|
|
4
4
|
export interface CellOutput {
|
|
5
5
|
call(value: CodeToolCallRecord): void;
|
|
6
|
-
observe(value: CodeObservation): void;
|
|
6
|
+
observe(value: CodeObservation, display?: CodeObservation, displayHeadTruncated?: boolean): void;
|
|
7
7
|
text(value: string, originalBytes?: number): void;
|
|
8
8
|
image(value: NonNullable<ToolExecutionResult['images']>[number]): void;
|
|
9
9
|
yield(): void;
|
|
@@ -6,6 +6,7 @@ import { TOOL_EXECUTION_LIMITS as limits } from '../execution-limits.js';
|
|
|
6
6
|
import { waitForToolCondition, waitMetadata } from '../wait.js';
|
|
7
7
|
import { BoundedTextOutput, renderCapturedText } from './text-output.js';
|
|
8
8
|
import { boundObservations, newObservationId } from './observations.js';
|
|
9
|
+
import { publicCodeObservations } from '../output-projection.js';
|
|
9
10
|
import { selectObservationImages, visibleObservation } from '../../session/code-observation-projection.js';
|
|
10
11
|
/** Keeps a failed tool call observable without discarding earlier explicit output. */
|
|
11
12
|
export class CodeCellExecutionError extends AgentError {
|
|
@@ -214,7 +215,26 @@ export class CodeCells {
|
|
|
214
215
|
releaseSession = await context.codeMode?.retainSession?.();
|
|
215
216
|
return run(source, { ...context, signal: controller.signal, waitInterrupt: undefined }, {
|
|
216
217
|
call: (value) => cell.calls.set(value.callId, value),
|
|
217
|
-
observe: (value) =>
|
|
218
|
+
observe: (value, display = value, displayHeadTruncated = false) => {
|
|
219
|
+
cell.observations.push(value);
|
|
220
|
+
const observations = [...(cell.display?.observations ?? []), display];
|
|
221
|
+
cell.display = publicCodeObservations(observations, {
|
|
222
|
+
cellId: cell.cellId,
|
|
223
|
+
status: cell.status,
|
|
224
|
+
observations,
|
|
225
|
+
outputSourceKnown: true,
|
|
226
|
+
...(cell.display?.outputSourceTruncated ||
|
|
227
|
+
(!displayHeadTruncated &&
|
|
228
|
+
(display.type === 'value'
|
|
229
|
+
? display.truncated === true
|
|
230
|
+
: display.result.truncated === true))
|
|
231
|
+
? { outputSourceTruncated: true }
|
|
232
|
+
: {}),
|
|
233
|
+
...(cell.display?.outputHeadTruncated || displayHeadTruncated
|
|
234
|
+
? { outputHeadTruncated: true }
|
|
235
|
+
: {})
|
|
236
|
+
});
|
|
237
|
+
},
|
|
218
238
|
text: (text, originalBytes) => cell.text.append(text, originalBytes),
|
|
219
239
|
image: (image) => cell.images.push(image),
|
|
220
240
|
yield: () => {
|
|
@@ -359,7 +379,14 @@ export class CodeCells {
|
|
|
359
379
|
observations: visible.observations.map(visibleObservation)
|
|
360
380
|
}),
|
|
361
381
|
data,
|
|
362
|
-
eventData: {
|
|
382
|
+
eventData: {
|
|
383
|
+
...data,
|
|
384
|
+
...cell.display,
|
|
385
|
+
status: cell.status,
|
|
386
|
+
cached: true,
|
|
387
|
+
...wait,
|
|
388
|
+
executionSource: cell.source
|
|
389
|
+
},
|
|
363
390
|
truncated: data.truncated
|
|
364
391
|
};
|
|
365
392
|
}
|
|
@@ -430,7 +457,12 @@ export class CodeCells {
|
|
|
430
457
|
calls: [...cell.calls.values()],
|
|
431
458
|
observations: remapped,
|
|
432
459
|
data,
|
|
433
|
-
eventData: {
|
|
460
|
+
eventData: {
|
|
461
|
+
...data,
|
|
462
|
+
...cell.display,
|
|
463
|
+
status: cell.status,
|
|
464
|
+
executionSource: cell.source
|
|
465
|
+
},
|
|
434
466
|
truncated: cell.truncated || structured.truncated,
|
|
435
467
|
...(visibleImages.length ? { images: visibleImages } : {})
|
|
436
468
|
};
|
|
@@ -2,7 +2,7 @@ import { randomUUID } from 'node:crypto';
|
|
|
2
2
|
import { AgentError, describeAgentError } from '../../error.js';
|
|
3
3
|
import { toolEventSummary } from '../../runtime/event-projection.js';
|
|
4
4
|
import { allowsToolCaller } from '../callers.js';
|
|
5
|
-
import { boundedModelToolOutput, structuredToolEventData, structuredToolFailureData } from '../output-projection.js';
|
|
5
|
+
import { boundedModelToolOutput, publicToolEventInput, publicExtensionToolEventData, publicEventOutputData, publicOutputTruncation, structuredToolEventData, structuredToolFailureData } from '../output-projection.js';
|
|
6
6
|
import { executeRegisteredTool } from '../runtime.js';
|
|
7
7
|
import { failedToolResultExtensions } from '../result-extensions.js';
|
|
8
8
|
import { permissionStopFromError } from '../permissions.js';
|
|
@@ -19,7 +19,13 @@ export function createCodeToolDispatch(input) {
|
|
|
19
19
|
signal.throwIfAborted();
|
|
20
20
|
const callId = call.callId ?? randomUUID();
|
|
21
21
|
const identity = { toolName: call.name, callId, parentCallId: input.parentCallId };
|
|
22
|
-
await input.emit({
|
|
22
|
+
await input.emit({
|
|
23
|
+
type: 'tool.started',
|
|
24
|
+
...identity,
|
|
25
|
+
input: definition.eventInputFields === undefined
|
|
26
|
+
? call.arguments
|
|
27
|
+
: publicToolEventInput(call.arguments, definition.eventInputFields)
|
|
28
|
+
});
|
|
23
29
|
try {
|
|
24
30
|
input.assertLive();
|
|
25
31
|
signal.throwIfAborted();
|
|
@@ -32,23 +38,35 @@ export function createCodeToolDispatch(input) {
|
|
|
32
38
|
...(expected ? { expectedMcpTool: expected } : {})
|
|
33
39
|
}, 'programmatic');
|
|
34
40
|
const bounded = boundedModelToolOutput(call.name, result.content, result.data);
|
|
35
|
-
const data =
|
|
36
|
-
? (result.eventData ?? result.data)
|
|
37
|
-
: result.
|
|
38
|
-
|
|
41
|
+
const data = call.name === 'exec' || call.name === 'exec_wait'
|
|
42
|
+
? structuredToolEventData(call.name, result.eventData ?? result.data)
|
|
43
|
+
: result.eventData === undefined
|
|
44
|
+
? structuredToolEventData(call.name, result.data)
|
|
45
|
+
: publicExtensionToolEventData(result.eventData);
|
|
46
|
+
const preview = toolEventSummary(result.eventSummary === undefined
|
|
47
|
+
? result.content
|
|
48
|
+
: typeof result.eventSummary === 'string'
|
|
49
|
+
? result.eventSummary
|
|
50
|
+
: '');
|
|
51
|
+
const outputTruncation = publicOutputTruncation(call.name, data);
|
|
39
52
|
input.assertLive();
|
|
40
53
|
await input.emit({
|
|
41
54
|
type: 'tool.completed',
|
|
42
55
|
...(result.resultExtensions ? { resultExtensions: result.resultExtensions } : {}),
|
|
43
56
|
...identity,
|
|
44
57
|
summary: preview.summary,
|
|
45
|
-
...(
|
|
58
|
+
...(preview.truncated ? { summaryTruncated: true } : {}),
|
|
59
|
+
...(outputTruncation ? { outputTruncation } : {}),
|
|
60
|
+
...(data === undefined ? {} : { data: publicEventOutputData(data) }),
|
|
46
61
|
truncated: result.truncated === true || bounded.truncated || preview.truncated,
|
|
47
62
|
...(result.artifacts ? { artifacts: result.artifacts } : {})
|
|
48
63
|
});
|
|
49
64
|
const { eventData: _eventData, ...guestResult } = result;
|
|
50
65
|
return {
|
|
51
66
|
...guestResult,
|
|
67
|
+
...((call.name === 'exec' || call.name === 'exec_wait') && data !== undefined
|
|
68
|
+
? { eventData: { ...data } }
|
|
69
|
+
: {}),
|
|
52
70
|
content: bounded.content,
|
|
53
71
|
truncated: result.truncated === true || bounded.truncated
|
|
54
72
|
};
|
|
@@ -300,7 +300,7 @@ export class CodeModeTool {
|
|
|
300
300
|
const images = [];
|
|
301
301
|
const observations = [];
|
|
302
302
|
let observationBytes = 0;
|
|
303
|
-
const appendObservation = (value, originalBytes) => {
|
|
303
|
+
const appendObservation = (value, originalBytes, display) => {
|
|
304
304
|
const capture = new BoundedTextOutput(Math.max(0, limits.codeMaxOutputBytes - observationBytes), '');
|
|
305
305
|
const body = value.type === 'tool_result' ? value.result.content : value.value;
|
|
306
306
|
capture.append(body);
|
|
@@ -323,7 +323,10 @@ export class CodeModeTool {
|
|
|
323
323
|
truncated: value.truncated === true || bounded.droppedBytes > 0
|
|
324
324
|
};
|
|
325
325
|
observations.push(observation);
|
|
326
|
-
output?.observe(observation
|
|
326
|
+
output?.observe(observation, display ?? value, display !== undefined &&
|
|
327
|
+
(display.type === 'tool_result'
|
|
328
|
+
? display.result.truncated === true
|
|
329
|
+
: display.truncated === true));
|
|
327
330
|
};
|
|
328
331
|
const toolResults = new Map();
|
|
329
332
|
let transportBytes = 0;
|
|
@@ -389,6 +392,7 @@ export class CodeModeTool {
|
|
|
389
392
|
return index;
|
|
390
393
|
};
|
|
391
394
|
let observation;
|
|
395
|
+
let displayObservation;
|
|
392
396
|
if (value.type === 'tool_result' && typeof value.operationId === 'string') {
|
|
393
397
|
const original = toolResults.get(value.operationId);
|
|
394
398
|
if (!original)
|
|
@@ -423,6 +427,23 @@ export class CodeModeTool {
|
|
|
423
427
|
...(indices?.length ? { imageIndices: indices } : {})
|
|
424
428
|
}
|
|
425
429
|
};
|
|
430
|
+
const display = original.result.eventData?.displayOutput;
|
|
431
|
+
if ((original.source.toolName === 'exec' || original.source.toolName === 'exec_wait') &&
|
|
432
|
+
display !== null &&
|
|
433
|
+
typeof display === 'object' &&
|
|
434
|
+
'stdout' in display &&
|
|
435
|
+
'stderr' in display &&
|
|
436
|
+
typeof display.stdout === 'string' &&
|
|
437
|
+
typeof display.stderr === 'string')
|
|
438
|
+
displayObservation = {
|
|
439
|
+
...observation,
|
|
440
|
+
result: {
|
|
441
|
+
content: '',
|
|
442
|
+
data: { stdout: display.stdout, stderr: display.stderr },
|
|
443
|
+
truncated: ('stdoutDroppedBytes' in display && Number(display.stdoutDroppedBytes) > 0) ||
|
|
444
|
+
('stderrDroppedBytes' in display && Number(display.stderrDroppedBytes) > 0)
|
|
445
|
+
}
|
|
446
|
+
};
|
|
426
447
|
}
|
|
427
448
|
else if (value.type === 'image') {
|
|
428
449
|
observation = {
|
|
@@ -444,12 +465,22 @@ export class CodeModeTool {
|
|
|
444
465
|
truncated: typeof value.originalBytes === 'number' &&
|
|
445
466
|
value.originalBytes > Buffer.byteLength(value.value)
|
|
446
467
|
};
|
|
468
|
+
if (typeof value.displayValue === 'string') {
|
|
469
|
+
if (Buffer.byteLength(value.displayValue) > 10_000)
|
|
470
|
+
throw new AgentError('MAR_AGENT_CODE_PROTOCOL_ERROR', 'Code Mode sent oversized display text.');
|
|
471
|
+
displayObservation = {
|
|
472
|
+
...observation,
|
|
473
|
+
value: value.displayValue,
|
|
474
|
+
truncated: typeof value.originalBytes === 'number' &&
|
|
475
|
+
value.originalBytes > Buffer.byteLength(value.displayValue)
|
|
476
|
+
};
|
|
477
|
+
}
|
|
447
478
|
}
|
|
448
479
|
else
|
|
449
480
|
throw new AgentError('MAR_AGENT_CODE_PROTOCOL_ERROR', 'Invalid observation frame.');
|
|
450
481
|
appendObservation(observation, value.type === 'value' && typeof value.originalBytes === 'number'
|
|
451
482
|
? value.originalBytes
|
|
452
|
-
: undefined);
|
|
483
|
+
: undefined, displayObservation);
|
|
453
484
|
}
|
|
454
485
|
else if (frame.kind === 'text' && typeof frame.value === 'string') {
|
|
455
486
|
const originalBytes = frame.originalBytes ?? Buffer.byteLength(frame.value);
|
|
@@ -527,10 +558,11 @@ export class CodeModeTool {
|
|
|
527
558
|
: undefined
|
|
528
559
|
}
|
|
529
560
|
});
|
|
530
|
-
const {
|
|
561
|
+
const { eventData: _eventData, ...guestOutput } = output;
|
|
562
|
+
const { content, ...structured } = guestOutput;
|
|
531
563
|
const projected = output.data !== undefined && content === JSON.stringify(output.data)
|
|
532
564
|
? structured
|
|
533
|
-
:
|
|
565
|
+
: guestOutput;
|
|
534
566
|
await write({ id, ok: true, output: projected });
|
|
535
567
|
}
|
|
536
568
|
catch (error) {
|
|
@@ -354,6 +354,7 @@ export async function createCodeModeEngine(options) {
|
|
|
354
354
|
}
|
|
355
355
|
const separatorBytes = textCalls ? 1 : 0;
|
|
356
356
|
const capture = new BoundedTextOutput(Math.max(0, input.limits.outputBytes - outputBytes - separatorBytes), '');
|
|
357
|
+
const display = new BoundedTextOutput(10_000, '', 'tail');
|
|
357
358
|
const emit = vm.newFunction('textChunk', (chunk) => {
|
|
358
359
|
if (!chunk || vm.typeof(chunk) !== 'string')
|
|
359
360
|
return {
|
|
@@ -366,7 +367,9 @@ export async function createCodeModeEngine(options) {
|
|
|
366
367
|
return {
|
|
367
368
|
error: guestError('MAR_AGENT_TOOL_OUTPUT_LIMIT', 'Text serialization chunk limit exceeded.')
|
|
368
369
|
};
|
|
369
|
-
|
|
370
|
+
const text = vm.getString(chunk);
|
|
371
|
+
capture.append(text);
|
|
372
|
+
display.append(text);
|
|
370
373
|
return vm.undefined;
|
|
371
374
|
});
|
|
372
375
|
const result = callGuest(formatText, vm.undefined, handle ?? vm.undefined, emit);
|
|
@@ -381,6 +384,7 @@ export async function createCodeModeEngine(options) {
|
|
|
381
384
|
type: 'value',
|
|
382
385
|
format: handle && vm.typeof(handle) === 'object' ? 'json' : 'text',
|
|
383
386
|
value: captured.text,
|
|
387
|
+
displayValue: display.snapshot().text,
|
|
384
388
|
originalBytes: captured.originalBytes
|
|
385
389
|
});
|
|
386
390
|
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
export function renderCapturedText(snapshot: any, imageCount?: number): string;
|
|
2
2
|
/** Bounded UTF-8 capture shared by trusted engines, the bridge and retained cells. */
|
|
3
3
|
export class BoundedTextOutput {
|
|
4
|
-
constructor(maximumBytes: any, separator?: string);
|
|
4
|
+
constructor(maximumBytes: any, separator?: string, retention?: string);
|
|
5
5
|
maximumBytes: any;
|
|
6
6
|
separator: string;
|
|
7
|
+
retention: string;
|
|
7
8
|
append(value: any, originalBytes?: number): void;
|
|
8
9
|
snapshot(): {
|
|
9
10
|
text: string;
|
|
@@ -6,9 +6,10 @@ export class BoundedTextOutput {
|
|
|
6
6
|
#retained = 0;
|
|
7
7
|
#hasValue = false;
|
|
8
8
|
#sealed = false;
|
|
9
|
-
constructor(maximumBytes, separator = '\n') {
|
|
9
|
+
constructor(maximumBytes, separator = '\n', retention = 'head') {
|
|
10
10
|
this.maximumBytes = maximumBytes;
|
|
11
11
|
this.separator = separator;
|
|
12
|
+
this.retention = retention;
|
|
12
13
|
}
|
|
13
14
|
append(value, originalBytes = Buffer.byteLength(value)) {
|
|
14
15
|
const separator = this.#hasValue ? this.separator : '';
|
|
@@ -16,10 +17,22 @@ export class BoundedTextOutput {
|
|
|
16
17
|
this.#original += Buffer.byteLength(separator) + originalBytes;
|
|
17
18
|
this.#capture(separator);
|
|
18
19
|
this.#capture(value);
|
|
19
|
-
if (originalBytes > Buffer.byteLength(value))
|
|
20
|
+
if (this.retention === 'head' && originalBytes > Buffer.byteLength(value))
|
|
20
21
|
this.#sealed = true;
|
|
21
22
|
}
|
|
22
23
|
#capture(value) {
|
|
24
|
+
if (this.retention === 'tail') {
|
|
25
|
+
let start = Math.max(0, value.length - this.maximumBytes);
|
|
26
|
+
if (start > 0 && /[\uDC00-\uDFFF]/u.test(value[start]))
|
|
27
|
+
start++;
|
|
28
|
+
const bytes = Buffer.from(this.#chunks.join('') + value.slice(start));
|
|
29
|
+
let offset = Math.max(0, bytes.length - this.maximumBytes);
|
|
30
|
+
while (offset < bytes.length && (bytes[offset] & 0xc0) === 0x80)
|
|
31
|
+
offset++;
|
|
32
|
+
this.#chunks = [bytes.subarray(offset).toString('utf8')];
|
|
33
|
+
this.#retained = bytes.length - offset;
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
23
36
|
const remaining = Math.max(0, this.maximumBytes - this.#used);
|
|
24
37
|
if (this.#sealed || !value.length)
|
|
25
38
|
return;
|
|
@@ -9,7 +9,7 @@ export interface ClientToolDefinition extends ModelToolDefinition {
|
|
|
9
9
|
outputSchema?: Record<string, unknown>;
|
|
10
10
|
/** Runtime-only policy. MCP defaults to both callers; other tools default to direct only. */
|
|
11
11
|
allowedCallers?: readonly ToolCaller[];
|
|
12
|
-
/** Runtime-only allowlist of
|
|
12
|
+
/** Runtime-only allowlist of scalars and bounded string arrays in public tool.started input. */
|
|
13
13
|
eventInputFields?: readonly string[];
|
|
14
14
|
/** Runtime-only exposure override; retains both permitted entry points with Code Mode enabled. */
|
|
15
15
|
codeModeExposure?: 'both';
|
|
@@ -145,6 +145,9 @@ export interface CodeCellEventData {
|
|
|
145
145
|
truncated?: boolean;
|
|
146
146
|
/** Public output projection discarded older text; not inferred from source truncation. */
|
|
147
147
|
outputHeadTruncated?: boolean;
|
|
148
|
+
/** Internal public-observation source fact, separate from local head clipping. */
|
|
149
|
+
outputSourceKnown?: boolean;
|
|
150
|
+
outputSourceTruncated?: boolean;
|
|
148
151
|
observations?: readonly import('../session/code-observation-contract.js').CodeObservation[];
|
|
149
152
|
error?: {
|
|
150
153
|
code: string;
|
package/dist/tools/execution.js
CHANGED
|
@@ -3,7 +3,7 @@ import { toolEventSummary } from '../runtime/event-projection.js';
|
|
|
3
3
|
import { allowsToolCaller } from './callers.js';
|
|
4
4
|
import { CodeCellExecutionError } from './code-mode/cells.js';
|
|
5
5
|
import { createCodeToolDispatch } from './code-mode/dispatch.js';
|
|
6
|
-
import { boundedModelToolOutput, publicToolEventInput, structuredToolArtifacts, publicExtensionToolEventData, structuredToolEventData, structuredToolFailureData } from './output-projection.js';
|
|
6
|
+
import { boundedModelToolOutput, publicToolEventInput, structuredToolArtifacts, publicExtensionToolEventData, publicOutputTruncation, publicEventOutputData, structuredToolEventData, structuredToolFailureData } from './output-projection.js';
|
|
7
7
|
import { publishModelResources } from '../runtime/resource-publication.js';
|
|
8
8
|
import { executeRegisteredTool } from './runtime.js';
|
|
9
9
|
import { failedToolResultExtensions } from './result-extensions.js';
|
|
@@ -137,6 +137,7 @@ export async function executeRoundTools(calls, scope) {
|
|
|
137
137
|
? structuredToolEventData(call.name, output.data)
|
|
138
138
|
: publicExtensionToolEventData(output.eventData);
|
|
139
139
|
const artifacts = structuredToolArtifacts(output.artifacts);
|
|
140
|
+
const outputTruncation = publicOutputTruncation(call.name, structuredData);
|
|
140
141
|
await scope.emit({
|
|
141
142
|
type: 'tool.completed',
|
|
142
143
|
...(output.resultExtensions ? { resultExtensions: output.resultExtensions } : {}),
|
|
@@ -145,8 +146,10 @@ export async function executeRoundTools(calls, scope) {
|
|
|
145
146
|
...(parentCallId === undefined ? {} : { parentCallId }),
|
|
146
147
|
summary: eventSummary.summary,
|
|
147
148
|
truncated: output.truncated === true || modelOutput.truncated || eventSummary.truncated,
|
|
149
|
+
...(eventSummary.truncated ? { summaryTruncated: true } : {}),
|
|
150
|
+
...(outputTruncation ? { outputTruncation } : {}),
|
|
148
151
|
...(artifacts === undefined ? {} : { artifacts }),
|
|
149
|
-
...(structuredData === undefined ? {} : { data: structuredData })
|
|
152
|
+
...(structuredData === undefined ? {} : { data: publicEventOutputData(structuredData) })
|
|
150
153
|
});
|
|
151
154
|
return {
|
|
152
155
|
call,
|
|
@@ -173,6 +176,7 @@ export async function executeRoundTools(calls, scope) {
|
|
|
173
176
|
(partial?.data === undefined
|
|
174
177
|
? undefined
|
|
175
178
|
: structuredToolEventData(call.name, partial.eventData ?? partial.data));
|
|
179
|
+
const outputTruncation = publicOutputTruncation(call.name, eventData);
|
|
176
180
|
await scope.emit({
|
|
177
181
|
type: 'tool.failed',
|
|
178
182
|
...(failedToolResultExtensions(error)
|
|
@@ -186,7 +190,8 @@ export async function executeRoundTools(calls, scope) {
|
|
|
186
190
|
...(permissionStopFromError(error)
|
|
187
191
|
? { permissionStop: permissionStopFromError(error) }
|
|
188
192
|
: {}),
|
|
189
|
-
...(
|
|
193
|
+
...(outputTruncation ? { outputTruncation } : {}),
|
|
194
|
+
...(eventData === undefined ? {} : { data: publicEventOutputData(eventData) })
|
|
190
195
|
});
|
|
191
196
|
return {
|
|
192
197
|
call,
|
|
@@ -12,6 +12,13 @@ export declare function boundedModelToolOutput(toolName: string, value: string,
|
|
|
12
12
|
};
|
|
13
13
|
/** The opted-in allowlist fails closed and never mutates the invocation arguments. */
|
|
14
14
|
export declare function publicToolEventInput(value: unknown, fields: readonly string[]): Readonly<Record<string, unknown>>;
|
|
15
|
+
/** Explicit observations are public, but their event projection must remain bounded. */
|
|
16
|
+
export declare function publicCodeObservations(values: unknown, data: CodeCellEventData): CodeCellEventData;
|
|
17
|
+
/** Classify only facts about the public body, never model or summary limits. */
|
|
18
|
+
export declare function publicOutputTruncation(toolName: string, data: unknown): 'HEAD' | 'SOURCE' | undefined;
|
|
19
|
+
/** Remove the internal display direction from published structured data. */
|
|
20
|
+
export declare function publicEventOutputData<T>(data: T): T;
|
|
21
|
+
export declare function liveOutputTruncation(output: import('./contracts.js').ExecOutputSnapshot): 'HEAD' | 'SOURCE' | undefined;
|
|
15
22
|
/** Project only known, validated tool result shapes into public structured events. */
|
|
16
23
|
export declare function structuredToolEventData(toolName: string, value: unknown): CodeCellEventData | ExecResult | ApplyPatchResult | ReadEventData | ViewImageResult | SleepResult | PublicToolMetadata | undefined;
|
|
17
24
|
/** Partial patch facts are allowlisted; arbitrary error details never become public event data. */
|
|
@@ -58,6 +58,9 @@ export function publicToolEventInput(value, fields) {
|
|
|
58
58
|
.filter((key) => Object.hasOwn(value, key) &&
|
|
59
59
|
(typeof value[key] === 'string' ||
|
|
60
60
|
typeof value[key] === 'boolean' ||
|
|
61
|
+
(Array.isArray(value[key]) &&
|
|
62
|
+
value[key].length <= 100 &&
|
|
63
|
+
value[key].every((item) => typeof item === 'string')) ||
|
|
61
64
|
(typeof value[key] === 'number' && Number.isFinite(value[key])) ||
|
|
62
65
|
value[key] === null))
|
|
63
66
|
.map((key) => [key, value[key]]));
|
|
@@ -68,7 +71,7 @@ export function publicToolEventInput(value, fields) {
|
|
|
68
71
|
}
|
|
69
72
|
}
|
|
70
73
|
/** Explicit observations are public, but their event projection must remain bounded. */
|
|
71
|
-
function publicCodeObservations(values, data) {
|
|
74
|
+
export function publicCodeObservations(values, data) {
|
|
72
75
|
if (!Array.isArray(values))
|
|
73
76
|
return data;
|
|
74
77
|
const retained = new Set(data.observations ?? []);
|
|
@@ -77,6 +80,7 @@ function publicCodeObservations(values, data) {
|
|
|
77
80
|
let remaining = 10_000;
|
|
78
81
|
let truncated = false;
|
|
79
82
|
let headTruncated = data.outputHeadTruncated === true;
|
|
83
|
+
let sourceTruncated = data.outputSourceTruncated === true;
|
|
80
84
|
const clip = (text) => {
|
|
81
85
|
let start = Math.max(0, text.length - remaining);
|
|
82
86
|
if (start > 0 && /[\uDC00-\uDFFF]/u.test(text[start]))
|
|
@@ -101,6 +105,8 @@ function publicCodeObservations(values, data) {
|
|
|
101
105
|
continue;
|
|
102
106
|
}
|
|
103
107
|
displayedCount++;
|
|
108
|
+
if (!data.outputSourceKnown && entry.truncated === true)
|
|
109
|
+
sourceTruncated = true;
|
|
104
110
|
truncated ||= entry.truncated === true;
|
|
105
111
|
const value = clip(entry.value);
|
|
106
112
|
selected.unshift({
|
|
@@ -116,8 +122,11 @@ function publicCodeObservations(values, data) {
|
|
|
116
122
|
else if (entry.type === 'tool_result' && isRecord(entry.source) && isRecord(entry.result)) {
|
|
117
123
|
const name = entry.source.toolName;
|
|
118
124
|
if (name !== 'exec' && name !== 'exec_wait') {
|
|
119
|
-
if (retained.has(entry))
|
|
125
|
+
if (retained.has(entry)) {
|
|
126
|
+
if (!data.outputSourceKnown && entry.result.truncated === true)
|
|
127
|
+
sourceTruncated = true;
|
|
120
128
|
selected.unshift(entry);
|
|
129
|
+
}
|
|
121
130
|
}
|
|
122
131
|
else if ((name === 'exec' || name === 'exec_wait') &&
|
|
123
132
|
typeof entry.id === 'string' &&
|
|
@@ -129,10 +138,12 @@ function publicCodeObservations(values, data) {
|
|
|
129
138
|
continue;
|
|
130
139
|
}
|
|
131
140
|
displayedCount++;
|
|
141
|
+
if (!data.outputSourceKnown && entry.result.truncated === true)
|
|
142
|
+
sourceTruncated = true;
|
|
132
143
|
truncated ||= entry.result.truncated === true;
|
|
133
|
-
const
|
|
134
|
-
const stderr = typeof
|
|
135
|
-
const stdout = typeof
|
|
144
|
+
const resultData = isRecord(entry.result.data) ? entry.result.data : undefined;
|
|
145
|
+
const stderr = typeof resultData?.stderr === 'string' ? clip(resultData.stderr) : undefined;
|
|
146
|
+
const stdout = typeof resultData?.stdout === 'string' ? clip(resultData.stdout) : undefined;
|
|
136
147
|
const content = stdout === undefined && stderr === undefined && typeof entry.result.content === 'string'
|
|
137
148
|
? clip(entry.result.content)
|
|
138
149
|
: '';
|
|
@@ -153,10 +164,48 @@ function publicCodeObservations(values, data) {
|
|
|
153
164
|
return {
|
|
154
165
|
...data,
|
|
155
166
|
observations: selected,
|
|
167
|
+
outputSourceKnown: true,
|
|
168
|
+
...(sourceTruncated ? { outputSourceTruncated: true } : {}),
|
|
156
169
|
...(truncated ? { truncated: true } : {}),
|
|
157
170
|
...(headTruncated ? { outputHeadTruncated: true } : {})
|
|
158
171
|
};
|
|
159
172
|
}
|
|
173
|
+
/** Classify only facts about the public body, never model or summary limits. */
|
|
174
|
+
export function publicOutputTruncation(toolName, data) {
|
|
175
|
+
if (!isRecord(data))
|
|
176
|
+
return undefined;
|
|
177
|
+
if (toolName === 'code_exec' || toolName === 'code_exec_wait') {
|
|
178
|
+
if (data.outputSourceTruncated === true)
|
|
179
|
+
return 'SOURCE';
|
|
180
|
+
if (data.outputHeadTruncated === true)
|
|
181
|
+
return 'HEAD';
|
|
182
|
+
}
|
|
183
|
+
if (toolName === 'exec' || toolName === 'exec_wait') {
|
|
184
|
+
const display = isRecord(data.displayOutput) ? data.displayOutput : undefined;
|
|
185
|
+
if (display)
|
|
186
|
+
return Number(display.stdoutDroppedBytes) > 0 || Number(display.stderrDroppedBytes) > 0
|
|
187
|
+
? 'HEAD'
|
|
188
|
+
: undefined;
|
|
189
|
+
if (data.truncated === true)
|
|
190
|
+
return 'SOURCE';
|
|
191
|
+
}
|
|
192
|
+
return undefined;
|
|
193
|
+
}
|
|
194
|
+
/** Remove the internal display direction from published structured data. */
|
|
195
|
+
export function publicEventOutputData(data) {
|
|
196
|
+
if (!isRecord(data))
|
|
197
|
+
return data;
|
|
198
|
+
const { outputHeadTruncated: _head, outputSourceKnown: _known, outputSourceTruncated: _source, ...rest } = data;
|
|
199
|
+
return rest;
|
|
200
|
+
}
|
|
201
|
+
export function liveOutputTruncation(output) {
|
|
202
|
+
if (!output.truncated)
|
|
203
|
+
return undefined;
|
|
204
|
+
return output.displayOutput &&
|
|
205
|
+
(output.displayOutput.stdoutDroppedBytes > 0 || output.displayOutput.stderrDroppedBytes > 0)
|
|
206
|
+
? 'HEAD'
|
|
207
|
+
: 'SOURCE';
|
|
208
|
+
}
|
|
160
209
|
/** Project only known, validated tool result shapes into public structured events. */
|
|
161
210
|
export function structuredToolEventData(toolName, value) {
|
|
162
211
|
if (!isRecord(value))
|
|
@@ -199,6 +248,8 @@ export function structuredToolEventData(toolName, value) {
|
|
|
199
248
|
: {}),
|
|
200
249
|
status: value.status,
|
|
201
250
|
...(value.outputHeadTruncated === true ? { outputHeadTruncated: true } : {}),
|
|
251
|
+
...(value.outputSourceKnown === true ? { outputSourceKnown: true } : {}),
|
|
252
|
+
...(value.outputSourceTruncated === true ? { outputSourceTruncated: true } : {}),
|
|
202
253
|
cached: value.cached === true,
|
|
203
254
|
...(['snapshot', 'completion', 'timeout', 'yield', 'interrupted'].includes(String(value.waitOutcome))
|
|
204
255
|
? { waitOutcome: value.waitOutcome }
|
package/dist/tools/runtime.js
CHANGED
|
@@ -2,7 +2,7 @@ import { AgentError } from '../error.js';
|
|
|
2
2
|
import { randomUUID } from 'node:crypto';
|
|
3
3
|
import { AGENT_TOOL_HANDLERS, BUILTIN_TOOL_DEFINITIONS } from './registry.js';
|
|
4
4
|
import { normalizeToolInput } from './input.js';
|
|
5
|
-
import { validateToolResult } from './output-projection.js';
|
|
5
|
+
import { liveOutputTruncation, validateToolResult } from './output-projection.js';
|
|
6
6
|
import { withToolResultExtensions } from './result-extensions.js';
|
|
7
7
|
import { viewResourceImage } from './local/view-image.js';
|
|
8
8
|
export async function executeRegisteredTool(name, arguments_, callId, context, caller = 'direct') {
|
|
@@ -58,6 +58,9 @@ export async function executeCapturedTool(kind, call, context) {
|
|
|
58
58
|
toolName: name,
|
|
59
59
|
callId,
|
|
60
60
|
...(context.parentCallId === undefined ? {} : { parentCallId: context.parentCallId }),
|
|
61
|
+
...(liveOutputTruncation(output)
|
|
62
|
+
? { outputTruncation: liveOutputTruncation(output) }
|
|
63
|
+
: {}),
|
|
61
64
|
output
|
|
62
65
|
})
|
|
63
66
|
}
|