@iowarp/clio-coder 0.3.4 → 0.3.6
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 +37 -2
- package/CONTRIBUTING.md +6 -6
- package/README.md +2 -2
- package/dist/{acp-S5R4RR5B.js → acp-2BEHC4DL.js} +4 -4
- package/dist/{agents-P6DMMVZY.js → agents-LNNFTM53.js} +13 -11
- package/dist/assets/codewiki.json +1 -1
- package/dist/{auth-2XCZLPKS.js → auth-KXXFI2VS.js} +6 -6
- package/dist/{chunk-YCWGATWI.js → chunk-24I7BN55.js} +2 -2
- package/dist/{chunk-EKMEHE4H.js → chunk-33YXPOE3.js} +2 -3
- package/dist/chunk-3BPUFZDL.js +37 -0
- package/dist/{chunk-WPQLXFOZ.js → chunk-43AOLP7E.js} +2 -2
- package/dist/{chunk-N4CZJQRK.js → chunk-5JGRAMKL.js} +4 -4
- package/dist/{chunk-BRXQQJFP.js → chunk-6US73PDB.js} +568 -47
- package/dist/{chunk-K6WL7QZT.js → chunk-6XXKFVSN.js} +2 -2
- package/dist/{chunk-QQK64KLB.js → chunk-CJUB2JJ2.js} +138 -20
- package/dist/{chunk-HV5X7OR2.js → chunk-CKXWIANG.js} +12 -12
- package/dist/{chunk-UZHIZC5S.js → chunk-CYQKWTG3.js} +61 -76
- package/dist/{chunk-QWU7ZBO7.js → chunk-DJVECN66.js} +204 -45
- package/dist/{chunk-ZWMF7253.js → chunk-E2ER4LJF.js} +304 -9
- package/dist/{chunk-7RXG6QRZ.js → chunk-EKY57CSP.js} +2 -75
- package/dist/{chunk-EDRHSCIE.js → chunk-EYPA3EGJ.js} +10 -2
- package/dist/{chunk-TTNYS3EA.js → chunk-G7MUEIGA.js} +1 -1
- package/dist/{chunk-BPGS2WCQ.js → chunk-GEYXPTRF.js} +2 -1
- package/dist/{chunk-BEY543CS.js → chunk-GOXNB3AO.js} +5 -2
- package/dist/{chunk-G4BMMOKF.js → chunk-HVDIIIQW.js} +2 -2
- package/dist/chunk-HWUFFB6L.js +83 -0
- package/dist/{chunk-35MKKU5R.js → chunk-K7T3E2SR.js} +15 -8
- package/dist/{chunk-VAWWTKDP.js → chunk-KHSFENX2.js} +2 -2
- package/dist/chunk-LCGCVYZ4.js +57 -0
- package/dist/{chunk-X6COSD2O.js → chunk-LYF7OHWH.js} +41 -14
- package/dist/{chunk-POHLU5DW.js → chunk-M6L6IDJG.js} +3 -3
- package/dist/{chunk-X4RCMKVQ.js → chunk-NDINPTJ4.js} +2 -2
- package/dist/{chunk-5M54SPOL.js → chunk-ODFEOB4F.js} +161 -5
- package/dist/{chunk-3JLKSKD7.js → chunk-OH3TOQTB.js} +5 -1
- package/dist/{chunk-MEQ45TQ4.js → chunk-PBTHKCPN.js} +18 -4
- package/dist/{chunk-ED4KHGC3.js → chunk-PPAMZ32Z.js} +9 -2
- package/dist/{chunk-QQL5RT5M.js → chunk-QM3F2GKX.js} +94 -36
- package/dist/{chunk-A2GZF7DC.js → chunk-QNQHSOLF.js} +4 -4
- package/dist/{chunk-KRPY7NTG.js → chunk-R46L2BIR.js} +3 -3
- package/dist/{chunk-BP4OYD6A.js → chunk-RY3LY4J5.js} +20 -2
- package/dist/{chunk-34475P3I.js → chunk-TSHXZTOQ.js} +5 -4
- package/dist/{chunk-VJWL6YS5.js → chunk-UUVG37B4.js} +2 -2
- package/dist/{chunk-2TZWSW76.js → chunk-WHGPSPT5.js} +2 -2
- package/dist/{chunk-TW3WDMVS.js → chunk-WHJYKASB.js} +2 -2
- package/dist/{chunk-YHZX5GEU.js → chunk-XAKHZX5N.js} +2 -2
- package/dist/{chunk-HXG4IURW.js → chunk-XE2VEJHX.js} +2 -2
- package/dist/{chunk-3HZ5RWN2.js → chunk-XF5N4U5A.js} +7 -6
- package/dist/{chunk-ZYKPLLNQ.js → chunk-XXQNGV4M.js} +590 -32
- package/dist/{chunk-4JUF2NNX.js → chunk-XYDYPRZI.js} +4 -4
- package/dist/{chunk-VMNQ6OZA.js → chunk-ZRGEBJ4T.js} +971 -794
- package/dist/{chunk-2LZI5CAG.js → chunk-ZXF4XRKW.js} +75 -33
- package/dist/{chunk-VSNATDE6.js → chunk-ZZMN5OM4.js} +2 -2
- package/dist/cli/index.js +31 -31
- package/dist/{clio-J5JIOIDS.js → clio-M2KGYUFZ.js} +2 -2
- package/dist/{code-nav-AXCXSBHX.js → code-nav-GQNL7XA6.js} +5 -5
- package/dist/codewiki/build-worker.js +4 -4
- package/dist/{components-KELWS457.js → components-5TTYYX6G.js} +3 -3
- package/dist/{config-OEBMIN2U.js → config-XUUYQIWO.js} +27 -25
- package/dist/{configure-PUQOSIXQ.js → configure-IHJ7YOMV.js} +7 -7
- package/dist/{context-URSXPBCK.js → context-74JLXAWD.js} +12 -12
- package/dist/{context-MGSE4Z2T.js → context-75MIWW3U.js} +24 -22
- package/dist/{context-EKDCKUUZ.js → context-ZQ7SIFJV.js} +8 -7
- package/dist/{context-clear-KDAJRNUK.js → context-clear-GYKWNUML.js} +24 -22
- package/dist/{context-index-BZ4UYMTC.js → context-index-SSR5ECNE.js} +3 -3
- package/dist/{context-working-set-SBKMPPI2.js → context-working-set-UX5KEP4J.js} +11 -10
- package/dist/{dispatch-runner-MSWN72NK.js → dispatch-runner-GIJBHNFL.js} +21 -20
- package/dist/{docs-2C2LTVT2.js → docs-6FZSCG5B.js} +3 -3
- package/dist/{doctor-7BSE27PJ.js → doctor-SVJ5BZCW.js} +4 -4
- package/dist/{eval-IZGDOO4H.js → eval-CG6LLBLD.js} +47 -232
- package/dist/{evidence-SR7WXB5B.js → evidence-ZYFIEN42.js} +19 -18
- package/dist/{evolve-K7VE2CBX.js → evolve-QGEXEMDW.js} +19 -18
- package/dist/{extensions-QVDOHDGJ.js → extensions-ADGNCJJD.js} +3 -3
- package/dist/{fleet-7XMJNQNF.js → fleet-S5R4ZOQY.js} +49 -30
- package/dist/{fleet-preflight-AQNAH644.js → fleet-preflight-BHSNPBMH.js} +2 -2
- package/dist/{init-JGNPAYXT.js → init-5DRU55YR.js} +31 -29
- package/dist/memory-7YKKR6UC.js +467 -0
- package/dist/{models-ZMMLFJNN.js → models-ZPOLRU2C.js} +10 -10
- package/dist/{monitor-2F3T5KHP.js → monitor-US5F5YGZ.js} +33 -18
- package/dist/{orchestrator-ORHT43JB.js → orchestrator-E2AL4T5N.js} +1092 -659
- package/dist/{paths-UXLN5YYZ.js → paths-E7KYAQWE.js} +3 -3
- package/dist/{reset-NXGTYNUO.js → reset-KZ652EK6.js} +3 -3
- package/dist/{run-RF4WJGMT.js → run-SRNBKDWD.js} +52 -40
- package/dist/{share-UT3W6E4M.js → share-CGZE33UP.js} +3 -3
- package/dist/{skills-PSACKC5Q.js → skills-S2X4DLY5.js} +4 -4
- package/dist/{skills-eval-WJSI55RZ.js → skills-eval-W2GGIC4R.js} +19 -18
- package/dist/{targets-PIIRAOYS.js → targets-54SWINWB.js} +14 -12
- package/dist/{terminal-lease-ULWXWNVY.js → terminal-lease-SAIF2OGY.js} +5 -4
- package/dist/{uninstall-FZCQCDKC.js → uninstall-BVLWXKBT.js} +3 -3
- package/dist/{upgrade-346TZ6AV.js → upgrade-JKAR27XC.js} +8 -8
- package/dist/{usage-6KKXR32N.js → usage-MSAWCLX4.js} +60 -27
- package/dist/{verifiers-4UUM6TEE.js → verifiers-NCBTHHN2.js} +60 -54
- package/dist/{wiki-generate-7STOCIFZ.js → wiki-generate-GUSOQ6ZP.js} +30 -28
- package/dist/worker/entry.js +69 -58
- package/dist/{workspace-G4ZWUIPR.js → workspace-ZJ6BFM3Q.js} +4 -4
- package/docs/README.md +3 -3
- package/docs/acp.md +1 -1
- package/docs/alcf-provider.md +1 -1
- package/docs/architecture.md +2 -2
- package/docs/artifact-placement.md +1 -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 +9 -7
- package/docs/configuration-and-targets.md +12 -1
- package/docs/context-engine.md +4 -2
- package/docs/context-working-set.md +4 -4
- package/docs/development-pipeline.md +1 -1
- package/docs/documentation-coverage.md +3 -3
- package/docs/documentation-guide.md +2 -2
- package/docs/eval-runner.md +1 -1
- package/docs/evals-internal.md +4 -45
- package/docs/evidence-and-memory.md +67 -7
- 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 +28 -2
- package/docs/installation-and-lifecycle.md +2 -2
- package/docs/middleware-and-components.md +19 -2
- package/docs/model-catalog.md +1 -1
- package/docs/observability.md +3 -3
- package/docs/proactive-memory.md +26 -16
- package/docs/prompt-envelope-and-tools.md +4 -2
- package/docs/provider-adapter-cookbook.md +1 -1
- package/docs/release-cut-checklist.md +38 -35
- package/docs/safety-model.md +29 -7
- package/docs/scientific-validation.md +3 -3
- package/docs/session-lifecycle.md +1 -1
- package/docs/skills-marketplace.md +1 -1
- package/docs/tool-usage.md +2 -2
- package/docs/trace-store.md +1 -1
- package/docs/troubleshooting.md +1 -1
- package/docs/tui-design.md +38 -4
- package/docs/worker-dispatch-mechanics.md +1 -1
- package/package.json +7 -4
- package/src/cli/agents.ts +2 -3
- package/src/cli/argv.ts +14 -1
- package/src/cli/fleet.ts +15 -0
- package/src/cli/index.ts +1 -1
- package/src/cli/memory.ts +272 -10
- package/src/cli/modes/json-stream.ts +2 -2
- package/src/cli/modes/print.ts +12 -1
- package/src/cli/run.ts +22 -2
- package/src/cli/targets.ts +12 -3
- package/src/cli/usage.ts +55 -7
- package/src/core/bus-events.ts +3 -0
- package/src/core/response-model-id.ts +134 -0
- package/src/core/toml.ts +62 -0
- package/src/core/workspace-files.ts +0 -1
- package/src/domains/agents/builtins/architect.md +1 -1
- package/src/domains/agents/catalog.ts +5 -4
- package/src/domains/agents/recipe.ts +54 -14
- package/src/domains/agents/result-contract.ts +7 -4
- package/src/domains/context/bootstrap.ts +36 -27
- package/src/domains/context/project-metadata.ts +19 -63
- package/src/domains/context/prompt-context.ts +8 -0
- package/src/domains/context/working-set/policies/index.ts +3 -4
- package/src/domains/dispatch/budget-envelope.ts +396 -0
- package/src/domains/dispatch/contract.ts +2 -0
- package/src/domains/dispatch/extension.ts +81 -27
- package/src/domains/dispatch/orphan-recovery.ts +1 -0
- package/src/domains/dispatch/receipt-integrity.ts +4 -0
- package/src/domains/dispatch/state.ts +1 -0
- package/src/domains/dispatch/types.ts +10 -3
- package/src/domains/dispatch/validation.ts +14 -0
- package/src/domains/dispatch/worker-spawn.ts +14 -3
- package/src/domains/eval/metrics/evidence.ts +0 -116
- package/src/domains/eval/metrics/invariants.ts +1 -1
- package/src/domains/eval/runners/clio-run.ts +1 -10
- package/src/domains/eval/runners/external-command.ts +2 -29
- package/src/domains/eval/schema/suite.ts +0 -7
- package/src/domains/eval/suites/run.ts +1 -7
- package/src/domains/memory/index.ts +22 -0
- package/src/domains/memory/operations.ts +58 -1
- package/src/domains/memory/promotion.ts +281 -0
- package/src/domains/memory/prompt-section.ts +25 -5
- package/src/domains/memory/proposal.ts +51 -7
- package/src/domains/memory/task-bank.ts +3 -2
- package/src/domains/memory/task-memory-handoff.ts +181 -24
- package/src/domains/memory/task-memory-policy.ts +3 -1
- package/src/domains/memory/types.ts +37 -0
- package/src/domains/memory/validate.ts +178 -0
- package/src/domains/middleware/memory-intervention.ts +35 -25
- package/src/domains/middleware/runtime.ts +6 -0
- package/src/domains/middleware/skills-reminder.ts +19 -4
- package/src/domains/middleware/stalled-turn.ts +43 -1
- package/src/domains/middleware/types.ts +10 -0
- package/src/domains/observability/contract.ts +6 -1
- package/src/domains/observability/cost.ts +20 -4
- package/src/domains/observability/extension.ts +2 -2
- package/src/domains/providers/index.ts +3 -0
- package/src/domains/providers/model-discovery.ts +9 -0
- package/src/domains/providers/runtime-resolution.ts +38 -1
- package/src/domains/providers/runtimes/common/probe-helpers.ts +97 -16
- package/src/domains/providers/types/context-window-slots.ts +18 -0
- package/src/domains/providers/types/runtime-descriptor.ts +3 -1
- package/src/domains/safety/call-target.ts +211 -14
- package/src/domains/safety/decision-presentation.ts +268 -0
- package/src/domains/safety/redaction.ts +73 -0
- package/src/domains/session/context-ledger.ts +10 -1
- package/src/domains/session/decision-board.ts +4 -0
- package/src/domains/session/entries.ts +3 -0
- package/src/domains/session/history.ts +68 -19
- package/src/domains/session/usage.ts +24 -7
- package/src/engine/acp/event-mapper.ts +7 -0
- package/src/engine/acp/server.ts +29 -2
- package/src/engine/apis/lmstudio.ts +25 -4
- package/src/engine/apis/openai-completions.ts +147 -22
- package/src/engine/claude/sdk-runtime.ts +8 -2
- package/src/engine/claude/tool-safety.ts +13 -0
- package/src/engine/loop-guard.ts +27 -3
- package/src/engine/worker-events.ts +4 -3
- package/src/engine/worker-runtime.ts +59 -54
- package/src/entry/orchestrator.ts +18 -1
- package/src/interactive/chat-loop-messages.ts +22 -0
- package/src/interactive/chat-loop.ts +13 -0
- package/src/interactive/chat-renderer.ts +19 -3
- package/src/interactive/clio-editor.ts +44 -7
- package/src/interactive/context-overlay.ts +43 -5
- package/src/interactive/cost-overlay.ts +39 -8
- package/src/interactive/dispatch-board.ts +212 -35
- package/src/interactive/footer/widgets.ts +13 -0
- package/src/interactive/interactive-application.ts +6 -1
- package/src/interactive/interactive-input-runtime.ts +11 -1
- package/src/interactive/interactive-presentation.ts +11 -1
- package/src/interactive/memory-overlay.ts +89 -4
- package/src/interactive/overlay-ask-user-lifecycle.ts +1 -1
- package/src/interactive/overlay-frame.ts +5 -2
- package/src/interactive/overlay-general-openers.ts +40 -1
- package/src/interactive/overlay-key-routing.ts +41 -1
- package/src/interactive/overlay-lifecycle.ts +11 -4
- package/src/interactive/overlay-permission-lifecycle.ts +23 -8
- package/src/interactive/overlay-transitions.ts +11 -0
- package/src/interactive/overlays/ask-user.ts +74 -30
- package/src/interactive/overlays/decisions.ts +3 -1
- package/src/interactive/permission-hint.ts +35 -0
- package/src/interactive/permission-overlay.ts +95 -45
- package/src/interactive/renderers/tool-execution.ts +19 -49
- package/src/interactive/session-last-turn.ts +8 -1
- package/src/interactive/session-usage-reseed.ts +36 -10
- package/src/interactive/slash-commands.ts +2 -2
- package/src/interactive/status/summary.ts +5 -0
- package/src/interactive/status/types.ts +5 -0
- package/src/interactive/terminal-lease.ts +1 -0
- package/src/interactive/turn-context.ts +96 -23
- package/src/interactive/turn-middleware.ts +1 -0
- package/src/interactive/turn-runtime.ts +37 -8
- package/src/interactive/turn-state.ts +3 -0
- package/src/interactive/worker-progress.ts +440 -0
- package/src/interactive/worker-stream.ts +51 -110
- package/src/tools/agent-tools.ts +28 -3
- package/src/tools/ask-user.ts +21 -1
- package/src/tools/context/index.ts +2 -2
- package/src/tools/dispatch-arguments.ts +8 -0
- package/src/tools/dispatch-event-text.ts +19 -0
- package/src/tools/dispatch.ts +24 -1
- package/src/tools/monitor.ts +15 -0
- package/src/tools/registry.ts +15 -5
- package/src/tools/result-disposition.ts +156 -0
- package/src/tools/result-shaping.ts +59 -1
- package/src/tools/verify/authoring.ts +55 -54
- package/src/tools/worker-evidence.ts +19 -0
- package/src/worker/spec-contract.ts +43 -3
- package/dist/chunk-EFADSJET.js +0 -18
- package/dist/memory-4ALKDJ4Q.js +0 -246
- package/src/domains/eval/metrics/chaos-stream.ts +0 -93
|
@@ -183,6 +183,7 @@ import { isGitRepository, recoverCleanupReadyCompeteGroups } from "../tools/comp
|
|
|
183
183
|
import { createDispatchBackgroundRegistry } from "../tools/dispatch-background.js";
|
|
184
184
|
import { createFileMutationObserver, createSkillActivationObserver } from "../tools/observers.js";
|
|
185
185
|
import { createRegistry } from "../tools/registry.js";
|
|
186
|
+
import { sweepExpiredToolOffloads } from "../tools/result-shaping.js";
|
|
186
187
|
|
|
187
188
|
export interface BootResult {
|
|
188
189
|
exitCode: number;
|
|
@@ -726,6 +727,7 @@ export async function bootOrchestrator(options: BootOptions = {}): Promise<BootR
|
|
|
726
727
|
termination.installSignalHandlers();
|
|
727
728
|
|
|
728
729
|
ensureClioState();
|
|
730
|
+
sweepExpiredToolOffloads();
|
|
729
731
|
timer.mark("install check");
|
|
730
732
|
|
|
731
733
|
// A hard-killed coordinator cannot run domain drains. Reconcile its compete
|
|
@@ -1553,8 +1555,13 @@ export async function bootOrchestrator(options: BootOptions = {}): Promise<BootR
|
|
|
1553
1555
|
getMemorySection: () => {
|
|
1554
1556
|
try {
|
|
1555
1557
|
const records = loadMemoryRecordsSync(clioDataDir());
|
|
1558
|
+
const settings = getCurrentSettings();
|
|
1559
|
+
const targetId = session?.current()?.target ?? settings.orchestrator?.target;
|
|
1560
|
+
const runtimeId = targetId ? providers.getTarget(targetId)?.runtime : undefined;
|
|
1556
1561
|
return buildMemoryPromptSection(records, {
|
|
1562
|
+
scopes: ["global", "repo", "runtime"],
|
|
1557
1563
|
activeRepository: canonicalMemoryRepositoryIdentity(process.cwd()),
|
|
1564
|
+
activeRuntime: runtimeId === undefined ? null : { kind: "runtime", key: runtimeId },
|
|
1558
1565
|
}).section;
|
|
1559
1566
|
} catch {
|
|
1560
1567
|
return "";
|
|
@@ -1562,7 +1569,17 @@ export async function bootOrchestrator(options: BootOptions = {}): Promise<BootR
|
|
|
1562
1569
|
},
|
|
1563
1570
|
getTaskMemoryHandoffSource: () => {
|
|
1564
1571
|
ensureTaskMemorySession();
|
|
1565
|
-
|
|
1572
|
+
const meta = session?.current();
|
|
1573
|
+
if (!meta) throw new Error("task memory handoff requires an active session");
|
|
1574
|
+
const settings = getCurrentSettings();
|
|
1575
|
+
const targetId = meta.target ?? settings.orchestrator?.target;
|
|
1576
|
+
const runtimeId = targetId ? providers.getTarget(targetId)?.runtime : undefined;
|
|
1577
|
+
return renderTaskMemoryHandoffSource(taskMemoryBank.snapshot(), {
|
|
1578
|
+
sessionId: meta.id,
|
|
1579
|
+
evidenceRefs: [`session-${meta.id}`],
|
|
1580
|
+
runtimeIds: runtimeId === undefined ? [] : [runtimeId],
|
|
1581
|
+
agentIds: [],
|
|
1582
|
+
});
|
|
1566
1583
|
},
|
|
1567
1584
|
registerDeferredReminderSink: (sink) => {
|
|
1568
1585
|
deferredMemoryReminderSink = sink;
|
|
@@ -7,6 +7,13 @@
|
|
|
7
7
|
|
|
8
8
|
import { randomUUID } from "node:crypto";
|
|
9
9
|
import { settingsPath } from "../core/config.js";
|
|
10
|
+
import {
|
|
11
|
+
addResponseModelIdObservationCount,
|
|
12
|
+
emptyResponseModelIdObservationCounts,
|
|
13
|
+
type ResponseModelIdObservation,
|
|
14
|
+
type ResponseModelIdObservationCounts,
|
|
15
|
+
responseModelIdObservationFromRecord,
|
|
16
|
+
} from "../core/response-model-id.js";
|
|
10
17
|
import type { PendingSkillRequest, PendingSkillToolPolicy, SkillDeclaredToolPolicy } from "../core/skill-activation.js";
|
|
11
18
|
import { ToolNames } from "../core/tool-names.js";
|
|
12
19
|
import { canonicalJson, sha256 } from "../domains/prompts/hash.js";
|
|
@@ -334,6 +341,7 @@ export function createAskUserToolPolicy(activeTools: ReadonlyArray<{ name: strin
|
|
|
334
341
|
status: "idle",
|
|
335
342
|
startedAt: now,
|
|
336
343
|
updatedAt: now,
|
|
344
|
+
exposure: "local",
|
|
337
345
|
rounds: [],
|
|
338
346
|
decisions: [],
|
|
339
347
|
inFlight: false,
|
|
@@ -356,6 +364,9 @@ export interface RunUsageSummary {
|
|
|
356
364
|
apiCalls: number;
|
|
357
365
|
hadReasoning: boolean;
|
|
358
366
|
hadUsage: boolean;
|
|
367
|
+
responseModelIdObservationCounts: ResponseModelIdObservationCounts;
|
|
368
|
+
lastResponseModelIdObservation: ResponseModelIdObservation;
|
|
369
|
+
lastDifferingResponseModelId: string | null;
|
|
359
370
|
}
|
|
360
371
|
|
|
361
372
|
/**
|
|
@@ -380,6 +391,9 @@ export function sumRunUsage(messages: ReadonlyArray<AgentMessage>): RunUsageSumm
|
|
|
380
391
|
apiCalls: 0,
|
|
381
392
|
hadReasoning: false,
|
|
382
393
|
hadUsage: false,
|
|
394
|
+
responseModelIdObservationCounts: emptyResponseModelIdObservationCounts(),
|
|
395
|
+
lastResponseModelIdObservation: { state: "not-observed" },
|
|
396
|
+
lastDifferingResponseModelId: null,
|
|
383
397
|
};
|
|
384
398
|
for (const raw of messages) {
|
|
385
399
|
const message = raw as
|
|
@@ -397,8 +411,15 @@ export function sumRunUsage(messages: ReadonlyArray<AgentMessage>): RunUsageSumm
|
|
|
397
411
|
};
|
|
398
412
|
if (!message || typeof message !== "object") continue;
|
|
399
413
|
if (message.role !== "assistant") continue;
|
|
414
|
+
const record = message as unknown as Record<string, unknown>;
|
|
400
415
|
const usage = message.usage;
|
|
401
416
|
if (!usage || typeof usage !== "object") continue;
|
|
417
|
+
summary.lastResponseModelIdObservation = responseModelIdObservationFromRecord(record, "not-observed");
|
|
418
|
+
addResponseModelIdObservationCount(summary.responseModelIdObservationCounts, summary.lastResponseModelIdObservation);
|
|
419
|
+
summary.lastDifferingResponseModelId =
|
|
420
|
+
typeof record.responseModel === "string" && record.responseModel.trim().length > 0
|
|
421
|
+
? record.responseModel.trim()
|
|
422
|
+
: null;
|
|
402
423
|
summary.hadUsage = true;
|
|
403
424
|
summary.apiCalls += 1;
|
|
404
425
|
const input = typeof usage.input === "number" ? usage.input : 0;
|
|
@@ -459,6 +480,7 @@ export function assistantSessionPayload(
|
|
|
459
480
|
const raw = message as unknown as Record<string, unknown>;
|
|
460
481
|
if (Array.isArray(raw.content)) payload.content = raw.content;
|
|
461
482
|
if (thinking.length > 0) payload.thinking = thinking;
|
|
483
|
+
payload.responseModelIdObservation = responseModelIdObservationFromRecord(raw, "not-observed");
|
|
462
484
|
for (const key of ["usage", "api", "provider", "model", "responseModel", "responseId", "diagnostics"]) {
|
|
463
485
|
if (raw[key] !== undefined) payload[key] = raw[key];
|
|
464
486
|
}
|
|
@@ -549,6 +549,12 @@ export function createChatLoop(deps: CreateChatLoopDeps): ChatLoop {
|
|
|
549
549
|
emitNotice,
|
|
550
550
|
toolStartTimes,
|
|
551
551
|
});
|
|
552
|
+
// A fresh ledger and footer render before the first submit. Start the same
|
|
553
|
+
// live capability probe that submit and resume use so those boot surfaces
|
|
554
|
+
// refresh to the probed model window as soon as provider discovery lands.
|
|
555
|
+
// The turn runtime coalesces the first submit onto this request, then its
|
|
556
|
+
// target and model TTL keeps later submits from repeating it.
|
|
557
|
+
void turnRuntime.ensureLiveCapabilitiesForSelectedModel().catch(() => {});
|
|
552
558
|
|
|
553
559
|
// --- bus subscriptions --------------------------------------------------
|
|
554
560
|
|
|
@@ -664,6 +670,7 @@ export function createChatLoop(deps: CreateChatLoopDeps): ChatLoop {
|
|
|
664
670
|
// 1. Accept the prompt: reset per-turn accounting, freeze the tool
|
|
665
671
|
// surface, fire turn_start, and assemble the submitted text.
|
|
666
672
|
state.turnToolCalls = 0;
|
|
673
|
+
state.turnToolNames = [];
|
|
667
674
|
state.turnSharedWorkerNote = isWorkerShareNote(text);
|
|
668
675
|
middlewareToolChoice.reset();
|
|
669
676
|
if (options.requestContinuation !== true) state.stalledTurnNudgeSpent = false;
|
|
@@ -988,6 +995,12 @@ export function createChatLoop(deps: CreateChatLoopDeps): ChatLoop {
|
|
|
988
995
|
middlewareToolChoice.reset();
|
|
989
996
|
state.lastTurnId = leafTurnId;
|
|
990
997
|
context.resetForSession();
|
|
998
|
+
// The resumed ledger renders before the first new turn (issue #189),
|
|
999
|
+
// and the window it renders against should be the probed one rather
|
|
1000
|
+
// than the catalog's, so the live capability probe the first submit
|
|
1001
|
+
// would run runs now, under the same TTL; the footer refreshes on the
|
|
1002
|
+
// provider-health event the probe publishes.
|
|
1003
|
+
void turnRuntime.ensureLiveCapabilitiesForSelectedModel().catch(() => {});
|
|
991
1004
|
state.replayedContextMessages = replayMessages ? [...replayMessages] : [];
|
|
992
1005
|
if (state.runtime) {
|
|
993
1006
|
state.runtime.agent.state.messages = [...state.replayedContextMessages];
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
* `isTransparentAssistantWrapper`.
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
+
import { existsSync } from "node:fs";
|
|
16
17
|
import { ToolNames } from "../core/tool-names.js";
|
|
17
18
|
import { foldWorkingSet } from "../domains/context/working-set/fold.js";
|
|
18
19
|
import { compactionCut } from "../domains/context/working-set/visible.js";
|
|
@@ -44,7 +45,7 @@ import type { AgentMessage } from "../engine/types.js";
|
|
|
44
45
|
import { toolPresentationPolicy } from "../tools/presentation.js";
|
|
45
46
|
import { toolResultPresentationText } from "../tools/result-disposition.js";
|
|
46
47
|
import type { ChatLoopEvent, RetryStatusPayload } from "./chat-loop.js";
|
|
47
|
-
import { isSelfExplainingAbort } from "./chat-loop-messages.js";
|
|
48
|
+
import { isSelfExplainingAbort, toolResultSummary } from "./chat-loop-messages.js";
|
|
48
49
|
import type { ChatPanel } from "./chat-panel.js";
|
|
49
50
|
import { renderBranchSummaryEntry } from "./renderers/branch-summary.js";
|
|
50
51
|
import { renderCompactionSummaryEntry } from "./renderers/compaction-summary.js";
|
|
@@ -440,6 +441,7 @@ function richMessageFromEntry(entry: MessageEntry, maxTextChars?: number): Agent
|
|
|
440
441
|
"api",
|
|
441
442
|
"provider",
|
|
442
443
|
"model",
|
|
444
|
+
"responseModelIdObservation",
|
|
443
445
|
"responseModel",
|
|
444
446
|
"responseId",
|
|
445
447
|
"diagnostics",
|
|
@@ -671,6 +673,19 @@ interface ReplayToolResult {
|
|
|
671
673
|
blockReason?: string;
|
|
672
674
|
}
|
|
673
675
|
|
|
676
|
+
/**
|
|
677
|
+
* Resolve a replayed offload pointer once, when the ledger row becomes a
|
|
678
|
+
* display event. Terminal repaints consume the recorded fact and never touch
|
|
679
|
+
* the filesystem.
|
|
680
|
+
*/
|
|
681
|
+
function replayResultSummary(result: ReplayToolResult): Record<string, unknown> | undefined {
|
|
682
|
+
const summary = result.resultSummary ?? toolResultSummary(result.result);
|
|
683
|
+
const offloadPath =
|
|
684
|
+
typeof summary.offloadPath === "string" && summary.offloadPath.length > 0 ? summary.offloadPath : null;
|
|
685
|
+
if (offloadPath === null) return result.resultSummary;
|
|
686
|
+
return { ...summary, offloadFileMissing: !existsSync(offloadPath) };
|
|
687
|
+
}
|
|
688
|
+
|
|
674
689
|
function extractToolResult(entry: MessageEntry): ReplayToolResult {
|
|
675
690
|
const payload = entry.payload;
|
|
676
691
|
const obj = payloadObject(payload);
|
|
@@ -1131,6 +1146,7 @@ export function rehydrateChatPanelFromTurns(
|
|
|
1131
1146
|
}
|
|
1132
1147
|
if (entry.role === "tool_result") {
|
|
1133
1148
|
const result = extractToolResult(entry);
|
|
1149
|
+
const resultSummary = replayResultSummary(result);
|
|
1134
1150
|
const evictedReason = workingSet.evicted.get(entry.turnId)?.reason;
|
|
1135
1151
|
const fallbackId = result.id ?? pendingToolIds.pop() ?? null;
|
|
1136
1152
|
if (fallbackId) {
|
|
@@ -1143,7 +1159,7 @@ export function rehydrateChatPanelFromTurns(
|
|
|
1143
1159
|
result: displayReplayToolResult(result.result, options.unboundedToolBodies === true),
|
|
1144
1160
|
isError: result.isError,
|
|
1145
1161
|
...(result.durationMs !== undefined ? { durationMs: result.durationMs } : {}),
|
|
1146
|
-
...(
|
|
1162
|
+
...(resultSummary !== undefined ? { resultSummary } : {}),
|
|
1147
1163
|
...(result.outcome !== undefined ? { outcome: result.outcome } : {}),
|
|
1148
1164
|
...(result.blockReason !== undefined ? { blockReason: result.blockReason } : {}),
|
|
1149
1165
|
...(evictedReason !== undefined ? { evictedReason } : {}),
|
|
@@ -1157,7 +1173,7 @@ export function rehydrateChatPanelFromTurns(
|
|
|
1157
1173
|
result: displayReplayToolResult(result.result, options.unboundedToolBodies === true),
|
|
1158
1174
|
isError: result.isError,
|
|
1159
1175
|
...(result.durationMs !== undefined ? { durationMs: result.durationMs } : {}),
|
|
1160
|
-
...(
|
|
1176
|
+
...(resultSummary !== undefined ? { resultSummary } : {}),
|
|
1161
1177
|
...(result.outcome === "blocked" ? { outcome: "blocked" as const } : {}),
|
|
1162
1178
|
...(result.blockReason !== undefined ? { blockReason: result.blockReason } : {}),
|
|
1163
1179
|
...(evictedReason !== undefined ? { evictedReason } : {}),
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { Editor, stripTerminalSequences, type TUI, truncateToWidth, visibleWidth } from "../engine/tui.js";
|
|
2
|
+
import { fitHintEntries } from "./overlay-frame.js";
|
|
3
|
+
import { permissionHintEntries } from "./permission-hint.js";
|
|
2
4
|
import type { ClioTheme } from "./theme/index.js";
|
|
3
5
|
import { clioTheme, editorTheme, GLYPH, rule } from "./theme/index.js";
|
|
4
6
|
|
|
5
7
|
const REVERSE_VIDEO_BLANK = `${String.fromCharCode(27)}[7m ${String.fromCharCode(27)}[0m`;
|
|
6
8
|
const EMPTY_PROMPT = "Ask Clio… / for commands";
|
|
9
|
+
const CONFIRM_PROMPT = "A parked call is waiting for your decision";
|
|
7
10
|
const MIN_HINT_WIDTH = 60;
|
|
8
11
|
|
|
9
12
|
function hasScrollIndicator(line: string): boolean {
|
|
@@ -18,6 +21,13 @@ export interface EditorChrome {
|
|
|
18
21
|
getThinkingLabel: () => string;
|
|
19
22
|
/** Whether Enter currently targets the active Clio response. */
|
|
20
23
|
isStreaming?: () => boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Whether a permission prompt owns the keyboard. The dialog once sat at the
|
|
26
|
+
* vertical center of a tall viewport, far from the composer. It now anchors
|
|
27
|
+
* above that composer, while the rail still says CONFIRM and carries the
|
|
28
|
+
* dialog's keys whenever the prompt owns input (issues #186 and #194).
|
|
29
|
+
*/
|
|
30
|
+
isAwaitingApproval?: () => boolean;
|
|
21
31
|
/** Whether the current draft will actually steer Clio or live dispatch work on Enter. */
|
|
22
32
|
willEnterSteer?: (text: string) => boolean;
|
|
23
33
|
/** Resolved submit binding, formatted for display. */
|
|
@@ -26,7 +36,7 @@ export interface EditorChrome {
|
|
|
26
36
|
getNewlineKeyLabel?: () => string;
|
|
27
37
|
}
|
|
28
38
|
|
|
29
|
-
type ComposerMode = "MESSAGE" | "FOLLOW-UP" | "STEER";
|
|
39
|
+
type ComposerMode = "MESSAGE" | "FOLLOW-UP" | "STEER" | "CONFIRM";
|
|
30
40
|
|
|
31
41
|
function normalizeThinkingHint(value: string): string {
|
|
32
42
|
return value
|
|
@@ -61,6 +71,7 @@ function styledRailLabel(theme: ClioTheme, chrome: EditorChrome): string {
|
|
|
61
71
|
}
|
|
62
72
|
|
|
63
73
|
function composerMode(chrome: EditorChrome, text: string): ComposerMode {
|
|
74
|
+
if (chrome.isAwaitingApproval?.() ?? false) return "CONFIRM";
|
|
64
75
|
if (!(chrome.isStreaming?.() ?? false)) return "MESSAGE";
|
|
65
76
|
const willSteer = chrome.willEnterSteer?.(text) ?? text.trim().length > 0;
|
|
66
77
|
return willSteer ? "STEER" : "FOLLOW-UP";
|
|
@@ -73,12 +84,27 @@ function lowerRailHint(theme: ClioTheme, chrome: EditorChrome): string {
|
|
|
73
84
|
);
|
|
74
85
|
}
|
|
75
86
|
|
|
76
|
-
|
|
87
|
+
/**
|
|
88
|
+
* The permission keys on the composer rail, fitted like the dialog footer so
|
|
89
|
+
* both surfaces narrow in the same order and never drop allow or stop first.
|
|
90
|
+
* The rule spends three columns around a right label, hence the subtraction.
|
|
91
|
+
*/
|
|
92
|
+
function confirmRailHint(theme: ClioTheme, width: number, hasDraft: boolean): string {
|
|
93
|
+
return theme.fg("warning", fitHintEntries(permissionHintEntries(hasDraft), Math.max(1, width - 3)));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function modeToken(mode: ComposerMode): "action" | "accentDeep" | "warning" {
|
|
97
|
+
if (mode === "STEER") return "action";
|
|
98
|
+
if (mode === "CONFIRM") return "warning";
|
|
99
|
+
return "accentDeep";
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function renderEmptyPrompt(line: string, width: number, theme: ClioTheme, text = EMPTY_PROMPT): string {
|
|
77
103
|
const cursorAt = line.indexOf(REVERSE_VIDEO_BLANK);
|
|
78
104
|
if (cursorAt < 0) return line;
|
|
79
105
|
const afterCursorAt = cursorAt + REVERSE_VIDEO_BLANK.length;
|
|
80
106
|
const available = Math.max(0, width - 1);
|
|
81
|
-
const prompt = truncateToWidth(theme.fg("dim",
|
|
107
|
+
const prompt = truncateToWidth(theme.fg("dim", text), available, "…", false);
|
|
82
108
|
const consumed = visibleWidth(prompt);
|
|
83
109
|
return `${line.slice(0, afterCursorAt)}${prompt}${line.slice(afterCursorAt + consumed)}`;
|
|
84
110
|
}
|
|
@@ -111,12 +137,12 @@ export class ClioEditor extends Editor {
|
|
|
111
137
|
// The base editor has already fitted the scroll indicator to this width.
|
|
112
138
|
// Prefix the mode and trim only the indicator rail's trailing fill, keeping
|
|
113
139
|
// its direction/count text and its own narrow-width fallback intact.
|
|
114
|
-
const modeLabel = theme.style(mode
|
|
140
|
+
const modeLabel = theme.style(modeToken(mode), mode, { bold: true });
|
|
115
141
|
lines[0] = truncateToWidth(`${modeLabel} ${lines[0] ?? ""}`, safeWidth, "", true);
|
|
116
142
|
} else {
|
|
117
143
|
lines[0] = rule(theme, safeWidth, {
|
|
118
144
|
left: mode,
|
|
119
|
-
leftToken: mode
|
|
145
|
+
leftToken: modeToken(mode),
|
|
120
146
|
right: styledRailLabel(theme, this.chrome),
|
|
121
147
|
fillToken: "frameStrong",
|
|
122
148
|
rightRaw: true,
|
|
@@ -124,10 +150,21 @@ export class ClioEditor extends Editor {
|
|
|
124
150
|
});
|
|
125
151
|
}
|
|
126
152
|
|
|
127
|
-
if (text.length === 0 && lines[1])
|
|
153
|
+
if (text.length === 0 && lines[1]) {
|
|
154
|
+
lines[1] = renderEmptyPrompt(lines[1], safeWidth, theme, mode === "CONFIRM" ? CONFIRM_PROMPT : EMPTY_PROMPT);
|
|
155
|
+
}
|
|
128
156
|
|
|
129
157
|
const bottomRail = findBottomRail(lines, safeWidth);
|
|
130
|
-
|
|
158
|
+
// The confirm keys render at every width: the send hint is a convenience
|
|
159
|
+
// that a narrow composer can drop, the allow and deny keys are not.
|
|
160
|
+
if (bottomRail >= 0 && mode === "CONFIRM") {
|
|
161
|
+
lines[bottomRail] = rule(theme, safeWidth, {
|
|
162
|
+
right: confirmRailHint(theme, safeWidth, text.length > 0),
|
|
163
|
+
fillToken: "frameStrong",
|
|
164
|
+
rightRaw: true,
|
|
165
|
+
rightTail: theme.style("frameStrong", "─", { bold: true }),
|
|
166
|
+
});
|
|
167
|
+
} else if (bottomRail >= 0 && safeWidth >= MIN_HINT_WIDTH) {
|
|
131
168
|
lines[bottomRail] = rule(theme, safeWidth, {
|
|
132
169
|
right: lowerRailHint(theme, this.chrome),
|
|
133
170
|
fillToken: "frameStrong",
|
|
@@ -3,6 +3,7 @@ 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
5
|
import type { WorkingSetView } from "../domains/context/working-set/contract.js";
|
|
6
|
+
import { formatContextWindowSlots } from "../domains/providers/index.js";
|
|
6
7
|
import type { ContextLedger, ContextLedgerGroup } from "../domains/session/context-ledger.js";
|
|
7
8
|
import { type OverlayHandle, Text, type TUI, visibleWidth } from "../engine/tui.js";
|
|
8
9
|
import { contextCategorySwatch, renderContextMeterGrid, renderEvictedTokensLine } from "./context-meter.js";
|
|
@@ -112,12 +113,34 @@ function coldReasonLabel(reason: string): string {
|
|
|
112
113
|
}
|
|
113
114
|
}
|
|
114
115
|
|
|
116
|
+
/** The operator's working-set configuration, as `/context` states it. */
|
|
117
|
+
export interface WorkingSetConfigView {
|
|
118
|
+
enabled: boolean;
|
|
119
|
+
policy: string;
|
|
120
|
+
}
|
|
121
|
+
|
|
115
122
|
/**
|
|
116
123
|
* The working-set section: what the projection has taken out of the window
|
|
117
124
|
* and how often the model has asked for it back. Churn is recalls over items
|
|
118
125
|
* evicted; a high number means the policy evicts what is still needed.
|
|
119
126
|
*/
|
|
120
|
-
|
|
127
|
+
/**
|
|
128
|
+
* The policy line names what is configured, not what last ran. `lastPolicyId`
|
|
129
|
+
* is stamped by the first applied event, and eviction runs only when pressure
|
|
130
|
+
* crosses `compaction.threshold`, so a whole session at the shipped default
|
|
131
|
+
* read `policy none` (issue #190). With no configuration in hand (older
|
|
132
|
+
* callers) the last applied policy is still the best available claim.
|
|
133
|
+
*/
|
|
134
|
+
function workingSetPolicyLabel(view: WorkingSetView, config: WorkingSetConfigView | null | undefined): string {
|
|
135
|
+
if (config === null || config === undefined) return `policy ${view.lastPolicyId ?? "none"}`;
|
|
136
|
+
if (!config.enabled) return "disabled";
|
|
137
|
+
const state = view.evictionEvents === 0 ? " · no events yet" : "";
|
|
138
|
+
const lastRan =
|
|
139
|
+
view.lastPolicyId !== null && view.lastPolicyId !== config.policy ? ` (last event by ${view.lastPolicyId})` : "";
|
|
140
|
+
return `policy ${config.policy}${lastRan}${state}`;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function renderWorkingSetLines(view: WorkingSetView, config: WorkingSetConfigView | null | undefined): string[] {
|
|
121
144
|
const theme = clioTheme();
|
|
122
145
|
const items = view.evicted.size;
|
|
123
146
|
const summary = [
|
|
@@ -128,7 +151,7 @@ function renderWorkingSetLines(view: WorkingSetView): string[] {
|
|
|
128
151
|
`churn ${formatChurn(view)}`,
|
|
129
152
|
].join(" · ");
|
|
130
153
|
return [
|
|
131
|
-
`${theme.fg("muted", "working set")} ${theme.fg("dim", "·")} ${theme.fg("accent",
|
|
154
|
+
`${theme.fg("muted", "working set")} ${theme.fg("dim", "·")} ${theme.fg("accent", workingSetPolicyLabel(view, config))}`,
|
|
132
155
|
theme.fg("dim", summary),
|
|
133
156
|
];
|
|
134
157
|
}
|
|
@@ -137,6 +160,7 @@ export function renderContextLedgerLines(
|
|
|
137
160
|
ledger: ContextLedger,
|
|
138
161
|
contentWidth: number,
|
|
139
162
|
workingSet?: WorkingSetView | null,
|
|
163
|
+
workingSetConfig?: WorkingSetConfigView | null,
|
|
140
164
|
): string[] {
|
|
141
165
|
const theme = clioTheme();
|
|
142
166
|
const lines: string[] = [];
|
|
@@ -154,7 +178,14 @@ export function renderContextLedgerLines(
|
|
|
154
178
|
|
|
155
179
|
if (ledger.contextWindow > 0) {
|
|
156
180
|
const source = ledger.measured ? "measured" : "≈ estimated";
|
|
157
|
-
|
|
181
|
+
// `196,608 (786,432 / 4 slots)`: a llama.cpp window that is one slot's
|
|
182
|
+
// share of the server's KV budget names the split, because the bare
|
|
183
|
+
// number reads as the whole server and the meter is measured against
|
|
184
|
+
// the share (issue #187).
|
|
185
|
+
const window = ledger.contextWindowSlots
|
|
186
|
+
? formatContextWindowSlots(ledger.contextWindow, ledger.contextWindowSlots)
|
|
187
|
+
: formatTokens(ledger.contextWindow);
|
|
188
|
+
const summary = `${formatTokens(ledger.usedTokens)} / ${window} tokens (${formatContextPercent(ledger.percent)})`;
|
|
158
189
|
const provenance = contextWindowProvenanceLabel(ledger.contextWindowSource);
|
|
159
190
|
const trailer = provenance ? `${provenance} window · ${source}` : source;
|
|
160
191
|
lines.push(`${theme.fg("title", summary)} ${theme.fg("dim", "·")} ${theme.fg("muted", trailer)}`);
|
|
@@ -170,7 +201,7 @@ export function renderContextLedgerLines(
|
|
|
170
201
|
|
|
171
202
|
lines.push("");
|
|
172
203
|
if (workingSet) {
|
|
173
|
-
for (const line of renderWorkingSetLines(workingSet)) lines.push(line);
|
|
204
|
+
for (const line of renderWorkingSetLines(workingSet, workingSetConfig)) lines.push(line);
|
|
174
205
|
lines.push("");
|
|
175
206
|
}
|
|
176
207
|
if (ledger.projectPreload && ledger.groups.some((group) => group.category === "project")) {
|
|
@@ -243,6 +274,8 @@ export interface OpenContextOverlayOptions {
|
|
|
243
274
|
};
|
|
244
275
|
/** Working-set fold at the live leaf; null or absent hides the section. */
|
|
245
276
|
getWorkingSet?: () => WorkingSetView | null;
|
|
277
|
+
/** The configured `context.workingSet` block, so the policy line states what is set. */
|
|
278
|
+
getWorkingSetConfig?: () => WorkingSetConfigView | null;
|
|
246
279
|
}
|
|
247
280
|
|
|
248
281
|
/**
|
|
@@ -257,7 +290,12 @@ export function openContextOverlay(
|
|
|
257
290
|
options?: OpenContextOverlayOptions,
|
|
258
291
|
): OverlayHandle {
|
|
259
292
|
const render = (): string =>
|
|
260
|
-
renderContextLedgerLines(
|
|
293
|
+
renderContextLedgerLines(
|
|
294
|
+
getLedger(),
|
|
295
|
+
DEFAULT_CONTENT_WIDTH,
|
|
296
|
+
options?.getWorkingSet?.() ?? null,
|
|
297
|
+
options?.getWorkingSetConfig?.() ?? null,
|
|
298
|
+
).join("\n");
|
|
261
299
|
const text = new Text(render(), 0, 0);
|
|
262
300
|
const handle = showClioOverlayFrame(tui, text, {
|
|
263
301
|
anchor: "center",
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import {
|
|
2
|
+
addResponseModelIdObservationCounts,
|
|
3
|
+
type ResponseModelIdObservationCounts,
|
|
4
|
+
responseModelIdObservationCountsLabel,
|
|
5
|
+
} from "../core/response-model-id.js";
|
|
1
6
|
import {
|
|
2
7
|
aggregateCostAmounts,
|
|
3
8
|
type CostAggregate,
|
|
@@ -16,7 +21,9 @@ export const COST_OVERLAY_WIDTH = DEFAULT_CONTENT_WIDTH + 4;
|
|
|
16
21
|
|
|
17
22
|
export interface CostRow {
|
|
18
23
|
providerId: string;
|
|
19
|
-
|
|
24
|
+
attributedModelId: string;
|
|
25
|
+
requestedModelIds: string[];
|
|
26
|
+
responseModelIdObservationCounts: ResponseModelIdObservationCounts;
|
|
20
27
|
runs: number;
|
|
21
28
|
tokens: number;
|
|
22
29
|
input: number;
|
|
@@ -33,9 +40,17 @@ function formatTokens(n: number): string {
|
|
|
33
40
|
}
|
|
34
41
|
|
|
35
42
|
export function aggregateCostEntries(entries: ReadonlyArray<CostEntry>): CostRow[] {
|
|
36
|
-
const grouped = new Map<
|
|
43
|
+
const grouped = new Map<
|
|
44
|
+
string,
|
|
45
|
+
{
|
|
46
|
+
row: Omit<CostRow, "cost" | "requestedModelIds" | "responseModelIdObservationCounts">;
|
|
47
|
+
requestedModelIds: Set<string>;
|
|
48
|
+
responseModelIdObservationCounts: ResponseModelIdObservationCounts;
|
|
49
|
+
entries: CostEntry[];
|
|
50
|
+
}
|
|
51
|
+
>();
|
|
37
52
|
for (const entry of entries) {
|
|
38
|
-
const key = `${entry.providerId}::${entry.
|
|
53
|
+
const key = `${entry.providerId}::${entry.attributedModelId}`;
|
|
39
54
|
const existing = grouped.get(key);
|
|
40
55
|
if (existing) {
|
|
41
56
|
existing.row.runs += 1;
|
|
@@ -46,13 +61,18 @@ export function aggregateCostEntries(entries: ReadonlyArray<CostEntry>): CostRow
|
|
|
46
61
|
existing.row.cacheWrite += entry.cacheWrite;
|
|
47
62
|
existing.row.reasoningTokens += entry.reasoningTokens;
|
|
48
63
|
existing.row.apiCalls += entry.apiCalls ?? 1;
|
|
64
|
+
for (const requestedModelId of entry.requestedModelIds) existing.requestedModelIds.add(requestedModelId);
|
|
65
|
+
addResponseModelIdObservationCounts(
|
|
66
|
+
existing.responseModelIdObservationCounts,
|
|
67
|
+
entry.responseModelIdObservationCounts,
|
|
68
|
+
);
|
|
49
69
|
existing.entries.push(entry);
|
|
50
70
|
continue;
|
|
51
71
|
}
|
|
52
72
|
grouped.set(key, {
|
|
53
73
|
row: {
|
|
54
74
|
providerId: entry.providerId,
|
|
55
|
-
|
|
75
|
+
attributedModelId: entry.attributedModelId,
|
|
56
76
|
runs: 1,
|
|
57
77
|
tokens: entry.tokens,
|
|
58
78
|
input: entry.input,
|
|
@@ -62,22 +82,31 @@ export function aggregateCostEntries(entries: ReadonlyArray<CostEntry>): CostRow
|
|
|
62
82
|
reasoningTokens: entry.reasoningTokens,
|
|
63
83
|
apiCalls: entry.apiCalls ?? 1,
|
|
64
84
|
},
|
|
85
|
+
requestedModelIds: new Set(entry.requestedModelIds),
|
|
86
|
+
responseModelIdObservationCounts: { ...entry.responseModelIdObservationCounts },
|
|
65
87
|
entries: [entry],
|
|
66
88
|
});
|
|
67
89
|
}
|
|
68
|
-
const rows = Array.from(grouped.values(), ({ row, entries }) => ({
|
|
90
|
+
const rows = Array.from(grouped.values(), ({ row, entries, requestedModelIds, responseModelIdObservationCounts }) => ({
|
|
69
91
|
...row,
|
|
92
|
+
requestedModelIds: [...requestedModelIds].sort(),
|
|
93
|
+
responseModelIdObservationCounts,
|
|
70
94
|
cost: aggregateCostAmounts(entries.map((entry) => ({ usd: entry.usd, provenance: entry.provenance }))),
|
|
71
95
|
}));
|
|
72
96
|
rows.sort((a, b) => {
|
|
73
97
|
if (a.providerId !== b.providerId) return a.providerId < b.providerId ? -1 : 1;
|
|
74
|
-
if (a.
|
|
98
|
+
if (a.attributedModelId !== b.attributedModelId) return a.attributedModelId < b.attributedModelId ? -1 : 1;
|
|
75
99
|
return 0;
|
|
76
100
|
});
|
|
77
101
|
return rows;
|
|
78
102
|
}
|
|
79
103
|
|
|
80
|
-
function sumRows(
|
|
104
|
+
function sumRows(
|
|
105
|
+
rows: ReadonlyArray<CostRow>,
|
|
106
|
+
): Omit<
|
|
107
|
+
CostRow,
|
|
108
|
+
"providerId" | "attributedModelId" | "requestedModelIds" | "responseModelIdObservationCounts" | "cost"
|
|
109
|
+
> {
|
|
81
110
|
return rows.reduce(
|
|
82
111
|
(acc, row) => ({
|
|
83
112
|
runs: acc.runs + row.runs,
|
|
@@ -156,6 +185,8 @@ function modelBlock(row: CostRow): string[] {
|
|
|
156
185
|
// block with token rows and no cost row.
|
|
157
186
|
const cost = formatCostAggregate(row.cost);
|
|
158
187
|
return kvBlock([
|
|
188
|
+
["requested model ids", row.requestedModelIds.join(", ")],
|
|
189
|
+
["response model id observation", responseModelIdObservationCountsLabel(row.responseModelIdObservationCounts)],
|
|
159
190
|
["turns", formatTokens(row.runs)],
|
|
160
191
|
["model calls", formatTokens(row.apiCalls)],
|
|
161
192
|
...(cost === null ? [] : [["cost", cost] as const]),
|
|
@@ -185,7 +216,7 @@ export function formatCostOverlayBodyLines(
|
|
|
185
216
|
} else {
|
|
186
217
|
for (const [index, row] of rows.entries()) {
|
|
187
218
|
if (index > 0) lines.push("");
|
|
188
|
-
lines.push(theme.style("accent", `${row.providerId} · ${row.
|
|
219
|
+
lines.push(theme.style("accent", `${row.providerId} · attributed model ${row.attributedModelId}`, { bold: true }));
|
|
189
220
|
for (const line of modelBlock(row)) {
|
|
190
221
|
lines.push(line);
|
|
191
222
|
}
|