@iowarp/clio-coder 0.3.2 → 0.3.3
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 +229 -457
- package/README.md +2 -2
- package/dist/{acp-BIYHVZIM.js → acp-P2AQILE2.js} +2 -2
- package/dist/{agents-YT6SSRIT.js → agents-72W3BI7I.js} +8 -8
- package/dist/assets/codewiki.json +1 -1
- package/dist/{chunk-WMSVI4G2.js → chunk-2DJ2KNFG.js} +3 -3
- package/dist/{chunk-2EHAIA3X.js → chunk-2TLUCQVG.js} +2 -2
- package/dist/{chunk-WVO7V2QY.js → chunk-4XUGQOHA.js} +2 -2
- package/dist/{chunk-IGLFWIYI.js → chunk-5UFT4SUX.js} +2 -2
- package/dist/{chunk-AO4RKG4M.js → chunk-6SGHMWE3.js} +3 -3
- package/dist/{chunk-X75S7HFS.js → chunk-COU2UHX6.js} +45 -19
- package/dist/{chunk-KJ5LWLOE.js → chunk-DSELYM6W.js} +2 -2
- package/dist/{chunk-G2DE3C7R.js → chunk-DUYJ5IO6.js} +2 -2
- package/dist/{chunk-EPVUXGXG.js → chunk-FNTMWMX5.js} +9 -9
- package/dist/{chunk-MNA4JGU4.js → chunk-J7CWMCQD.js} +2 -2
- package/dist/{chunk-MBS4V7ZP.js → chunk-KZWTDYJF.js} +4 -4
- package/dist/{chunk-LBNRH5WM.js → chunk-LM5TQCJZ.js} +3 -3
- package/dist/{chunk-OHHN2SO4.js → chunk-LW6DSM3M.js} +7 -7
- package/dist/{chunk-MQSRRFWA.js → chunk-LWLEKMDQ.js} +56 -2
- package/dist/{chunk-V4RXGQ5Q.js → chunk-OC7FIQPC.js} +2 -2
- package/dist/{chunk-3ZXDFGR5.js → chunk-PAJK6MAQ.js} +2 -2
- package/dist/{chunk-7EYHLWU7.js → chunk-PIWWS5BL.js} +3 -3
- package/dist/{chunk-6EJV5X2W.js → chunk-SRDMMSEP.js} +3 -3
- package/dist/{chunk-ARBGF5F7.js → chunk-TZK7PACC.js} +2 -2
- package/dist/{chunk-QTYWRVRA.js → chunk-UFIIWP2H.js} +2 -2
- package/dist/{chunk-J5HN4RYU.js → chunk-V6RTAOC2.js} +2 -2
- package/dist/{chunk-SRF2PJNW.js → chunk-VPAYEGVX.js} +2 -2
- package/dist/{chunk-77VKQEHF.js → chunk-X6IAEBZR.js} +2 -2
- package/dist/{chunk-4KLWL3UC.js → chunk-XBXAASKX.js} +2 -2
- package/dist/{chunk-MAW544W2.js → chunk-ZWMF7253.js} +4 -4
- package/dist/cli/index.js +17 -17
- package/dist/{clio-4LY5K2AC.js → clio-JOU4FXVA.js} +2 -2
- package/dist/{config-GTLUW2PR.js → config-XCDVKR23.js} +7 -7
- package/dist/{configure-R6A64DHX.js → configure-4GAP54ZW.js} +5 -5
- package/dist/{context-RW5HC47S.js → context-4UOGGLQ5.js} +6 -6
- package/dist/{context-JFZEJ7W5.js → context-77FM5DV5.js} +9 -9
- package/dist/{context-clear-6ZHBAZZT.js → context-clear-XXJRLCJJ.js} +6 -6
- package/dist/{dispatch-runner-VKBRCWQC.js → dispatch-runner-QPRDDBDX.js} +7 -7
- package/dist/{doctor-KI767GSN.js → doctor-HR46URBJ.js} +5 -5
- package/dist/{evidence-UA6AWDQQ.js → evidence-6HG2PY2B.js} +4 -4
- package/dist/{evolve-QNTFGV6Z.js → evolve-K7YU3NCY.js} +4 -4
- package/dist/{fleet-Q7UOMUSG.js → fleet-VY3HHKN6.js} +15 -15
- package/dist/{init-WBB65ZHQ.js → init-JYGXI3FK.js} +12 -12
- package/dist/{memory-MD3O64RI.js → memory-WFZMGYHX.js} +5 -5
- package/dist/{models-BZU34YWD.js → models-I5QWSEOM.js} +7 -7
- package/dist/{monitor-MEQA5C3I.js → monitor-GE4ID3IA.js} +5 -5
- package/dist/{orchestrator-CGFKEP27.js → orchestrator-EM5MC3HM.js} +1482 -1055
- package/dist/{run-IV4Q6RLN.js → run-ZU3QMZPZ.js} +18 -18
- package/dist/{skills-LQEKRDTN.js → skills-X5VXCRNQ.js} +2 -2
- package/dist/{skills-eval-3DC4HEWS.js → skills-eval-WKIHWTHR.js} +5 -5
- package/dist/{targets-C4SSGQOB.js → targets-SNCPI2NR.js} +8 -8
- package/dist/{terminal-lease-IT5JW2NR.js → terminal-lease-BNAHVHBS.js} +2 -2
- package/dist/{upgrade-7TT7SQ3G.js → upgrade-JQHHPQ4K.js} +7 -7
- package/dist/{usage-GV4PKT3M.js → usage-OR4O5SMZ.js} +5 -5
- package/dist/{verify-G6V4D2G7.js → verify-375KUB3Y.js} +4 -4
- package/dist/{wiki-generate-DQF6Z66B.js → wiki-generate-UEXP2ARI.js} +11 -11
- package/dist/worker/entry.js +8 -8
- 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-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 +1 -1
- package/docs/configuration-and-targets.md +1 -1
- package/docs/context-engine.md +1 -1
- package/docs/development-pipeline.md +1 -1
- package/docs/documentation-coverage.md +2 -2
- package/docs/documentation-guide.md +1 -1
- package/docs/eval-runner.md +1 -1
- package/docs/evals-internal.md +1 -1
- package/docs/evidence-and-memory.md +2 -2
- package/docs/evolution.md +1 -1
- package/docs/exit-codes-and-output.md +1 -1
- package/docs/extensions-and-sharing.md +2 -2
- package/docs/fleet-dispatch.md +1 -1
- package/docs/installation-and-lifecycle.md +6 -6
- package/docs/middleware-and-components.md +1 -1
- package/docs/model-catalog.md +1 -1
- package/docs/observability.md +3 -3
- package/docs/performance-methodology.md +2 -2
- package/docs/proactive-memory.md +1 -1
- package/docs/prompt-envelope-and-tools.md +1 -1
- package/docs/provider-adapter-cookbook.md +1 -1
- package/docs/release-cut-checklist.md +30 -30
- package/docs/safety-model.md +2 -2
- package/docs/scientific-validation.md +3 -3
- package/docs/session-lifecycle.md +2 -2
- 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 +2 -2
- package/docs/worker-dispatch-mechanics.md +1 -1
- package/package.json +1 -1
- package/src/core/git-commit-attribution.ts +46 -21
- package/src/domains/config/keybindings.ts +3 -3
- package/src/interactive/chat-panel.ts +555 -244
- package/src/interactive/chat-renderer.ts +50 -19
- package/src/interactive/editor-submit.ts +26 -1
- package/src/interactive/footer/widgets.ts +22 -20
- package/src/interactive/footer-panel.ts +6 -1
- package/src/interactive/interactive-application.ts +2 -0
- package/src/interactive/interactive-event-projection.ts +12 -0
- package/src/interactive/interactive-slash-runtime.ts +12 -7
- package/src/interactive/overlays/ask-user.ts +146 -24
- package/src/interactive/renderers/tool-execution.ts +151 -56
- package/src/interactive/status/index.ts +12 -1
- package/src/interactive/status/reasoning.ts +87 -0
- package/src/interactive/status/summary.ts +13 -2
- package/src/interactive/transcript-detail.ts +120 -0
- package/src/tools/builtin-tool-catalog.ts +7 -1
- package/src/tools/presentation.ts +107 -0
- package/src/tools/registry.ts +6 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import type { ReasoningTokenProvenance, RunTally, TurnSummary } from "./types.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* One projection of a turn's reasoning spend for every surface that shows it.
|
|
5
|
+
*
|
|
6
|
+
* Reasoning counts used to be derived in three places (the status tally, the
|
|
7
|
+
* chat panel's own `message_end` re-derivation, and a chars/4 estimate over the
|
|
8
|
+
* visible thinking text), so the transcript, the turn receipt, and the footer
|
|
9
|
+
* could each report a different number for the same turn. Everything now reads
|
|
10
|
+
* `RunTally`/`TurnSummary` through this module, so a surface can differ in
|
|
11
|
+
* layout but never in the number or its provenance.
|
|
12
|
+
*
|
|
13
|
+
* `unmeasured` is a view-only state: nothing has been folded yet, so the count
|
|
14
|
+
* is unknown rather than zero. It never reaches `TurnSummary`, which keeps its
|
|
15
|
+
* persisted `provider | estimated | mixed` vocabulary.
|
|
16
|
+
*/
|
|
17
|
+
export type ReasoningProvenance = ReasoningTokenProvenance | "unmeasured";
|
|
18
|
+
|
|
19
|
+
export interface ReasoningUsageView {
|
|
20
|
+
tokens: number;
|
|
21
|
+
provenance: ReasoningProvenance;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const UNMEASURED_REASONING: ReasoningUsageView = { tokens: 0, provenance: "unmeasured" };
|
|
25
|
+
|
|
26
|
+
function provenanceOf(hadProvider: boolean, hadEstimated: boolean): ReasoningProvenance {
|
|
27
|
+
if (hadProvider && hadEstimated) return "mixed";
|
|
28
|
+
if (hadProvider) return "provider";
|
|
29
|
+
if (hadEstimated) return "estimated";
|
|
30
|
+
return "unmeasured";
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function finiteTokens(value: unknown): number {
|
|
34
|
+
return typeof value === "number" && Number.isFinite(value) ? Math.max(0, value) : 0;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Live view of the in-flight turn, folded message by message as the run settles. */
|
|
38
|
+
export function reasoningFromTally(tally: RunTally | undefined): ReasoningUsageView {
|
|
39
|
+
if (!tally) return UNMEASURED_REASONING;
|
|
40
|
+
const provenance = provenanceOf(tally.hadProviderReasoning === true, tally.hadEstimatedReasoning === true);
|
|
41
|
+
if (provenance === "unmeasured") return UNMEASURED_REASONING;
|
|
42
|
+
return { tokens: finiteTokens(tally.reasoningTokens), provenance };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Settled view of a turn, including one replayed from a persisted summary. */
|
|
46
|
+
export function reasoningFromSummary(summary: TurnSummary | undefined): ReasoningUsageView {
|
|
47
|
+
if (!summary || typeof summary.reasoningTokens !== "number") return UNMEASURED_REASONING;
|
|
48
|
+
// A summary persisted before provenance was recorded carries a count and no
|
|
49
|
+
// label. `summaryFromRunTally` writes both together, so this only covers
|
|
50
|
+
// replayed history, where the count came from provider usage.
|
|
51
|
+
return { tokens: finiteTokens(summary.reasoningTokens), provenance: summary.reasoningTokenProvenance ?? "provider" };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Compact token text for the surfaces that have no formatter of their own. The
|
|
56
|
+
* footer passes its own `formatFooterTokens` so its chips keep the width
|
|
57
|
+
* budget the rest of the footer is measured against.
|
|
58
|
+
*/
|
|
59
|
+
export function compactReasoningTokens(value: number): string {
|
|
60
|
+
if (value < 1000) return String(Math.round(value));
|
|
61
|
+
if (value < 1_000_000) {
|
|
62
|
+
const scaled = (value / 1000).toFixed(1);
|
|
63
|
+
return `${scaled.endsWith(".0") ? scaled.slice(0, -2) : scaled}k`;
|
|
64
|
+
}
|
|
65
|
+
const scaled = (value / 1_000_000).toFixed(1);
|
|
66
|
+
return `${scaled.endsWith(".0") ? scaled.slice(0, -2) : scaled}M`;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* `r123` for a provider-attested count, `r≈123` for anything Clio inferred.
|
|
71
|
+
* Null when there is nothing to state: an unmeasured turn, or one that spent no
|
|
72
|
+
* reasoning tokens at all (a chip reading `r0` names the provenance of zero).
|
|
73
|
+
*/
|
|
74
|
+
export function formatReasoningChip(
|
|
75
|
+
view: ReasoningUsageView,
|
|
76
|
+
format: (value: number) => string = compactReasoningTokens,
|
|
77
|
+
): string | null {
|
|
78
|
+
if (view.provenance === "unmeasured" || view.tokens <= 0) return null;
|
|
79
|
+
return `r${view.provenance === "provider" ? "" : "≈"}${format(view.tokens)}`;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function formatReasoningLabel(view: ReasoningUsageView): string {
|
|
83
|
+
if (view.provenance === "provider") return "provider-reported";
|
|
84
|
+
if (view.provenance === "estimated") return "estimated";
|
|
85
|
+
if (view.provenance === "mixed") return "mixed";
|
|
86
|
+
return "unmeasured";
|
|
87
|
+
}
|
|
@@ -18,6 +18,7 @@ interface UsageLike {
|
|
|
18
18
|
output?: number;
|
|
19
19
|
cacheRead?: number;
|
|
20
20
|
cacheWrite?: number;
|
|
21
|
+
estimated?: boolean;
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
function assistantThinkingText(message: AgentMessage): string {
|
|
@@ -91,7 +92,10 @@ export function foldMessageIntoRunTally(tally: RunTally, message: AgentMessage):
|
|
|
91
92
|
cacheReadTokens: tally.cacheReadTokens + (usage ? finite(usage.cacheRead) : 0),
|
|
92
93
|
cacheWriteTokens: tally.cacheWriteTokens + (usage ? finite(usage.cacheWrite) : 0),
|
|
93
94
|
};
|
|
94
|
-
|
|
95
|
+
// Interrupted turns carry Clio's own estimated usage object. It retains the
|
|
96
|
+
// completed-record shape, including `reasoning: 0`, but that zero is not a
|
|
97
|
+
// provider attestation and must not suppress the thinking-text fallback.
|
|
98
|
+
const reasoning = usage && usage.estimated !== true ? extractReasoningTokens(usage) : null;
|
|
95
99
|
if (reasoning !== null) {
|
|
96
100
|
next.reasoningTokens += reasoning;
|
|
97
101
|
next.hadProviderReasoning = true;
|
|
@@ -102,7 +106,14 @@ export function foldMessageIntoRunTally(tally: RunTally, message: AgentMessage):
|
|
|
102
106
|
// reported as mixed rather than silently dropping the unreported block.
|
|
103
107
|
const estimated = estimateReasoningTextTokens(assistantThinkingText(message));
|
|
104
108
|
if (estimated !== null) {
|
|
105
|
-
|
|
109
|
+
// A chars/4 estimate over displayed thinking text can outrun what the
|
|
110
|
+
// provider says the call generated (summarized reasoning, a rail that
|
|
111
|
+
// re-renders the same block). Reported output is the ceiling for anything
|
|
112
|
+
// inferred: reasoning is part of that output, never more than it. No
|
|
113
|
+
// clamp exists upstream in the adapters, so it lives here, once.
|
|
114
|
+
const reportedOutput =
|
|
115
|
+
usage && typeof usage.output === "number" && Number.isFinite(usage.output) ? Math.max(0, usage.output) : null;
|
|
116
|
+
next.reasoningTokens += reportedOutput === null ? estimated : Math.min(estimated, reportedOutput);
|
|
106
117
|
next.hadEstimatedReasoning = true;
|
|
107
118
|
}
|
|
108
119
|
return next;
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transcript detail policy: the one place `/output minimal|default|verbose`
|
|
3
|
+
* becomes a decision about how much of each foldable transcript block is open
|
|
4
|
+
* before the operator touches it.
|
|
5
|
+
*
|
|
6
|
+
* The chat panel used to consult the verbosity string in four places, each
|
|
7
|
+
* spelling the rule a little differently, and nothing else (running rows, live
|
|
8
|
+
* tool output, worker cards in replay) consulted it at all. The panel now reads
|
|
9
|
+
* this policy once per frame and threads it down; the verbosity string never
|
|
10
|
+
* reaches a renderer.
|
|
11
|
+
*
|
|
12
|
+
* Operator toggles (`Alt+O`, `Alt+R`, expand-all, collapse-all) are overrides
|
|
13
|
+
* layered on top of this policy, not fights with it: the effective state of a
|
|
14
|
+
* block is `override ?? policy`, and `resolveFold` is that one rule.
|
|
15
|
+
*
|
|
16
|
+
* Pure module: no I/O, no UI imports.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import type { OutputVerbosity } from "../core/defaults.js";
|
|
20
|
+
import type { ToolFoldDefault, ToolPresentationPolicy } from "../tools/presentation.js";
|
|
21
|
+
|
|
22
|
+
/** A block's fold state. The same vocabulary serves tools, workers, thinking, and local bash. */
|
|
23
|
+
export type Fold = ToolFoldDefault;
|
|
24
|
+
|
|
25
|
+
/** An operator's explicit choice for one block, or none. */
|
|
26
|
+
export type FoldOverride = Fold | undefined;
|
|
27
|
+
|
|
28
|
+
export interface TranscriptDetailPolicy {
|
|
29
|
+
/** Finished tool call body: folded subline, the tool's own default, or open. */
|
|
30
|
+
toolBody: "folded" | "per-tool" | "expanded";
|
|
31
|
+
/** In-flight tool call: one-line row with elapsed, or the header plus streaming body. */
|
|
32
|
+
runningTool: "row" | "body";
|
|
33
|
+
/** Reasoning stretch: bare marker, folded marker with live progress, or the open rail. */
|
|
34
|
+
thinking: "marker" | "folded" | "rail";
|
|
35
|
+
/** Worker block: folded card, the origin default, or open. */
|
|
36
|
+
worker: "folded" | "origin" | "expanded";
|
|
37
|
+
/** Settled turn receipt: none, `turn · in N · out M`, or the full receipt. */
|
|
38
|
+
receipt: "none" | "compact" | "full";
|
|
39
|
+
/** Failed tool under its folded row: the bounded excerpt, or the full body. */
|
|
40
|
+
errors: "excerpt" | "body";
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const MINIMAL: TranscriptDetailPolicy = {
|
|
44
|
+
toolBody: "folded",
|
|
45
|
+
runningTool: "row",
|
|
46
|
+
thinking: "marker",
|
|
47
|
+
worker: "folded",
|
|
48
|
+
receipt: "none",
|
|
49
|
+
errors: "excerpt",
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const DEFAULT: TranscriptDetailPolicy = {
|
|
53
|
+
toolBody: "per-tool",
|
|
54
|
+
runningTool: "row",
|
|
55
|
+
thinking: "folded",
|
|
56
|
+
worker: "origin",
|
|
57
|
+
receipt: "compact",
|
|
58
|
+
errors: "excerpt",
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const VERBOSE: TranscriptDetailPolicy = {
|
|
62
|
+
toolBody: "expanded",
|
|
63
|
+
runningTool: "body",
|
|
64
|
+
thinking: "rail",
|
|
65
|
+
worker: "expanded",
|
|
66
|
+
receipt: "full",
|
|
67
|
+
errors: "body",
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Map a verbosity to the full policy table. An absent verbosity (a panel built
|
|
72
|
+
* without settings) is the balanced default, so the literal never has to be
|
|
73
|
+
* spelled by the caller.
|
|
74
|
+
*/
|
|
75
|
+
export function transcriptDetail(verbosity: OutputVerbosity | undefined): TranscriptDetailPolicy {
|
|
76
|
+
switch (verbosity) {
|
|
77
|
+
case "minimal":
|
|
78
|
+
return MINIMAL;
|
|
79
|
+
case "verbose":
|
|
80
|
+
return VERBOSE;
|
|
81
|
+
default:
|
|
82
|
+
return DEFAULT;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Effective state of one block: the operator's override when set, else the policy's answer. */
|
|
87
|
+
export function resolveFold(override: FoldOverride, policyFold: Fold): Fold {
|
|
88
|
+
return override ?? policyFold;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** The fold the policy gives a finished tool call, through the tool's own presentation when asked to. */
|
|
92
|
+
export function policyToolFold(detail: TranscriptDetailPolicy, presentation: ToolPresentationPolicy): Fold {
|
|
93
|
+
if (detail.toolBody === "per-tool") return presentation.foldDefault;
|
|
94
|
+
return detail.toolBody;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** The fold the policy gives an in-flight tool call. */
|
|
98
|
+
export function policyRunningToolFold(detail: TranscriptDetailPolicy): Fold {
|
|
99
|
+
return detail.runningTool === "body" ? "expanded" : "folded";
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** The fold the policy gives a reasoning stretch. */
|
|
103
|
+
export function policyThinkingFold(detail: TranscriptDetailPolicy): Fold {
|
|
104
|
+
return detail.thinking === "rail" ? "expanded" : "folded";
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* The fold the policy gives a worker block. `askedByModel` is the origin rule
|
|
109
|
+
* the caller already knows: a run the model asked for folds, the operator's own
|
|
110
|
+
* run opens.
|
|
111
|
+
*/
|
|
112
|
+
export function policyWorkerFold(detail: TranscriptDetailPolicy, askedByModel: boolean): Fold {
|
|
113
|
+
if (detail.worker === "origin") return askedByModel ? "folded" : "expanded";
|
|
114
|
+
return detail.worker;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** The override that flips a block away from its current effective state. */
|
|
118
|
+
export function toggledFold(effective: Fold): Fold {
|
|
119
|
+
return effective === "expanded" ? "folded" : "expanded";
|
|
120
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type ToolName, ToolNames } from "../core/tool-names.js";
|
|
2
2
|
import { OBSERVATION_POLICY_SLACK_BYTES, OBSERVE_SELF_CAPS } from "./observation.js";
|
|
3
|
+
import { toolPresentationPolicy } from "./presentation.js";
|
|
3
4
|
import { readMaxBytes } from "./read.js";
|
|
4
5
|
import type { ToolMetadata, ToolSourceInfo, ToolSpec } from "./registry.js";
|
|
5
6
|
|
|
@@ -255,9 +256,14 @@ export function toolPromptHintsForNames(names: ReadonlyArray<ToolName>): Readonl
|
|
|
255
256
|
return hints;
|
|
256
257
|
}
|
|
257
258
|
|
|
259
|
+
// Every builtin carries its transcript presentation on its metadata, resolved
|
|
260
|
+
// from the one declaration table in presentation.ts, so the registry and the
|
|
261
|
+
// live panel (which has no registry) answer the fold question identically.
|
|
258
262
|
function withBuiltinMetadata<T extends ToolSpec>(spec: T): T {
|
|
259
263
|
const metadata = TOOL_METADATA[spec.name];
|
|
260
|
-
return metadata
|
|
264
|
+
return metadata
|
|
265
|
+
? withMetadata(spec, { ...metadata, presentation: toolPresentationPolicy(spec.name, undefined) })
|
|
266
|
+
: spec;
|
|
261
267
|
}
|
|
262
268
|
|
|
263
269
|
export function builtin<T extends ToolSpec>(spec: T, sourceInfo: ToolSourceInfo): T {
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed tool presentation policy. Answers, per tool, what the transcript's
|
|
3
|
+
* balanced (`/output default`) view needs to know: does the block open folded
|
|
4
|
+
* or expanded, does the folded row keep a mutation diff visible, and does a
|
|
5
|
+
* failed folded row carry an output excerpt.
|
|
6
|
+
*
|
|
7
|
+
* The panel must not decide any of that by tool name. It asks this module,
|
|
8
|
+
* which resolves the answer from two inputs: the registered presentation
|
|
9
|
+
* metadata for the tool (declared once here and attached to `ToolMetadata` by
|
|
10
|
+
* the builtin catalog) and the argument-sensitive resource-read rule. The
|
|
11
|
+
* lookup is a plain object read, so it stays cheap enough to call on every
|
|
12
|
+
* frame, and it needs no registry instance: the live chat panel has none.
|
|
13
|
+
*
|
|
14
|
+
* Pure module: no I/O, no registry construction, no UI imports.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { ToolNames } from "../core/tool-names.js";
|
|
18
|
+
|
|
19
|
+
export type ToolFoldDefault = "expanded" | "folded";
|
|
20
|
+
|
|
21
|
+
export interface ToolPresentationPolicy {
|
|
22
|
+
/** How a fresh block for this call renders before the operator touches it. */
|
|
23
|
+
foldDefault: ToolFoldDefault;
|
|
24
|
+
/**
|
|
25
|
+
* Keep the mutation diff under the folded row. A folded `edit` that hides
|
|
26
|
+
* what it changed tells the operator nothing they could act on; the diff is
|
|
27
|
+
* the row's whole point and stays visible, bounded, until the body is opened.
|
|
28
|
+
*/
|
|
29
|
+
showDiffWhenFolded: boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Carry the last non-empty output line on a failed folded row. Bash pioneered
|
|
32
|
+
* this so a failed command stays diagnosable without opening its body; every
|
|
33
|
+
* tool that fails with text gets the same courtesy.
|
|
34
|
+
*/
|
|
35
|
+
failureExcerpt: boolean;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const FOLDED: ToolPresentationPolicy = { foldDefault: "folded", showDiffWhenFolded: false, failureExcerpt: true };
|
|
39
|
+
const FOLDED_WITH_DIFF: ToolPresentationPolicy = {
|
|
40
|
+
foldDefault: "folded",
|
|
41
|
+
showDiffWhenFolded: true,
|
|
42
|
+
failureExcerpt: true,
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Per-tool presentation declarations. Every builtin folds by default: a
|
|
47
|
+
* routine turn of six reads used to open six bodies, and the one-line row
|
|
48
|
+
* already carries the call, its outcome facts, size, and settlement. Mutations
|
|
49
|
+
* keep their diff under the folded row. Everything unlisted, including dynamic
|
|
50
|
+
* tools, folds the same way.
|
|
51
|
+
*/
|
|
52
|
+
export const TOOL_PRESENTATION: Readonly<Record<string, ToolPresentationPolicy>> = {
|
|
53
|
+
[ToolNames.Read]: FOLDED,
|
|
54
|
+
[ToolNames.Grep]: FOLDED,
|
|
55
|
+
[ToolNames.Find]: FOLDED,
|
|
56
|
+
[ToolNames.Ls]: FOLDED,
|
|
57
|
+
[ToolNames.CodeNav]: FOLDED,
|
|
58
|
+
[ToolNames.Context]: FOLDED,
|
|
59
|
+
[ToolNames.CredentialPresent]: FOLDED,
|
|
60
|
+
[ToolNames.Write]: FOLDED_WITH_DIFF,
|
|
61
|
+
[ToolNames.Edit]: FOLDED_WITH_DIFF,
|
|
62
|
+
[ToolNames.Bash]: FOLDED,
|
|
63
|
+
[ToolNames.Git]: FOLDED,
|
|
64
|
+
[ToolNames.Verify]: FOLDED,
|
|
65
|
+
[ToolNames.Dispatch]: FOLDED,
|
|
66
|
+
[ToolNames.Monitor]: FOLDED,
|
|
67
|
+
[ToolNames.Steer]: FOLDED,
|
|
68
|
+
[ToolNames.Tasks]: FOLDED,
|
|
69
|
+
[ToolNames.Ledger]: FOLDED,
|
|
70
|
+
[ToolNames.WebFetch]: FOLDED,
|
|
71
|
+
[ToolNames.AskUser]: FOLDED,
|
|
72
|
+
[ToolNames.Artifact]: FOLDED,
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
function readStringField(args: unknown, key: string): string | null {
|
|
76
|
+
if (typeof args !== "object" || args === null || Array.isArray(args)) return null;
|
|
77
|
+
const value = (args as Record<string, unknown>)[key];
|
|
78
|
+
return typeof value === "string" && value.length > 0 ? value : null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Compact resource-read classification. Reads of skill/handbook/agent
|
|
83
|
+
* instruction files and docs pages collapse to one labeled line and never
|
|
84
|
+
* auto-expand; their bodies are reference material, not task output.
|
|
85
|
+
*/
|
|
86
|
+
export function classifyResourceRead(toolName: string, args: unknown): string | null {
|
|
87
|
+
if (toolName !== ToolNames.Read) return null;
|
|
88
|
+
const path = readStringField(args, "path");
|
|
89
|
+
if (path === null) return null;
|
|
90
|
+
const normalized = path.replace(/\\/g, "/");
|
|
91
|
+
const base = normalized.split("/").pop() ?? "";
|
|
92
|
+
if (base === "SKILL.md") return "skill";
|
|
93
|
+
if (base === "CLIO-CODER.md") return "handbook";
|
|
94
|
+
if (base === "AGENTS.md") return "agents";
|
|
95
|
+
if (/(^|\/)docs\//.test(normalized)) return "docs";
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Resolve the presentation policy for one call. Argument-sensitive rules win
|
|
101
|
+
* over the per-tool declaration because a resource read is a property of the
|
|
102
|
+
* path, not of the `read` tool.
|
|
103
|
+
*/
|
|
104
|
+
export function toolPresentationPolicy(toolName: string, args: unknown): ToolPresentationPolicy {
|
|
105
|
+
if (classifyResourceRead(toolName, args) !== null) return FOLDED;
|
|
106
|
+
return TOOL_PRESENTATION[toolName] ?? FOLDED;
|
|
107
|
+
}
|
package/src/tools/registry.ts
CHANGED
|
@@ -26,6 +26,7 @@ import { hashToolCall } from "../domains/safety/loop-detector.js";
|
|
|
26
26
|
import { detectValidationCommand } from "../domains/safety/protected-artifacts.js";
|
|
27
27
|
import { askUserExposure } from "./ask-user.js";
|
|
28
28
|
import { type DispatchPlanView, describeDispatchPlan } from "./dispatch-plan.js";
|
|
29
|
+
import type { ToolPresentationPolicy } from "./presentation.js";
|
|
29
30
|
import { shapeToolResult } from "./result-shaping.js";
|
|
30
31
|
|
|
31
32
|
/**
|
|
@@ -76,6 +77,11 @@ export interface ToolMetadata {
|
|
|
76
77
|
* need none; the schema description covers them.
|
|
77
78
|
*/
|
|
78
79
|
promptHint?: string;
|
|
80
|
+
/**
|
|
81
|
+
* How transcript surfaces present this tool's block under `/output default`.
|
|
82
|
+
* Optional: tools that declare nothing fold like every other tool.
|
|
83
|
+
*/
|
|
84
|
+
presentation?: ToolPresentationPolicy;
|
|
79
85
|
}
|
|
80
86
|
|
|
81
87
|
export interface ToolSpec {
|