mixdog 0.9.47 → 0.9.49
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/package.json +14 -4
- package/scripts/agent-parallel-smoke.mjs +4 -1
- package/scripts/agent-route-batch-test.mjs +40 -0
- package/scripts/code-graph-aggregate-cwd-test.mjs +154 -0
- package/scripts/code-graph-description-contract.mjs +185 -0
- package/scripts/code-graph-disk-hit-test.mjs +55 -0
- package/scripts/code-graph-dispatch-test.mjs +96 -0
- package/scripts/compact-pressure-test.mjs +40 -0
- package/scripts/deferred-tool-loading-test.mjs +233 -0
- package/scripts/execution-completion-dedup-test.mjs +48 -0
- package/scripts/explore-prompt-policy-test.mjs +88 -3
- package/scripts/live-worker-smoke.mjs +68 -16
- package/scripts/memory-core-input-test.mjs +33 -13
- package/scripts/native-edit-wire-test.mjs +152 -0
- package/scripts/openai-oauth-ws-1006-retry-test.mjs +294 -16
- package/scripts/patch-binary-cache-test.mjs +181 -0
- package/scripts/prompt-immediate-render-test.mjs +89 -0
- package/scripts/provider-toolcall-test.mjs +280 -15
- package/scripts/shell-failure-diagnostics-test.mjs +211 -0
- package/scripts/statusline-quota-hysteresis-test.mjs +26 -1
- package/scripts/streaming-tail-window-test.mjs +29 -0
- package/scripts/tool-failures.mjs +21 -3
- package/scripts/tool-smoke.mjs +263 -38
- package/scripts/tool-tui-presentation-test.mjs +17 -1
- package/scripts/tui-perf-run.ps1 +26 -0
- package/scripts/tui-transcript-perf-test.mjs +7 -1
- package/scripts/verify-release-assets-test.mjs +647 -0
- package/scripts/verify-release-assets.mjs +293 -0
- package/scripts/windows-hide-spawn-options-test.mjs +19 -0
- package/src/cli.mjs +1 -0
- package/src/runtime/agent/orchestrator/agent-trace-format.mjs +16 -5
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +35 -11
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +35 -11
- package/src/runtime/agent/orchestrator/providers/custom-tool-wire.mjs +28 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +34 -34
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +45 -38
- package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +36 -11
- package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +4 -4
- package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +10 -6
- package/src/runtime/agent/orchestrator/session/loop/deferred-call-through.mjs +4 -3
- package/src/runtime/agent/orchestrator/session/loop/recall-fasttrack.mjs +4 -3
- package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +16 -1
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +2 -2
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +0 -1
- package/src/runtime/agent/orchestrator/session/tool-batch.mjs +13 -8
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +30 -16
- package/src/runtime/agent/orchestrator/tools/code-graph/build.mjs +25 -8
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +69 -4
- package/src/runtime/agent/orchestrator/tools/code-graph-prewarm-worker.mjs +5 -4
- package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/graph-manifest.json +12 -12
- package/src/runtime/agent/orchestrator/tools/patch/native-server.mjs +12 -7
- package/src/runtime/agent/orchestrator/tools/patch-binary-fetcher.mjs +77 -18
- package/src/runtime/agent/orchestrator/tools/patch-manifest.json +11 -11
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +99 -24
- package/src/runtime/memory/lib/memory-action-handlers.mjs +21 -11
- package/src/runtime/memory/tool-defs.mjs +1 -2
- package/src/session-runtime/context-status.mjs +25 -16
- package/src/session-runtime/model-route-api.mjs +2 -0
- package/src/session-runtime/runtime-core.mjs +2 -2
- package/src/session-runtime/session-turn-api.mjs +4 -1
- package/src/session-runtime/tool-catalog.mjs +113 -19
- package/src/session-runtime/tool-defs.mjs +1 -0
- package/src/standalone/agent-tool/helpers.mjs +25 -6
- package/src/standalone/agent-tool.mjs +75 -41
- package/src/tui/App.jsx +4 -0
- package/src/tui/app/input-parsers.mjs +8 -9
- package/src/tui/components/Markdown.jsx +6 -1
- package/src/tui/components/Message.jsx +11 -2
- package/src/tui/components/PromptInput.jsx +19 -21
- package/src/tui/components/Spinner.jsx +4 -4
- package/src/tui/components/StatusLine.jsx +6 -6
- package/src/tui/components/TranscriptItem.jsx +2 -2
- package/src/tui/components/prompt-input/immediate-render.mjs +47 -0
- package/src/tui/components/tool-execution/surface-detail.mjs +14 -9
- package/src/tui/dist/index.mjs +130 -45
- package/src/tui/engine/agent-job-feed.mjs +21 -2
- package/src/tui/engine/turn.mjs +12 -1
- package/src/tui/markdown/measure-rendered-rows.mjs +1 -1
- package/src/tui/markdown/streaming-markdown.mjs +20 -0
- package/src/ui/statusline.mjs +5 -5
- package/src/vendor/statusline/src/gateway/session-routes.mjs +22 -10
|
@@ -406,6 +406,9 @@ export class ExecResult {
|
|
|
406
406
|
this.signal = opts.signal || null;
|
|
407
407
|
this.timedOut = opts.timedOut === true;
|
|
408
408
|
this.killed = opts.killed === true;
|
|
409
|
+
// Why mixdog initiated a tree kill. Keep this separate from `signal`,
|
|
410
|
+
// which is the process-reported termination signal (SIGTERM/SIGKILL).
|
|
411
|
+
this.killCause = opts.killCause || null;
|
|
409
412
|
this.stdoutPath = opts.stdoutPath || null;
|
|
410
413
|
this.stdoutFileSize = opts.stdoutFileSize || 0;
|
|
411
414
|
this.stderrPath = opts.stderrPath || null;
|
|
@@ -463,7 +466,40 @@ async function _spawnShellWithRetry({ shell, argv, spawnOptions, shellArg, cwd }
|
|
|
463
466
|
let attempt = 0;
|
|
464
467
|
for (;;) {
|
|
465
468
|
try {
|
|
466
|
-
|
|
469
|
+
const child = spawn(shell, argv, spawnOptions);
|
|
470
|
+
// spawn() reports ENOENT/EACCES on nextTick. Keep a guard listener
|
|
471
|
+
// attached from the same synchronous frame as spawn(), and do not
|
|
472
|
+
// resolve this helper until the child emits `spawn`. The caller adopts
|
|
473
|
+
// the guard atomically (new listener first, guard removal second), so
|
|
474
|
+
// there is never an unhandled-error window across either await.
|
|
475
|
+
let bufferedError = null;
|
|
476
|
+
const guardError = (err) => { bufferedError = bufferedError || err; };
|
|
477
|
+
child.on('error', guardError);
|
|
478
|
+
try {
|
|
479
|
+
await new Promise((resolveSpawn, rejectSpawn) => {
|
|
480
|
+
const onSpawn = () => {
|
|
481
|
+
child.removeListener('error', onError);
|
|
482
|
+
resolveSpawn();
|
|
483
|
+
};
|
|
484
|
+
const onError = (err) => {
|
|
485
|
+
child.removeListener('spawn', onSpawn);
|
|
486
|
+
rejectSpawn(err);
|
|
487
|
+
};
|
|
488
|
+
child.once('spawn', onSpawn);
|
|
489
|
+
child.once('error', onError);
|
|
490
|
+
});
|
|
491
|
+
} catch (err) {
|
|
492
|
+
child.removeListener('error', guardError);
|
|
493
|
+
throw err;
|
|
494
|
+
}
|
|
495
|
+
return {
|
|
496
|
+
child,
|
|
497
|
+
adoptErrorHandler(handler) {
|
|
498
|
+
child.on('error', handler);
|
|
499
|
+
child.removeListener('error', guardError);
|
|
500
|
+
if (bufferedError) handler(bufferedError);
|
|
501
|
+
},
|
|
502
|
+
};
|
|
467
503
|
} catch (err) {
|
|
468
504
|
try {
|
|
469
505
|
console.error('[shell-spawn-retry] ' + JSON.stringify({
|
|
@@ -507,6 +543,12 @@ export function execShellCommand({
|
|
|
507
543
|
const taskOutput = new TaskOutput(taskId);
|
|
508
544
|
let timedOut = false;
|
|
509
545
|
let killed = false;
|
|
546
|
+
let killCause = null;
|
|
547
|
+
let failurePhase = null;
|
|
548
|
+
let failureReason = null;
|
|
549
|
+
let spawnError = null;
|
|
550
|
+
let pendingChildError = null;
|
|
551
|
+
let settle = null;
|
|
510
552
|
let settled = false;
|
|
511
553
|
let timer = null;
|
|
512
554
|
let abortHandler = null;
|
|
@@ -535,7 +577,9 @@ export function execShellCommand({
|
|
|
535
577
|
// outside the timeout branch. Function declaration so callers placed
|
|
536
578
|
// above settle()'s const definition still resolve via hoisting; the
|
|
537
579
|
// 5 s deadline always fires after settle is constructed.
|
|
538
|
-
function _treeKillForceSettle() {
|
|
580
|
+
function _treeKillForceSettle(cause) {
|
|
581
|
+
killed = true;
|
|
582
|
+
killCause = killCause || cause || 'runtime-guard';
|
|
539
583
|
treeKill(child);
|
|
540
584
|
const _killDeadline = setTimeout(() => {
|
|
541
585
|
if (settled) return;
|
|
@@ -574,7 +618,7 @@ export function execShellCommand({
|
|
|
574
618
|
const argv = Array.isArray(shellArgs) && shellArgs.length > 0
|
|
575
619
|
? [...shellArgs, _spawnCommand]
|
|
576
620
|
: [shellArg, _spawnCommand];
|
|
577
|
-
|
|
621
|
+
const spawned = await _spawnShellWithRetry({
|
|
578
622
|
shell,
|
|
579
623
|
argv,
|
|
580
624
|
shellArg,
|
|
@@ -598,6 +642,14 @@ export function execShellCommand({
|
|
|
598
642
|
detached: process.platform !== 'win32',
|
|
599
643
|
},
|
|
600
644
|
});
|
|
645
|
+
child = spawned.child;
|
|
646
|
+
spawned.adoptErrorHandler((err) => {
|
|
647
|
+
spawnError = spawnError || err;
|
|
648
|
+
failurePhase = 'tool';
|
|
649
|
+
failureReason = 'spawn failed';
|
|
650
|
+
if (settle) settle(1, null);
|
|
651
|
+
else pendingChildError = pendingChildError || err;
|
|
652
|
+
});
|
|
601
653
|
startChildGuardian({
|
|
602
654
|
childPid: child.pid,
|
|
603
655
|
childGroupPid: child.pid,
|
|
@@ -624,8 +676,7 @@ export function execShellCommand({
|
|
|
624
676
|
// before spawn returned (synchronous reentry from a parent abort), so
|
|
625
677
|
// the child doesn't run for the full timeoutMs window.
|
|
626
678
|
if (abortSignal && abortSignal.aborted) {
|
|
627
|
-
|
|
628
|
-
_treeKillForceSettle();
|
|
679
|
+
_treeKillForceSettle('cancellation');
|
|
629
680
|
}
|
|
630
681
|
|
|
631
682
|
child.stdout.setEncoding('utf-8');
|
|
@@ -640,13 +691,12 @@ export function execShellCommand({
|
|
|
640
691
|
// by a successful exit code on a truncated capture.
|
|
641
692
|
const _abortOnCaptureError = () => {
|
|
642
693
|
if (taskOutput.writeError && !killed && !settled && !autoBackgrounded) {
|
|
643
|
-
|
|
644
|
-
_treeKillForceSettle();
|
|
694
|
+
_treeKillForceSettle('output-capture-error');
|
|
645
695
|
}
|
|
646
696
|
};
|
|
647
697
|
|
|
648
698
|
let sizeWatchdog = null;
|
|
649
|
-
|
|
699
|
+
settle = async (exitCode, signal) => {
|
|
650
700
|
if (settled || autoBackgrounded) return;
|
|
651
701
|
settled = true;
|
|
652
702
|
if (timer) {
|
|
@@ -678,6 +728,7 @@ export function execShellCommand({
|
|
|
678
728
|
catch (err) { taskOutput.writeError = taskOutput.writeError || err; }
|
|
679
729
|
try { stderr = await taskOutput.getStderr(); }
|
|
680
730
|
catch (err) { taskOutput.writeError = taskOutput.writeError || err; }
|
|
731
|
+
if (spawnError && !stderr) stderr = String(spawnError.message || spawnError);
|
|
681
732
|
// Inline-only path: nothing spilled. Nothing to clean up.
|
|
682
733
|
// Spilled but tiny: drop the files — outputFilePath would duplicate
|
|
683
734
|
// the inline body. Spilled and large: keep the files, caller renders
|
|
@@ -698,6 +749,7 @@ export function execShellCommand({
|
|
|
698
749
|
signal,
|
|
699
750
|
timedOut,
|
|
700
751
|
killed,
|
|
752
|
+
killCause,
|
|
701
753
|
stdoutPath: taskOutput.spilled ? taskOutput.stdoutPath : null,
|
|
702
754
|
stdoutFileSize: taskOutput.stdoutFileSize,
|
|
703
755
|
stderrPath: taskOutput.spilled ? taskOutput.stderrPath : null,
|
|
@@ -705,6 +757,8 @@ export function execShellCommand({
|
|
|
705
757
|
taskId,
|
|
706
758
|
partialOutput,
|
|
707
759
|
outputCaptureError: taskOutput.writeError,
|
|
760
|
+
failurePhase,
|
|
761
|
+
failureReason,
|
|
708
762
|
}),
|
|
709
763
|
);
|
|
710
764
|
};
|
|
@@ -715,7 +769,7 @@ export function execShellCommand({
|
|
|
715
769
|
// after stdio is fully drained, so getStdout()/getStderr() see the
|
|
716
770
|
// complete capture.
|
|
717
771
|
child.once('close', (code, signal) => settle(code, signal));
|
|
718
|
-
|
|
772
|
+
if (pendingChildError) settle(1, null);
|
|
719
773
|
// 'close' only fires after stdio drains; a forked grandchild that
|
|
720
774
|
// inherited stdout/stderr fds can hold them open past direct-child
|
|
721
775
|
// exit and stall settle() until timeoutMs. 'exit' fires on direct
|
|
@@ -801,8 +855,12 @@ export function execShellCommand({
|
|
|
801
855
|
// is honored by the kill path too.)
|
|
802
856
|
if (!job) {
|
|
803
857
|
autoBackgrounded = false;
|
|
804
|
-
if (reason === 'timeout')
|
|
805
|
-
|
|
858
|
+
if (reason === 'timeout') {
|
|
859
|
+
timedOut = true;
|
|
860
|
+
_treeKillForceSettle('timeout');
|
|
861
|
+
} else {
|
|
862
|
+
_treeKillForceSettle('background-adoption-failed');
|
|
863
|
+
}
|
|
806
864
|
return;
|
|
807
865
|
}
|
|
808
866
|
// Wire the lifecycle: on close, write the exit-code file FIRST then
|
|
@@ -815,14 +873,6 @@ export function execShellCommand({
|
|
|
815
873
|
try { writeFileSync(job.donePath, ''); } catch {}
|
|
816
874
|
});
|
|
817
875
|
}
|
|
818
|
-
// Adoption committed. If a cancel already fired (before or during the
|
|
819
|
-
// adoption window), bring the now-adopted child down via its jobId so the
|
|
820
|
-
// promotion can't outlive a user abort. Idempotent with the still-attached
|
|
821
|
-
// abort handler's treeKill.
|
|
822
|
-
if (abortSignal && abortSignal.aborted) {
|
|
823
|
-
try { killShellJob(job.jobId); } catch {}
|
|
824
|
-
try { treeKill(child); } catch {}
|
|
825
|
-
}
|
|
826
876
|
// Snapshot the partial output captured so far for the immediate result.
|
|
827
877
|
let stdout = '';
|
|
828
878
|
let stderr = '';
|
|
@@ -831,6 +881,33 @@ export function execShellCommand({
|
|
|
831
881
|
try { stderr = await taskOutput.getStderr(); }
|
|
832
882
|
catch (err) { taskOutput.writeError = taskOutput.writeError || err; }
|
|
833
883
|
const jobId = job ? job.jobId : null;
|
|
884
|
+
// Re-check after the awaited capture reads: cancellation can race after
|
|
885
|
+
// adoption commits. Never report that cancelled process as a successful
|
|
886
|
+
// still-running background task.
|
|
887
|
+
if (abortSignal && abortSignal.aborted) {
|
|
888
|
+
killed = true;
|
|
889
|
+
killCause = 'cancellation';
|
|
890
|
+
try { killShellJob(jobId); } catch {}
|
|
891
|
+
try { treeKill(child); } catch {}
|
|
892
|
+
resolve(new ExecResult({
|
|
893
|
+
stdout,
|
|
894
|
+
stderr,
|
|
895
|
+
exitCode: null,
|
|
896
|
+
signal: child.signalCode || null,
|
|
897
|
+
timedOut: false,
|
|
898
|
+
killed: true,
|
|
899
|
+
killCause,
|
|
900
|
+
stdoutPath,
|
|
901
|
+
stdoutFileSize: taskOutput.stdoutFileSize,
|
|
902
|
+
stderrPath: taskOutput.spilled ? taskOutput.stderrPath : null,
|
|
903
|
+
stderrFileSize: taskOutput.stderrFileSize,
|
|
904
|
+
taskId,
|
|
905
|
+
partialOutput: true,
|
|
906
|
+
outputCaptureError: taskOutput.writeError,
|
|
907
|
+
backgrounded: false,
|
|
908
|
+
}));
|
|
909
|
+
return;
|
|
910
|
+
}
|
|
834
911
|
const secs = Math.max(0, Math.round((Date.now() - _startMs) / 1000));
|
|
835
912
|
const _verb = reason === 'timeout'
|
|
836
913
|
? `moved to background at timeout after ${secs}s`
|
|
@@ -880,7 +957,7 @@ export function execShellCommand({
|
|
|
880
957
|
return;
|
|
881
958
|
}
|
|
882
959
|
timedOut = true;
|
|
883
|
-
_treeKillForceSettle();
|
|
960
|
+
_treeKillForceSettle('timeout');
|
|
884
961
|
}, timeoutMs);
|
|
885
962
|
if (timer.unref) timer.unref();
|
|
886
963
|
}
|
|
@@ -921,16 +998,14 @@ export function execShellCommand({
|
|
|
921
998
|
if (settled || autoBackgrounded) return;
|
|
922
999
|
_abortOnCaptureError();
|
|
923
1000
|
if (taskOutput.totalDiskBytes() > SHELL_OUTPUT_DISK_CAP) {
|
|
924
|
-
|
|
925
|
-
_treeKillForceSettle();
|
|
1001
|
+
_treeKillForceSettle('output-limit');
|
|
926
1002
|
}
|
|
927
1003
|
}, SIZE_WATCHDOG_INTERVAL_MS);
|
|
928
1004
|
if (sizeWatchdog.unref) sizeWatchdog.unref();
|
|
929
1005
|
|
|
930
1006
|
if (abortSignal) {
|
|
931
1007
|
abortHandler = () => {
|
|
932
|
-
|
|
933
|
-
_treeKillForceSettle();
|
|
1008
|
+
_treeKillForceSettle('cancellation');
|
|
934
1009
|
};
|
|
935
1010
|
try {
|
|
936
1011
|
abortSignal.addEventListener('abort', abortHandler, { once: true });
|
|
@@ -622,7 +622,7 @@ export function createMemoryActionHandlers({
|
|
|
622
622
|
// project=<pool> lets the UI thread project_id into the follow-up
|
|
623
623
|
// promote/dismiss call — matters under project_id:'*' listing where
|
|
624
624
|
// rows span pools. Uses the same COMMON/slug convention as op:'list'.
|
|
625
|
-
`id=${c.id} project=${c.project_id == null ? 'COMMON' : c.project_id}
|
|
625
|
+
`id=${c.id} project=${c.project_id == null ? 'COMMON' : c.project_id} score=${c.score == null ? '-' : c.score.toFixed(2)} ${c.element} — ${String(c.summary || '').slice(0, 200)} (${c.reason})`,
|
|
626
626
|
).join('\n'),
|
|
627
627
|
}
|
|
628
628
|
}
|
|
@@ -634,11 +634,11 @@ export function createMemoryActionHandlers({
|
|
|
634
634
|
if (op === 'promote') {
|
|
635
635
|
const entry = await promoteCoreCandidate(coreDataDir, args.id, { ...args, scope })
|
|
636
636
|
const mergeNote = entry.merged_with ? ` (merged into core id=${entry.merged_with}, sim=${entry.sim})` : ''
|
|
637
|
-
return { text: `core promoted candidate id=${args.id} → core id=${entry.id}${mergeNote}:
|
|
637
|
+
return { text: `core promoted candidate id=${args.id} → core id=${entry.id}${mergeNote}: ${entry.element}` }
|
|
638
638
|
}
|
|
639
639
|
// dismiss
|
|
640
640
|
const removed = await dismissCoreCandidate(coreDataDir, args.id, { scope })
|
|
641
|
-
return { text: `core candidate dismissed (id=${removed.id}):
|
|
641
|
+
return { text: `core candidate dismissed (id=${removed.id}): ${removed.element}` }
|
|
642
642
|
} catch (e) {
|
|
643
643
|
return { text: `core ${op} failed: ${e.message}`, isError: true }
|
|
644
644
|
}
|
|
@@ -656,10 +656,15 @@ export function createMemoryActionHandlers({
|
|
|
656
656
|
})()
|
|
657
657
|
try {
|
|
658
658
|
if (op === 'add' || op === 'edit') {
|
|
659
|
-
|
|
659
|
+
// Category is intentionally absent from the public memory schema.
|
|
660
|
+
// New direct entries use normalizeCoreInput's internal compatibility
|
|
661
|
+
// default; edits omit the field so editCore preserves the stored value.
|
|
662
|
+
const categoryFreeArgs = { ...args }
|
|
663
|
+
delete categoryFreeArgs.category
|
|
664
|
+
const normalized = normalizeCoreInput(categoryFreeArgs, {
|
|
660
665
|
requireElement: true,
|
|
661
666
|
requireSummary: true,
|
|
662
|
-
requireCategory:
|
|
667
|
+
requireCategory: false,
|
|
663
668
|
})
|
|
664
669
|
const errors = [...normalized.errors]
|
|
665
670
|
if (op === 'add') {
|
|
@@ -680,7 +685,12 @@ export function createMemoryActionHandlers({
|
|
|
680
685
|
if (errors.length) {
|
|
681
686
|
return { text: `core ${op}: ${errors.join('; ')}`, isError: true }
|
|
682
687
|
}
|
|
683
|
-
args = {
|
|
688
|
+
args = {
|
|
689
|
+
...categoryFreeArgs,
|
|
690
|
+
element: normalized.element,
|
|
691
|
+
summary: normalized.summary,
|
|
692
|
+
...(op === 'add' ? { category: normalized.category } : {}),
|
|
693
|
+
}
|
|
684
694
|
}
|
|
685
695
|
if (projectId === '*' && op !== 'list') {
|
|
686
696
|
return { text: `core ${op}: project_id "*" only valid for op="list"`, isError: true }
|
|
@@ -689,7 +699,7 @@ export function createMemoryActionHandlers({
|
|
|
689
699
|
if (projectId !== '*') {
|
|
690
700
|
const entries = await listCore(coreDataDir, projectId)
|
|
691
701
|
if (entries.length === 0) return { text: 'core: empty' }
|
|
692
|
-
return { text: entries.map(e => `id=${e.id}
|
|
702
|
+
return { text: entries.map(e => `id=${e.id} ${e.element} — ${String(e.summary || '').slice(0, 200)}`).join('\n') }
|
|
693
703
|
}
|
|
694
704
|
// Cross-pool listing — group by project_id, COMMON first
|
|
695
705
|
const entries = await listCore(coreDataDir, '*')
|
|
@@ -704,22 +714,22 @@ export function createMemoryActionHandlers({
|
|
|
704
714
|
for (const [key, rows] of groups) {
|
|
705
715
|
lines.push(`${key === null ? 'COMMON' : key}:`)
|
|
706
716
|
for (const e of rows) {
|
|
707
|
-
lines.push(` id=${e.id}
|
|
717
|
+
lines.push(` id=${e.id} ${e.element} — ${String(e.summary || '').slice(0, 200)}`)
|
|
708
718
|
}
|
|
709
719
|
}
|
|
710
720
|
return { text: lines.join('\n') }
|
|
711
721
|
}
|
|
712
722
|
if (op === 'add') {
|
|
713
723
|
const entry = await addCore(coreDataDir, args, projectId)
|
|
714
|
-
return { text: `core added (id=${entry.id}):
|
|
724
|
+
return { text: `core added (id=${entry.id}): ${entry.element} — ${entry.summary.slice(0, 200)}` }
|
|
715
725
|
}
|
|
716
726
|
if (op === 'edit') {
|
|
717
727
|
const entry = await editCoreImpl(coreDataDir, args.id, args)
|
|
718
|
-
return { text: `core edited (id=${entry.id}):
|
|
728
|
+
return { text: `core edited (id=${entry.id}): ${entry.element} — ${entry.summary.slice(0, 200)}` }
|
|
719
729
|
}
|
|
720
730
|
if (op === 'delete') {
|
|
721
731
|
const removed = await deleteCore(coreDataDir, args.id)
|
|
722
|
-
return { text: `core deleted (id=${removed.id}):
|
|
732
|
+
return { text: `core deleted (id=${removed.id}): ${removed.element}` }
|
|
723
733
|
}
|
|
724
734
|
} catch (e) {
|
|
725
735
|
return { text: `core ${op} failed: ${e.message}`, isError: true }
|
|
@@ -8,7 +8,7 @@ export const TOOL_DEFS = [
|
|
|
8
8
|
name: 'memory',
|
|
9
9
|
title: 'Memory Cycle',
|
|
10
10
|
annotations: { title: 'Memory Cycle', readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
|
|
11
|
-
description: 'Core-memory mutation and status; use recall for retrieval. Store durable rules/preferences/facts, one compact ENGLISH clause each — never transient task state. add requires project_id+
|
|
11
|
+
description: 'Core-memory mutation and status; use recall for retrieval. Store durable rules/preferences/facts, one compact ENGLISH clause each — never transient task state. add requires project_id+summary; edit works by id alone.',
|
|
12
12
|
inputSchema: {
|
|
13
13
|
type: 'object',
|
|
14
14
|
properties: {
|
|
@@ -17,7 +17,6 @@ export const TOOL_DEFS = [
|
|
|
17
17
|
id: { type: 'number', description: 'Exact memory id.' },
|
|
18
18
|
element: { type: 'string', maxLength: 40, description: 'Memory key/title. Defaults to the first 40 chars of summary. Max 40 chars.' },
|
|
19
19
|
summary: { type: 'string', maxLength: 100, description: 'Memory content: one short English clause, max 100 chars.' },
|
|
20
|
-
category: { type: 'string', enum: ['rule','constraint','decision','fact','goal','preference','task','issue'], description: 'Category.' },
|
|
21
20
|
status: { type: 'string', enum: ['pending','active','archived'], description: 'Lifecycle status.' },
|
|
22
21
|
limit: { type: 'number', description: 'Max rows/items.' },
|
|
23
22
|
confirm: { type: 'string', description: 'Exact confirmation phrase for destructive actions.' },
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
estimateRequestReserveTokens,
|
|
3
3
|
estimateToolSchemaTokens,
|
|
4
|
-
estimateTranscriptContextUsage,
|
|
5
4
|
contextMessagesRevision,
|
|
6
5
|
resolveSessionCompactPolicy,
|
|
7
6
|
summarizeContextMessages,
|
|
8
7
|
toolSchemaSignature,
|
|
9
8
|
} from '../runtime/agent/orchestrator/session/context-utils.mjs';
|
|
10
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
resolveCompactionPressureTokens,
|
|
11
|
+
resolveWorkerCompactPolicy,
|
|
12
|
+
} from '../runtime/agent/orchestrator/session/loop/compact-policy.mjs';
|
|
11
13
|
import { estimateToolSchemaBreakdown } from './tool-catalog.mjs';
|
|
12
14
|
|
|
13
15
|
const DEFERRED_CATALOG_TOOL_PROVIDERS = new Set(['anthropic', 'anthropic-oauth']);
|
|
@@ -84,6 +86,15 @@ export function createContextStatus({ getSession, getRoute, getCurrentCwd, getMo
|
|
|
84
86
|
lastOutputTokens: Number(session?.lastOutputTokens || 0),
|
|
85
87
|
lastCachedReadTokens: Number(session?.lastCachedReadTokens || 0),
|
|
86
88
|
lastCacheWriteTokens: Number(session?.lastCacheWriteTokens || 0),
|
|
89
|
+
contextPressureBaselineTokens: Number(session?.contextPressureBaselineTokens || 0),
|
|
90
|
+
contextPressureBaselineOutputTokens: Number(session?.contextPressureBaselineOutputTokens || 0),
|
|
91
|
+
contextPressureBaselineMessageCount: Number(session?.contextPressureBaselineMessageCount ?? -1),
|
|
92
|
+
contextPressureBaselineUpdatedAt: Number(session?.contextPressureBaselineUpdatedAt || 0),
|
|
93
|
+
contextPressureBaselineBoundary: session?.contextPressureBaselineBoundary || null,
|
|
94
|
+
contextPressureBaselineProvider: session?.contextPressureBaselineProvider || null,
|
|
95
|
+
contextPressureBaselineModel: session?.contextPressureBaselineModel || null,
|
|
96
|
+
contextPressureBaselineToolSignature: session?.contextPressureBaselineToolSignature || null,
|
|
97
|
+
contextPressureBaselinePrefixSignature: session?.contextPressureBaselinePrefixSignature || null,
|
|
87
98
|
totalInputTokens: Number(session?.totalInputTokens || 0),
|
|
88
99
|
totalUncachedInputTokens: Number(session?.totalUncachedInputTokens || 0),
|
|
89
100
|
totalOutputTokens: Number(session?.totalOutputTokens || 0),
|
|
@@ -148,10 +159,6 @@ export function createContextStatus({ getSession, getRoute, getCurrentCwd, getMo
|
|
|
148
159
|
const rawWindow = Number(session?.rawContextWindow || session?.contextWindow || 0);
|
|
149
160
|
const effectiveWindow = Number(session?.contextWindow || rawWindow || 0);
|
|
150
161
|
const lastContextTokens = Number(session?.lastContextTokens || 0);
|
|
151
|
-
const estimatedContextTokens = estimateTranscriptContextUsage(messages, requestTools, {
|
|
152
|
-
messageCount: messageSummary.count,
|
|
153
|
-
estimatedMessageTokens: messageSummary.estimatedTokens,
|
|
154
|
-
});
|
|
155
162
|
const compactAt = Number(session?.compaction?.lastChangedAt || session?.compaction?.lastCompactAt || 0);
|
|
156
163
|
const usageAt = Number(session?.lastContextTokensUpdatedAt || 0);
|
|
157
164
|
const lastUsageStale = !!lastContextTokens && (
|
|
@@ -161,14 +168,6 @@ export function createContextStatus({ getSession, getRoute, getCurrentCwd, getMo
|
|
|
161
168
|
);
|
|
162
169
|
const compactBoundaryTokens = Number(session?.compactBoundaryTokens || session?.compaction?.boundaryTokens || 0);
|
|
163
170
|
const displayWindow = compactBoundaryTokens || effectiveWindow;
|
|
164
|
-
// The transcript estimate is the single source of truth for the displayed
|
|
165
|
-
// context footprint. Provider-reported input_tokens (lastContextTokens)
|
|
166
|
-
// swing non-monotonically and are not window-bounded on some providers
|
|
167
|
-
// (e.g. OpenAI gpt-5.5 Responses API), so they are kept only as secondary
|
|
168
|
-
// metadata (lastApiRequestTokens / usage.lastContextTokens) and never feed
|
|
169
|
-
// the gauge numerator.
|
|
170
|
-
const usedTokens = estimatedContextTokens;
|
|
171
|
-
const freeTokens = displayWindow ? Math.max(0, displayWindow - usedTokens) : 0;
|
|
172
171
|
// Use the worker policy when a boundary is available so target/reserve
|
|
173
172
|
// headroom, trigger, buffer tokens, and buffer ratio stay identical to the
|
|
174
173
|
// auto-compact decision. Fall back only for incomplete session metadata.
|
|
@@ -176,6 +175,16 @@ export function createContextStatus({ getSession, getRoute, getCurrentCwd, getMo
|
|
|
176
175
|
const compactPolicy = workerCompactPolicy?.boundaryTokens
|
|
177
176
|
? workerCompactPolicy
|
|
178
177
|
: resolveSessionCompactPolicy(session || {}, compactBoundaryTokens);
|
|
178
|
+
// Match the pre-provider-send auto-compact check exactly: the gauge uses
|
|
179
|
+
// the same provider-baseline-or-estimate pressure, including request and
|
|
180
|
+
// configured reserves, rather than a separate transcript-only estimate.
|
|
181
|
+
const compactionPressureTokens = resolveCompactionPressureTokens(
|
|
182
|
+
messageSummary.estimatedTokens,
|
|
183
|
+
compactPolicy,
|
|
184
|
+
{ messages, sessionRef: session },
|
|
185
|
+
);
|
|
186
|
+
const usedTokens = compactionPressureTokens;
|
|
187
|
+
const freeTokens = displayWindow ? Math.max(0, displayWindow - usedTokens) : 0;
|
|
179
188
|
const compactTriggerTokens = compactPolicy.triggerTokens || 0;
|
|
180
189
|
const compactBufferTokens = compactPolicy.bufferTokens || 0;
|
|
181
190
|
const compactBufferRatio = Number.isFinite(compactPolicy.bufferRatio)
|
|
@@ -193,7 +202,7 @@ export function createContextStatus({ getSession, getRoute, getCurrentCwd, getMo
|
|
|
193
202
|
effectiveContextWindowPercent: session?.effectiveContextWindowPercent || null,
|
|
194
203
|
usedTokens,
|
|
195
204
|
usedSource: 'estimated',
|
|
196
|
-
currentEstimatedTokens:
|
|
205
|
+
currentEstimatedTokens: compactionPressureTokens,
|
|
197
206
|
lastApiRequestTokens: lastContextTokens || 0,
|
|
198
207
|
lastApiRequestStale: lastUsageStale,
|
|
199
208
|
freeTokens,
|
|
@@ -203,7 +212,7 @@ export function createContextStatus({ getSession, getRoute, getCurrentCwd, getMo
|
|
|
203
212
|
triggerTokens: compactTriggerTokens || null,
|
|
204
213
|
bufferTokens: compactBufferTokens || null,
|
|
205
214
|
bufferRatio: compactBufferRatio,
|
|
206
|
-
currentEstimatedTokens:
|
|
215
|
+
currentEstimatedTokens: compactionPressureTokens,
|
|
207
216
|
lastApiRequestTokens: lastContextTokens || 0,
|
|
208
217
|
lastApiRequestStale: lastUsageStale,
|
|
209
218
|
},
|
|
@@ -21,6 +21,7 @@ import { SUMMARY_PREFIX } from '../runtime/agent/orchestrator/session/compact.mj
|
|
|
21
21
|
import {
|
|
22
22
|
hasUserConversationMessage,
|
|
23
23
|
} from '../runtime/agent/orchestrator/session/manager/prompt-utils.mjs';
|
|
24
|
+
import { rebuildDeferredToolSurfaceForProvider } from './tool-catalog.mjs';
|
|
24
25
|
|
|
25
26
|
function isSummaryAnchorMessage(message) {
|
|
26
27
|
return message?.role === 'user'
|
|
@@ -179,6 +180,7 @@ export function createModelRouteApi(deps) {
|
|
|
179
180
|
}
|
|
180
181
|
if (session) {
|
|
181
182
|
const route = getRoute();
|
|
183
|
+
rebuildDeferredToolSurfaceForProvider(session, route.provider);
|
|
182
184
|
const updated = mgr.updateSessionRoute?.(session.id, {
|
|
183
185
|
provider: route.provider,
|
|
184
186
|
model: route.model,
|
|
@@ -303,8 +303,8 @@ const {
|
|
|
303
303
|
agentRouteFromConfig,
|
|
304
304
|
} = createWorkflowRouteHelpers({ resolveDefaultProvider, findPreset });
|
|
305
305
|
|
|
306
|
-
export function __renderToolSearchForTest(args = {}, session = {}, mode = 'full') {
|
|
307
|
-
return renderToolSearch(args, session, mode);
|
|
306
|
+
export function __renderToolSearchForTest(args = {}, session = {}, mode = 'full', options = {}) {
|
|
307
|
+
return renderToolSearch(args, session, mode, options);
|
|
308
308
|
}
|
|
309
309
|
|
|
310
310
|
export function __saveModelSettingsForTest(cfgMod, route, options = {}) {
|
|
@@ -320,7 +320,10 @@ export function createSessionTurnApi(deps) {
|
|
|
320
320
|
const catalog = Array.isArray(surface?.deferredToolCatalog)
|
|
321
321
|
? surface.deferredToolCatalog
|
|
322
322
|
: (Array.isArray(surface?.tools) ? surface.tools : []);
|
|
323
|
-
const activeNames = new Set(
|
|
323
|
+
const activeNames = new Set([
|
|
324
|
+
...(surface?.tools || []).map((tool) => tool?.name).filter(Boolean),
|
|
325
|
+
...(surface?.deferredCallableTools || []),
|
|
326
|
+
]);
|
|
324
327
|
const needle = clean(query).toLowerCase();
|
|
325
328
|
const rows = catalog.map((tool) => toolRow(tool, activeNames)).filter((row) => row.name);
|
|
326
329
|
const counts = splitToolStatusCounts(rows);
|