@iowarp/clio-coder 0.3.2 → 0.3.4
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/CHANGELOG.md +269 -458
- package/CONTRIBUTING.md +1 -1
- package/README.md +3 -3
- package/dist/{acp-BIYHVZIM.js → acp-S5R4RR5B.js} +7 -6
- package/dist/{agents-YT6SSRIT.js → agents-P6DMMVZY.js} +24 -21
- package/dist/assets/codewiki.json +1 -1
- package/dist/{auth-5TWEIYDN.js → auth-2XCZLPKS.js} +12 -8
- package/dist/{chunk-GGXXDWE4.js → chunk-22NAGB7X.js} +2 -2
- package/dist/{chunk-WMSVI4G2.js → chunk-2LZI5CAG.js} +133 -13
- package/dist/{chunk-OAO4GE4M.js → chunk-2TZWSW76.js} +2 -2
- package/dist/{chunk-OOJYHWRB.js → chunk-34475P3I.js} +2 -2
- package/dist/{chunk-WVO7V2QY.js → chunk-35MKKU5R.js} +4 -4
- package/dist/{chunk-LBNRH5WM.js → chunk-3HZ5RWN2.js} +5 -5
- package/dist/{chunk-AGYYIBLL.js → chunk-3JLKSKD7.js} +2 -2
- package/dist/{chunk-MBS4V7ZP.js → chunk-4JUF2NNX.js} +7 -7
- package/dist/{chunk-ZDOOVTXZ.js → chunk-4OC57DA6.js} +27 -4
- package/dist/chunk-5M54SPOL.js +926 -0
- package/dist/{chunk-STBPMHSX.js → chunk-7RXG6QRZ.js} +51 -11
- package/dist/{chunk-77VKQEHF.js → chunk-A2GZF7DC.js} +5 -5
- package/dist/{chunk-A3CYT5EX.js → chunk-AD2SYQYC.js} +55 -2
- package/dist/chunk-AOCYTWAV.js +449 -0
- package/dist/chunk-BEY543CS.js +258 -0
- package/dist/{chunk-6N5PTWMY.js → chunk-BP4OYD6A.js} +32 -13
- package/dist/chunk-BPGS2WCQ.js +612 -0
- package/dist/{chunk-J5HN4RYU.js → chunk-BRXQQJFP.js} +8 -8
- package/dist/chunk-CFGTUFWB.js +67 -0
- package/dist/{chunk-CBCAPZAA.js → chunk-E25LMLRW.js} +2 -2
- package/dist/{chunk-G2DE3C7R.js → chunk-EDRHSCIE.js} +4 -4
- package/dist/{chunk-4KLWL3UC.js → chunk-EFADSJET.js} +2 -2
- package/dist/{chunk-M6SHUN7Q.js → chunk-FO5ZOVUY.js} +2 -2
- package/dist/chunk-FYYLNIL5.js +313 -0
- package/dist/{chunk-EPVUXGXG.js → chunk-HV5X7OR2.js} +14 -12
- package/dist/{chunk-TZTZS7QK.js → chunk-HXG4IURW.js} +5 -3
- package/dist/{chunk-IGLFWIYI.js → chunk-K6WL7QZT.js} +3 -3
- package/dist/chunk-K7VKOLQQ.js +15 -0
- package/dist/{chunk-BMEMKKIT.js → chunk-KOHPCX4K.js} +2 -2
- package/dist/{chunk-V4RXGQ5Q.js → chunk-KRPY7NTG.js} +10 -7
- package/dist/chunk-LL4KHSZI.js +22 -0
- package/dist/{chunk-KJ5LWLOE.js → chunk-MEQ45TQ4.js} +15 -9
- package/dist/{chunk-5UUP6MWO.js → chunk-MV3K5QF2.js} +5 -436
- package/dist/{chunk-AO4RKG4M.js → chunk-N4CZJQRK.js} +5 -5
- package/dist/{chunk-ARBGF5F7.js → chunk-NILBFAPG.js} +14 -8
- package/dist/chunk-OZNBF4L3.js +23 -0
- package/dist/{verify-G6V4D2G7.js → chunk-PCZJO5TI.js} +127 -42
- package/dist/chunk-QQK64KLB.js +1360 -0
- package/dist/{chunk-6EJV5X2W.js → chunk-QQL5RT5M.js} +979 -1619
- package/dist/{chunk-LZSJBIVT.js → chunk-QWU7ZBO7.js} +70 -720
- package/dist/{chunk-2EHAIA3X.js → chunk-RD5U66HV.js} +3 -3
- package/dist/{chunk-OKGUZO2U.js → chunk-SPULKLCF.js} +4 -3
- package/dist/{chunk-OQ33BKR3.js → chunk-TTNYS3EA.js} +3 -60
- package/dist/chunk-TW3WDMVS.js +677 -0
- package/dist/chunk-TZSKNMZG.js +434 -0
- package/dist/{chunk-7MNJORFF.js → chunk-UL3WSD3F.js} +6 -1
- package/dist/{chunk-7EYHLWU7.js → chunk-UZHIZC5S.js} +7 -7
- package/dist/{chunk-QTYWRVRA.js → chunk-VAWWTKDP.js} +8 -8
- package/dist/{chunk-X75S7HFS.js → chunk-VEZEGCGW.js} +214 -20
- package/dist/{chunk-OHHN2SO4.js → chunk-VMNQ6OZA.js} +98 -202
- package/dist/chunk-VSNATDE6.js +122 -0
- package/dist/chunk-W6GROXXM.js +69 -0
- package/dist/chunk-WPQLXFOZ.js +375 -0
- package/dist/{chunk-ORBHGJC5.js → chunk-WR67VIZY.js} +3 -3
- package/dist/{chunk-3ZXDFGR5.js → chunk-X6COSD2O.js} +5 -5
- package/dist/chunk-ZGVHUX3M.js +66 -0
- package/dist/{chunk-MAW544W2.js → chunk-ZWMF7253.js} +4 -4
- package/dist/{chunk-MQSRRFWA.js → chunk-ZYKPLLNQ.js} +563 -546
- package/dist/cli/index.js +27 -23
- package/dist/{clio-4LY5K2AC.js → clio-J5JIOIDS.js} +7 -6
- package/dist/{code-nav-7AX6FYE6.js → code-nav-AXCXSBHX.js} +5 -3
- package/dist/{config-GTLUW2PR.js → config-OEBMIN2U.js} +37 -27
- package/dist/{configure-R6A64DHX.js → configure-PUQOSIXQ.js} +16 -13
- package/dist/{context-5VKGUVJJ.js → context-EKDCKUUZ.js} +82 -7
- package/dist/{context-RW5HC47S.js → context-MGSE4Z2T.js} +33 -23
- package/dist/{context-JFZEJ7W5.js → context-URSXPBCK.js} +17 -9
- package/dist/{context-clear-6ZHBAZZT.js → context-clear-KDAJRNUK.js} +33 -23
- package/dist/context-working-set-SBKMPPI2.js +1552 -0
- package/dist/{dispatch-runner-VKBRCWQC.js → dispatch-runner-MSWN72NK.js} +43 -29
- package/dist/{doctor-KI767GSN.js → doctor-7BSE27PJ.js} +10 -10
- package/dist/{eval-XSSNATB4.js → eval-IZGDOO4H.js} +9 -8
- package/dist/{evidence-UA6AWDQQ.js → evidence-SR7WXB5B.js} +51 -23
- package/dist/{evolve-QNTFGV6Z.js → evolve-K7VE2CBX.js} +30 -20
- package/dist/{fleet-Q7UOMUSG.js → fleet-7XMJNQNF.js} +48 -38
- package/dist/{fleet-preflight-DDN536IT.js → fleet-preflight-AQNAH644.js} +3 -3
- package/dist/{init-WBB65ZHQ.js → init-JGNPAYXT.js} +41 -31
- package/dist/{memory-MD3O64RI.js → memory-4ALKDJ4Q.js} +32 -22
- package/dist/{models-BZU34YWD.js → models-ZMMLFJNN.js} +22 -19
- package/dist/{monitor-MEQA5C3I.js → monitor-2F3T5KHP.js} +55 -43
- package/dist/{orchestrator-CGFKEP27.js → orchestrator-ORHT43JB.js} +2507 -1896
- package/dist/{reset-L2FQEE3E.js → reset-NXGTYNUO.js} +4 -3
- package/dist/{run-IV4Q6RLN.js → run-RF4WJGMT.js} +51 -41
- package/dist/{share-S5BZQC5I.js → share-UT3W6E4M.js} +5 -4
- package/dist/{skills-LQEKRDTN.js → skills-PSACKC5Q.js} +2 -2
- package/dist/{skills-eval-3DC4HEWS.js → skills-eval-WJSI55RZ.js} +34 -24
- package/dist/{targets-C4SSGQOB.js → targets-PIIRAOYS.js} +23 -20
- package/dist/{terminal-lease-IT5JW2NR.js → terminal-lease-ULWXWNVY.js} +5 -4
- package/dist/{upgrade-7TT7SQ3G.js → upgrade-346TZ6AV.js} +18 -17
- package/dist/{usage-GV4PKT3M.js → usage-6KKXR32N.js} +34 -24
- package/dist/verifiers-4UUM6TEE.js +1214 -0
- package/dist/verify-X5HDROLA.js +25 -0
- package/dist/{wiki-generate-DQF6Z66B.js → wiki-generate-7STOCIFZ.js} +42 -31
- package/dist/worker/entry.js +33 -24
- package/docs/README.md +8 -7
- package/docs/acp.md +1 -1
- package/docs/alcf-provider.md +1 -1
- package/docs/architecture.md +2 -2
- package/docs/artifact-versions.md +1 -1
- package/docs/built-in-agents.md +1 -1
- package/docs/capacity-and-scheduling.md +1 -1
- package/docs/commands-and-modes.md +53 -21
- package/docs/config-knobs-audit.md +1 -2
- package/docs/configuration-and-targets.md +15 -1
- package/docs/context-engine.md +64 -12
- package/docs/context-working-set.md +194 -0
- package/docs/development-pipeline.md +1 -1
- package/docs/documentation-coverage.md +5 -5
- package/docs/documentation-guide.md +6 -5
- package/docs/environment-variables.md +2 -1
- package/docs/eval-runner.md +1 -1
- package/docs/evals-internal.md +14 -1
- package/docs/evidence-and-memory.md +74 -2
- package/docs/evolution.md +1 -1
- package/docs/exit-codes-and-output.md +1 -1
- package/docs/extensions-and-sharing.md +2 -2
- package/docs/fleet-dispatch.md +22 -7
- package/docs/glossary.md +21 -1
- package/docs/installation-and-lifecycle.md +6 -6
- package/docs/middleware-and-components.md +1 -1
- package/docs/model-catalog.md +7 -9
- package/docs/observability.md +4 -4
- package/docs/performance-methodology.md +2 -2
- package/docs/proactive-memory.md +1 -1
- package/docs/prompt-envelope-and-tools.md +4 -4
- package/docs/provider-adapter-cookbook.md +1 -1
- package/docs/release-cut-checklist.md +35 -35
- package/docs/safety-model.md +23 -4
- package/docs/scientific-validation.md +21 -3
- package/docs/session-lifecycle.md +3 -3
- package/docs/skills-marketplace.md +1 -1
- package/docs/tool-usage.md +79 -12
- package/docs/trace-store.md +1 -1
- package/docs/troubleshooting.md +1 -1
- package/docs/tui-design.md +2 -2
- package/docs/worker-dispatch-mechanics.md +11 -1
- package/package.json +8 -11
- package/skills/meta/clio-test/SKILL.md +20 -17
- package/skills/meta/clio-test/evals.md +3 -3
- package/skills/meta/clio-test/references/harness.md +35 -6
- package/skills/meta/clio-test/references/test-map.md +20 -10
- package/skills/registry.yaml +2 -2
- package/skills/skill-marketplace.json +1 -1
- package/src/cli/context-working-set.ts +513 -0
- package/src/cli/context.ts +8 -0
- package/src/cli/evidence.ts +20 -2
- package/src/cli/index.ts +4 -0
- package/src/cli/verifiers.ts +325 -0
- package/src/core/bash-exec.ts +39 -14
- package/src/core/bus-events.ts +19 -4
- package/src/core/config.ts +54 -0
- package/src/core/defaults.ts +50 -3
- package/src/core/git-commit-attribution.ts +46 -21
- package/src/core/verification-scripts.ts +6 -0
- package/src/domains/agents/builtins/verifier.md +3 -0
- package/src/domains/config/classify.ts +1 -0
- package/src/domains/config/keybindings.ts +3 -3
- package/src/domains/context/working-set/contract.ts +161 -0
- package/src/domains/context/working-set/defaults.ts +28 -0
- package/src/domains/context/working-set/engine.ts +203 -0
- package/src/domains/context/working-set/fold.ts +62 -0
- package/src/domains/context/working-set/horizon.ts +38 -0
- package/src/domains/context/working-set/marker.ts +103 -0
- package/src/domains/context/working-set/path-index.ts +436 -0
- package/src/domains/context/working-set/payload.ts +152 -0
- package/src/domains/context/working-set/policies/age-horizon.ts +55 -0
- package/src/domains/context/working-set/policies/index.ts +21 -0
- package/src/domains/context/working-set/policies/structural.ts +160 -0
- package/src/domains/context/working-set/project.ts +132 -0
- package/src/domains/context/working-set/protect.ts +109 -0
- package/src/domains/context/working-set/recall.ts +177 -0
- package/src/domains/context/working-set/replay/controls.ts +112 -0
- package/src/domains/context/working-set/replay/load-clio.ts +199 -0
- package/src/domains/context/working-set/replay/metrics.ts +185 -0
- package/src/domains/context/working-set/replay/reference-graph.ts +79 -0
- package/src/domains/context/working-set/replay/report.ts +139 -0
- package/src/domains/context/working-set/replay/runner.ts +325 -0
- package/src/domains/context/working-set/replay/synthetic.ts +422 -0
- package/src/domains/context/working-set/replay/trace.ts +21 -0
- package/src/domains/context/working-set/visible.ts +54 -0
- package/src/domains/evidence/build.ts +112 -45
- package/src/domains/evidence/eval.ts +24 -7
- package/src/domains/evidence/index.ts +53 -0
- package/src/domains/evidence/ordering.ts +12 -0
- package/src/domains/evidence/run-trust.ts +221 -0
- package/src/domains/evidence/store.ts +46 -6
- package/src/domains/evidence/trust-status.ts +854 -0
- package/src/domains/evidence/types.ts +26 -0
- package/src/domains/middleware/memory-intervention.ts +3 -0
- package/src/domains/middleware/stalled-turn.ts +165 -4
- package/src/domains/safety/autonomy.ts +1 -1
- package/src/domains/safety/default-path-policy.ts +8 -0
- package/src/domains/safety/finish-contract.ts +4 -3
- package/src/domains/safety/policy-engine.ts +48 -6
- package/src/domains/session/compaction/compact.ts +23 -1
- package/src/domains/session/compaction/cut-point.ts +2 -0
- package/src/domains/session/compaction/tokens.ts +16 -1
- package/src/domains/session/context-ledger.ts +2 -0
- package/src/domains/session/entries.ts +107 -1
- package/src/domains/session/manager.ts +9 -2
- package/src/domains/session/migrations/index.ts +22 -3
- package/src/engine/acp/server.ts +3 -0
- package/src/engine/agent.ts +18 -1
- package/src/engine/session.ts +9 -3
- package/src/entry/orchestrator.ts +16 -4
- package/src/interactive/chat-loop-messages.ts +18 -6
- package/src/interactive/chat-panel.ts +571 -244
- package/src/interactive/chat-renderer.ts +79 -39
- package/src/interactive/context-meter.ts +10 -0
- package/src/interactive/context-overlay.ts +81 -6
- package/src/interactive/context-recall-command.ts +110 -0
- package/src/interactive/editor-submit.ts +26 -1
- package/src/interactive/footer/widgets.ts +22 -20
- package/src/interactive/footer-panel.ts +6 -1
- package/src/interactive/interactive-application.ts +2 -0
- package/src/interactive/interactive-event-projection.ts +12 -0
- package/src/interactive/interactive-slash-runtime.ts +49 -8
- package/src/interactive/model-session-replay.ts +21 -0
- package/src/interactive/overlay-general-openers.ts +6 -0
- package/src/interactive/overlay-session-lifecycle.ts +8 -4
- package/src/interactive/overlays/ask-user.ts +146 -24
- package/src/interactive/renderers/tool-execution.ts +167 -56
- package/src/interactive/session-transcript.ts +2 -2
- package/src/interactive/slash-commands.ts +29 -2
- package/src/interactive/status/index.ts +12 -1
- package/src/interactive/status/reasoning.ts +87 -0
- package/src/interactive/status/summary.ts +13 -2
- package/src/interactive/transcript-detail.ts +120 -0
- package/src/interactive/turn-context.ts +238 -88
- package/src/interactive/turn-middleware.ts +6 -6
- package/src/tools/agent-tools.ts +11 -4
- package/src/tools/bash.ts +144 -82
- package/src/tools/builtin-tool-catalog.ts +18 -6
- package/src/tools/context/index.ts +105 -3
- package/src/tools/context/surface.ts +3 -2
- package/src/tools/core-bootstrap.ts +21 -0
- package/src/tools/dispatch-runner.ts +9 -7
- package/src/tools/monitor.ts +28 -20
- package/src/tools/presentation.ts +107 -0
- package/src/tools/registry.ts +65 -7
- package/src/tools/result-disposition.ts +550 -0
- package/src/tools/result-shaping.ts +262 -19
- package/src/tools/safe-exec.ts +2 -0
- package/src/tools/verify/authoring.ts +1119 -0
- package/src/tools/verify/catalog.ts +346 -0
- package/src/tools/verify/index.ts +13 -3
- package/src/tools/verify/scripts.ts +135 -37
- package/src/tools/verify/surface.ts +9 -5
- package/src/tools/worker-evidence.ts +35 -12
- package/dist/chunk-MNA4JGU4.js +0 -255
- package/dist/chunk-SRF2PJNW.js +0 -184
- package/dist/chunk-T6YILFSB.js +0 -80
- package/dist/chunk-VAKQQHWR.js +0 -434
|
@@ -13,6 +13,9 @@
|
|
|
13
13
|
* `isTransparentAssistantWrapper`.
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
+
import { ToolNames } from "../core/tool-names.js";
|
|
17
|
+
import { foldWorkingSet } from "../domains/context/working-set/fold.js";
|
|
18
|
+
import { compactionCut } from "../domains/context/working-set/visible.js";
|
|
16
19
|
import type {
|
|
17
20
|
BashExecutionEntry,
|
|
18
21
|
BranchSummaryEntry,
|
|
@@ -38,6 +41,8 @@ import {
|
|
|
38
41
|
} from "../engine/messages.js";
|
|
39
42
|
import { wrapTextWithAnsi } from "../engine/tui.js";
|
|
40
43
|
import type { AgentMessage } from "../engine/types.js";
|
|
44
|
+
import { toolPresentationPolicy } from "../tools/presentation.js";
|
|
45
|
+
import { toolResultPresentationText } from "../tools/result-disposition.js";
|
|
41
46
|
import type { ChatLoopEvent, RetryStatusPayload } from "./chat-loop.js";
|
|
42
47
|
import { isSelfExplainingAbort } from "./chat-loop-messages.js";
|
|
43
48
|
import type { ChatPanel } from "./chat-panel.js";
|
|
@@ -53,6 +58,7 @@ import {
|
|
|
53
58
|
type StreamPacer,
|
|
54
59
|
type StreamPacerSlice,
|
|
55
60
|
} from "./stream-pacer.js";
|
|
61
|
+
import { type FoldOverride, policyToolFold, resolveFold, type TranscriptDetailPolicy } from "./transcript-detail.js";
|
|
56
62
|
import { readWorkerReceiptFactsForReplay } from "./worker-receipts.js";
|
|
57
63
|
import { workerEntriesFromRunEntries } from "./worker-replay.js";
|
|
58
64
|
import type { WorkerReceiptReader } from "./worker-stream.js";
|
|
@@ -468,18 +474,22 @@ function toolCallMessageFromEntry(entry: MessageEntry): AgentMessage {
|
|
|
468
474
|
} as unknown as AgentMessage;
|
|
469
475
|
}
|
|
470
476
|
|
|
471
|
-
function toolResultContent(result: unknown): unknown[] {
|
|
477
|
+
function toolResultContent(result: unknown, unbounded = false): unknown[] {
|
|
472
478
|
const obj = payloadObject(result);
|
|
473
479
|
if (Array.isArray(obj?.content)) {
|
|
474
|
-
return cloneContentBlocks(obj.content, MAX_REPLAY_TEXT_CHARS) ?? [];
|
|
480
|
+
return cloneContentBlocks(obj.content, unbounded ? undefined : MAX_REPLAY_TEXT_CHARS) ?? [];
|
|
475
481
|
}
|
|
476
|
-
if (isTextResult(result)) return [{ type: "text", text: truncateReplayText(result.text) }];
|
|
477
|
-
if (typeof result === "string") return [{ type: "text", text: truncateReplayText(result) }];
|
|
478
|
-
return [{ type: "text", text: stringifyPreview(result, 10_000) }];
|
|
482
|
+
if (isTextResult(result)) return [{ type: "text", text: unbounded ? result.text : truncateReplayText(result.text) }];
|
|
483
|
+
if (typeof result === "string") return [{ type: "text", text: unbounded ? result : truncateReplayText(result) }];
|
|
484
|
+
return [{ type: "text", text: stringifyPreview(result, unbounded ? Number.POSITIVE_INFINITY : 10_000) }];
|
|
479
485
|
}
|
|
480
486
|
|
|
481
|
-
function displayReplayToolResult(result: unknown): unknown {
|
|
482
|
-
const
|
|
487
|
+
function displayReplayToolResult(result: unknown, unbounded = false): unknown {
|
|
488
|
+
const presentationText = toolResultPresentationText(result);
|
|
489
|
+
const content =
|
|
490
|
+
presentationText === null
|
|
491
|
+
? toolResultContent(result, unbounded)
|
|
492
|
+
: [{ type: "text", text: unbounded ? presentationText : truncateReplayText(presentationText) }];
|
|
483
493
|
// Preserve the details record (observation envelope, exec records) so the
|
|
484
494
|
// replayed ledger line carries the same outcome facts as the live one.
|
|
485
495
|
const details = payloadObject(result)?.details;
|
|
@@ -704,19 +714,28 @@ function appendReplayLine(chatPanel: ChatPanel, text: string): void {
|
|
|
704
714
|
chatPanel.appendReplayBlock((width) => renderReplayLine(truncateReplayText(text), width));
|
|
705
715
|
}
|
|
706
716
|
|
|
707
|
-
function renderBashExecutionEntry(
|
|
717
|
+
function renderBashExecutionEntry(
|
|
718
|
+
entry: BashExecutionEntry,
|
|
719
|
+
width: number,
|
|
720
|
+
folded: boolean,
|
|
721
|
+
unbounded: boolean,
|
|
722
|
+
): string[] {
|
|
723
|
+
const normalizedOutput = entry.output.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\s+$/g, "");
|
|
708
724
|
return renderBashTranscriptExecution(
|
|
709
725
|
{
|
|
710
726
|
command: entry.command,
|
|
711
|
-
output:
|
|
727
|
+
output: unbounded ? normalizedOutput : truncateReplayText(normalizedOutput),
|
|
712
728
|
running: false,
|
|
713
729
|
exitCode: entry.exitCode,
|
|
714
730
|
cancelled: entry.cancelled,
|
|
715
731
|
truncated: entry.truncated,
|
|
716
732
|
fullOutputPath: entry.fullOutputPath,
|
|
717
733
|
excludeFromContext: entry.excludeFromContext,
|
|
734
|
+
folded,
|
|
718
735
|
},
|
|
719
736
|
width,
|
|
737
|
+
undefined,
|
|
738
|
+
{ unbounded, diffStyle: "plain" },
|
|
720
739
|
);
|
|
721
740
|
}
|
|
722
741
|
|
|
@@ -823,13 +842,6 @@ function truncateAtTurn(entries: ReadonlyArray<SessionEntry>, uptoTurnId?: strin
|
|
|
823
842
|
return entries.slice(0, index + 1);
|
|
824
843
|
}
|
|
825
844
|
|
|
826
|
-
function latestCompactionIndex(entries: ReadonlyArray<SessionEntry>): number {
|
|
827
|
-
for (let i = entries.length - 1; i >= 0; i--) {
|
|
828
|
-
if (entries[i]?.kind === "compactionSummary") return i;
|
|
829
|
-
}
|
|
830
|
-
return -1;
|
|
831
|
-
}
|
|
832
|
-
|
|
833
845
|
function toolCallIdsInEntry(entry: SessionEntry): string[] {
|
|
834
846
|
if (entry.kind !== "message") return [];
|
|
835
847
|
if (entry.role === "tool_call") return [extractToolCall(entry).id];
|
|
@@ -907,19 +919,13 @@ export function selectReplayEntries(
|
|
|
907
919
|
): SessionEntry[] {
|
|
908
920
|
const active = filterEntriesToActivePath(turns, options.activeLeafTurnId ?? options.uptoTurnId);
|
|
909
921
|
const entries = truncateAtTurn(active, options.uptoTurnId);
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
const
|
|
915
|
-
const
|
|
916
|
-
|
|
917
|
-
: -1;
|
|
918
|
-
if (firstKeptIndex >= 0 && firstKeptIndex < compactionIndex) {
|
|
919
|
-
selected.push(...entries.slice(firstKeptIndex, compactionIndex));
|
|
920
|
-
}
|
|
921
|
-
selected.push(...entries.slice(compactionIndex + 1));
|
|
922
|
-
return dropLegacyToolResultAssistantDuplicates(repairToolResultOrphans(entries, selected, compactionIndex));
|
|
922
|
+
// The cut itself is the working-set layer's definition of "what the model
|
|
923
|
+
// can see", shared with the eviction policy input so the two cannot drift.
|
|
924
|
+
const cut = compactionCut(entries);
|
|
925
|
+
if (cut.compactionIndex < 0) return dropLegacyToolResultAssistantDuplicates(entries);
|
|
926
|
+
const compaction = entries[cut.compactionIndex] as CompactionSummaryEntry;
|
|
927
|
+
const selected: SessionEntry[] = [compaction, ...cut.visible];
|
|
928
|
+
return dropLegacyToolResultAssistantDuplicates(repairToolResultOrphans(entries, selected, cut.compactionIndex));
|
|
923
929
|
}
|
|
924
930
|
|
|
925
931
|
function dropLegacyToolResultAssistantDuplicates(entries: ReadonlyArray<SessionEntry>): SessionEntry[] {
|
|
@@ -1031,6 +1037,11 @@ export function buildReplayAgentMessagesFromTurns(
|
|
|
1031
1037
|
// It reaches the model only when an operator shares it, and a share
|
|
1032
1038
|
// is already a user message by the time it lands in the ledger.
|
|
1033
1039
|
case "workerRun":
|
|
1040
|
+
// Working-set entries are folded into a view by the context domain
|
|
1041
|
+
// and applied as a projection before this builder runs; the
|
|
1042
|
+
// entries themselves never become messages.
|
|
1043
|
+
case "contextEviction":
|
|
1044
|
+
case "contextRecall":
|
|
1034
1045
|
break;
|
|
1035
1046
|
}
|
|
1036
1047
|
}
|
|
@@ -1066,7 +1077,13 @@ export function rehydrateChatPanelFromTurns(
|
|
|
1066
1077
|
options: RehydrateChatPanelOptions = {},
|
|
1067
1078
|
): void {
|
|
1068
1079
|
const pendingToolIds: string[] = [];
|
|
1080
|
+
let runAssistantMessages: AgentMessage[] = [];
|
|
1069
1081
|
const selected = selectReplayEntries(turns, options);
|
|
1082
|
+
// The transcript shows the ledger, never the projection: an evicted result
|
|
1083
|
+
// still renders its full body here, tagged with the reason it left the
|
|
1084
|
+
// model's working set. Folded once over the same active path the replay
|
|
1085
|
+
// uses, so a /tree switch cannot tag a row from an abandoned branch.
|
|
1086
|
+
const workingSet = foldWorkingSet(turns, options.activeLeafTurnId ?? options.uptoTurnId);
|
|
1070
1087
|
// One block per assignment, drawn where its first attempt started. Later
|
|
1071
1088
|
// attempts of the same assignment fold into that block as `↻` rail lines, so
|
|
1072
1089
|
// a failover replays as the one run it was rather than as two.
|
|
@@ -1079,6 +1096,7 @@ export function rehydrateChatPanelFromTurns(
|
|
|
1079
1096
|
switch (entry.kind) {
|
|
1080
1097
|
case "message": {
|
|
1081
1098
|
if (entry.role === "user") {
|
|
1099
|
+
runAssistantMessages = [];
|
|
1082
1100
|
const text = truncateReplayText(replayedUserText(entry));
|
|
1083
1101
|
if (text.length > 0) chatPanel.appendUser(text);
|
|
1084
1102
|
break;
|
|
@@ -1094,7 +1112,8 @@ export function rehydrateChatPanelFromTurns(
|
|
|
1094
1112
|
(message as { stopReason?: string; errorMessage?: string }).errorMessage = failure.errorMessage;
|
|
1095
1113
|
}
|
|
1096
1114
|
chatPanel.applyEvent({ type: "message_end", message });
|
|
1097
|
-
|
|
1115
|
+
runAssistantMessages.push(message);
|
|
1116
|
+
chatPanel.applyEvent({ type: "agent_end", messages: runAssistantMessages });
|
|
1098
1117
|
}
|
|
1099
1118
|
break;
|
|
1100
1119
|
}
|
|
@@ -1112,6 +1131,7 @@ export function rehydrateChatPanelFromTurns(
|
|
|
1112
1131
|
}
|
|
1113
1132
|
if (entry.role === "tool_result") {
|
|
1114
1133
|
const result = extractToolResult(entry);
|
|
1134
|
+
const evictedReason = workingSet.evicted.get(entry.turnId)?.reason;
|
|
1115
1135
|
const fallbackId = result.id ?? pendingToolIds.pop() ?? null;
|
|
1116
1136
|
if (fallbackId) {
|
|
1117
1137
|
const pendingIndex = pendingToolIds.indexOf(fallbackId);
|
|
@@ -1120,12 +1140,13 @@ export function rehydrateChatPanelFromTurns(
|
|
|
1120
1140
|
type: "tool_execution_end",
|
|
1121
1141
|
toolCallId: fallbackId,
|
|
1122
1142
|
toolName: result.name,
|
|
1123
|
-
result: displayReplayToolResult(result.result),
|
|
1143
|
+
result: displayReplayToolResult(result.result, options.unboundedToolBodies === true),
|
|
1124
1144
|
isError: result.isError,
|
|
1125
1145
|
...(result.durationMs !== undefined ? { durationMs: result.durationMs } : {}),
|
|
1126
1146
|
...(result.resultSummary !== undefined ? { resultSummary: result.resultSummary } : {}),
|
|
1127
1147
|
...(result.outcome !== undefined ? { outcome: result.outcome } : {}),
|
|
1128
1148
|
...(result.blockReason !== undefined ? { blockReason: result.blockReason } : {}),
|
|
1149
|
+
...(evictedReason !== undefined ? { evictedReason } : {}),
|
|
1129
1150
|
} as ChatLoopEvent);
|
|
1130
1151
|
} else {
|
|
1131
1152
|
chatPanel.appendReplayBlock((width) =>
|
|
@@ -1133,12 +1154,13 @@ export function rehydrateChatPanelFromTurns(
|
|
|
1133
1154
|
{
|
|
1134
1155
|
toolCallId: result.id ?? "",
|
|
1135
1156
|
toolName: result.name,
|
|
1136
|
-
result: displayReplayToolResult(result.result),
|
|
1157
|
+
result: displayReplayToolResult(result.result, options.unboundedToolBodies === true),
|
|
1137
1158
|
isError: result.isError,
|
|
1138
1159
|
...(result.durationMs !== undefined ? { durationMs: result.durationMs } : {}),
|
|
1139
1160
|
...(result.resultSummary !== undefined ? { resultSummary: result.resultSummary } : {}),
|
|
1140
1161
|
...(result.outcome === "blocked" ? { outcome: "blocked" as const } : {}),
|
|
1141
1162
|
...(result.blockReason !== undefined ? { blockReason: result.blockReason } : {}),
|
|
1163
|
+
...(evictedReason !== undefined ? { evictedReason } : {}),
|
|
1142
1164
|
},
|
|
1143
1165
|
width,
|
|
1144
1166
|
{ unbounded: options.unboundedToolBodies === true },
|
|
@@ -1159,9 +1181,25 @@ export function rehydrateChatPanelFromTurns(
|
|
|
1159
1181
|
}
|
|
1160
1182
|
break;
|
|
1161
1183
|
}
|
|
1162
|
-
case "bashExecution":
|
|
1163
|
-
|
|
1184
|
+
case "bashExecution": {
|
|
1185
|
+
let fold: FoldOverride;
|
|
1186
|
+
const presentation = toolPresentationPolicy(ToolNames.Bash, undefined);
|
|
1187
|
+
const policyFold = (detail: TranscriptDetailPolicy) => policyToolFold(detail, presentation);
|
|
1188
|
+
const unbounded = options.unboundedToolBodies === true;
|
|
1189
|
+
chatPanel.appendReplayBlock(
|
|
1190
|
+
(width, detail) =>
|
|
1191
|
+
renderBashExecutionEntry(entry, width, resolveFold(fold, policyFold(detail)) === "folded", unbounded),
|
|
1192
|
+
undefined,
|
|
1193
|
+
{
|
|
1194
|
+
policyFold,
|
|
1195
|
+
fold: () => fold,
|
|
1196
|
+
setFold: (next: FoldOverride) => {
|
|
1197
|
+
fold = next;
|
|
1198
|
+
},
|
|
1199
|
+
},
|
|
1200
|
+
);
|
|
1164
1201
|
break;
|
|
1202
|
+
}
|
|
1165
1203
|
case "custom":
|
|
1166
1204
|
if (rendersCustomEntry(entry)) chatPanel.appendReplayBlock((width) => renderCustomEntry(entry, width));
|
|
1167
1205
|
break;
|
|
@@ -1208,6 +1246,8 @@ export function rehydrateChatPanelFromTurns(
|
|
|
1208
1246
|
case "label":
|
|
1209
1247
|
case "taskLedger":
|
|
1210
1248
|
case "decisionLedger":
|
|
1249
|
+
case "contextEviction":
|
|
1250
|
+
case "contextRecall":
|
|
1211
1251
|
break;
|
|
1212
1252
|
}
|
|
1213
1253
|
}
|
|
@@ -1220,9 +1260,9 @@ export function rehydrateChatPanelFromTurns(
|
|
|
1220
1260
|
isError: true,
|
|
1221
1261
|
});
|
|
1222
1262
|
}
|
|
1223
|
-
//
|
|
1224
|
-
//
|
|
1225
|
-
//
|
|
1226
|
-
//
|
|
1227
|
-
chatPanel.
|
|
1263
|
+
// A rehydrated transcript starts from the transcript detail policy alone:
|
|
1264
|
+
// whatever the operator had opened or folded before the switch belonged to
|
|
1265
|
+
// the transcript they left. /export reaches the same policy with a verbose
|
|
1266
|
+
// panel, so nothing here is terminal-only.
|
|
1267
|
+
chatPanel.clearFoldOverrides();
|
|
1228
1268
|
}
|
|
@@ -156,6 +156,16 @@ export function renderContextMeterGrid(
|
|
|
156
156
|
return lines;
|
|
157
157
|
}
|
|
158
158
|
|
|
159
|
+
/**
|
|
160
|
+
* Evicted tokens live outside the window: the working-set projection removed
|
|
161
|
+
* them from what the model sees, so they are not a meter category and must
|
|
162
|
+
* not claim cells or a legend row. One line after the legend names them.
|
|
163
|
+
*/
|
|
164
|
+
export function renderEvictedTokensLine(evictedTokens: number, theme: ClioTheme = clioTheme()): string {
|
|
165
|
+
const tokens = Math.round(Math.max(0, evictedTokens)).toLocaleString("en-US");
|
|
166
|
+
return `${theme.fg("dim", GLYPH.contextReserve)} ${theme.fg("muted", "evicted (outside window)")} ${theme.fg("dim", `${tokens} tokens`)}`;
|
|
167
|
+
}
|
|
168
|
+
|
|
159
169
|
/** A small colored swatch for a category, for inline legends. */
|
|
160
170
|
export function contextCategorySwatch(category: ContextLedgerCategory, theme: ClioTheme = clioTheme()): string {
|
|
161
171
|
return theme.fg(CONTEXT_CATEGORY_TOKEN[category], contextCategoryGlyph(category));
|
|
@@ -2,9 +2,10 @@ import { homedir } from "node:os";
|
|
|
2
2
|
import { relative } from "node:path";
|
|
3
3
|
import { BusChannels } from "../core/bus-events.js";
|
|
4
4
|
import type { SafeEventBus } from "../core/event-bus.js";
|
|
5
|
+
import type { WorkingSetView } from "../domains/context/working-set/contract.js";
|
|
5
6
|
import type { ContextLedger, ContextLedgerGroup } from "../domains/session/context-ledger.js";
|
|
6
7
|
import { type OverlayHandle, Text, type TUI, visibleWidth } from "../engine/tui.js";
|
|
7
|
-
import { contextCategorySwatch, renderContextMeterGrid } from "./context-meter.js";
|
|
8
|
+
import { contextCategorySwatch, renderContextMeterGrid, renderEvictedTokensLine } from "./context-meter.js";
|
|
8
9
|
import { buildHint, showClioOverlayFrame } from "./overlay-frame.js";
|
|
9
10
|
import { abbreviateModelId, type ClioToken, clioTheme, formatContextPercent } from "./theme/index.js";
|
|
10
11
|
|
|
@@ -81,7 +82,62 @@ function legendRow(group: ContextLedgerGroup, contentWidth: number): string {
|
|
|
81
82
|
return `${swatch} ${theme.fg(labelToken, labelText)} ${theme.fg("muted", right)}`;
|
|
82
83
|
}
|
|
83
84
|
|
|
84
|
-
|
|
85
|
+
function evictedTokens(view: WorkingSetView): number {
|
|
86
|
+
let total = 0;
|
|
87
|
+
for (const state of view.evicted.values()) total += state.tokensFreed;
|
|
88
|
+
return total;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function formatChurn(view: WorkingSetView): string {
|
|
92
|
+
if (view.itemsEvicted === 0) return "n/a";
|
|
93
|
+
return (view.recalls / view.itemsEvicted).toFixed(2);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Prose for one cache-disturbance reason. The wire values are stamped by
|
|
98
|
+
* `noteColdReason` in turn-context.ts and persisted on the assistant entry's
|
|
99
|
+
* `promptCache.expectedColdReasons`; the overlay reads them back, so an unknown
|
|
100
|
+
* reason renders as itself rather than disappearing.
|
|
101
|
+
*/
|
|
102
|
+
function coldReasonLabel(reason: string): string {
|
|
103
|
+
switch (reason) {
|
|
104
|
+
case "working_set_evict":
|
|
105
|
+
return "working-set eviction";
|
|
106
|
+
case "compaction":
|
|
107
|
+
return "compaction";
|
|
108
|
+
case "dispatch":
|
|
109
|
+
return "dispatch traffic";
|
|
110
|
+
default:
|
|
111
|
+
return reason;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* The working-set section: what the projection has taken out of the window
|
|
117
|
+
* and how often the model has asked for it back. Churn is recalls over items
|
|
118
|
+
* evicted; a high number means the policy evicts what is still needed.
|
|
119
|
+
*/
|
|
120
|
+
function renderWorkingSetLines(view: WorkingSetView): string[] {
|
|
121
|
+
const theme = clioTheme();
|
|
122
|
+
const items = view.evicted.size;
|
|
123
|
+
const summary = [
|
|
124
|
+
`${items} evicted item${items === 1 ? "" : "s"}`,
|
|
125
|
+
`${formatTokens(evictedTokens(view))} tokens`,
|
|
126
|
+
`${view.evictionEvents} event${view.evictionEvents === 1 ? "" : "s"}`,
|
|
127
|
+
`${view.recalls} recall${view.recalls === 1 ? "" : "s"}`,
|
|
128
|
+
`churn ${formatChurn(view)}`,
|
|
129
|
+
].join(" · ");
|
|
130
|
+
return [
|
|
131
|
+
`${theme.fg("muted", "working set")} ${theme.fg("dim", "·")} ${theme.fg("accent", `policy ${view.lastPolicyId ?? "none"}`)}`,
|
|
132
|
+
theme.fg("dim", summary),
|
|
133
|
+
];
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function renderContextLedgerLines(
|
|
137
|
+
ledger: ContextLedger,
|
|
138
|
+
contentWidth: number,
|
|
139
|
+
workingSet?: WorkingSetView | null,
|
|
140
|
+
): string[] {
|
|
85
141
|
const theme = clioTheme();
|
|
86
142
|
const lines: string[] = [];
|
|
87
143
|
|
|
@@ -110,8 +166,13 @@ export function renderContextLedgerLines(ledger: ContextLedger, contentWidth: nu
|
|
|
110
166
|
lines.push("");
|
|
111
167
|
|
|
112
168
|
for (const group of ledger.meter) lines.push(legendRow(group, contentWidth));
|
|
169
|
+
if (workingSet && workingSet.evicted.size > 0) lines.push(renderEvictedTokensLine(evictedTokens(workingSet), theme));
|
|
113
170
|
|
|
114
171
|
lines.push("");
|
|
172
|
+
if (workingSet) {
|
|
173
|
+
for (const line of renderWorkingSetLines(workingSet)) lines.push(line);
|
|
174
|
+
lines.push("");
|
|
175
|
+
}
|
|
115
176
|
if (ledger.projectPreload && ledger.groups.some((group) => group.category === "project")) {
|
|
116
177
|
lines.push(theme.fg("dim", `project preload: ${ledger.projectPreload}`));
|
|
117
178
|
}
|
|
@@ -141,11 +202,21 @@ export function renderContextLedgerLines(ledger: ContextLedger, contentWidth: nu
|
|
|
141
202
|
const uncached =
|
|
142
203
|
cache.uncachedInputTokens !== null ? `uncached input ${formatTokens(cache.uncachedInputTokens)}` : null;
|
|
143
204
|
const line = ["prompt cache:", shell, "·", backend, "·", read, ...(uncached ? ["·", uncached] : [])].join(" ");
|
|
205
|
+
// Reasons Clio recorded before the run: working-set eviction, summary
|
|
206
|
+
// compaction, and dispatch traffic all move the byte prefix a local
|
|
207
|
+
// single-slot backend caches, so a cold turn after one of them is the
|
|
208
|
+
// expected outcome rather than a provider surprise.
|
|
209
|
+
const coldReasons = cache.backendVerdict === "cold" ? (cache.expectedColdReasons ?? []) : [];
|
|
144
210
|
// A reused shell with a cold backend means Clio kept the bytes stable
|
|
145
211
|
// but the provider re-prefilled anyway; surface that disagreement
|
|
146
|
-
// instead of hiding it.
|
|
147
|
-
|
|
212
|
+
// instead of hiding it. An expected reason explains the same numbers, so
|
|
213
|
+
// it is reported on its own line and not as a warning.
|
|
214
|
+
const misleading = cache.shellReused && cache.backendVerdict === "cold" && coldReasons.length === 0;
|
|
148
215
|
lines.push(theme.fg(misleading ? "warning" : "dim", line));
|
|
216
|
+
if (coldReasons.length > 0) {
|
|
217
|
+
const reasons = coldReasons.map(coldReasonLabel).join(", ");
|
|
218
|
+
lines.push(theme.fg("dim", `last cold turn: ${reasons} (expected)`));
|
|
219
|
+
}
|
|
149
220
|
}
|
|
150
221
|
|
|
151
222
|
if (ledger.lastCompaction) {
|
|
@@ -170,6 +241,8 @@ export interface OpenContextOverlayOptions {
|
|
|
170
241
|
onEvent(handler: (event: { type: string }) => void): () => void;
|
|
171
242
|
isStreaming(): boolean;
|
|
172
243
|
};
|
|
244
|
+
/** Working-set fold at the live leaf; null or absent hides the section. */
|
|
245
|
+
getWorkingSet?: () => WorkingSetView | null;
|
|
173
246
|
}
|
|
174
247
|
|
|
175
248
|
/**
|
|
@@ -183,7 +256,9 @@ export function openContextOverlay(
|
|
|
183
256
|
getLedger: () => ContextLedger,
|
|
184
257
|
options?: OpenContextOverlayOptions,
|
|
185
258
|
): OverlayHandle {
|
|
186
|
-
const
|
|
259
|
+
const render = (): string =>
|
|
260
|
+
renderContextLedgerLines(getLedger(), DEFAULT_CONTENT_WIDTH, options?.getWorkingSet?.() ?? null).join("\n");
|
|
261
|
+
const text = new Text(render(), 0, 0);
|
|
187
262
|
const handle = showClioOverlayFrame(tui, text, {
|
|
188
263
|
anchor: "center",
|
|
189
264
|
width: CONTEXT_OVERLAY_WIDTH,
|
|
@@ -192,7 +267,7 @@ export function openContextOverlay(
|
|
|
192
267
|
});
|
|
193
268
|
|
|
194
269
|
const refresh = (): void => {
|
|
195
|
-
text.setText(
|
|
270
|
+
text.setText(render());
|
|
196
271
|
text.invalidate();
|
|
197
272
|
tui.requestRender();
|
|
198
273
|
};
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `/context recall <ref>`: the operator's half of working-set recall.
|
|
3
|
+
*
|
|
4
|
+
* The model recalls through `context(scope="recall", ref=...)`, which puts the
|
|
5
|
+
* body back in front of the model at the tail of the working set. This command
|
|
6
|
+
* answers a different question, asked by a person: what was in the result the
|
|
7
|
+
* marker replaced. So the body goes to the transcript and nowhere else. It is
|
|
8
|
+
* never submitted as a turn, never replayed, and never counted against the
|
|
9
|
+
* context window, which is why an operator can read a 40k-line build log back
|
|
10
|
+
* without paying for it.
|
|
11
|
+
*
|
|
12
|
+
* Everything else is identical to the tool path, deliberately: the same
|
|
13
|
+
* `resolveRecall` over the same fold at the same live leaf, the same
|
|
14
|
+
* `contextRecall` ledger entry (with `trigger: "operator"`), and the same
|
|
15
|
+
* `BusChannels.ContextRecalled` publication. A recall is a churn signal
|
|
16
|
+
* whoever asked for it, and a policy that keeps evicting something a human
|
|
17
|
+
* keeps reading back is a policy worth changing.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import type { ContextRecalledPayload } from "../core/bus-events.js";
|
|
21
|
+
import type { EvictedState } from "../domains/context/working-set/contract.js";
|
|
22
|
+
import { foldWorkingSet } from "../domains/context/working-set/fold.js";
|
|
23
|
+
import {
|
|
24
|
+
buildRecallFields,
|
|
25
|
+
recallErrorMessage,
|
|
26
|
+
recallParentTurnId,
|
|
27
|
+
resolveRecall,
|
|
28
|
+
} from "../domains/context/working-set/recall.js";
|
|
29
|
+
import type { SessionEntryInput } from "../domains/session/contract.js";
|
|
30
|
+
import type { SessionEntry } from "../domains/session/entries.js";
|
|
31
|
+
|
|
32
|
+
/** Ledger access the command needs, mirroring the context tool's `ContextSessionDeps`. */
|
|
33
|
+
export interface OperatorRecallDeps {
|
|
34
|
+
hasSession(): boolean;
|
|
35
|
+
readEntries(): ReadonlyArray<SessionEntry>;
|
|
36
|
+
/** The live append point (`/tree` pin or tree leaf); undefined lets the fold infer it. */
|
|
37
|
+
activeLeafTurnId(): string | undefined;
|
|
38
|
+
appendEntry(entry: SessionEntryInput): SessionEntry;
|
|
39
|
+
/** Publisher for `BusChannels.ContextRecalled`; the runtime supplies the bus. */
|
|
40
|
+
onRecalled?: (payload: ContextRecalledPayload) => void;
|
|
41
|
+
now?: () => number;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export type OperatorRecallOutcome =
|
|
45
|
+
| {
|
|
46
|
+
ok: true;
|
|
47
|
+
/** One-line summary for the notice bar. */
|
|
48
|
+
headline: string;
|
|
49
|
+
/** The original body, byte-exact, for the transcript. */
|
|
50
|
+
body: string;
|
|
51
|
+
}
|
|
52
|
+
| { ok: false; message: string };
|
|
53
|
+
|
|
54
|
+
function formatTokens(tokens: number): string {
|
|
55
|
+
return tokens.toLocaleString("en-US");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Ref, why it left, what it costs to read, and where the full artifact lives
|
|
60
|
+
* when the original result was offloaded. Nothing else: the body is on the next
|
|
61
|
+
* line and the operator is already looking at it.
|
|
62
|
+
*/
|
|
63
|
+
function headlineFor(ref: string, tokens: number, state: EvictedState | undefined, offloadPath?: string): string {
|
|
64
|
+
const parts = [ref];
|
|
65
|
+
if (state !== undefined) {
|
|
66
|
+
parts.push(state.by === undefined ? `evicted: ${state.reason}` : `evicted: ${state.reason} by ${state.by}`);
|
|
67
|
+
}
|
|
68
|
+
parts.push(`${formatTokens(tokens)} tokens`);
|
|
69
|
+
if (offloadPath !== undefined) parts.push(`offload: ${offloadPath}`);
|
|
70
|
+
return `[/context recall] ${parts.join(" · ")}`;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function runOperatorRecall(ref: string, deps: OperatorRecallDeps): OperatorRecallOutcome {
|
|
74
|
+
if (!deps.hasSession()) {
|
|
75
|
+
return { ok: false, message: "[/context recall] no active session; start one with /new or /resume first" };
|
|
76
|
+
}
|
|
77
|
+
const trimmed = ref.trim();
|
|
78
|
+
if (trimmed.length === 0) {
|
|
79
|
+
return { ok: false, message: "[/context recall] needs a ref: the turnId named in an [evicted ...] marker" };
|
|
80
|
+
}
|
|
81
|
+
const entries = deps.readEntries();
|
|
82
|
+
const leaf = deps.activeLeafTurnId();
|
|
83
|
+
const view = foldWorkingSet(entries, leaf);
|
|
84
|
+
const resolved = resolveRecall(entries, view, trimmed, leaf);
|
|
85
|
+
// A ref that resolves to nothing is usually a typo or a stale marker; the
|
|
86
|
+
// shared message ends with the refs the operator could have typed instead.
|
|
87
|
+
if (!resolved.ok)
|
|
88
|
+
return { ok: false, message: `[/context recall] ${recallErrorMessage(resolved.error, entries, view)}` };
|
|
89
|
+
const { result } = resolved;
|
|
90
|
+
const fields = buildRecallFields(result, { trigger: "operator" });
|
|
91
|
+
try {
|
|
92
|
+
deps.appendEntry({ ...fields, parentTurnId: recallParentTurnId(entries, leaf) });
|
|
93
|
+
} catch (err) {
|
|
94
|
+
return {
|
|
95
|
+
ok: false,
|
|
96
|
+
message: `[/context recall] recall of ${result.ref.entry} could not be recorded: ${err instanceof Error ? err.message : String(err)}`,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
deps.onRecalled?.({
|
|
100
|
+
ref: result.ref.entry,
|
|
101
|
+
trigger: "operator",
|
|
102
|
+
tokensReadmitted: result.tokens,
|
|
103
|
+
at: deps.now?.() ?? Date.now(),
|
|
104
|
+
});
|
|
105
|
+
return {
|
|
106
|
+
ok: true,
|
|
107
|
+
headline: headlineFor(result.ref.entry, result.tokens, view.evicted.get(result.ref.entry), result.offloadPath),
|
|
108
|
+
body: result.body,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { combineBashOutput, runBashCommand } from "../core/bash-exec.js";
|
|
2
2
|
import type { PendingSkillRequest } from "../core/skill-activation.js";
|
|
3
|
+
import { ToolNames } from "../core/tool-names.js";
|
|
3
4
|
import type { DispatchContract } from "../domains/dispatch/contract.js";
|
|
4
5
|
import type { SessionContract, SessionEntry } from "../domains/session/index.js";
|
|
6
|
+
import { toolPresentationPolicy } from "../tools/presentation.js";
|
|
5
7
|
import type { ChatLoop } from "./chat-loop.js";
|
|
6
8
|
import type { ChatPanel } from "./chat-panel.js";
|
|
7
9
|
import { bashExecutionEntryInput, parseEditorBashCommand } from "./editor-bash.js";
|
|
@@ -14,6 +16,13 @@ import {
|
|
|
14
16
|
import { type ExternalEditResult, editTextExternally, resolveExternalEditor } from "./external-editor.js";
|
|
15
17
|
import { type BashTranscriptExecution, renderBashTranscriptExecution } from "./renderers/tool-execution.js";
|
|
16
18
|
import { parseSlashCommand, type RunIo, type SlashCommand, type SlashCommandDispatchResult } from "./slash-commands.js";
|
|
19
|
+
import {
|
|
20
|
+
type FoldOverride,
|
|
21
|
+
policyRunningToolFold,
|
|
22
|
+
policyToolFold,
|
|
23
|
+
resolveFold,
|
|
24
|
+
type TranscriptDetailPolicy,
|
|
25
|
+
} from "./transcript-detail.js";
|
|
17
26
|
|
|
18
27
|
const EDITOR_BASH_TIMEOUT_MS = 300_000;
|
|
19
28
|
|
|
@@ -154,16 +163,32 @@ export function createEditorSubmitController(deps: EditorSubmitDeps): EditorSubm
|
|
|
154
163
|
totalBytes: 0,
|
|
155
164
|
excludeFromContext: parsed.excludeFromContext,
|
|
156
165
|
};
|
|
166
|
+
// The operator's bash row follows the same rule as a model bash call: the
|
|
167
|
+
// transcript detail policy (through bash's presentation once settled)
|
|
168
|
+
// unless the operator overrode this one block with the expand key.
|
|
169
|
+
let fold: FoldOverride;
|
|
170
|
+
const bashPresentation = toolPresentationPolicy(ToolNames.Bash, undefined);
|
|
171
|
+
const policyFold = (detail: TranscriptDetailPolicy) =>
|
|
172
|
+
execution.running ? policyRunningToolFold(detail) : policyToolFold(detail, bashPresentation);
|
|
157
173
|
deps.chatPanel.appendReplayBlock(
|
|
158
|
-
(width) =>
|
|
174
|
+
(width, detail) =>
|
|
159
175
|
renderBashTranscriptExecution(
|
|
160
176
|
{
|
|
161
177
|
...execution,
|
|
178
|
+
folded: resolveFold(fold, policyFold(detail)) === "folded",
|
|
162
179
|
...(execution.running ? { elapsedMs: Math.max(0, performance.now() - startedAt) } : {}),
|
|
163
180
|
},
|
|
164
181
|
width,
|
|
165
182
|
),
|
|
166
183
|
() => execution.running,
|
|
184
|
+
{
|
|
185
|
+
policyFold,
|
|
186
|
+
fold: () => fold,
|
|
187
|
+
setFold: (next: FoldOverride) => {
|
|
188
|
+
fold = next;
|
|
189
|
+
deps.ui.requestRender();
|
|
190
|
+
},
|
|
191
|
+
},
|
|
167
192
|
);
|
|
168
193
|
deps.ui.requestRender();
|
|
169
194
|
|
|
@@ -19,7 +19,13 @@ import {
|
|
|
19
19
|
dispatchStatusPresentation,
|
|
20
20
|
} from "../dispatch-board.js";
|
|
21
21
|
import { buildSegmentedContextBar, CONTEXT_BAR_LABEL_WIDTH, formatFooterTokens } from "../footer-panel.js";
|
|
22
|
-
import {
|
|
22
|
+
import {
|
|
23
|
+
type AgentStatus,
|
|
24
|
+
formatReasoningChip,
|
|
25
|
+
reasoningFromSummary,
|
|
26
|
+
spinnerFrame,
|
|
27
|
+
type TurnSummary,
|
|
28
|
+
} from "../status/index.js";
|
|
23
29
|
import {
|
|
24
30
|
type ClioTheme,
|
|
25
31
|
type ClioToken,
|
|
@@ -706,11 +712,8 @@ export function formatLastTurn(theme: ClioTheme, summary: TurnSummary): string {
|
|
|
706
712
|
];
|
|
707
713
|
// Zero suppresses the chip, the same rule the chat panel's turn line follows:
|
|
708
714
|
// a turn that spent no reasoning tokens states nothing by printing `r0`.
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
summary.reasoningTokenProvenance === "estimated" || summary.reasoningTokenProvenance === "mixed" ? "≈" : "";
|
|
712
|
-
parts.push(theme.fg("reason", `r${marker}${summary.reasoningTokens}`));
|
|
713
|
-
}
|
|
715
|
+
const chip = formatReasoningChip(reasoningFromSummary(summary), String);
|
|
716
|
+
if (chip !== null) parts.push(theme.fg("reason", chip));
|
|
714
717
|
if (summary.cacheReadTokens > 0 || summary.cacheWriteTokens > 0) {
|
|
715
718
|
parts.push(theme.fg("dim", `cache ${summary.cacheReadTokens}/${summary.cacheWriteTokens}`));
|
|
716
719
|
}
|
|
@@ -802,6 +805,17 @@ function liveTokenValue(
|
|
|
802
805
|
return joined.length > 0 ? joined : null;
|
|
803
806
|
}
|
|
804
807
|
|
|
808
|
+
/**
|
|
809
|
+
* The last turn's reasoning chip, or null when the turn spent none. Every
|
|
810
|
+
* surface that shows reasoning reads the same projection, so the transcript,
|
|
811
|
+
* this footer, and the receipt cannot disagree about the count or whether it
|
|
812
|
+
* is provider-attested.
|
|
813
|
+
*/
|
|
814
|
+
function reasoningChip(theme: ClioTheme, lastTurn: TurnSummary): string | null {
|
|
815
|
+
const chip = formatReasoningChip(reasoningFromSummary(lastTurn), formatFooterTokens);
|
|
816
|
+
return chip === null ? null : theme.fg("reason", chip);
|
|
817
|
+
}
|
|
818
|
+
|
|
805
819
|
function lastTurnOutcome(theme: ClioTheme, lastTurn: TurnSummary): string {
|
|
806
820
|
const stop = stopReasonStyle(lastTurn.stopReason);
|
|
807
821
|
return theme.fg(stop.token, `${stop.glyph} ${formatCompactMs(lastTurn.elapsedMs)}`);
|
|
@@ -813,12 +827,7 @@ function lastTurnDetails(theme: ClioTheme, lastTurn: TurnSummary): string {
|
|
|
813
827
|
"muted",
|
|
814
828
|
`${GLYPH.up}${formatFooterTokens(lastTurn.inputTokens)} ${GLYPH.down}${formatFooterTokens(lastTurn.outputTokens)}`,
|
|
815
829
|
),
|
|
816
|
-
|
|
817
|
-
? theme.fg(
|
|
818
|
-
"reason",
|
|
819
|
-
`r${lastTurn.reasoningTokenProvenance === "estimated" || lastTurn.reasoningTokenProvenance === "mixed" ? "≈" : ""}${formatFooterTokens(lastTurn.reasoningTokens)}`,
|
|
820
|
-
)
|
|
821
|
-
: null,
|
|
830
|
+
reasoningChip(theme, lastTurn),
|
|
822
831
|
lastTurn.cacheReadTokens > 0 || lastTurn.cacheWriteTokens > 0
|
|
823
832
|
? theme.fg(
|
|
824
833
|
"dim",
|
|
@@ -1202,14 +1211,7 @@ export function buildMetricStrip(
|
|
|
1202
1211
|
`${GLYPH.up}${formatFooterTokens(lastTurn.inputTokens)} ${GLYPH.down}${formatFooterTokens(lastTurn.outputTokens)}`,
|
|
1203
1212
|
),
|
|
1204
1213
|
);
|
|
1205
|
-
candidates.push(
|
|
1206
|
-
lastTurn.reasoningTokens !== undefined && lastTurn.reasoningTokens > 0
|
|
1207
|
-
? theme.fg(
|
|
1208
|
-
"reason",
|
|
1209
|
-
`r${lastTurn.reasoningTokenProvenance === "estimated" || lastTurn.reasoningTokenProvenance === "mixed" ? "≈" : ""}${formatFooterTokens(lastTurn.reasoningTokens)}`,
|
|
1210
|
-
)
|
|
1211
|
-
: null,
|
|
1212
|
-
);
|
|
1214
|
+
candidates.push(reasoningChip(theme, lastTurn));
|
|
1213
1215
|
// Held back so the session totals below outrank them. The chip list is cut
|
|
1214
1216
|
// to `maxChipsCount` before it is measured, and with cache and tools ahead
|
|
1215
1217
|
// of the totals an 80-column terminal spent the whole budget on per-turn
|