@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
|
@@ -32,6 +32,7 @@ export type { ModelCapabilityPatchTarget } from "./model-capabilities.js";
|
|
|
32
32
|
export { applyModelCapabilityPatch, resolveModelCapabilities } from "./model-capabilities.js";
|
|
33
33
|
export {
|
|
34
34
|
canonicalizeWireModelId,
|
|
35
|
+
contextSlotsForModel,
|
|
35
36
|
hasLiveModelCatalog,
|
|
36
37
|
loadedContextWindowForModel,
|
|
37
38
|
type ModelResidency,
|
|
@@ -96,6 +97,7 @@ export {
|
|
|
96
97
|
refineRuntimeTargetWithModelHints,
|
|
97
98
|
resolveRuntimeTarget,
|
|
98
99
|
runtimeResolutionWarnings,
|
|
100
|
+
runtimeResolutionWarningsBesideThinkingNotice,
|
|
99
101
|
runtimeTargetSnapshot,
|
|
100
102
|
} from "./runtime-resolution.js";
|
|
101
103
|
export {
|
|
@@ -126,6 +128,7 @@ export {
|
|
|
126
128
|
EMPTY_CAPABILITIES,
|
|
127
129
|
VALID_THINKING_LEVELS,
|
|
128
130
|
} from "./types/capability-flags.js";
|
|
131
|
+
export { type ContextWindowSlots, formatContextWindowSlots } from "./types/context-window-slots.js";
|
|
129
132
|
export { type CostProvenance, normalizeCostProvenance } from "./types/cost-provenance.js";
|
|
130
133
|
export type { KnowledgeBase, KnowledgeBaseEntry, KnowledgeBaseHit } from "./types/knowledge-base.js";
|
|
131
134
|
export type {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { TargetStatus } from "./contract.js";
|
|
2
2
|
import { listKnownModelsForRuntime } from "./support.js";
|
|
3
|
+
import type { ContextWindowSlots } from "./types/context-window-slots.js";
|
|
3
4
|
|
|
4
5
|
export type ProviderModelSource = "configured" | "live" | "catalog" | "default";
|
|
5
6
|
|
|
@@ -45,6 +46,14 @@ export function loadedContextWindowForModel(
|
|
|
45
46
|
return typeof reported === "number" && Number.isFinite(reported) && reported > 0 ? reported : null;
|
|
46
47
|
}
|
|
47
48
|
|
|
49
|
+
/** How the server splits its KV budget for this model, when the probe saw it split. */
|
|
50
|
+
export function contextSlotsForModel(
|
|
51
|
+
status: DiscoveryStatus | null | undefined,
|
|
52
|
+
modelId: string,
|
|
53
|
+
): ContextWindowSlots | null {
|
|
54
|
+
return status?.discoveredModelStates?.[modelId]?.contextSlots ?? null;
|
|
55
|
+
}
|
|
56
|
+
|
|
48
57
|
/**
|
|
49
58
|
* Residency as one view, so the planner and the "not resident" notice cannot
|
|
50
59
|
* disagree about the same model. A reported loaded window settles it whatever
|
|
@@ -5,7 +5,7 @@ import { getCatalogModelForRuntime, resolveCostProvenance } from "./catalog.js";
|
|
|
5
5
|
import type { ProvidersContract, TargetStatus } from "./contract.js";
|
|
6
6
|
import { isDispatchEligibleRuntime, isOrchestratorEligibleRuntime, isTargetEligibleRuntime } from "./eligibility.js";
|
|
7
7
|
import { probeCapabilitiesForModel, resolveModelCapabilities } from "./model-capabilities.js";
|
|
8
|
-
import { hasLiveModelCatalog, loadedContextWindowForModel } from "./model-discovery.js";
|
|
8
|
+
import { contextSlotsForModel, hasLiveModelCatalog, loadedContextWindowForModel } from "./model-discovery.js";
|
|
9
9
|
import {
|
|
10
10
|
type ReasoningClass,
|
|
11
11
|
type ResolvedModelRuntimeCapabilities,
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
resolveTargetRuntimeCapabilities,
|
|
15
15
|
} from "./model-runtime-capabilities.js";
|
|
16
16
|
import type { CapabilityFlags, ThinkingLevel } from "./types/capability-flags.js";
|
|
17
|
+
import type { ContextWindowSlots } from "./types/context-window-slots.js";
|
|
17
18
|
import type { CostProvenance } from "./types/cost-provenance.js";
|
|
18
19
|
import type { KnowledgeBase } from "./types/knowledge-base.js";
|
|
19
20
|
import type {
|
|
@@ -52,6 +53,12 @@ export interface ContextWindowDetails {
|
|
|
52
53
|
effectiveContextWindow: number;
|
|
53
54
|
/** Where `effectiveContextWindow` came from. */
|
|
54
55
|
contextWindowSource: ContextWindowSource;
|
|
56
|
+
/**
|
|
57
|
+
* Present when the probed window is a per-request share of the server's
|
|
58
|
+
* KV budget (llama.cpp `--ctx-size` over `--parallel` slots), so the
|
|
59
|
+
* operator surfaces can render `196,608 (786,432 / 4 slots)`.
|
|
60
|
+
*/
|
|
61
|
+
contextWindowSlots: ContextWindowSlots | null;
|
|
55
62
|
/** The window is below what this kind of work wants. An actionable degradation. */
|
|
56
63
|
warning: string | null;
|
|
57
64
|
/** The window is a placeholder rather than something the target reported. */
|
|
@@ -406,6 +413,8 @@ export function resolveRuntimeTarget(
|
|
|
406
413
|
providers.knowledgeBase,
|
|
407
414
|
probedContextWindow,
|
|
408
415
|
loadedContextWindow,
|
|
416
|
+
undefined,
|
|
417
|
+
contextSlotsForModel(status, wireModelId),
|
|
409
418
|
);
|
|
410
419
|
capabilities.contextWindow = contextWindowDetails.effectiveContextWindow;
|
|
411
420
|
if (contextWindowDetails.warning) {
|
|
@@ -513,6 +522,7 @@ export function refineRuntimeTargetWithModelHints(
|
|
|
513
522
|
// hand the planner the declared window back on the first refinement.
|
|
514
523
|
target.contextWindowDetails.loadedContextWindow,
|
|
515
524
|
modelHintContextWindow,
|
|
525
|
+
target.contextWindowDetails.contextWindowSlots,
|
|
516
526
|
);
|
|
517
527
|
capabilities.contextWindow = contextWindowDetails.effectiveContextWindow;
|
|
518
528
|
|
|
@@ -582,6 +592,21 @@ export function runtimeResolutionWarnings(diagnostics: ReadonlyArray<RuntimeReso
|
|
|
582
592
|
return diagnostics.filter((entry) => entry.severity === "warning").map((entry) => entry.message);
|
|
583
593
|
}
|
|
584
594
|
|
|
595
|
+
/**
|
|
596
|
+
* The warnings a surface that prints its own thinking-clamp line should
|
|
597
|
+
* announce. `thinking-coerced` and `thinking-<kind>` are the two halves of
|
|
598
|
+
* that one line, so when the resolved thinking carries a notice they are
|
|
599
|
+
* dropped here: an always-on model printed three lines saying one thing
|
|
600
|
+
* (issue #191). With no notice, a bare coercion is still worth a line.
|
|
601
|
+
*/
|
|
602
|
+
export function runtimeResolutionWarningsBesideThinkingNotice(
|
|
603
|
+
diagnostics: ReadonlyArray<RuntimeResolutionDiagnostic>,
|
|
604
|
+
thinkingNotice: string,
|
|
605
|
+
): string[] {
|
|
606
|
+
if (thinkingNotice.trim().length === 0) return runtimeResolutionWarnings(diagnostics);
|
|
607
|
+
return runtimeResolutionWarnings(diagnostics.filter((entry) => !entry.code.startsWith("thinking-")));
|
|
608
|
+
}
|
|
609
|
+
|
|
585
610
|
/**
|
|
586
611
|
* Minimum context Clio is built for, applied to every tier rather than only to
|
|
587
612
|
* local-native. A hosted target that reports less than this is as unable to
|
|
@@ -623,6 +648,7 @@ export function resolveContextWindowDetails(
|
|
|
623
648
|
probedContextWindow: number | null,
|
|
624
649
|
loadedContextWindow: number | null = null,
|
|
625
650
|
modelHintContextWindow?: number,
|
|
651
|
+
probedContextSlots: ContextWindowSlots | null = null,
|
|
626
652
|
): ContextWindowDetails {
|
|
627
653
|
const catalogModel = getCatalogModelForRuntime(runtime.id, wireModelId);
|
|
628
654
|
const kbHit = knowledgeBase?.lookup(wireModelId) ?? null;
|
|
@@ -707,6 +733,16 @@ export function resolveContextWindowDetails(
|
|
|
707
733
|
`Run 'clio-coder targets --probe' to read the real one.`;
|
|
708
734
|
}
|
|
709
735
|
|
|
736
|
+
// The split explains the probed number and nothing else: once an override
|
|
737
|
+
// or a loaded window decides the figure, `786,432 / 4 slots` no longer
|
|
738
|
+
// describes it.
|
|
739
|
+
const contextWindowSlots =
|
|
740
|
+
source === "probe" &&
|
|
741
|
+
probedContextSlots !== null &&
|
|
742
|
+
Math.floor(probedContextSlots.totalContextSize / probedContextSlots.slots) === effective
|
|
743
|
+
? probedContextSlots
|
|
744
|
+
: null;
|
|
745
|
+
|
|
710
746
|
return {
|
|
711
747
|
declaredContextWindow,
|
|
712
748
|
probedContextWindow,
|
|
@@ -714,6 +750,7 @@ export function resolveContextWindowDetails(
|
|
|
714
750
|
desiredContextWindow: desired,
|
|
715
751
|
effectiveContextWindow: effective,
|
|
716
752
|
contextWindowSource: source,
|
|
753
|
+
contextWindowSlots,
|
|
717
754
|
warning,
|
|
718
755
|
provenanceNotice,
|
|
719
756
|
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { probeHttp, probeJson } from "../../probe/http.js";
|
|
2
2
|
import type { CapabilityFlags } from "../../types/capability-flags.js";
|
|
3
|
+
import { type ContextWindowSlots, formatContextWindowSlots } from "../../types/context-window-slots.js";
|
|
3
4
|
import type { ProbeContext, ProbeModelStatus, ProbeResult } from "../../types/runtime-descriptor.js";
|
|
4
5
|
import type { TargetDescriptor } from "../../types/target-descriptor.js";
|
|
5
6
|
|
|
@@ -88,7 +89,15 @@ export async function probeOpenAIModelCatalog(
|
|
|
88
89
|
// A reported loaded context is itself the residency answer: nothing
|
|
89
90
|
// serves a window for a model it has not loaded.
|
|
90
91
|
(loadedContext !== undefined ? { state: "loaded" as const } : undefined);
|
|
91
|
-
|
|
92
|
+
// The slot split rides on the load-state record because it is the same
|
|
93
|
+
// kind of fact: how this server is serving this model. A row with no
|
|
94
|
+
// recognized state still gets one, as `unknown`, so the split is kept
|
|
95
|
+
// without claiming residency the server did not report.
|
|
96
|
+
const contextSlots = contextSlotsFromEntry(row) ?? (detailRow ? contextSlotsFromEntry(detailRow) : undefined);
|
|
97
|
+
const withSlots = contextSlots ? { ...(state ?? { state: "unknown" as const }), contextSlots } : state;
|
|
98
|
+
if (withSlots) {
|
|
99
|
+
modelStates[row.id] = loadedContext === undefined ? withSlots : { ...withSlots, contextLength: loadedContext };
|
|
100
|
+
}
|
|
92
101
|
}
|
|
93
102
|
return { models, modelCapabilities, modelStates };
|
|
94
103
|
}
|
|
@@ -159,7 +168,15 @@ function normalizeModelState(raw: string | undefined): ProbeModelStatus["state"]
|
|
|
159
168
|
if (!value) return undefined;
|
|
160
169
|
if (value === "loaded" || value === "ready" || value === "running" || value === "active") return "loaded";
|
|
161
170
|
if (value === "loading" || value === "pending" || value === "queued" || value === "starting") return "loading";
|
|
162
|
-
if (
|
|
171
|
+
if (
|
|
172
|
+
value === "unloaded" ||
|
|
173
|
+
value === "not-loaded" ||
|
|
174
|
+
value === "idle" ||
|
|
175
|
+
value === "sleeping" ||
|
|
176
|
+
value === "stopped"
|
|
177
|
+
) {
|
|
178
|
+
return "unloaded";
|
|
179
|
+
}
|
|
163
180
|
if (value === "failed" || value === "error" || value === "errored") return "failed";
|
|
164
181
|
if (value === "unknown") return "unknown";
|
|
165
182
|
return undefined;
|
|
@@ -203,12 +220,16 @@ function statusArgsFromEntry(row: Record<string, unknown>): string[] {
|
|
|
203
220
|
return argsFromStatus(status);
|
|
204
221
|
}
|
|
205
222
|
|
|
223
|
+
function contextSlotsFromEntry(row: Record<string, unknown>): ContextWindowSlots | undefined {
|
|
224
|
+
return llamaCppRequestContextWindow(parseLlamaCppServerFlags(statusArgsFromEntry(row)))?.slots;
|
|
225
|
+
}
|
|
226
|
+
|
|
206
227
|
function capabilitiesFromOpenAIModelEntry(row: Record<string, unknown>): Partial<CapabilityFlags> {
|
|
207
228
|
const caps: Partial<CapabilityFlags> = {};
|
|
208
229
|
const meta = nestedRecord(row, "meta");
|
|
209
230
|
const flags = parseLlamaCppServerFlags(statusArgsFromEntry(row));
|
|
210
231
|
const contextWindow =
|
|
211
|
-
|
|
232
|
+
llamaCppRequestContextWindow(flags)?.contextWindow ??
|
|
212
233
|
firstPositiveNumber(row, [
|
|
213
234
|
// What is actually loaded outranks what the model could support: a
|
|
214
235
|
// model served at 8k out of a possible 262k has an 8k window today,
|
|
@@ -310,10 +331,43 @@ export interface LlamaCppServerFlags {
|
|
|
310
331
|
topK?: number;
|
|
311
332
|
nGpuLayers?: number;
|
|
312
333
|
parallel?: number;
|
|
334
|
+
/** `--kv-unified` / `-kvu` true, `--no-kv-unified` false, absent when neither was given. */
|
|
335
|
+
kvUnified?: boolean;
|
|
313
336
|
mmproj?: string;
|
|
314
337
|
chatTemplateKwargs?: string;
|
|
315
338
|
}
|
|
316
339
|
|
|
340
|
+
export interface LlamaCppRequestContextWindow {
|
|
341
|
+
/** What one request can use. */
|
|
342
|
+
contextWindow: number;
|
|
343
|
+
/** Present when `contextWindow` is a quotient of the server's total. */
|
|
344
|
+
slots?: ContextWindowSlots;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* The window one request actually gets from a llama.cpp server.
|
|
349
|
+
*
|
|
350
|
+
* `--ctx-size` is the total KV budget of the process. Without `--kv-unified`
|
|
351
|
+
* the server splits it evenly across `--parallel` slots, so a router started
|
|
352
|
+
* with `--ctx-size 786432 --parallel 4 --no-kv-unified` admits 196,608 tokens
|
|
353
|
+
* per request while reporting 786,432 as its context size. Reading the total
|
|
354
|
+
* as the window armed autocompact at a number the server would never admit
|
|
355
|
+
* and walked a long session into a hard context failure with the meter at
|
|
356
|
+
* 25% (issue #187). With `--kv-unified` every slot shares one sequence and
|
|
357
|
+
* the total is the window.
|
|
358
|
+
*/
|
|
359
|
+
export function llamaCppRequestContextWindow(flags: LlamaCppServerFlags): LlamaCppRequestContextWindow | undefined {
|
|
360
|
+
const total = positiveNumber(flags.contextSize);
|
|
361
|
+
if (total === undefined) return undefined;
|
|
362
|
+
const parallel = positiveNumber(flags.parallel);
|
|
363
|
+
if (parallel === undefined || parallel <= 1 || flags.kvUnified === true) return { contextWindow: Math.floor(total) };
|
|
364
|
+
const slots = Math.floor(parallel);
|
|
365
|
+
return {
|
|
366
|
+
contextWindow: Math.floor(total / slots),
|
|
367
|
+
slots: { totalContextSize: Math.floor(total), slots },
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
|
|
317
371
|
export interface LlamaCppStatusEnrichment {
|
|
318
372
|
discoveredCapabilities?: Partial<CapabilityFlags>;
|
|
319
373
|
modelId?: string;
|
|
@@ -329,22 +383,37 @@ function argsFromStatus(status: unknown): string[] {
|
|
|
329
383
|
return [];
|
|
330
384
|
}
|
|
331
385
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
const value = args[index + 1];
|
|
336
|
-
return value && !value.startsWith("--") ? value : undefined;
|
|
386
|
+
/** `-1` is a value (`--reasoning-budget -1`); `-np` and `--jinja` are flags. */
|
|
387
|
+
function looksLikeFlag(token: string): boolean {
|
|
388
|
+
return token.startsWith("-") && Number.isNaN(Number(token));
|
|
337
389
|
}
|
|
338
390
|
|
|
339
|
-
|
|
340
|
-
|
|
391
|
+
/**
|
|
392
|
+
* The value after the first of `flags` present, or undefined. A token that
|
|
393
|
+
* reads as the next flag is not a value, which is how boolean flags read as
|
|
394
|
+
* present-without-value. Short spellings (`-c`, `-np`) come after the long
|
|
395
|
+
* one so the long form wins when both are given.
|
|
396
|
+
*/
|
|
397
|
+
function valueAfter(args: ReadonlyArray<string>, ...flags: ReadonlyArray<string>): string | undefined {
|
|
398
|
+
for (const flag of flags) {
|
|
399
|
+
const index = args.indexOf(flag);
|
|
400
|
+
if (index < 0) continue;
|
|
401
|
+
const value = args[index + 1];
|
|
402
|
+
return value && !looksLikeFlag(value) ? value : undefined;
|
|
403
|
+
}
|
|
404
|
+
return undefined;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
function numberFlag(args: ReadonlyArray<string>, ...flags: ReadonlyArray<string>): number | undefined {
|
|
408
|
+
const value = valueAfter(args, ...flags);
|
|
341
409
|
if (value === undefined) return undefined;
|
|
342
410
|
const parsed = Number(value);
|
|
343
411
|
return Number.isFinite(parsed) ? parsed : undefined;
|
|
344
412
|
}
|
|
345
413
|
|
|
346
|
-
function booleanFlag(args: ReadonlyArray<string>,
|
|
347
|
-
|
|
414
|
+
function booleanFlag(args: ReadonlyArray<string>, ...flags: ReadonlyArray<string>): boolean | undefined {
|
|
415
|
+
const flag = flags.find((candidate) => args.includes(candidate));
|
|
416
|
+
if (flag === undefined) return undefined;
|
|
348
417
|
const value = valueAfter(args, flag);
|
|
349
418
|
if (value === undefined) return true;
|
|
350
419
|
const normalized = value.toLowerCase();
|
|
@@ -353,9 +422,9 @@ function booleanFlag(args: ReadonlyArray<string>, flag: string): boolean | undef
|
|
|
353
422
|
return undefined;
|
|
354
423
|
}
|
|
355
424
|
|
|
356
|
-
function parseLlamaCppServerFlags(args: ReadonlyArray<string>): LlamaCppServerFlags {
|
|
425
|
+
export function parseLlamaCppServerFlags(args: ReadonlyArray<string>): LlamaCppServerFlags {
|
|
357
426
|
const flags: LlamaCppServerFlags = {};
|
|
358
|
-
const ctxSize = numberFlag(args, "--ctx-size");
|
|
427
|
+
const ctxSize = numberFlag(args, "--ctx-size", "-c");
|
|
359
428
|
if (ctxSize !== undefined) flags.contextSize = ctxSize;
|
|
360
429
|
const maxTokens = numberFlag(args, "--n-predict");
|
|
361
430
|
if (maxTokens !== undefined) flags.maxTokens = maxTokens;
|
|
@@ -375,8 +444,14 @@ function parseLlamaCppServerFlags(args: ReadonlyArray<string>): LlamaCppServerFl
|
|
|
375
444
|
if (topK !== undefined) flags.topK = topK;
|
|
376
445
|
const nGpuLayers = numberFlag(args, "--n-gpu-layers");
|
|
377
446
|
if (nGpuLayers !== undefined) flags.nGpuLayers = nGpuLayers;
|
|
378
|
-
const parallel = numberFlag(args, "--parallel");
|
|
447
|
+
const parallel = numberFlag(args, "--parallel", "-np");
|
|
379
448
|
if (parallel !== undefined) flags.parallel = parallel;
|
|
449
|
+
// The negative spelling is its own flag, and the last one given wins, which
|
|
450
|
+
// is how llama.cpp itself resolves a repeated boolean option.
|
|
451
|
+
const kvUnifiedAt = Math.max(args.lastIndexOf("--kv-unified"), args.lastIndexOf("-kvu"));
|
|
452
|
+
const noKvUnifiedAt = args.lastIndexOf("--no-kv-unified");
|
|
453
|
+
if (noKvUnifiedAt > kvUnifiedAt) flags.kvUnified = false;
|
|
454
|
+
else if (kvUnifiedAt >= 0) flags.kvUnified = booleanFlag(args, "--kv-unified", "-kvu") ?? true;
|
|
380
455
|
const cacheTypeK = valueAfter(args, "--cache-type-k");
|
|
381
456
|
if (cacheTypeK) flags.cacheTypeK = cacheTypeK;
|
|
382
457
|
const cacheTypeV = valueAfter(args, "--cache-type-v");
|
|
@@ -418,7 +493,8 @@ export async function probeLlamaCppModelStatus(
|
|
|
418
493
|
if (args.length === 0) return { notes: statusNotes(selected.id, selected.status) };
|
|
419
494
|
const flags = parseLlamaCppServerFlags(args);
|
|
420
495
|
const caps: Partial<CapabilityFlags> = {};
|
|
421
|
-
|
|
496
|
+
const window = llamaCppRequestContextWindow(flags);
|
|
497
|
+
if (window !== undefined) caps.contextWindow = window.contextWindow;
|
|
422
498
|
if (flags.maxTokens !== undefined && flags.maxTokens > 0) caps.maxTokens = flags.maxTokens;
|
|
423
499
|
if (flags.reasoning === true || flags.reasoningBudget !== undefined) caps.reasoning = true;
|
|
424
500
|
if (flags.mmproj) caps.vision = true;
|
|
@@ -426,6 +502,11 @@ export async function probeLlamaCppModelStatus(
|
|
|
426
502
|
const enrichment: LlamaCppStatusEnrichment = { modelId: selected.id, serverFlags: flags };
|
|
427
503
|
if (Object.keys(caps).length > 0) enrichment.discoveredCapabilities = caps;
|
|
428
504
|
const notes = statusNotes(selected.id, selected.status);
|
|
505
|
+
if (window?.slots) {
|
|
506
|
+
notes.push(
|
|
507
|
+
`${selected.id} context window ${formatContextWindowSlots(window.contextWindow, window.slots)}: --ctx-size is split across --parallel slots without --kv-unified`,
|
|
508
|
+
);
|
|
509
|
+
}
|
|
429
510
|
if (notes.length > 0) enrichment.notes = notes;
|
|
430
511
|
return enrichment;
|
|
431
512
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A server that shares one KV budget across request slots serves each request
|
|
3
|
+
* a quotient of it. llama.cpp splits `--ctx-size` evenly across `--parallel`
|
|
4
|
+
* slots unless `--kv-unified`, so a router started with `--ctx-size 786432
|
|
5
|
+
* --parallel 4 --no-kv-unified` admits 196,608 tokens per request. The total
|
|
6
|
+
* and the slot count are kept beside the quotient so the operator surfaces can
|
|
7
|
+
* say where the number came from.
|
|
8
|
+
*/
|
|
9
|
+
export interface ContextWindowSlots {
|
|
10
|
+
totalContextSize: number;
|
|
11
|
+
slots: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** `196,608 (786,432 / 4 slots)`: the per-request window with its derivation. */
|
|
15
|
+
export function formatContextWindowSlots(contextWindow: number, slots: ContextWindowSlots): string {
|
|
16
|
+
const format = (n: number): string => Math.round(n).toLocaleString("en-US");
|
|
17
|
+
return `${format(contextWindow)} (${format(slots.totalContextSize)} / ${slots.slots} slots)`;
|
|
18
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Api, Model } from "../../../engine/types.js";
|
|
2
|
-
|
|
3
2
|
import type { CapabilityFlags } from "./capability-flags.js";
|
|
3
|
+
import type { ContextWindowSlots } from "./context-window-slots.js";
|
|
4
4
|
import type { CompleteOptions, CompletionChunk, EmbedResult, InfillOptions, RerankResult } from "./inference.js";
|
|
5
5
|
import type { KnowledgeBaseHit } from "./knowledge-base.js";
|
|
6
6
|
import type { TargetDescriptor } from "./target-descriptor.js";
|
|
@@ -61,6 +61,8 @@ export type ProbeModelLoadState = "loaded" | "loading" | "unloaded" | "failed" |
|
|
|
61
61
|
export interface ProbeModelStatus {
|
|
62
62
|
state: ProbeModelLoadState;
|
|
63
63
|
detail?: string;
|
|
64
|
+
/** The per-request window is `totalContextSize / slots`; absent when the server does not split. */
|
|
65
|
+
contextSlots?: ContextWindowSlots;
|
|
64
66
|
/**
|
|
65
67
|
* Context the runtime has this model loaded at, when it reports one. LM
|
|
66
68
|
* Studio serves a loaded instance at whatever window it was opened with,
|
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
* or spoof the UI that approves it.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
+
import { isSecretArgKey, redactSecretString } from "./redaction.js";
|
|
11
|
+
|
|
10
12
|
const ESC_CHAR = String.fromCharCode(27);
|
|
11
13
|
const BEL_CHAR = String.fromCharCode(7);
|
|
12
14
|
// Built through the constructor so no control character appears in a regex
|
|
@@ -39,21 +41,216 @@ export function sanitizeCallTargetText(value: string): string {
|
|
|
39
41
|
}
|
|
40
42
|
|
|
41
43
|
/**
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
44
|
+
* What a tool call is doing, in the bounded form that may cross the worker
|
|
45
|
+
* stdout seam. The verb comes from a fixed vocabulary and the object from a
|
|
46
|
+
* fixed allowlist of argument fields, so an argument the table does not name
|
|
47
|
+
* cannot reach an operator surface however it is spelled.
|
|
48
|
+
*/
|
|
49
|
+
export interface CallActionDescriptor {
|
|
50
|
+
/** One word from the vocabulary below naming what the call does. */
|
|
51
|
+
verb: string;
|
|
52
|
+
/** The redacted, bounded thing the call acts on. Absent when nothing safe is derivable. */
|
|
53
|
+
object?: string;
|
|
54
|
+
/** Whether the object was cut to {@link CALL_ACTION_OBJECT_MAX_CHARS}. */
|
|
55
|
+
truncated?: boolean;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Characters of object text a descriptor carries. Bounded here rather than at
|
|
60
|
+
* the renderer: this string crosses a process boundary, so a hostile argument
|
|
61
|
+
* must be small before it is transported, not after.
|
|
46
62
|
*/
|
|
47
|
-
export
|
|
63
|
+
export const CALL_ACTION_OBJECT_MAX_CHARS = 64;
|
|
64
|
+
|
|
65
|
+
/** Maximum characters carried by the approval overlay's one-line call target. */
|
|
66
|
+
export const CALL_TARGET_MAX_CHARS = 120;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* The verb and object field for each tool Clio ships, plus the ACP tool kinds
|
|
70
|
+
* a delegated peer reports. A tool absent from this table gets the neutral
|
|
71
|
+
* `calling` verb, never a verb guessed from its name.
|
|
72
|
+
*/
|
|
73
|
+
const CALL_ACTION_VOCABULARY: Readonly<Record<string, { verb: string; field: string }>> = {
|
|
74
|
+
read: { verb: "reading", field: "path" },
|
|
75
|
+
edit: { verb: "editing", field: "path" },
|
|
76
|
+
write: { verb: "writing", field: "path" },
|
|
77
|
+
ls: { verb: "listing", field: "path" },
|
|
78
|
+
bash: { verb: "running", field: "command" },
|
|
79
|
+
grep: { verb: "searching", field: "pattern" },
|
|
80
|
+
find: { verb: "finding", field: "pattern" },
|
|
81
|
+
web_fetch: { verb: "fetching", field: "url" },
|
|
82
|
+
git: { verb: "git", field: "op" },
|
|
83
|
+
verify: { verb: "verifying", field: "check" },
|
|
84
|
+
code_nav: { verb: "navigating", field: "query" },
|
|
85
|
+
context: { verb: "context", field: "scope" },
|
|
86
|
+
artifact: { verb: "writing", field: "kind" },
|
|
87
|
+
monitor: { verb: "monitoring", field: "run_id" },
|
|
88
|
+
steer: { verb: "steering", field: "run_id" },
|
|
89
|
+
tasks: { verb: "tasks", field: "action" },
|
|
90
|
+
dispatch: { verb: "dispatching", field: "agent" },
|
|
91
|
+
// ACP tool kinds. A peer names its own argument fields, so these rely on
|
|
92
|
+
// the shared allowlist below rather than on a field this side can predict.
|
|
93
|
+
execute: { verb: "running", field: "command" },
|
|
94
|
+
search: { verb: "searching", field: "query" },
|
|
95
|
+
fetch: { verb: "fetching", field: "url" },
|
|
96
|
+
delete: { verb: "deleting", field: "path" },
|
|
97
|
+
move: { verb: "moving", field: "path" },
|
|
98
|
+
think: { verb: "thinking", field: "" },
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Argument fields any tool may surface as its object. A field outside this
|
|
103
|
+
* list is never read, so a tool that hides a credential in `body`, `headers`,
|
|
104
|
+
* or `env` cannot leak it through a descriptor.
|
|
105
|
+
*/
|
|
106
|
+
const CALL_ACTION_OBJECT_FIELDS = ["path", "file_path", "command", "pattern", "query", "url"] as const;
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Argument fields whose values may reach the approval overlay for each tool.
|
|
110
|
+
* Fields are ordered by decision value: the first present field is the plain
|
|
111
|
+
* target, and any later present fields are labeled facts. Everything outside
|
|
112
|
+
* the tool's list is described only by type and size.
|
|
113
|
+
*/
|
|
114
|
+
const CALL_TARGET_FIELDS: Readonly<Record<string, ReadonlyArray<string>>> = {
|
|
115
|
+
read: ["path", "offset", "limit", "tail"],
|
|
116
|
+
grep: ["pattern", "path", "mode", "glob", "ignore_case", "literal", "context", "limit", "include_ignored"],
|
|
117
|
+
find: ["pattern", "path", "order", "limit", "include_ignored"],
|
|
118
|
+
ls: ["path", "limit"],
|
|
119
|
+
code_nav: ["mode", "query", "limit"],
|
|
120
|
+
context: ["scope", "query", "name", "limit", "ref", "include_tree"],
|
|
121
|
+
credential_present: ["name", "source", "file"],
|
|
122
|
+
write: ["path"],
|
|
123
|
+
edit: ["path"],
|
|
124
|
+
bash: ["command", "cwd", "timeout_ms", "output_policy"],
|
|
125
|
+
git: ["op", "path", "cached", "stat", "name_only", "limit", "cwd", "timeout_ms", "max_output_bytes"],
|
|
126
|
+
verify: ["check", "path", "browser", "cwd", "timeout_ms", "max_output_bytes"],
|
|
127
|
+
dispatch: [
|
|
128
|
+
"list",
|
|
129
|
+
"mode",
|
|
130
|
+
"agent",
|
|
131
|
+
"target",
|
|
132
|
+
"model",
|
|
133
|
+
"node",
|
|
134
|
+
"autonomy",
|
|
135
|
+
"tool_profile",
|
|
136
|
+
"thinking_level",
|
|
137
|
+
"detach",
|
|
138
|
+
"timeout_ms",
|
|
139
|
+
],
|
|
140
|
+
monitor: ["run_id", "mode"],
|
|
141
|
+
steer: ["run_id", "action"],
|
|
142
|
+
tasks: ["action", "id"],
|
|
143
|
+
ledger: ["action", "kind", "path", "line", "target", "passed", "since"],
|
|
144
|
+
web_fetch: ["url", "method", "timeout_ms", "max_bytes", "format"],
|
|
145
|
+
ask_user: ["action", "mode", "max_rounds", "exposure"],
|
|
146
|
+
artifact: ["kind", "path", "title"],
|
|
147
|
+
// ACP tool kinds use only fields whose meaning the protocol defines.
|
|
148
|
+
execute: ["command", "cwd"],
|
|
149
|
+
search: ["query", "path"],
|
|
150
|
+
fetch: ["url", "method"],
|
|
151
|
+
delete: ["path"],
|
|
152
|
+
move: ["path", "target"],
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
/** Sanitize, scrub, and bound one candidate object string. Null when nothing is left. */
|
|
156
|
+
function boundActionObject(value: unknown): { object: string; truncated: boolean } | null {
|
|
157
|
+
if (typeof value !== "string") return null;
|
|
158
|
+
const clean = sanitizeCallTargetText(redactSecretString(value));
|
|
159
|
+
if (clean.length === 0) return null;
|
|
160
|
+
if (clean.length <= CALL_ACTION_OBJECT_MAX_CHARS) return { object: clean, truncated: false };
|
|
161
|
+
return { object: clean.slice(0, CALL_ACTION_OBJECT_MAX_CHARS), truncated: true };
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Compose the redacted action descriptor for one tool call. Called at the
|
|
166
|
+
* trusted seams that hold the arguments (the tool registry's admission path,
|
|
167
|
+
* the Claude tool mapper, and the ACP update mapper) so the descriptor, and
|
|
168
|
+
* never the arguments, is what crosses into an operator surface.
|
|
169
|
+
*
|
|
170
|
+
* Returns null when the tool is unknown and no allowlisted field is present:
|
|
171
|
+
* a bare `calling` with nothing after it says less than the tool name already
|
|
172
|
+
* on the row.
|
|
173
|
+
*/
|
|
174
|
+
export function describeCallAction(
|
|
175
|
+
tool: string,
|
|
176
|
+
args: Record<string, unknown> | undefined,
|
|
177
|
+
): CallActionDescriptor | null {
|
|
178
|
+
const known = CALL_ACTION_VOCABULARY[tool];
|
|
179
|
+
const candidates = known?.field ? [known.field, ...CALL_ACTION_OBJECT_FIELDS] : [...CALL_ACTION_OBJECT_FIELDS];
|
|
180
|
+
let bounded: { object: string; truncated: boolean } | null = null;
|
|
181
|
+
if (args) {
|
|
182
|
+
for (const field of candidates) {
|
|
183
|
+
if (isSecretArgKey(field)) continue;
|
|
184
|
+
bounded = boundActionObject(args[field]);
|
|
185
|
+
if (bounded !== null) break;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
if (known === undefined && bounded === null) return null;
|
|
189
|
+
const verb = known?.verb ?? "calling";
|
|
190
|
+
if (bounded === null) return { verb };
|
|
191
|
+
return { verb, object: bounded.object, ...(bounded.truncated ? { truncated: true } : {}) };
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function renderAllowedTargetValue(value: unknown): string | null {
|
|
195
|
+
if (typeof value === "string") {
|
|
196
|
+
const rendered = sanitizeCallTargetText(redactSecretString(value));
|
|
197
|
+
return rendered.length > 0 ? rendered : null;
|
|
198
|
+
}
|
|
199
|
+
if (typeof value === "number" && Number.isFinite(value)) return String(value);
|
|
200
|
+
if (typeof value === "boolean") return String(value);
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function countLabel(count: number, singular: string, plural: string): string {
|
|
205
|
+
return `${count} ${count === 1 ? singular : plural}`;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/** Describe an argument without copying any part of its value into display text. */
|
|
209
|
+
function summarizeUnlistedTargetValue(value: unknown): string {
|
|
210
|
+
if (typeof value === "string") {
|
|
211
|
+
return `<string ${countLabel(Buffer.byteLength(value, "utf8"), "byte", "bytes")}>`;
|
|
212
|
+
}
|
|
213
|
+
if (Array.isArray(value)) return `<array ${countLabel(value.length, "item", "items")}>`;
|
|
214
|
+
if (value !== null && typeof value === "object") {
|
|
215
|
+
return `<object ${countLabel(Object.keys(value).length, "field", "fields")}>`;
|
|
216
|
+
}
|
|
217
|
+
if (value === null) return "<null 0 values>";
|
|
218
|
+
if (typeof value === "undefined") return "<undefined 0 values>";
|
|
219
|
+
return `<${typeof value} 1 value>`;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function targetFieldName(value: string): string {
|
|
223
|
+
return sanitizeCallTargetText(value).slice(0, 32) || "field";
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Derive the operator-facing object of a call for the approval overlay. Only
|
|
228
|
+
* values in the named tool's allowlist may render. Every other argument is
|
|
229
|
+
* summarized by its field name, type, and size, so an unexpected credential
|
|
230
|
+
* or pasted document still informs the decision without disclosing content.
|
|
231
|
+
* Returns an empty string when the call carries no arguments.
|
|
232
|
+
*/
|
|
233
|
+
export function describeCallTarget(tool: string, args: Record<string, unknown> | undefined): string {
|
|
48
234
|
if (!args) return "";
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
235
|
+
const allowedFields = CALL_TARGET_FIELDS[tool] ?? [];
|
|
236
|
+
const allowed = new Set(allowedFields);
|
|
237
|
+
const parts: string[] = [];
|
|
238
|
+
for (const field of allowedFields) {
|
|
239
|
+
if (!(field in args)) continue;
|
|
240
|
+
if (isSecretArgKey(field)) {
|
|
241
|
+
parts.push(`${targetFieldName(field)}=${summarizeUnlistedTargetValue(args[field])}`);
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
const rendered = renderAllowedTargetValue(args[field]);
|
|
245
|
+
if (rendered === null) {
|
|
246
|
+
parts.push(`${targetFieldName(field)}=${summarizeUnlistedTargetValue(args[field])}`);
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
parts.push(parts.length === 0 ? rendered : `${targetFieldName(field)}=${rendered}`);
|
|
250
|
+
}
|
|
251
|
+
for (const [field, value] of Object.entries(args)) {
|
|
252
|
+
if (allowed.has(field)) continue;
|
|
253
|
+
parts.push(`${targetFieldName(field)}=${summarizeUnlistedTargetValue(value)}`);
|
|
58
254
|
}
|
|
255
|
+
return sanitizeCallTargetText(parts.join(" · ")).slice(0, CALL_TARGET_MAX_CHARS);
|
|
59
256
|
}
|