@myagentroam/agent 0.11.42 → 0.11.44
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/host/local-exec-tool.js +50 -26
- package/dist/runtime/async-queue.d.ts +1 -1
- package/dist/runtime/async-queue.js +4 -2
- package/dist/runtime/events.d.ts +1 -1
- package/dist/runtime/wait.js +13 -6
- package/dist/sdk/session-execution.d.ts +1 -0
- package/dist/sdk/session-execution.js +26 -3
- package/dist/tools/code-mode/cells.d.ts +1 -1
- package/dist/tools/code-mode/cells.js +156 -2
- package/dist/tools/code-mode/dispatch.js +3 -0
- 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 +14 -0
- package/dist/tools/execution.js +16 -3
- package/dist/tools/local/exec.js +5 -3
- package/dist/tools/output-projection.d.ts +2 -0
- package/dist/tools/output-projection.js +24 -2
- package/dist/tools/runtime-context.d.ts +1 -1
- package/dist/tools/runtime.js +2 -1
- package/package.json +1 -1
|
@@ -169,7 +169,8 @@ export class ExecTool {
|
|
|
169
169
|
...(child.signalCode == null ? {} : { exitSignal: child.signalCode }),
|
|
170
170
|
finalized: false,
|
|
171
171
|
terminalResultReturned: false,
|
|
172
|
-
waiters: new Set()
|
|
172
|
+
waiters: new Set(),
|
|
173
|
+
outputListeners: new Set()
|
|
173
174
|
};
|
|
174
175
|
this.#processes.set(id, managed);
|
|
175
176
|
if (native) {
|
|
@@ -188,6 +189,8 @@ export class ExecTool {
|
|
|
188
189
|
return;
|
|
189
190
|
const decoded = Buffer.from(managed[name].decoder.write(chunk));
|
|
190
191
|
appendDisplay(managed[name === 'stdout' ? 'stdoutDisplay' : 'stderrDisplay'], decoded);
|
|
192
|
+
for (const listener of managed.outputListeners)
|
|
193
|
+
listener(name, decoded);
|
|
191
194
|
appendOutput(managed[name], decoded, this.maxOutputBytes, chunk.length);
|
|
192
195
|
appendOutput(managed[name === 'stdout' ? 'stdoutHistory' : 'stderrHistory'], decoded, this.maxOutputBytes, chunk.length);
|
|
193
196
|
notifyProcessChange(managed);
|
|
@@ -226,6 +229,8 @@ export class ExecTool {
|
|
|
226
229
|
else {
|
|
227
230
|
const message = Buffer.from(error.message);
|
|
228
231
|
appendDisplay(managed.stderrDisplay, message);
|
|
232
|
+
for (const listener of managed.outputListeners)
|
|
233
|
+
listener('stderr', message);
|
|
229
234
|
appendOutput(managed.stderr, message, this.maxOutputBytes);
|
|
230
235
|
appendOutput(managed.stderrHistory, message, this.maxOutputBytes);
|
|
231
236
|
notifyProcessChange(managed);
|
|
@@ -421,7 +426,13 @@ export class ExecTool {
|
|
|
421
426
|
stderrBytes: process.stderr.totalBytes,
|
|
422
427
|
stdoutDroppedBytes: stdout.droppedBytes,
|
|
423
428
|
stderrDroppedBytes: stderr.droppedBytes,
|
|
424
|
-
displayOutput: displayOutput(process)
|
|
429
|
+
displayOutput: displayOutput(process),
|
|
430
|
+
executionSource: {
|
|
431
|
+
kind: 'SHELL',
|
|
432
|
+
text: process.command.slice(0, 10_000),
|
|
433
|
+
cwd: process.cwd.slice(0, 10_000),
|
|
434
|
+
truncated: process.command.length > 10_000 || process.cwd.length > 10_000
|
|
435
|
+
}
|
|
425
436
|
}
|
|
426
437
|
});
|
|
427
438
|
while (this.#completed.size > TOOL_EXECUTION_LIMITS.execMaxCompletedResults)
|
|
@@ -484,7 +495,13 @@ export class ExecTool {
|
|
|
484
495
|
stderrBytes: process.stderr.totalBytes,
|
|
485
496
|
stdoutDroppedBytes: stdout.droppedBytes,
|
|
486
497
|
stderrDroppedBytes: stderr.droppedBytes,
|
|
487
|
-
displayOutput: displayOutput(process)
|
|
498
|
+
displayOutput: displayOutput(process),
|
|
499
|
+
executionSource: {
|
|
500
|
+
kind: 'SHELL',
|
|
501
|
+
text: process.command.slice(0, 10_000),
|
|
502
|
+
cwd: process.cwd.slice(0, 10_000),
|
|
503
|
+
truncated: process.command.length > 10_000 || process.cwd.length > 10_000
|
|
504
|
+
}
|
|
488
505
|
};
|
|
489
506
|
}
|
|
490
507
|
async dispose() {
|
|
@@ -529,6 +546,10 @@ function finalizeManagedProcess(managed, maximumOutputBytes, exitCode) {
|
|
|
529
546
|
const stderrEnd = Buffer.from(managed.stderr.decoder.end());
|
|
530
547
|
appendDisplay(managed.stdoutDisplay, stdoutEnd);
|
|
531
548
|
appendDisplay(managed.stderrDisplay, stderrEnd);
|
|
549
|
+
for (const listener of managed.outputListeners) {
|
|
550
|
+
listener('stdout', stdoutEnd);
|
|
551
|
+
listener('stderr', stderrEnd);
|
|
552
|
+
}
|
|
532
553
|
appendOutput(managed.stdout, stdoutEnd, maximumOutputBytes);
|
|
533
554
|
appendOutput(managed.stderr, stderrEnd, maximumOutputBytes);
|
|
534
555
|
appendOutput(managed.stdoutHistory, stdoutEnd, maximumOutputBytes);
|
|
@@ -642,34 +663,36 @@ async function waitForProcessCompletion(processId, managed, waitMs, signal, inte
|
|
|
642
663
|
stopOutput?.();
|
|
643
664
|
}
|
|
644
665
|
}
|
|
645
|
-
/** Coalesce
|
|
666
|
+
/** Coalesce decoded chunks into bounded per-call frames without blocking the process. */
|
|
646
667
|
function watchProcessOutput(processId, managed, onOutput) {
|
|
647
668
|
let stopped = false;
|
|
648
|
-
let
|
|
669
|
+
let stdout = { value: Buffer.alloc(0), droppedBytes: 0 };
|
|
670
|
+
let stderr = { value: Buffer.alloc(0), droppedBytes: 0 };
|
|
649
671
|
let timer;
|
|
650
672
|
let inFlight;
|
|
651
673
|
let failed = false;
|
|
674
|
+
let initialSnapshot = managed.stdoutDisplay.value.length > 0 || managed.stderrDisplay.value.length > 0;
|
|
652
675
|
const publish = () => {
|
|
653
|
-
|
|
654
|
-
const
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
};
|
|
661
|
-
const stdout = preview(managed.stdout);
|
|
662
|
-
const stderr = preview(managed.stderr);
|
|
676
|
+
const frameOut = stdout;
|
|
677
|
+
const frameErr = stderr;
|
|
678
|
+
stdout = { value: Buffer.alloc(0), droppedBytes: 0 };
|
|
679
|
+
stderr = { value: Buffer.alloc(0), droppedBytes: 0 };
|
|
680
|
+
const snapshot = displayOutput(managed);
|
|
681
|
+
const snapshotFrame = initialSnapshot || frameOut.droppedBytes > 0 || frameErr.droppedBytes > 0;
|
|
682
|
+
initialSnapshot = false;
|
|
663
683
|
inFlight = Promise.resolve()
|
|
664
684
|
.then(() => {
|
|
665
685
|
if (stopped)
|
|
666
686
|
return;
|
|
667
687
|
return onOutput({
|
|
668
688
|
processId,
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
689
|
+
mode: snapshotFrame ? 'snapshot' : 'delta',
|
|
690
|
+
stdout: snapshotFrame ? snapshot.stdout : frameOut.value.toString('utf8'),
|
|
691
|
+
stderr: snapshotFrame ? snapshot.stderr : frameErr.value.toString('utf8'),
|
|
692
|
+
truncated: snapshotFrame
|
|
693
|
+
? snapshot.stdoutDroppedBytes > 0 || snapshot.stderrDroppedBytes > 0
|
|
694
|
+
: frameOut.droppedBytes > 0 || frameErr.droppedBytes > 0,
|
|
695
|
+
displayOutput: snapshot
|
|
673
696
|
});
|
|
674
697
|
})
|
|
675
698
|
.catch(() => {
|
|
@@ -678,7 +701,7 @@ function watchProcessOutput(processId, managed, onOutput) {
|
|
|
678
701
|
})
|
|
679
702
|
.finally(() => {
|
|
680
703
|
inFlight = undefined;
|
|
681
|
-
if (
|
|
704
|
+
if (stdout.value.length || stderr.value.length)
|
|
682
705
|
schedule();
|
|
683
706
|
});
|
|
684
707
|
};
|
|
@@ -691,17 +714,18 @@ function watchProcessOutput(processId, managed, onOutput) {
|
|
|
691
714
|
}, TOOL_EXECUTION_LIMITS.execProgressIntervalMs);
|
|
692
715
|
timer.unref();
|
|
693
716
|
};
|
|
694
|
-
const changed = () => {
|
|
695
|
-
if (
|
|
717
|
+
const changed = (channel, chunk) => {
|
|
718
|
+
if (chunk.length === 0)
|
|
696
719
|
return;
|
|
697
|
-
|
|
720
|
+
appendDisplay(channel === 'stdout' ? stdout : stderr, chunk);
|
|
698
721
|
schedule();
|
|
699
722
|
};
|
|
700
|
-
managed.
|
|
701
|
-
|
|
723
|
+
managed.outputListeners.add(changed);
|
|
724
|
+
if (initialSnapshot)
|
|
725
|
+
schedule();
|
|
702
726
|
return () => {
|
|
703
727
|
stopped = true;
|
|
704
|
-
managed.
|
|
728
|
+
managed.outputListeners.delete(changed);
|
|
705
729
|
if (timer)
|
|
706
730
|
clearTimeout(timer);
|
|
707
731
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export declare class AsyncQueue<T> implements AsyncIterable<T> {
|
|
2
2
|
#private;
|
|
3
3
|
/** Best-effort live values are delivered only to an actively waiting consumer. */
|
|
4
|
-
pushIfWaiting(value: T):
|
|
4
|
+
pushIfWaiting(value: T): boolean;
|
|
5
5
|
push(value: T): void;
|
|
6
6
|
close(): void;
|
|
7
7
|
discardIfUnobserved(): boolean;
|
|
@@ -8,8 +8,10 @@ export class AsyncQueue {
|
|
|
8
8
|
#discarded = false;
|
|
9
9
|
/** Best-effort live values are delivered only to an actively waiting consumer. */
|
|
10
10
|
pushIfWaiting(value) {
|
|
11
|
-
if (this.#waiterOffset
|
|
12
|
-
|
|
11
|
+
if (this.#waiterOffset >= this.#waiters.length || this.#closed || this.#discarded)
|
|
12
|
+
return false;
|
|
13
|
+
this.push(value);
|
|
14
|
+
return true;
|
|
13
15
|
}
|
|
14
16
|
push(value) {
|
|
15
17
|
if (this.#closed || this.#discarded)
|
package/dist/runtime/events.d.ts
CHANGED
|
@@ -91,7 +91,7 @@ export type AgentEvent = AgentEventBase & ({
|
|
|
91
91
|
truncated?: boolean;
|
|
92
92
|
} | {
|
|
93
93
|
type: 'tool.output';
|
|
94
|
-
toolName: 'exec' | 'exec_wait';
|
|
94
|
+
toolName: 'exec' | 'exec_wait' | 'code_exec_wait';
|
|
95
95
|
callId: string;
|
|
96
96
|
parentCallId?: string;
|
|
97
97
|
output: import('../tools/contracts.js').ExecOutputSnapshot;
|
package/dist/runtime/wait.js
CHANGED
|
@@ -40,12 +40,19 @@ export function waitForToolCondition(input) {
|
|
|
40
40
|
reject(error);
|
|
41
41
|
};
|
|
42
42
|
const check = () => {
|
|
43
|
-
if (
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
43
|
+
if (settled)
|
|
44
|
+
return;
|
|
45
|
+
try {
|
|
46
|
+
if (input.signal?.aborted)
|
|
47
|
+
abort();
|
|
48
|
+
else if (input.ready())
|
|
49
|
+
finish('ready');
|
|
50
|
+
else if (input.interrupt?.pending())
|
|
51
|
+
finish('interrupted');
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
finish('interrupted', error);
|
|
55
|
+
}
|
|
49
56
|
};
|
|
50
57
|
const abort = () => finish('interrupted', input.signal?.reason ??
|
|
51
58
|
new AgentError('MAR_AGENT_EXECUTION_CANCELLED', 'Execution cancelled.'));
|
|
@@ -65,6 +65,7 @@ export class SessionExecution {
|
|
|
65
65
|
terminalAdmitted = false;
|
|
66
66
|
releaseLease;
|
|
67
67
|
eventSequence = 0;
|
|
68
|
+
missedOutput = new Set();
|
|
68
69
|
persistedTerminal = false;
|
|
69
70
|
completionAdmitted = false;
|
|
70
71
|
appendedMessages = [];
|
|
@@ -235,10 +236,32 @@ export class SessionExecution {
|
|
|
235
236
|
full.type === 'execution.failed' ||
|
|
236
237
|
full.type === 'execution.cancelled')
|
|
237
238
|
this.#observedTerminal = structuredClone(full);
|
|
238
|
-
if (full.type === 'tool.output')
|
|
239
|
-
this.
|
|
240
|
-
|
|
239
|
+
if (full.type === 'tool.output') {
|
|
240
|
+
const missed = this.missedOutput.has(full.callId);
|
|
241
|
+
const deliver = missed && full.output.displayOutput
|
|
242
|
+
? {
|
|
243
|
+
...full,
|
|
244
|
+
output: {
|
|
245
|
+
...full.output,
|
|
246
|
+
mode: 'snapshot',
|
|
247
|
+
stdout: full.output.displayOutput.stdout,
|
|
248
|
+
stderr: full.output.displayOutput.stderr,
|
|
249
|
+
truncated: full.output.truncated ||
|
|
250
|
+
full.output.displayOutput.stdoutDroppedBytes > 0 ||
|
|
251
|
+
full.output.displayOutput.stderrDroppedBytes > 0
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
: full;
|
|
255
|
+
if (this.events.pushIfWaiting(deliver))
|
|
256
|
+
this.missedOutput.delete(full.callId);
|
|
257
|
+
else
|
|
258
|
+
this.missedOutput.add(full.callId);
|
|
259
|
+
}
|
|
260
|
+
else {
|
|
261
|
+
if (full.type === 'tool.completed' || full.type === 'tool.failed')
|
|
262
|
+
this.missedOutput.delete(full.callId);
|
|
241
263
|
this.events.push(full);
|
|
264
|
+
}
|
|
242
265
|
ports.observe?.(full);
|
|
243
266
|
this.compactRequests.publish(full);
|
|
244
267
|
try {
|
|
@@ -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 {
|
|
@@ -20,6 +21,110 @@ export class CodeCellExecutionError extends AgentError {
|
|
|
20
21
|
this.result = result;
|
|
21
22
|
}
|
|
22
23
|
}
|
|
24
|
+
/** A waiting call owns its bounded display window; delivery never delays the cell. */
|
|
25
|
+
function forwardCellOutput(publish) {
|
|
26
|
+
let stopped = false;
|
|
27
|
+
let failed = false;
|
|
28
|
+
let inFlight = false;
|
|
29
|
+
let pending = false;
|
|
30
|
+
let latest;
|
|
31
|
+
// Source metadata is bounded by the cell's tool-call budget; text shares one window.
|
|
32
|
+
const sources = new Map();
|
|
33
|
+
const trim = (channel) => {
|
|
34
|
+
let excess = [...sources.values()].reduce((bytes, source) => bytes + Buffer.byteLength(source[channel]), 0) - limits.execProgressMaxOutputBytes;
|
|
35
|
+
for (const source of sources.values()) {
|
|
36
|
+
if (excess <= 0)
|
|
37
|
+
break;
|
|
38
|
+
const bytes = Buffer.from(source[channel]);
|
|
39
|
+
let removed = Math.min(excess, bytes.length);
|
|
40
|
+
while (removed < bytes.length && (bytes[removed] & 0xc0) === 0x80)
|
|
41
|
+
removed++;
|
|
42
|
+
source[channel] = bytes.subarray(removed).toString('utf8');
|
|
43
|
+
excess -= removed;
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
const snapshot = () => {
|
|
47
|
+
const entries = [...sources.values()];
|
|
48
|
+
const stdout = entries.map((source) => source.stdout).join('');
|
|
49
|
+
const stderr = entries.map((source) => source.stderr).join('');
|
|
50
|
+
const stdoutDroppedBytes = entries.reduce((bytes, source) => bytes + source.stdoutBytes, 0) - Buffer.byteLength(stdout);
|
|
51
|
+
const stderrDroppedBytes = entries.reduce((bytes, source) => bytes + source.stderrBytes, 0) - Buffer.byteLength(stderr);
|
|
52
|
+
return {
|
|
53
|
+
...latest,
|
|
54
|
+
mode: 'snapshot',
|
|
55
|
+
stdout,
|
|
56
|
+
stderr,
|
|
57
|
+
truncated: entries.some((source) => source.truncated) ||
|
|
58
|
+
stdoutDroppedBytes > 0 ||
|
|
59
|
+
stderrDroppedBytes > 0,
|
|
60
|
+
displayOutput: {
|
|
61
|
+
stdout,
|
|
62
|
+
stderr,
|
|
63
|
+
stdoutDroppedBytes,
|
|
64
|
+
stderrDroppedBytes
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
const send = (output) => {
|
|
69
|
+
inFlight = true;
|
|
70
|
+
void Promise.resolve()
|
|
71
|
+
.then(() => (stopped ? undefined : publish(output)))
|
|
72
|
+
.catch(() => {
|
|
73
|
+
failed = true;
|
|
74
|
+
})
|
|
75
|
+
.finally(() => {
|
|
76
|
+
inFlight = false;
|
|
77
|
+
if (!stopped && !failed && pending) {
|
|
78
|
+
pending = false;
|
|
79
|
+
send(snapshot());
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
};
|
|
83
|
+
return {
|
|
84
|
+
onOutput(output) {
|
|
85
|
+
if (stopped || failed)
|
|
86
|
+
return;
|
|
87
|
+
const isSnapshot = output.mode !== 'delta';
|
|
88
|
+
const source = sources.get(output.processId) ?? {
|
|
89
|
+
stdout: '',
|
|
90
|
+
stderr: '',
|
|
91
|
+
stdoutBytes: 0,
|
|
92
|
+
stderrBytes: 0,
|
|
93
|
+
truncated: false
|
|
94
|
+
};
|
|
95
|
+
if (isSnapshot) {
|
|
96
|
+
source.stdout = output.stdout;
|
|
97
|
+
source.stderr = output.stderr;
|
|
98
|
+
source.stdoutBytes =
|
|
99
|
+
Buffer.byteLength(output.stdout) + (output.displayOutput?.stdoutDroppedBytes ?? 0);
|
|
100
|
+
source.stderrBytes =
|
|
101
|
+
Buffer.byteLength(output.stderr) + (output.displayOutput?.stderrDroppedBytes ?? 0);
|
|
102
|
+
source.truncated = output.truncated;
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
source.stdout += output.stdout;
|
|
106
|
+
source.stderr += output.stderr;
|
|
107
|
+
source.stdoutBytes += Buffer.byteLength(output.stdout);
|
|
108
|
+
source.stderrBytes += Buffer.byteLength(output.stderr);
|
|
109
|
+
source.truncated ||= output.truncated;
|
|
110
|
+
}
|
|
111
|
+
sources.set(output.processId, source);
|
|
112
|
+
trim('stdout');
|
|
113
|
+
trim('stderr');
|
|
114
|
+
latest = output;
|
|
115
|
+
if (inFlight)
|
|
116
|
+
pending = true;
|
|
117
|
+
else
|
|
118
|
+
send(isSnapshot ? snapshot() : { ...output, displayOutput: snapshot().displayOutput });
|
|
119
|
+
},
|
|
120
|
+
stop() {
|
|
121
|
+
stopped = true;
|
|
122
|
+
pending = false;
|
|
123
|
+
sources.clear();
|
|
124
|
+
latest = undefined;
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
}
|
|
23
128
|
function options(value, wait) {
|
|
24
129
|
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
25
130
|
throw new AgentError('MAR_AGENT_TOOL_INPUT_INVALID', 'Code Exec options must be an object.');
|
|
@@ -77,6 +182,7 @@ export class CodeCells {
|
|
|
77
182
|
throw new AgentError('MAR_AGENT_PROCESS_LIMIT', `The Local Host shared running-cell limit (${limits.codeMaxConcurrentCells}) was reached.`);
|
|
78
183
|
const controller = new AbortController();
|
|
79
184
|
const cell = {
|
|
185
|
+
source: { kind: 'CODE', text: source.slice(0, 10_000), truncated: source.length > 10_000 },
|
|
80
186
|
cellId: this.#handles.create(),
|
|
81
187
|
sessionId: context.sessionId,
|
|
82
188
|
executionId: context.executionId,
|
|
@@ -90,9 +196,14 @@ export class CodeCells {
|
|
|
90
196
|
yielded: false,
|
|
91
197
|
truncated: false,
|
|
92
198
|
listeners: new Set(),
|
|
199
|
+
outputListeners: new Set(),
|
|
93
200
|
done: Promise.resolve()
|
|
94
201
|
};
|
|
95
202
|
this.#cells.set(cell.cellId, cell);
|
|
203
|
+
const unsubscribeOutput = context.codeMode?.subscribeToolOutput?.((output) => {
|
|
204
|
+
for (const listener of cell.outputListeners)
|
|
205
|
+
listener(output);
|
|
206
|
+
});
|
|
96
207
|
const notify = () => {
|
|
97
208
|
for (const listener of cell.listeners)
|
|
98
209
|
listener();
|
|
@@ -104,7 +215,18 @@ export class CodeCells {
|
|
|
104
215
|
releaseSession = await context.codeMode?.retainSession?.();
|
|
105
216
|
return run(source, { ...context, signal: controller.signal, waitInterrupt: undefined }, {
|
|
106
217
|
call: (value) => cell.calls.set(value.callId, value),
|
|
107
|
-
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
|
+
...(cell.display?.outputHeadTruncated || displayHeadTruncated
|
|
226
|
+
? { outputHeadTruncated: true }
|
|
227
|
+
: {})
|
|
228
|
+
});
|
|
229
|
+
},
|
|
108
230
|
text: (text, originalBytes) => cell.text.append(text, originalBytes),
|
|
109
231
|
image: (image) => cell.images.push(image),
|
|
110
232
|
yield: () => {
|
|
@@ -124,6 +246,7 @@ export class CodeCells {
|
|
|
124
246
|
outcome = controller.signal.aborted ? 'cancelled' : 'failed';
|
|
125
247
|
})
|
|
126
248
|
.then(async () => {
|
|
249
|
+
unsubscribeOutput?.();
|
|
127
250
|
try {
|
|
128
251
|
await context.codeMode?.onSettled?.({
|
|
129
252
|
cellId: cell.cellId,
|
|
@@ -179,7 +302,23 @@ export class CodeCells {
|
|
|
179
302
|
abort();
|
|
180
303
|
});
|
|
181
304
|
}
|
|
182
|
-
|
|
305
|
+
const forward = cell.executionId !== context.executionId &&
|
|
306
|
+
input.action === 'poll' &&
|
|
307
|
+
input.yieldMs > 0 &&
|
|
308
|
+
context.onOutput
|
|
309
|
+
? forwardCellOutput(context.onOutput)
|
|
310
|
+
: undefined;
|
|
311
|
+
if (forward)
|
|
312
|
+
cell.outputListeners.add(forward.onOutput);
|
|
313
|
+
try {
|
|
314
|
+
return await this.#wait(cell, input, context.signal, false, context.codeMode?.supportsImages === true, input.action === 'poll' ? context.waitInterrupt : undefined);
|
|
315
|
+
}
|
|
316
|
+
finally {
|
|
317
|
+
if (forward) {
|
|
318
|
+
cell.outputListeners.delete(forward.onOutput);
|
|
319
|
+
forward.stop();
|
|
320
|
+
}
|
|
321
|
+
}
|
|
183
322
|
}
|
|
184
323
|
async #wait(cell, input, signal, initial, supportsImages, interrupt) {
|
|
185
324
|
const outcome = input.yieldMs === 0
|
|
@@ -232,6 +371,14 @@ export class CodeCells {
|
|
|
232
371
|
observations: visible.observations.map(visibleObservation)
|
|
233
372
|
}),
|
|
234
373
|
data,
|
|
374
|
+
eventData: {
|
|
375
|
+
...data,
|
|
376
|
+
...cell.display,
|
|
377
|
+
status: cell.status,
|
|
378
|
+
cached: true,
|
|
379
|
+
...wait,
|
|
380
|
+
executionSource: cell.source
|
|
381
|
+
},
|
|
235
382
|
truncated: data.truncated
|
|
236
383
|
};
|
|
237
384
|
}
|
|
@@ -247,6 +394,7 @@ export class CodeCells {
|
|
|
247
394
|
return {
|
|
248
395
|
...cell.terminal,
|
|
249
396
|
data,
|
|
397
|
+
eventData: { ...data, executionSource: cell.source },
|
|
250
398
|
content: JSON.stringify(data),
|
|
251
399
|
truncated: cell.terminal.truncated || bounded.truncated
|
|
252
400
|
};
|
|
@@ -301,6 +449,12 @@ export class CodeCells {
|
|
|
301
449
|
calls: [...cell.calls.values()],
|
|
302
450
|
observations: remapped,
|
|
303
451
|
data,
|
|
452
|
+
eventData: {
|
|
453
|
+
...data,
|
|
454
|
+
...cell.display,
|
|
455
|
+
status: cell.status,
|
|
456
|
+
executionSource: cell.source
|
|
457
|
+
},
|
|
304
458
|
truncated: cell.truncated || structured.truncated,
|
|
305
459
|
...(visibleImages.length ? { images: visibleImages } : {})
|
|
306
460
|
};
|
|
@@ -49,6 +49,9 @@ export function createCodeToolDispatch(input) {
|
|
|
49
49
|
const { eventData: _eventData, ...guestResult } = result;
|
|
50
50
|
return {
|
|
51
51
|
...guestResult,
|
|
52
|
+
...((call.name === 'exec' || call.name === 'exec_wait') && data !== undefined
|
|
53
|
+
? { eventData: { ...data } }
|
|
54
|
+
: {}),
|
|
52
55
|
content: bounded.content,
|
|
53
56
|
truncated: result.truncated === true || bounded.truncated
|
|
54
57
|
};
|
|
@@ -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;
|
|
@@ -41,6 +41,7 @@ export interface ToolExecutionContext {
|
|
|
41
41
|
readonly data: Buffer;
|
|
42
42
|
}[];
|
|
43
43
|
codeMode?: {
|
|
44
|
+
subscribeToolOutput?(listener: (output: ExecOutputSnapshot) => void): () => void;
|
|
44
45
|
retainSession?(): Promise<() => Promise<void>>;
|
|
45
46
|
onSettled?(result: CodeCellEventData): Promise<void>;
|
|
46
47
|
supportsImages?: boolean;
|
|
@@ -94,6 +95,8 @@ export interface AgentToolImageArtifact {
|
|
|
94
95
|
export type AgentToolArtifact = AgentToolImageArtifact;
|
|
95
96
|
export interface ExecOutputSnapshot {
|
|
96
97
|
processId: string;
|
|
98
|
+
/** Omitted for legacy snapshot sources; delta is a real decoded process chunk. */
|
|
99
|
+
mode?: 'delta' | 'snapshot';
|
|
97
100
|
stdout: string;
|
|
98
101
|
stderr: string;
|
|
99
102
|
truncated: boolean;
|
|
@@ -106,6 +109,13 @@ export interface ExecDisplayOutput {
|
|
|
106
109
|
stdoutDroppedBytes: number;
|
|
107
110
|
stderrDroppedBytes: number;
|
|
108
111
|
}
|
|
112
|
+
/** Bounded original input for public presentation, never added to model results. */
|
|
113
|
+
export interface ExecutionSource {
|
|
114
|
+
kind: 'SHELL' | 'CODE';
|
|
115
|
+
text: string;
|
|
116
|
+
cwd?: string;
|
|
117
|
+
truncated: boolean;
|
|
118
|
+
}
|
|
109
119
|
export interface ToolExecutionResult {
|
|
110
120
|
calls?: readonly import('../session/code-observation-contract.js').CodeToolCallRecord[];
|
|
111
121
|
content: string;
|
|
@@ -124,6 +134,7 @@ export interface ToolExecutionResult {
|
|
|
124
134
|
resultExtensions?: readonly import('./result-extension-contract.js').ToolResultExtension[];
|
|
125
135
|
}
|
|
126
136
|
export interface CodeCellEventData {
|
|
137
|
+
executionSource?: ExecutionSource;
|
|
127
138
|
permissionStop?: import('./permission-contract.js').PermissionStop;
|
|
128
139
|
calls?: readonly import('../session/code-observation-contract.js').CodeToolCallRecord[];
|
|
129
140
|
cellId: string;
|
|
@@ -132,6 +143,8 @@ export interface CodeCellEventData {
|
|
|
132
143
|
reason?: 'steer';
|
|
133
144
|
cached?: boolean;
|
|
134
145
|
truncated?: boolean;
|
|
146
|
+
/** Public output projection discarded older text; not inferred from source truncation. */
|
|
147
|
+
outputHeadTruncated?: boolean;
|
|
135
148
|
observations?: readonly import('../session/code-observation-contract.js').CodeObservation[];
|
|
136
149
|
error?: {
|
|
137
150
|
code: string;
|
|
@@ -139,6 +152,7 @@ export interface CodeCellEventData {
|
|
|
139
152
|
};
|
|
140
153
|
}
|
|
141
154
|
export interface ExecResult {
|
|
155
|
+
executionSource?: ExecutionSource;
|
|
142
156
|
status: 'running' | 'completed' | 'cancelled';
|
|
143
157
|
waitOutcome?: import('./wait.js').ToolWaitOutcome;
|
|
144
158
|
reason?: 'steer';
|
package/dist/tools/execution.js
CHANGED
|
@@ -24,6 +24,7 @@ export async function createRoundToolRuntimeContext(scope, call, signal) {
|
|
|
24
24
|
const assertSessionLive = scope.assertLive;
|
|
25
25
|
const assertExecutionLive = scope.assertExecutionLive;
|
|
26
26
|
const emitCodeEvent = scope.emit;
|
|
27
|
+
const outputListeners = new Set();
|
|
27
28
|
const retainSession = codeMode.retainSession?.bind(codeMode);
|
|
28
29
|
let retained = 0;
|
|
29
30
|
const assertDispatchLive = () => {
|
|
@@ -40,6 +41,10 @@ export async function createRoundToolRuntimeContext(scope, call, signal) {
|
|
|
40
41
|
signal,
|
|
41
42
|
codeMode: {
|
|
42
43
|
...codeMode,
|
|
44
|
+
subscribeToolOutput: (listener) => {
|
|
45
|
+
outputListeners.add(listener);
|
|
46
|
+
return () => outputListeners.delete(listener);
|
|
47
|
+
},
|
|
43
48
|
...(retainSession === undefined
|
|
44
49
|
? {}
|
|
45
50
|
: {
|
|
@@ -67,7 +72,12 @@ export async function createRoundToolRuntimeContext(scope, call, signal) {
|
|
|
67
72
|
parentCallId: call.callId,
|
|
68
73
|
schedule: scope.schedule,
|
|
69
74
|
assertLive: assertDispatchLive,
|
|
70
|
-
emit: (event) =>
|
|
75
|
+
emit: (event) => {
|
|
76
|
+
if (event.type === 'tool.output')
|
|
77
|
+
for (const listener of outputListeners)
|
|
78
|
+
listener(event.output);
|
|
79
|
+
return emitCodeEvent(event, assertDispatchLive);
|
|
80
|
+
}
|
|
71
81
|
})
|
|
72
82
|
},
|
|
73
83
|
...(scope.extensionToolNames.has(call.name) && recentImages !== undefined
|
|
@@ -118,7 +128,10 @@ export async function executeRoundTools(calls, scope) {
|
|
|
118
128
|
: typeof output.eventSummary === 'string'
|
|
119
129
|
? output.eventSummary
|
|
120
130
|
: '');
|
|
121
|
-
const structuredData = call.name === 'exec' ||
|
|
131
|
+
const structuredData = call.name === 'exec' ||
|
|
132
|
+
call.name === 'exec_wait' ||
|
|
133
|
+
call.name === 'code_exec' ||
|
|
134
|
+
call.name === 'code_exec_wait'
|
|
122
135
|
? structuredToolEventData(call.name, output.eventData ?? output.data)
|
|
123
136
|
: output.eventData === undefined
|
|
124
137
|
? structuredToolEventData(call.name, output.data)
|
|
@@ -159,7 +172,7 @@ export async function executeRoundTools(calls, scope) {
|
|
|
159
172
|
const eventData = failureData ??
|
|
160
173
|
(partial?.data === undefined
|
|
161
174
|
? undefined
|
|
162
|
-
: structuredToolEventData(call.name, partial.data));
|
|
175
|
+
: structuredToolEventData(call.name, partial.eventData ?? partial.data));
|
|
163
176
|
await scope.emit({
|
|
164
177
|
type: 'tool.failed',
|
|
165
178
|
...(failedToolResultExtensions(error)
|
package/dist/tools/local/exec.js
CHANGED
|
@@ -2,7 +2,7 @@ import { AgentError } from '../../error.js';
|
|
|
2
2
|
import { TOOL_EXECUTION_LIMITS } from '../execution-limits.js';
|
|
3
3
|
/** Share the model-facing body across direct calls and original Code Mode observations. */
|
|
4
4
|
export function formatExecResult(result) {
|
|
5
|
-
const { displayOutput, ...modelResult } = result;
|
|
5
|
+
const { displayOutput, executionSource, ...modelResult } = result;
|
|
6
6
|
const visible = { ...modelResult };
|
|
7
7
|
if (result.stderr === '')
|
|
8
8
|
delete visible.stderr;
|
|
@@ -22,8 +22,10 @@ export function formatExecResult(result) {
|
|
|
22
22
|
delete visible.cached;
|
|
23
23
|
return {
|
|
24
24
|
content: JSON.stringify(visible),
|
|
25
|
-
data: displayOutput === undefined ? result : modelResult,
|
|
26
|
-
...(displayOutput === undefined
|
|
25
|
+
data: executionSource === undefined && displayOutput === undefined ? result : modelResult,
|
|
26
|
+
...(displayOutput === undefined && executionSource === undefined
|
|
27
|
+
? {}
|
|
28
|
+
: { eventData: { ...modelResult, displayOutput, executionSource } }),
|
|
27
29
|
truncated: result.truncated
|
|
28
30
|
};
|
|
29
31
|
}
|
|
@@ -12,6 +12,8 @@ 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;
|
|
15
17
|
/** Project only known, validated tool result shapes into public structured events. */
|
|
16
18
|
export declare function structuredToolEventData(toolName: string, value: unknown): CodeCellEventData | ExecResult | ApplyPatchResult | ReadEventData | ViewImageResult | SleepResult | PublicToolMetadata | undefined;
|
|
17
19
|
/** Partial patch facts are allowlisted; arbitrary error details never become public event data. */
|
|
@@ -68,7 +68,7 @@ export function publicToolEventInput(value, fields) {
|
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
70
|
/** Explicit observations are public, but their event projection must remain bounded. */
|
|
71
|
-
function publicCodeObservations(values, data) {
|
|
71
|
+
export function publicCodeObservations(values, data) {
|
|
72
72
|
if (!Array.isArray(values))
|
|
73
73
|
return data;
|
|
74
74
|
const retained = new Set(data.observations ?? []);
|
|
@@ -76,6 +76,7 @@ function publicCodeObservations(values, data) {
|
|
|
76
76
|
let displayedCount = 0;
|
|
77
77
|
let remaining = 10_000;
|
|
78
78
|
let truncated = false;
|
|
79
|
+
let headTruncated = data.outputHeadTruncated === true;
|
|
79
80
|
const clip = (text) => {
|
|
80
81
|
let start = Math.max(0, text.length - remaining);
|
|
81
82
|
if (start > 0 && /[\uDC00-\uDFFF]/u.test(text[start]))
|
|
@@ -83,6 +84,7 @@ function publicCodeObservations(values, data) {
|
|
|
83
84
|
const result = text.slice(start);
|
|
84
85
|
remaining -= result.length;
|
|
85
86
|
truncated ||= start > 0;
|
|
87
|
+
headTruncated ||= start > 0;
|
|
86
88
|
return result;
|
|
87
89
|
};
|
|
88
90
|
for (let index = values.length - 1; index >= 0; index--) {
|
|
@@ -95,6 +97,7 @@ function publicCodeObservations(values, data) {
|
|
|
95
97
|
(entry.format === 'text' || entry.format === 'json' || entry.format === 'image')) {
|
|
96
98
|
if (displayedCount >= 32) {
|
|
97
99
|
truncated = true;
|
|
100
|
+
headTruncated = true;
|
|
98
101
|
continue;
|
|
99
102
|
}
|
|
100
103
|
displayedCount++;
|
|
@@ -122,6 +125,7 @@ function publicCodeObservations(values, data) {
|
|
|
122
125
|
(entry.outcome === 'completed' || entry.outcome === 'failed')) {
|
|
123
126
|
if (displayedCount >= 32) {
|
|
124
127
|
truncated = true;
|
|
128
|
+
headTruncated = true;
|
|
125
129
|
continue;
|
|
126
130
|
}
|
|
127
131
|
displayedCount++;
|
|
@@ -146,7 +150,12 @@ function publicCodeObservations(values, data) {
|
|
|
146
150
|
}
|
|
147
151
|
}
|
|
148
152
|
}
|
|
149
|
-
return {
|
|
153
|
+
return {
|
|
154
|
+
...data,
|
|
155
|
+
observations: selected,
|
|
156
|
+
...(truncated ? { truncated: true } : {}),
|
|
157
|
+
...(headTruncated ? { outputHeadTruncated: true } : {})
|
|
158
|
+
};
|
|
150
159
|
}
|
|
151
160
|
/** Project only known, validated tool result shapes into public structured events. */
|
|
152
161
|
export function structuredToolEventData(toolName, value) {
|
|
@@ -176,7 +185,20 @@ export function structuredToolEventData(toolName, value) {
|
|
|
176
185
|
['running', 'completed', 'failed', 'cancelled'].includes(String(value.status)))
|
|
177
186
|
return publicCodeObservations(value.observations, {
|
|
178
187
|
cellId: value.cellId,
|
|
188
|
+
...(isRecord(value.executionSource) &&
|
|
189
|
+
value.executionSource.kind === 'CODE' &&
|
|
190
|
+
typeof value.executionSource.text === 'string'
|
|
191
|
+
? {
|
|
192
|
+
executionSource: {
|
|
193
|
+
kind: 'CODE',
|
|
194
|
+
text: value.executionSource.text.slice(0, 10_000),
|
|
195
|
+
truncated: value.executionSource.truncated === true ||
|
|
196
|
+
value.executionSource.text.length > 10_000
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
: {}),
|
|
179
200
|
status: value.status,
|
|
201
|
+
...(value.outputHeadTruncated === true ? { outputHeadTruncated: true } : {}),
|
|
180
202
|
cached: value.cached === true,
|
|
181
203
|
...(['snapshot', 'completion', 'timeout', 'yield', 'interrupted'].includes(String(value.waitOutcome))
|
|
182
204
|
? { waitOutcome: value.waitOutcome }
|
|
@@ -19,7 +19,7 @@ export interface ToolImageStore extends ResourceStore {
|
|
|
19
19
|
}
|
|
20
20
|
export type ToolRuntimeEvent = {
|
|
21
21
|
type: 'tool.output';
|
|
22
|
-
toolName: 'exec' | 'exec_wait';
|
|
22
|
+
toolName: 'exec' | 'exec_wait' | 'code_exec_wait';
|
|
23
23
|
callId: string;
|
|
24
24
|
parentCallId?: string;
|
|
25
25
|
output: import('./contracts.js').ExecOutputSnapshot;
|
package/dist/tools/runtime.js
CHANGED
|
@@ -50,7 +50,8 @@ export async function executeCapturedTool(kind, call, context) {
|
|
|
50
50
|
mode: context.mode,
|
|
51
51
|
signal: context.signal,
|
|
52
52
|
waitInterrupt: context.waitInterrupt,
|
|
53
|
-
...((name === 'exec' || name === 'exec_wait'
|
|
53
|
+
...((name === 'exec' || name === 'exec_wait' || name === 'code_exec_wait') &&
|
|
54
|
+
callId !== undefined
|
|
54
55
|
? {
|
|
55
56
|
onOutput: (output) => context.emit({
|
|
56
57
|
type: 'tool.output',
|