@myagentroam/agent 0.9.142 → 0.9.144
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/agent-core/model/anthropic-messages.js +24 -13
- package/dist/agent-core/model/contracts.d.ts +4 -0
- package/dist/agent-core/model/openai-responses.js +16 -9
- package/dist/agent-core/runtime/async-queue.js +7 -5
- package/dist/agent-core/runtime/context/gc-tokens.js +4 -2
- package/dist/agent-core/runtime/context/gc.js +147 -14
- package/dist/agent-core/runtime/context/projection.js +32 -1
- package/dist/agent-core/runtime/events.d.ts +1 -0
- package/dist/agent-core/runtime/execution.js +4 -0
- package/dist/agent-core/sdk/session.js +1 -1
- package/dist/agent-core/session/context-gc-checkpoint.d.ts +4 -1
- package/dist/agent-core/session/context-gc-checkpoint.js +123 -26
- package/dist/agent-core/session/context-gc-observations.d.ts +38 -0
- package/dist/agent-core/session/context-gc-observations.js +148 -0
- package/dist/agent-core/session/context-gc-replacements.d.ts +1 -1
- package/dist/agent-core/session/context-gc-replacements.js +6 -4
- package/dist/agent-core/tools/callers.js +1 -1
- package/dist/agent-core/tools/code-mode/cells.d.ts +3 -0
- package/dist/agent-core/tools/code-mode/cells.js +62 -6
- package/dist/agent-core/tools/code-mode/dispatch.js +7 -4
- package/dist/agent-core/tools/code-mode/engine.d.ts +12 -0
- package/dist/agent-core/tools/code-mode/index.js +183 -14
- package/dist/agent-core/tools/code-mode/observations.d.ts +103 -0
- package/dist/agent-core/tools/code-mode/observations.js +195 -0
- package/dist/agent-core/tools/code-mode/quickjs-engine.js +121 -20
- package/dist/agent-core/tools/code-mode/worker.js +8 -1
- package/dist/agent-core/tools/contracts.d.ts +4 -0
- package/dist/agent-core/tools/execution.d.ts +2 -0
- package/dist/agent-core/tools/execution.js +10 -1
- package/dist/agent-core/tools/output-projection.js +35 -1
- package/package.json +1 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
2
|
import { posix, win32 } from 'node:path';
|
|
3
3
|
import { isDeepStrictEqual } from 'node:util';
|
|
4
|
+
import { codeCallEvidence, callsFromRecord, codeObservationEvidence, observationsFromRecord, retainedCodeInputs } from './context-gc-observations.js';
|
|
4
5
|
import { parseReadContextOutput } from '../tools/local/read-context.js';
|
|
5
6
|
import { codeCellCompletionSequence, contextGcApplyPatchInputReplacement, contextGcCodeModeInputReplacement, contextGcCodeModeOutputReplacement, contextGcExecInputReplacement, contextGcExecOutputReplacement, contextGcPatchResultFromCompletedPayload, contextGcStaleReadReplacement, contextGcWebOutputReplacement, normalizeContextGcCodeModeInput, normalizeContextGcExecResult } from './context-gc-replacements.js';
|
|
6
7
|
import { contextGcSubagentNeedsRetainedPriorFinal, contextGcSubagentOutputReplacement, contextGcSubagentSnapshotIsPreciselyCovered, contextGcSubagentSnapshotIsSuperseded, normalizeContextGcSubagentSnapshot } from './context-gc-subagent.js';
|
|
@@ -57,7 +58,7 @@ export function contextGcLogicalPathKey(path, workspace, platform) {
|
|
|
57
58
|
return resolved;
|
|
58
59
|
}
|
|
59
60
|
export function contextGcTargetKey(target) {
|
|
60
|
-
return `${target.sequence}:${target.recordId}:${target.role}:${target.callId ?? ''}:${target.toolName ?? ''}`;
|
|
61
|
+
return `${target.sequence}:${target.recordId}:${target.role}:${target.callId ?? ''}:${target.toolName ?? ''}${target.observationId ? `:${target.observationId}` : ''}`;
|
|
61
62
|
}
|
|
62
63
|
export function contextGcReplacementKey(replacement) {
|
|
63
64
|
return contextGcTargetKey(replacement.target);
|
|
@@ -154,6 +155,8 @@ export function validateContextGcCheckpoint(records, record, workspace = session
|
|
|
154
155
|
if (contextGcSourceProjectionHash(records, payload.boundarySequence, payload.base) !==
|
|
155
156
|
payload.sourceProjectionHash)
|
|
156
157
|
return undefined;
|
|
158
|
+
// Later cell completion cannot retroactively authorize an earlier checkpoint.
|
|
159
|
+
records = records.filter((item) => item.sequence <= payload.boundarySequence);
|
|
157
160
|
const recordsBySequence = new Map(records.map((item) => [item.sequence, item]));
|
|
158
161
|
if (!triggerMatchesRecords(records, recordsBySequence, payload))
|
|
159
162
|
return undefined;
|
|
@@ -225,13 +228,39 @@ function targetMatchesRecord(target, record) {
|
|
|
225
228
|
const messageCallId = modelContextCallId(record.payload);
|
|
226
229
|
const messageToolName = modelContextToolName(record.payload);
|
|
227
230
|
return ((target.callId === undefined || target.callId === messageCallId) &&
|
|
231
|
+
(target.observationId === undefined ||
|
|
232
|
+
observationsFromRecord(record)?.some((value) => value.id === target.observationId) ===
|
|
233
|
+
true) &&
|
|
228
234
|
(target.toolName === undefined || target.toolName === messageToolName));
|
|
229
235
|
}
|
|
230
236
|
/** A checkpoint draft is valid only when its replacements match the source records and evidence. */
|
|
231
237
|
function replacementMatchesRecords(replacement, records, recordsBySequence, workspace, platform) {
|
|
232
|
-
|
|
238
|
+
let source = recordsBySequence.get(replacement.target.sequence);
|
|
233
239
|
if (!source || !isRecord(source.payload))
|
|
234
240
|
return false;
|
|
241
|
+
if (replacement.target.observationId) {
|
|
242
|
+
const selected = observationsFromRecord(source)?.find((value) => value.id === replacement.target.observationId);
|
|
243
|
+
if (!selected)
|
|
244
|
+
return false;
|
|
245
|
+
if (selected.type === 'tool_result') {
|
|
246
|
+
const evidence = codeObservationEvidence(records, source, selected);
|
|
247
|
+
if (!evidence)
|
|
248
|
+
return false;
|
|
249
|
+
source = {
|
|
250
|
+
...source,
|
|
251
|
+
payload: {
|
|
252
|
+
...source.payload,
|
|
253
|
+
callId: selected.source.callId,
|
|
254
|
+
content: selected.result.content
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
else
|
|
259
|
+
source = { ...source, payload: { ...source.payload, content: selected.value } };
|
|
260
|
+
recordsBySequence = new Map(recordsBySequence).set(source.sequence, source);
|
|
261
|
+
}
|
|
262
|
+
if (!isRecord(source.payload))
|
|
263
|
+
return false;
|
|
235
264
|
if (replacement.kind === 'READ_OUTPUT_ITEMS') {
|
|
236
265
|
if (!workspace || toolNameForCallId(records, replacement.callId, source.sequence) !== 'read')
|
|
237
266
|
return false;
|
|
@@ -243,7 +272,7 @@ function replacementMatchesRecords(replacement, records, recordsBySequence, work
|
|
|
243
272
|
const originalItems = new Map(parsed.items.map((item) => [item.index, item]));
|
|
244
273
|
return replacement.items.every((item) => {
|
|
245
274
|
const original = originalItems.get(item.index);
|
|
246
|
-
const evidence = successfulPatchEvidenceForPath(records, recordsBySequence, item.evidenceSequence, item.path, workspace, platform);
|
|
275
|
+
const evidence = successfulPatchEvidenceForPath(records, recordsBySequence, item.evidenceSequence, item.path, workspace, platform, item.supersededByCallId);
|
|
247
276
|
return (original?.pathTruncated === false &&
|
|
248
277
|
original?.path === item.path &&
|
|
249
278
|
evidence !== undefined &&
|
|
@@ -266,18 +295,19 @@ function replacementMatchesRecords(replacement, records, recordsBySequence, work
|
|
|
266
295
|
result !== undefined &&
|
|
267
296
|
isDeepStrictEqual(replacement.replacementArguments, contextGcApplyPatchInputReplacement(source.payload.arguments, result, replacement.evidenceSequence, applyPatchReplacementFormatVersion(replacement.replacementArguments))));
|
|
268
297
|
}
|
|
269
|
-
if (replacement.kind === 'TOOL_CALL_INPUT' &&
|
|
270
|
-
(replacement.toolName === 'code_mode' || replacement.toolName === 'code_exec')) {
|
|
298
|
+
if (replacement.kind === 'TOOL_CALL_INPUT' && replacement.toolName === 'code_exec') {
|
|
271
299
|
const evidence = successfulToolEvidence(records, recordsBySequence, replacement.evidenceSequence, replacement.toolName);
|
|
272
300
|
const input = normalizeContextGcCodeModeInput(source.payload.arguments);
|
|
273
301
|
return (evidence?.callId === replacement.callId &&
|
|
274
302
|
codeCellCompletionSequence(records, evidence.completed) !== undefined &&
|
|
275
303
|
evidence.call.sequence === replacement.target.sequence &&
|
|
276
304
|
input !== undefined &&
|
|
277
|
-
isDeepStrictEqual(replacement.replacementArguments, contextGcCodeModeInputReplacement(input)
|
|
305
|
+
isDeepStrictEqual(replacement.replacementArguments, contextGcCodeModeInputReplacement(input, observationsFromRecord(evidence.result)
|
|
306
|
+
? retainedCodeInputs(records, replacement.callId)
|
|
307
|
+
: [])));
|
|
278
308
|
}
|
|
279
309
|
if (replacement.kind === 'CODE_MODE_OUTPUT') {
|
|
280
|
-
const evidence = ['
|
|
310
|
+
const evidence = ['code_exec', 'code_exec_wait']
|
|
281
311
|
.map((name) => successfulToolEvidence(records, recordsBySequence, replacement.evidenceSequence, name))
|
|
282
312
|
.find((item) => item !== undefined);
|
|
283
313
|
return (evidence?.callId === replacement.callId &&
|
|
@@ -288,12 +318,16 @@ function replacementMatchesRecords(replacement, records, recordsBySequence, work
|
|
|
288
318
|
isRecord(evidence.completed.payload) &&
|
|
289
319
|
typeof evidence.completed.payload.truncated === 'boolean' &&
|
|
290
320
|
replacement.replacementContent ===
|
|
291
|
-
contextGcCodeModeOutputReplacement(
|
|
321
|
+
contextGcCodeModeOutputReplacement(source.payload.content, replacement.target.observationId
|
|
322
|
+
? observationsFromRecord(source)?.some((value) => value.id === replacement.target.observationId &&
|
|
323
|
+
value.type === 'value' &&
|
|
324
|
+
value.truncated === true) === true
|
|
325
|
+
: evidence.completed.payload.truncated));
|
|
292
326
|
}
|
|
293
327
|
if (replacement.kind === 'SUBAGENT_OUTPUT') {
|
|
294
328
|
const retainedPriorFinal = replacement.retainedPriorFinal;
|
|
295
329
|
const sourceEvidence = successfulToolEvidence(records, recordsBySequence, source.sequence, replacement.toolName);
|
|
296
|
-
const terminalEvidence = successfulToolEvidence(records, recordsBySequence, replacement.evidenceSequence, replacement.terminalToolName);
|
|
330
|
+
const terminalEvidence = successfulToolEvidence(records, recordsBySequence, replacement.evidenceSequence, replacement.terminalToolName, replacement.terminalCallId);
|
|
297
331
|
if (!sourceEvidence ||
|
|
298
332
|
sourceEvidence.callId !== replacement.callId ||
|
|
299
333
|
!terminalEvidence ||
|
|
@@ -304,7 +338,7 @@ function replacementMatchesRecords(replacement, records, recordsBySequence, work
|
|
|
304
338
|
let retainedPriorFinalEvidence;
|
|
305
339
|
let retainedPriorFinalSnapshot;
|
|
306
340
|
if (retainedPriorFinal !== undefined) {
|
|
307
|
-
retainedPriorFinalEvidence = successfulToolEvidence(records, recordsBySequence, retainedPriorFinal.evidenceSequence, retainedPriorFinal.toolName);
|
|
341
|
+
retainedPriorFinalEvidence = successfulToolEvidence(records, recordsBySequence, retainedPriorFinal.evidenceSequence, retainedPriorFinal.toolName, retainedPriorFinal.callId);
|
|
308
342
|
if (!retainedPriorFinalEvidence ||
|
|
309
343
|
retainedPriorFinalEvidence.callId !== retainedPriorFinal.callId ||
|
|
310
344
|
!isRecord(retainedPriorFinalEvidence.result.payload))
|
|
@@ -319,7 +353,7 @@ function replacementMatchesRecords(replacement, records, recordsBySequence, work
|
|
|
319
353
|
(retainedPriorFinal !== undefined) &&
|
|
320
354
|
(retainedPriorFinalEvidence === undefined ||
|
|
321
355
|
retainedPriorFinalEvidence.result.sequence < sourceEvidence.result.sequence) &&
|
|
322
|
-
sourceEvidence.
|
|
356
|
+
sourceEvidence.completed.sequence < terminalEvidence.completed.sequence &&
|
|
323
357
|
(contextGcSubagentSnapshotIsSuperseded(sourceSnapshot, terminalSnapshot, retainedPriorFinalSnapshot) ||
|
|
324
358
|
contextGcSubagentSnapshotIsPreciselyCovered(sourceSnapshot, terminalSnapshot)) &&
|
|
325
359
|
replacement.replacementContent ===
|
|
@@ -351,8 +385,8 @@ function replacementMatchesRecords(replacement, records, recordsBySequence, work
|
|
|
351
385
|
return false;
|
|
352
386
|
if (replacement.kind === 'TOOL_CALL_INPUT')
|
|
353
387
|
return isDeepStrictEqual(replacement.replacementArguments, contextGcExecInputReplacement(source.payload.arguments, execInputReplacementFormatVersion(replacement.replacementArguments), replacement.toolName));
|
|
354
|
-
const targetEvidence = successfulToolEvidence(records, recordsBySequence, source.sequence, 'exec') ??
|
|
355
|
-
successfulToolEvidence(records, recordsBySequence, source.sequence, 'exec_wait');
|
|
388
|
+
const targetEvidence = successfulToolEvidence(records, recordsBySequence, source.sequence, 'exec', replacement.callId) ??
|
|
389
|
+
successfulToolEvidence(records, recordsBySequence, source.sequence, 'exec_wait', replacement.callId);
|
|
356
390
|
const result = normalizeContextGcExecResult(source.payload.content, targetEvidence?.completed.payload);
|
|
357
391
|
return (result !== undefined &&
|
|
358
392
|
replacement.replacementContent ===
|
|
@@ -401,7 +435,7 @@ function toolNameForCallId(records, callId, beforeSequence) {
|
|
|
401
435
|
? call.payload.name
|
|
402
436
|
: undefined;
|
|
403
437
|
}
|
|
404
|
-
function successfulToolEvidence(records, recordsBySequence, evidenceSequence, toolName) {
|
|
438
|
+
function successfulToolEvidence(records, recordsBySequence, evidenceSequence, toolName, expectedCallId) {
|
|
405
439
|
const result = recordsBySequence.get(evidenceSequence);
|
|
406
440
|
if (result?.type !== 'model.context' ||
|
|
407
441
|
!isRecord(result.payload) ||
|
|
@@ -410,8 +444,28 @@ function successfulToolEvidence(records, recordsBySequence, evidenceSequence, to
|
|
|
410
444
|
return undefined;
|
|
411
445
|
const callId = result.payload.callId;
|
|
412
446
|
const call = modelToolCallForCallId(records, callId, result.sequence);
|
|
413
|
-
if (!call ||
|
|
447
|
+
if (!call ||
|
|
448
|
+
!isRecord(call.payload) ||
|
|
449
|
+
call.payload.name !== toolName ||
|
|
450
|
+
(expectedCallId && expectedCallId !== callId)) {
|
|
451
|
+
for (const observation of observationsFromRecord(result) ?? []) {
|
|
452
|
+
if (observation.type !== 'tool_result' ||
|
|
453
|
+
observation.source.toolName !== toolName ||
|
|
454
|
+
(expectedCallId && observation.source.callId !== expectedCallId))
|
|
455
|
+
continue;
|
|
456
|
+
const evidence = codeObservationEvidence(records, result, observation);
|
|
457
|
+
if (evidence)
|
|
458
|
+
return evidence;
|
|
459
|
+
}
|
|
460
|
+
for (const entry of callsFromRecord(result) ?? []) {
|
|
461
|
+
if (entry.toolName !== toolName || (expectedCallId && entry.callId !== expectedCallId))
|
|
462
|
+
continue;
|
|
463
|
+
const evidence = codeCallEvidence(records, result, entry);
|
|
464
|
+
if (evidence)
|
|
465
|
+
return evidence;
|
|
466
|
+
}
|
|
414
467
|
return undefined;
|
|
468
|
+
}
|
|
415
469
|
const completed = records.findLast((record) => record.sequence > call.sequence &&
|
|
416
470
|
record.sequence < result.sequence &&
|
|
417
471
|
record.type === 'tool.completed' &&
|
|
@@ -441,11 +495,29 @@ function hasFailedToolEvidence(records, call, result, toolName) {
|
|
|
441
495
|
record.payload.toolName === toolName);
|
|
442
496
|
}
|
|
443
497
|
function modelToolCallForCallId(records, callId, beforeSequence) {
|
|
444
|
-
|
|
498
|
+
const direct = records.findLast((record) => record.sequence < beforeSequence &&
|
|
445
499
|
record.type === 'model.context' &&
|
|
446
500
|
isRecord(record.payload) &&
|
|
447
501
|
record.payload.role === 'tool_call' &&
|
|
448
502
|
record.payload.callId === callId);
|
|
503
|
+
if (direct)
|
|
504
|
+
return direct;
|
|
505
|
+
const started = records.findLast((record) => record.sequence < beforeSequence &&
|
|
506
|
+
record.type === 'tool.started' &&
|
|
507
|
+
isRecord(record.payload) &&
|
|
508
|
+
record.payload.callId === callId &&
|
|
509
|
+
typeof record.payload.parentCallId === 'string');
|
|
510
|
+
return started && isRecord(started.payload)
|
|
511
|
+
? {
|
|
512
|
+
...started,
|
|
513
|
+
payload: {
|
|
514
|
+
role: 'tool_call',
|
|
515
|
+
name: started.payload.toolName,
|
|
516
|
+
callId,
|
|
517
|
+
arguments: started.payload.input
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
: undefined;
|
|
449
521
|
}
|
|
450
522
|
function modelToolResultForCall(records, call, beforeSequence) {
|
|
451
523
|
if (!isRecord(call.payload) || typeof call.payload.callId !== 'string')
|
|
@@ -458,8 +530,8 @@ function modelToolResultForCall(records, call, beforeSequence) {
|
|
|
458
530
|
record.payload.role === 'tool' &&
|
|
459
531
|
record.payload.callId === callId);
|
|
460
532
|
}
|
|
461
|
-
function successfulPatchEvidenceForPath(records, recordsBySequence, evidenceSequence, path, workspace, platform) {
|
|
462
|
-
const evidence = successfulToolEvidence(records, recordsBySequence, evidenceSequence, 'apply_patch');
|
|
533
|
+
function successfulPatchEvidenceForPath(records, recordsBySequence, evidenceSequence, path, workspace, platform, expectedCallId) {
|
|
534
|
+
const evidence = successfulToolEvidence(records, recordsBySequence, evidenceSequence, 'apply_patch', expectedCallId);
|
|
463
535
|
if (!evidence || !isRecord(evidence.completed.payload))
|
|
464
536
|
return undefined;
|
|
465
537
|
const result = contextGcPatchResultFromCompletedPayload(evidence.completed.payload);
|
|
@@ -475,8 +547,9 @@ function successfulPatchEvidenceForPath(records, recordsBySequence, evidenceSequ
|
|
|
475
547
|
: undefined;
|
|
476
548
|
}
|
|
477
549
|
function isSuccessfulExecLifecycleEvidence(replacement, source, records, recordsBySequence) {
|
|
478
|
-
const
|
|
479
|
-
|
|
550
|
+
const expectedTerminal = replacement.kind === 'EXEC_OUTPUT' ? replacement.terminalCallId : undefined;
|
|
551
|
+
const evidence = successfulToolEvidence(records, recordsBySequence, replacement.evidenceSequence, 'exec', expectedTerminal) ??
|
|
552
|
+
successfulToolEvidence(records, recordsBySequence, replacement.evidenceSequence, 'exec_wait', expectedTerminal);
|
|
480
553
|
if (!evidence || !isRecord(evidence.call.payload) || !isRecord(evidence.result.payload))
|
|
481
554
|
return false;
|
|
482
555
|
const finalResult = normalizeContextGcExecResult(evidence.result.payload.content, evidence.completed.payload);
|
|
@@ -497,7 +570,7 @@ function isSuccessfulExecLifecycleEvidence(replacement, source, records, records
|
|
|
497
570
|
: modelToolResultForCall(records, targetCall, evidence.result.sequence);
|
|
498
571
|
if (!targetResult)
|
|
499
572
|
return false;
|
|
500
|
-
const targetEvidence = successfulToolEvidence(records, recordsBySequence, targetResult.sequence, targetCall.payload.name);
|
|
573
|
+
const targetEvidence = successfulToolEvidence(records, recordsBySequence, targetResult.sequence, targetCall.payload.name, replacement.callId);
|
|
501
574
|
if (!targetEvidence || targetEvidence.call.sequence !== targetCall.sequence)
|
|
502
575
|
return false;
|
|
503
576
|
if (targetEvidence.callId === evidence.callId)
|
|
@@ -597,7 +670,7 @@ function isContextGcReplacement(value) {
|
|
|
597
670
|
if (!isRecord(value) ||
|
|
598
671
|
!isContextGcTarget(value.target) ||
|
|
599
672
|
!isNonemptyString(value.callId) ||
|
|
600
|
-
value.target.callId !== value.callId)
|
|
673
|
+
(value.target.callId !== value.callId && value.target.observationId === undefined))
|
|
601
674
|
return false;
|
|
602
675
|
if (value.kind === 'READ_OUTPUT_ITEMS') {
|
|
603
676
|
const items = value.items;
|
|
@@ -607,7 +680,14 @@ function isContextGcReplacement(value) {
|
|
|
607
680
|
Array.isArray(items) &&
|
|
608
681
|
items.length > 0 &&
|
|
609
682
|
items.every((item) => isRecord(item) &&
|
|
610
|
-
hasOnlyKeys(item, [
|
|
683
|
+
hasOnlyKeys(item, [
|
|
684
|
+
'index',
|
|
685
|
+
'path',
|
|
686
|
+
'replacement',
|
|
687
|
+
'evidenceSequence',
|
|
688
|
+
'supersededByCallId'
|
|
689
|
+
]) &&
|
|
690
|
+
(item.supersededByCallId === undefined || isNonemptyString(item.supersededByCallId)) &&
|
|
611
691
|
isNonnegativeInteger(item.index) &&
|
|
612
692
|
isNonemptyString(item.path) &&
|
|
613
693
|
isNonemptyString(item.replacement) &&
|
|
@@ -634,7 +714,6 @@ function isContextGcReplacement(value) {
|
|
|
634
714
|
(value.toolName === 'apply_patch' ||
|
|
635
715
|
value.toolName === 'exec' ||
|
|
636
716
|
value.toolName === 'exec_wait' ||
|
|
637
|
-
value.toolName === 'code_mode' ||
|
|
638
717
|
value.toolName === 'code_exec') &&
|
|
639
718
|
value.target.toolName === value.toolName &&
|
|
640
719
|
Array.isArray(value.providerAliasTargets) &&
|
|
@@ -697,14 +776,32 @@ function isContextGcReplacement(value) {
|
|
|
697
776
|
isNonemptyString(value.replacementContent) &&
|
|
698
777
|
isPositiveInteger(value.evidenceSequence));
|
|
699
778
|
return (value.kind === 'EXEC_OUTPUT' &&
|
|
700
|
-
hasOnlyKeys(value, [
|
|
779
|
+
hasOnlyKeys(value, [
|
|
780
|
+
'kind',
|
|
781
|
+
'callId',
|
|
782
|
+
'target',
|
|
783
|
+
'replacementContent',
|
|
784
|
+
'evidenceSequence',
|
|
785
|
+
'terminalCallId'
|
|
786
|
+
]) &&
|
|
787
|
+
(value.terminalCallId === undefined || isNonemptyString(value.terminalCallId)) &&
|
|
701
788
|
value.target.role === 'tool' &&
|
|
702
789
|
isNonemptyString(value.replacementContent) &&
|
|
703
790
|
isPositiveInteger(value.evidenceSequence));
|
|
704
791
|
}
|
|
705
792
|
function isContextGcTarget(value) {
|
|
706
793
|
return (isRecord(value) &&
|
|
707
|
-
hasOnlyKeys(value, [
|
|
794
|
+
hasOnlyKeys(value, [
|
|
795
|
+
'sequence',
|
|
796
|
+
'recordId',
|
|
797
|
+
'role',
|
|
798
|
+
'callId',
|
|
799
|
+
'toolName',
|
|
800
|
+
'sourceHash',
|
|
801
|
+
'observationId'
|
|
802
|
+
]) &&
|
|
803
|
+
(value.observationId === undefined ||
|
|
804
|
+
(value.role === 'tool' && isNonemptyString(value.observationId))) &&
|
|
708
805
|
isPositiveInteger(value.sequence) &&
|
|
709
806
|
isNonemptyString(value.recordId) &&
|
|
710
807
|
(value.role === 'tool_call' || value.role === 'tool' || value.role === 'provider') &&
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { SessionRecord } from './jsonl-store.js';
|
|
2
|
+
import { type CodeObservation, type CodeToolCallRecord } from '../tools/code-mode/observations.js';
|
|
3
|
+
/** Resolve private observation identity to real dispatch facts, not Provider tool calls. */
|
|
4
|
+
export declare function codeObservationEvidence(records: readonly SessionRecord[], result: SessionRecord, observation: Extract<CodeObservation, {
|
|
5
|
+
type: 'tool_result';
|
|
6
|
+
}>): {
|
|
7
|
+
callId: string;
|
|
8
|
+
call: SessionRecord;
|
|
9
|
+
completed: SessionRecord;
|
|
10
|
+
result: SessionRecord;
|
|
11
|
+
} | undefined;
|
|
12
|
+
export declare function observationsFromRecord(record: SessionRecord): CodeObservation[] | undefined;
|
|
13
|
+
export declare function codeCallEvidence(records: readonly SessionRecord[], result: SessionRecord, entry: CodeToolCallRecord): {
|
|
14
|
+
result: {
|
|
15
|
+
payload: {
|
|
16
|
+
role: string;
|
|
17
|
+
callId: string;
|
|
18
|
+
content: string;
|
|
19
|
+
};
|
|
20
|
+
schemaVersion: 1;
|
|
21
|
+
recordId: string;
|
|
22
|
+
sequence: number;
|
|
23
|
+
timestamp: string;
|
|
24
|
+
sessionId: string;
|
|
25
|
+
executionId?: string;
|
|
26
|
+
turnId?: string;
|
|
27
|
+
type: string;
|
|
28
|
+
};
|
|
29
|
+
callId: string;
|
|
30
|
+
call: SessionRecord;
|
|
31
|
+
completed: SessionRecord;
|
|
32
|
+
} | undefined;
|
|
33
|
+
export declare function callsFromRecord(record: SessionRecord): CodeToolCallRecord[] | undefined;
|
|
34
|
+
export declare function retainedCodeInputs(records: readonly SessionRecord[], parentCallId: string): {
|
|
35
|
+
callId: unknown;
|
|
36
|
+
toolName: unknown;
|
|
37
|
+
input: unknown;
|
|
38
|
+
}[];
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { isDeepStrictEqual } from 'node:util';
|
|
2
|
+
import { codeCellCompletionSequence, contextGcPatchResultFromCompletedPayload } from './context-gc-replacements.js';
|
|
3
|
+
import { parseCodeCalls, parseObservations } from '../tools/code-mode/observations.js';
|
|
4
|
+
/** Resolve private observation identity to real dispatch facts, not Provider tool calls. */
|
|
5
|
+
export function codeObservationEvidence(records, result, observation) {
|
|
6
|
+
if (observation.outcome !== 'completed')
|
|
7
|
+
return undefined;
|
|
8
|
+
const started = records.findLast((record) => record.sequence < result.sequence &&
|
|
9
|
+
record.type === 'tool.started' &&
|
|
10
|
+
isRecord(record.payload) &&
|
|
11
|
+
record.payload.callId === observation.source.callId &&
|
|
12
|
+
record.payload.toolName === observation.source.toolName &&
|
|
13
|
+
typeof record.payload.parentCallId === 'string' &&
|
|
14
|
+
isDeepStrictEqual(record.payload.input, observation.source.input));
|
|
15
|
+
if (!started || !isRecord(started.payload))
|
|
16
|
+
return undefined;
|
|
17
|
+
const parentId = started.payload.parentCallId;
|
|
18
|
+
const parent = records.find((record) => record.type === 'model.context' &&
|
|
19
|
+
record.sequence < started.sequence &&
|
|
20
|
+
isRecord(record.payload) &&
|
|
21
|
+
record.payload.role === 'tool_call' &&
|
|
22
|
+
record.payload.callId === parentId &&
|
|
23
|
+
record.payload.name === 'code_exec');
|
|
24
|
+
if (!parent)
|
|
25
|
+
return undefined;
|
|
26
|
+
const original = records.find((record) => record.recordId === result.recordId) ?? result;
|
|
27
|
+
if (!isRecord(original.payload))
|
|
28
|
+
return undefined;
|
|
29
|
+
const outerId = original.payload.callId;
|
|
30
|
+
const outer = records.findLast((record) => record.sequence < result.sequence &&
|
|
31
|
+
record.type === 'tool.completed' &&
|
|
32
|
+
isRecord(record.payload) &&
|
|
33
|
+
record.payload.callId === outerId &&
|
|
34
|
+
(record.payload.toolName === 'code_exec' || record.payload.toolName === 'code_exec_wait'));
|
|
35
|
+
if (!outer ||
|
|
36
|
+
codeCellCompletionSequence(records, outer) === undefined ||
|
|
37
|
+
records.some((record) => record.sequence < result.sequence &&
|
|
38
|
+
record.type === 'tool.failed' &&
|
|
39
|
+
isRecord(record.payload) &&
|
|
40
|
+
record.payload.callId === outerId))
|
|
41
|
+
return undefined;
|
|
42
|
+
if (original.payload.callId !== parentId) {
|
|
43
|
+
const initial = records.find((record) => record.sequence < outer.sequence &&
|
|
44
|
+
record.type === 'tool.completed' &&
|
|
45
|
+
isRecord(record.payload) &&
|
|
46
|
+
record.payload.callId === parentId);
|
|
47
|
+
if (!initial ||
|
|
48
|
+
!isRecord(outer.payload) ||
|
|
49
|
+
outer.payload.toolName !== 'code_exec_wait' ||
|
|
50
|
+
!isRecord(initial.payload) ||
|
|
51
|
+
!isRecord(outer.payload.data) ||
|
|
52
|
+
!isRecord(initial.payload.data) ||
|
|
53
|
+
typeof outer.payload.data.cellId !== 'string' ||
|
|
54
|
+
outer.payload.data.cellId !== initial.payload.data.cellId)
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
const completed = records.findLast((record) => record.sequence > started.sequence &&
|
|
58
|
+
record.sequence < result.sequence &&
|
|
59
|
+
record.type === 'tool.completed' &&
|
|
60
|
+
isRecord(record.payload) &&
|
|
61
|
+
record.payload.callId === observation.source.callId &&
|
|
62
|
+
record.payload.toolName === observation.source.toolName &&
|
|
63
|
+
record.payload.parentCallId === parentId);
|
|
64
|
+
if (!completed ||
|
|
65
|
+
records.some((record) => record.sequence > started.sequence &&
|
|
66
|
+
record.sequence < result.sequence &&
|
|
67
|
+
record.type === 'tool.failed' &&
|
|
68
|
+
isRecord(record.payload) &&
|
|
69
|
+
record.payload.callId === observation.source.callId))
|
|
70
|
+
return undefined;
|
|
71
|
+
const call = {
|
|
72
|
+
...started,
|
|
73
|
+
payload: {
|
|
74
|
+
role: 'tool_call',
|
|
75
|
+
name: observation.source.toolName,
|
|
76
|
+
callId: observation.source.callId,
|
|
77
|
+
arguments: observation.source.input
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
const selected = {
|
|
81
|
+
...result,
|
|
82
|
+
payload: {
|
|
83
|
+
role: 'tool',
|
|
84
|
+
callId: observation.source.callId,
|
|
85
|
+
content: observation.result.content,
|
|
86
|
+
observationId: observation.id
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
return { callId: observation.source.callId, call, completed, result: selected };
|
|
90
|
+
}
|
|
91
|
+
export function observationsFromRecord(record) {
|
|
92
|
+
return isRecord(record.payload) ? parseObservations(record.payload.observations) : undefined;
|
|
93
|
+
}
|
|
94
|
+
export function codeCallEvidence(records, result, entry) {
|
|
95
|
+
if (entry.status !== 'completed')
|
|
96
|
+
return undefined;
|
|
97
|
+
const evidence = codeObservationEvidence(records, result, {
|
|
98
|
+
type: 'tool_result',
|
|
99
|
+
id: `call:${entry.callId}`,
|
|
100
|
+
source: { callId: entry.callId, toolName: entry.toolName, input: entry.input },
|
|
101
|
+
outcome: 'completed',
|
|
102
|
+
result: { content: '' }
|
|
103
|
+
});
|
|
104
|
+
if (!evidence || !isRecord(evidence.completed.payload))
|
|
105
|
+
return undefined;
|
|
106
|
+
return {
|
|
107
|
+
...evidence,
|
|
108
|
+
result: {
|
|
109
|
+
...evidence.result,
|
|
110
|
+
payload: {
|
|
111
|
+
role: 'tool',
|
|
112
|
+
callId: entry.callId,
|
|
113
|
+
content: JSON.stringify(evidence.completed.payload.data ?? {})
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
export function callsFromRecord(record) {
|
|
119
|
+
return isRecord(record.payload) ? parseCodeCalls(record.payload.calls) : undefined;
|
|
120
|
+
}
|
|
121
|
+
export function retainedCodeInputs(records, parentCallId) {
|
|
122
|
+
return records.flatMap((record) => {
|
|
123
|
+
if (!isRecord(record.payload) ||
|
|
124
|
+
record.type !== 'tool.started' ||
|
|
125
|
+
record.payload.parentCallId !== parentCallId)
|
|
126
|
+
return [];
|
|
127
|
+
const call = record.payload;
|
|
128
|
+
const completedPatch = call.toolName === 'apply_patch' &&
|
|
129
|
+
records.some((next) => next.sequence > record.sequence &&
|
|
130
|
+
next.type === 'tool.completed' &&
|
|
131
|
+
isRecord(next.payload) &&
|
|
132
|
+
next.payload.callId === call.callId &&
|
|
133
|
+
next.payload.toolName === 'apply_patch' &&
|
|
134
|
+
next.payload.parentCallId === parentCallId &&
|
|
135
|
+
contextGcPatchResultFromCompletedPayload(next.payload) !== undefined) &&
|
|
136
|
+
!records.some((next) => next.sequence > record.sequence &&
|
|
137
|
+
next.type === 'tool.failed' &&
|
|
138
|
+
isRecord(next.payload) &&
|
|
139
|
+
next.payload.callId === call.callId);
|
|
140
|
+
// Successful patches have durable file evidence; other inputs retain their direct-mode semantics.
|
|
141
|
+
return completedPatch
|
|
142
|
+
? []
|
|
143
|
+
: [{ callId: call.callId, toolName: call.toolName, input: call.input }];
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
function isRecord(value) {
|
|
147
|
+
return !!value && typeof value === 'object' && !Array.isArray(value);
|
|
148
|
+
}
|
|
@@ -4,7 +4,7 @@ export interface ContextGcCodeModeInput {
|
|
|
4
4
|
readonly inputMode: 'freeform' | 'json';
|
|
5
5
|
}
|
|
6
6
|
export declare function normalizeContextGcCodeModeInput(arguments_: unknown): ContextGcCodeModeInput | undefined;
|
|
7
|
-
export declare function contextGcCodeModeInputReplacement(input: ContextGcCodeModeInput): unknown;
|
|
7
|
+
export declare function contextGcCodeModeInputReplacement(input: ContextGcCodeModeInput, retainedInputs?: readonly unknown[]): unknown;
|
|
8
8
|
export declare function contextGcCodeModeOutputReplacement(originalContent: string, truncated: boolean): string;
|
|
9
9
|
export interface ContextGcPatchFile {
|
|
10
10
|
readonly path: string;
|
|
@@ -14,9 +14,13 @@ export function normalizeContextGcCodeModeInput(arguments_) {
|
|
|
14
14
|
return { code: arguments_.code, inputMode: 'json' };
|
|
15
15
|
return undefined;
|
|
16
16
|
}
|
|
17
|
-
export function contextGcCodeModeInputReplacement(input) {
|
|
17
|
+
export function contextGcCodeModeInputReplacement(input, retainedInputs = []) {
|
|
18
18
|
const replacement = `<context_gc kind="completed_code_mode_input" originalBytes="${Buffer.byteLength(input.code)}" lines="${lineCount(input.code)}" inputMode="${input.inputMode}">Completed Code Mode source was reclaimed after six successful rollouts. The paired output, images, completion evidence, and nested tool side effects remain in history.</context_gc>`;
|
|
19
|
-
|
|
19
|
+
const content = replacement +
|
|
20
|
+
(retainedInputs.length
|
|
21
|
+
? `\nRetained tool instructions: ${JSON.stringify(retainedInputs)}`
|
|
22
|
+
: '');
|
|
23
|
+
return input.inputMode === 'freeform' ? content : { code: content };
|
|
20
24
|
}
|
|
21
25
|
export function contextGcCodeModeOutputReplacement(originalContent, truncated) {
|
|
22
26
|
const preview = unicodeHeadTail(originalContent, CODE_MODE_OUTPUT_PREVIEW_CHARACTERS);
|
|
@@ -320,8 +324,6 @@ function isNonnegativeInteger(value) {
|
|
|
320
324
|
export function codeCellCompletionSequence(records, completed) {
|
|
321
325
|
if (!isRecord(completed.payload))
|
|
322
326
|
return undefined;
|
|
323
|
-
if (completed.payload.toolName === 'code_mode')
|
|
324
|
-
return completed.sequence;
|
|
325
327
|
const data = completed.payload.data;
|
|
326
328
|
if (!isRecord(data) || typeof data.cellId !== 'string')
|
|
327
329
|
return undefined;
|
|
@@ -13,7 +13,7 @@ export function toolCallers(tool) {
|
|
|
13
13
|
export function allowsToolCaller(tool, caller) {
|
|
14
14
|
// Control tools cannot be widened by a Host override or historical definition.
|
|
15
15
|
if (caller === 'programmatic' &&
|
|
16
|
-
['code_exec', 'code_exec_wait', '
|
|
16
|
+
['code_exec', 'code_exec_wait', 'tool_search', 'question', 'skill'].includes(tool.name))
|
|
17
17
|
return false;
|
|
18
18
|
return toolCallers(tool).includes(caller);
|
|
19
19
|
}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { MarAgentError } from '../../error.js';
|
|
2
2
|
import type { ToolExecutionContext, ToolExecutionResult } from '../contracts.js';
|
|
3
|
+
import { type CodeObservation } from './observations.js';
|
|
3
4
|
export interface CellOutput {
|
|
5
|
+
call(value: import('./observations.js').CodeToolCallRecord): void;
|
|
6
|
+
observe(value: CodeObservation): void;
|
|
4
7
|
text(value: string, originalBytes?: number): void;
|
|
5
8
|
image(value: NonNullable<ToolExecutionResult['images']>[number]): void;
|
|
6
9
|
yield(): void;
|