@kolisachint/hoocode-agent 0.5.31 → 0.5.32
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 +131 -0
- package/dist/core/agent-session-runtime.d.ts +28 -0
- package/dist/core/agent-session-runtime.d.ts.map +1 -1
- package/dist/core/agent-session-runtime.js +61 -2
- package/dist/core/agent-session-runtime.js.map +1 -1
- package/dist/core/keybindings.d.ts +102 -30
- package/dist/core/keybindings.d.ts.map +1 -1
- package/dist/core/keybindings.js +123 -29
- package/dist/core/keybindings.js.map +1 -1
- package/dist/core/settings-defaults.d.ts +1 -1
- package/dist/core/settings-defaults.d.ts.map +1 -1
- package/dist/core/settings-defaults.js +1 -1
- package/dist/core/settings-defaults.js.map +1 -1
- package/dist/core/settings-manager.d.ts +10 -3
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +19 -8
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/settings-types.d.ts +1 -1
- package/dist/core/settings-types.d.ts.map +1 -1
- package/dist/core/settings-types.js.map +1 -1
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +5 -1
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/core/tool-output-view.d.ts +33 -0
- package/dist/core/tool-output-view.d.ts.map +1 -0
- package/dist/core/tool-output-view.js +31 -0
- package/dist/core/tool-output-view.js.map +1 -0
- package/dist/core/tools/edit.d.ts.map +1 -1
- package/dist/core/tools/edit.js +4 -0
- package/dist/core/tools/edit.js.map +1 -1
- package/dist/modes/interactive/command-executor.d.ts +20 -0
- package/dist/modes/interactive/command-executor.d.ts.map +1 -1
- package/dist/modes/interactive/command-executor.js +138 -9
- package/dist/modes/interactive/command-executor.js.map +1 -1
- package/dist/modes/interactive/components/footer.d.ts +4 -0
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +31 -2
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/components/scoped-models-selector.d.ts +2 -2
- package/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/scoped-models-selector.js +2 -2
- package/dist/modes/interactive/components/scoped-models-selector.js.map +1 -1
- package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/session-selector.js +2 -2
- package/dist/modes/interactive/components/session-selector.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector.d.ts +3 -2
- package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +13 -12
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/components/tool-chain-summary.d.ts +65 -0
- package/dist/modes/interactive/components/tool-chain-summary.d.ts.map +1 -0
- package/dist/modes/interactive/components/tool-chain-summary.js +216 -0
- package/dist/modes/interactive/components/tool-chain-summary.js.map +1 -0
- package/dist/modes/interactive/components/tool-chain.d.ts +61 -0
- package/dist/modes/interactive/components/tool-chain.d.ts.map +1 -0
- package/dist/modes/interactive/components/tool-chain.js +156 -0
- package/dist/modes/interactive/components/tool-chain.js.map +1 -0
- package/dist/modes/interactive/components/tool-execution.d.ts +55 -9
- package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-execution.js +173 -33
- package/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/dist/modes/interactive/components/tool-signal.d.ts +50 -0
- package/dist/modes/interactive/components/tool-signal.d.ts.map +1 -0
- package/dist/modes/interactive/components/tool-signal.js +151 -0
- package/dist/modes/interactive/components/tool-signal.js.map +1 -0
- package/dist/modes/interactive/interactive-mode.d.ts +50 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +239 -29
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/resource-display.d.ts +9 -0
- package/dist/modes/interactive/resource-display.d.ts.map +1 -1
- package/dist/modes/interactive/resource-display.js +13 -0
- package/dist/modes/interactive/resource-display.js.map +1 -1
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package.json +1 -1
- package/package.json +4 -4
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { type Component } from "@kolisachint/hoocode-tui";
|
|
2
|
+
/** The single most identifying argument of a call, as a one-line string. */
|
|
3
|
+
export declare function toolSubject(args: unknown, cwd: string): string;
|
|
4
|
+
export interface ToolSignalInput {
|
|
5
|
+
toolName: string;
|
|
6
|
+
args: unknown;
|
|
7
|
+
cwd: string;
|
|
8
|
+
result?: {
|
|
9
|
+
content: Array<{
|
|
10
|
+
type: string;
|
|
11
|
+
text?: string;
|
|
12
|
+
data?: string;
|
|
13
|
+
mimeType?: string;
|
|
14
|
+
}>;
|
|
15
|
+
isError: boolean;
|
|
16
|
+
};
|
|
17
|
+
isPartial: boolean;
|
|
18
|
+
showImages: boolean;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* The flush-right signal: how much output came back, or why none did.
|
|
22
|
+
*
|
|
23
|
+
* Line count is the useful magnitude for nearly every tool here — it is what
|
|
24
|
+
* separates a search that found one hit from one that found four hundred. A
|
|
25
|
+
* result with no text (a successful `edit`, a `TodoWrite`) has no magnitude
|
|
26
|
+
* worth printing, so it reports the outcome instead.
|
|
27
|
+
*/
|
|
28
|
+
export declare function toolSignal(input: ToolSignalInput): {
|
|
29
|
+
text: string;
|
|
30
|
+
color: "success" | "error" | "warning";
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Render one radar row, already padded and coloured, without the status dot —
|
|
34
|
+
* the caller prepends that so the dot stays inline with the first line the same
|
|
35
|
+
* way it does in the other two views.
|
|
36
|
+
*/
|
|
37
|
+
export declare function renderToolSignalLine(input: ToolSignalInput, width: number): string;
|
|
38
|
+
/**
|
|
39
|
+
* Component wrapper so a radar row slots into the same container the call
|
|
40
|
+
* renderers use, and re-renders on resize like everything else.
|
|
41
|
+
*/
|
|
42
|
+
export declare class ToolSignalComponent implements Component {
|
|
43
|
+
private input;
|
|
44
|
+
private memo?;
|
|
45
|
+
constructor(input: ToolSignalInput);
|
|
46
|
+
setInput(input: ToolSignalInput): void;
|
|
47
|
+
invalidate(): void;
|
|
48
|
+
render(width: number): string[];
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=tool-signal.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-signal.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/tool-signal.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAiC,MAAM,0BAA0B,CAAC;AAiEzF,4EAA4E;AAC5E,wBAAgB,WAAW,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAqB9D;AAED,MAAM,WAAW,eAAe;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,OAAO,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE;QAAE,OAAO,EAAE,KAAK,CAAC;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,IAAI,CAAC,EAAE,MAAM,CAAC;YAAC,IAAI,CAAC,EAAE,MAAM,CAAC;YAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC;IACjH,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,OAAO,CAAC;CACpB;AAED;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,eAAe,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,SAAS,GAAG,OAAO,GAAG,SAAS,CAAA;CAAE,CAe3G;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,eAAe,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAqBlF;AAED;;;GAGG;AACH,qBAAa,mBAAoB,YAAW,SAAS;IACpD,OAAO,CAAC,KAAK,CAAkB;IAC/B,OAAO,CAAC,IAAI,CAAC,CAAmC;IAEhD,YAAY,KAAK,EAAE,eAAe,EAEjC;IAED,QAAQ,CAAC,KAAK,EAAE,eAAe,GAAG,IAAI,CAGrC;IAED,UAAU,IAAI,IAAI,CAEjB;IAED,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAK9B;CACD","sourcesContent":["import { type Component, truncateToWidth, visibleWidth } from \"@kolisachint/hoocode-tui\";\nimport { getTextOutput } from \"../../../core/tools/render-utils.js\";\nimport { getCwdRelativePath } from \"../../../utils/paths.js\";\nimport { theme } from \"../theme/theme.js\";\n\n/**\n * The \"radar\" view's one-line signal for a tool call.\n *\n * Radar answers a different question than the other two views. `glance` and\n * `full` show what a tool *said*; radar shows the *shape of the run* — the same\n * three columns for every tool, so a screen of them reads as a map:\n *\n * ● bash npm run check 412 lines\n * ● read packages/tui/src/keys.ts 1451 lines\n * ● grep toolOutputView 27 lines\n * ● edit src/core/keybindings.ts ok\n * ● find src/**.test.ts error\n *\n * Column 1 is the status dot the other views already use. Column 2 is the tool,\n * padded to a fixed width so the verbs line up and a run of reads is a visible\n * block. Column 3 is the call's subject. Flush right is the signal: how much\n * came back, which is what tells you whether a search actually found anything\n * without opening it.\n */\n\n/** Width of the tool-name column. Fits every built-in name (`websearch`, `TodoWrite`). */\nconst VERB_WIDTH = 9;\n\n/** Gap between the verb column and the subject. */\nconst VERB_GAP = 2;\n\n/** Minimum space kept for the flush-right signal before the subject is trimmed. */\nconst MIN_SIGNAL_GAP = 2;\n\n/**\n * Argument names that carry a call's subject, most specific first.\n *\n * Extensions and MCP servers bring their own argument names, so the list is a\n * preference order rather than a contract: anything unmatched falls back to the\n * first string-valued argument.\n */\nconst SUBJECT_KEYS = [\n\t\"command\",\n\t\"file_path\",\n\t// Before `path`: for grep/find the pattern is what identifies the call and\n\t// the path is only the scope it was run over.\n\t\"pattern\",\n\t\"query\",\n\t\"url\",\n\t\"path\",\n\t\"prompt\",\n\t\"description\",\n\t\"task_id\",\n\t\"subagent_type\",\n\t\"name\",\n\t\"id\",\n];\n\nfunction firstStringArg(args: Record<string, unknown>): string | undefined {\n\tfor (const value of Object.values(args)) {\n\t\tif (typeof value === \"string\" && value.trim()) return value;\n\t}\n\treturn undefined;\n}\n\n/** The single most identifying argument of a call, as a one-line string. */\nexport function toolSubject(args: unknown, cwd: string): string {\n\tif (typeof args !== \"object\" || args === null || Array.isArray(args)) return \"\";\n\n\tconst record = args as Record<string, unknown>;\n\tlet subject: string | undefined;\n\tfor (const key of SUBJECT_KEYS) {\n\t\tconst value = record[key];\n\t\tif (typeof value === \"string\" && value.trim()) {\n\t\t\tsubject = value;\n\t\t\tbreak;\n\t\t}\n\t}\n\tsubject ??= firstStringArg(record);\n\tif (!subject) return \"\";\n\n\t// Paths are the most common subject and the least readable in absolute form.\n\tconst relative = getCwdRelativePath(subject, cwd);\n\tconst display = relative && relative.length < subject.length ? relative : subject;\n\n\t// Multi-line commands (heredocs, chained scripts) collapse to their first line.\n\treturn display.replace(/\\s+/g, \" \").trim();\n}\n\nexport interface ToolSignalInput {\n\ttoolName: string;\n\targs: unknown;\n\tcwd: string;\n\tresult?: { content: Array<{ type: string; text?: string; data?: string; mimeType?: string }>; isError: boolean };\n\tisPartial: boolean;\n\tshowImages: boolean;\n}\n\n/**\n * The flush-right signal: how much output came back, or why none did.\n *\n * Line count is the useful magnitude for nearly every tool here — it is what\n * separates a search that found one hit from one that found four hundred. A\n * result with no text (a successful `edit`, a `TodoWrite`) has no magnitude\n * worth printing, so it reports the outcome instead.\n */\nexport function toolSignal(input: ToolSignalInput): { text: string; color: \"success\" | \"error\" | \"warning\" } {\n\tif (!input.result) {\n\t\treturn { text: input.isPartial ? \"running\" : \"\", color: \"warning\" };\n\t}\n\tif (input.result.isError) {\n\t\treturn { text: \"error\", color: \"error\" };\n\t}\n\n\tconst output = getTextOutput(input.result, input.showImages);\n\tif (!output.trim()) {\n\t\treturn { text: \"ok\", color: \"success\" };\n\t}\n\n\tconst lines = output.split(\"\\n\").length;\n\treturn { text: `${lines} ${lines === 1 ? \"line\" : \"lines\"}`, color: \"success\" };\n}\n\n/**\n * Render one radar row, already padded and coloured, without the status dot —\n * the caller prepends that so the dot stays inline with the first line the same\n * way it does in the other two views.\n */\nexport function renderToolSignalLine(input: ToolSignalInput, width: number): string {\n\tconst name = input.toolName.slice(0, VERB_WIDTH);\n\tconst verb = name.padEnd(VERB_WIDTH + VERB_GAP, \" \");\n\tconst signal = toolSignal(input);\n\tconst subject = toolSubject(input.args, input.cwd);\n\n\t// `width` already excludes the caller's status-dot prefix.\n\tconst available = Math.max(0, width);\n\tconst signalWidth = signal.text ? visibleWidth(signal.text) + MIN_SIGNAL_GAP : 0;\n\tconst subjectWidth = Math.max(0, available - visibleWidth(verb) - signalWidth);\n\tconst shownSubject = subjectWidth > 0 ? truncateToWidth(subject, subjectWidth) : \"\";\n\n\tconst left = verb + shownSubject;\n\tconst pad = Math.max(signal.text ? MIN_SIGNAL_GAP : 0, available - visibleWidth(left) - visibleWidth(signal.text));\n\n\treturn (\n\t\ttheme.fg(\"toolTitle\", theme.bold(verb)) +\n\t\ttheme.fg(\"toolOutput\", shownSubject) +\n\t\t\" \".repeat(pad) +\n\t\ttheme.fg(signal.color, signal.text)\n\t);\n}\n\n/**\n * Component wrapper so a radar row slots into the same container the call\n * renderers use, and re-renders on resize like everything else.\n */\nexport class ToolSignalComponent implements Component {\n\tprivate input: ToolSignalInput;\n\tprivate memo?: { width: number; out: string[] };\n\n\tconstructor(input: ToolSignalInput) {\n\t\tthis.input = input;\n\t}\n\n\tsetInput(input: ToolSignalInput): void {\n\t\tthis.input = input;\n\t\tthis.memo = undefined;\n\t}\n\n\tinvalidate(): void {\n\t\tthis.memo = undefined;\n\t}\n\n\trender(width: number): string[] {\n\t\tif (this.memo && this.memo.width === width) return this.memo.out;\n\t\tconst out = [renderToolSignalLine(this.input, width)];\n\t\tthis.memo = { width, out };\n\t\treturn out;\n\t}\n}\n"]}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { truncateToWidth, visibleWidth } from "@kolisachint/hoocode-tui";
|
|
2
|
+
import { getTextOutput } from "../../../core/tools/render-utils.js";
|
|
3
|
+
import { getCwdRelativePath } from "../../../utils/paths.js";
|
|
4
|
+
import { theme } from "../theme/theme.js";
|
|
5
|
+
/**
|
|
6
|
+
* The "radar" view's one-line signal for a tool call.
|
|
7
|
+
*
|
|
8
|
+
* Radar answers a different question than the other two views. `glance` and
|
|
9
|
+
* `full` show what a tool *said*; radar shows the *shape of the run* — the same
|
|
10
|
+
* three columns for every tool, so a screen of them reads as a map:
|
|
11
|
+
*
|
|
12
|
+
* ● bash npm run check 412 lines
|
|
13
|
+
* ● read packages/tui/src/keys.ts 1451 lines
|
|
14
|
+
* ● grep toolOutputView 27 lines
|
|
15
|
+
* ● edit src/core/keybindings.ts ok
|
|
16
|
+
* ● find src/**.test.ts error
|
|
17
|
+
*
|
|
18
|
+
* Column 1 is the status dot the other views already use. Column 2 is the tool,
|
|
19
|
+
* padded to a fixed width so the verbs line up and a run of reads is a visible
|
|
20
|
+
* block. Column 3 is the call's subject. Flush right is the signal: how much
|
|
21
|
+
* came back, which is what tells you whether a search actually found anything
|
|
22
|
+
* without opening it.
|
|
23
|
+
*/
|
|
24
|
+
/** Width of the tool-name column. Fits every built-in name (`websearch`, `TodoWrite`). */
|
|
25
|
+
const VERB_WIDTH = 9;
|
|
26
|
+
/** Gap between the verb column and the subject. */
|
|
27
|
+
const VERB_GAP = 2;
|
|
28
|
+
/** Minimum space kept for the flush-right signal before the subject is trimmed. */
|
|
29
|
+
const MIN_SIGNAL_GAP = 2;
|
|
30
|
+
/**
|
|
31
|
+
* Argument names that carry a call's subject, most specific first.
|
|
32
|
+
*
|
|
33
|
+
* Extensions and MCP servers bring their own argument names, so the list is a
|
|
34
|
+
* preference order rather than a contract: anything unmatched falls back to the
|
|
35
|
+
* first string-valued argument.
|
|
36
|
+
*/
|
|
37
|
+
const SUBJECT_KEYS = [
|
|
38
|
+
"command",
|
|
39
|
+
"file_path",
|
|
40
|
+
// Before `path`: for grep/find the pattern is what identifies the call and
|
|
41
|
+
// the path is only the scope it was run over.
|
|
42
|
+
"pattern",
|
|
43
|
+
"query",
|
|
44
|
+
"url",
|
|
45
|
+
"path",
|
|
46
|
+
"prompt",
|
|
47
|
+
"description",
|
|
48
|
+
"task_id",
|
|
49
|
+
"subagent_type",
|
|
50
|
+
"name",
|
|
51
|
+
"id",
|
|
52
|
+
];
|
|
53
|
+
function firstStringArg(args) {
|
|
54
|
+
for (const value of Object.values(args)) {
|
|
55
|
+
if (typeof value === "string" && value.trim())
|
|
56
|
+
return value;
|
|
57
|
+
}
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
/** The single most identifying argument of a call, as a one-line string. */
|
|
61
|
+
export function toolSubject(args, cwd) {
|
|
62
|
+
if (typeof args !== "object" || args === null || Array.isArray(args))
|
|
63
|
+
return "";
|
|
64
|
+
const record = args;
|
|
65
|
+
let subject;
|
|
66
|
+
for (const key of SUBJECT_KEYS) {
|
|
67
|
+
const value = record[key];
|
|
68
|
+
if (typeof value === "string" && value.trim()) {
|
|
69
|
+
subject = value;
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
subject ??= firstStringArg(record);
|
|
74
|
+
if (!subject)
|
|
75
|
+
return "";
|
|
76
|
+
// Paths are the most common subject and the least readable in absolute form.
|
|
77
|
+
const relative = getCwdRelativePath(subject, cwd);
|
|
78
|
+
const display = relative && relative.length < subject.length ? relative : subject;
|
|
79
|
+
// Multi-line commands (heredocs, chained scripts) collapse to their first line.
|
|
80
|
+
return display.replace(/\s+/g, " ").trim();
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* The flush-right signal: how much output came back, or why none did.
|
|
84
|
+
*
|
|
85
|
+
* Line count is the useful magnitude for nearly every tool here — it is what
|
|
86
|
+
* separates a search that found one hit from one that found four hundred. A
|
|
87
|
+
* result with no text (a successful `edit`, a `TodoWrite`) has no magnitude
|
|
88
|
+
* worth printing, so it reports the outcome instead.
|
|
89
|
+
*/
|
|
90
|
+
export function toolSignal(input) {
|
|
91
|
+
if (!input.result) {
|
|
92
|
+
return { text: input.isPartial ? "running" : "", color: "warning" };
|
|
93
|
+
}
|
|
94
|
+
if (input.result.isError) {
|
|
95
|
+
return { text: "error", color: "error" };
|
|
96
|
+
}
|
|
97
|
+
const output = getTextOutput(input.result, input.showImages);
|
|
98
|
+
if (!output.trim()) {
|
|
99
|
+
return { text: "ok", color: "success" };
|
|
100
|
+
}
|
|
101
|
+
const lines = output.split("\n").length;
|
|
102
|
+
return { text: `${lines} ${lines === 1 ? "line" : "lines"}`, color: "success" };
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Render one radar row, already padded and coloured, without the status dot —
|
|
106
|
+
* the caller prepends that so the dot stays inline with the first line the same
|
|
107
|
+
* way it does in the other two views.
|
|
108
|
+
*/
|
|
109
|
+
export function renderToolSignalLine(input, width) {
|
|
110
|
+
const name = input.toolName.slice(0, VERB_WIDTH);
|
|
111
|
+
const verb = name.padEnd(VERB_WIDTH + VERB_GAP, " ");
|
|
112
|
+
const signal = toolSignal(input);
|
|
113
|
+
const subject = toolSubject(input.args, input.cwd);
|
|
114
|
+
// `width` already excludes the caller's status-dot prefix.
|
|
115
|
+
const available = Math.max(0, width);
|
|
116
|
+
const signalWidth = signal.text ? visibleWidth(signal.text) + MIN_SIGNAL_GAP : 0;
|
|
117
|
+
const subjectWidth = Math.max(0, available - visibleWidth(verb) - signalWidth);
|
|
118
|
+
const shownSubject = subjectWidth > 0 ? truncateToWidth(subject, subjectWidth) : "";
|
|
119
|
+
const left = verb + shownSubject;
|
|
120
|
+
const pad = Math.max(signal.text ? MIN_SIGNAL_GAP : 0, available - visibleWidth(left) - visibleWidth(signal.text));
|
|
121
|
+
return (theme.fg("toolTitle", theme.bold(verb)) +
|
|
122
|
+
theme.fg("toolOutput", shownSubject) +
|
|
123
|
+
" ".repeat(pad) +
|
|
124
|
+
theme.fg(signal.color, signal.text));
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Component wrapper so a radar row slots into the same container the call
|
|
128
|
+
* renderers use, and re-renders on resize like everything else.
|
|
129
|
+
*/
|
|
130
|
+
export class ToolSignalComponent {
|
|
131
|
+
input;
|
|
132
|
+
memo;
|
|
133
|
+
constructor(input) {
|
|
134
|
+
this.input = input;
|
|
135
|
+
}
|
|
136
|
+
setInput(input) {
|
|
137
|
+
this.input = input;
|
|
138
|
+
this.memo = undefined;
|
|
139
|
+
}
|
|
140
|
+
invalidate() {
|
|
141
|
+
this.memo = undefined;
|
|
142
|
+
}
|
|
143
|
+
render(width) {
|
|
144
|
+
if (this.memo && this.memo.width === width)
|
|
145
|
+
return this.memo.out;
|
|
146
|
+
const out = [renderToolSignalLine(this.input, width)];
|
|
147
|
+
this.memo = { width, out };
|
|
148
|
+
return out;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
//# sourceMappingURL=tool-signal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-signal.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/tool-signal.ts"],"names":[],"mappings":"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","sourcesContent":["import { type Component, truncateToWidth, visibleWidth } from \"@kolisachint/hoocode-tui\";\nimport { getTextOutput } from \"../../../core/tools/render-utils.js\";\nimport { getCwdRelativePath } from \"../../../utils/paths.js\";\nimport { theme } from \"../theme/theme.js\";\n\n/**\n * The \"radar\" view's one-line signal for a tool call.\n *\n * Radar answers a different question than the other two views. `glance` and\n * `full` show what a tool *said*; radar shows the *shape of the run* — the same\n * three columns for every tool, so a screen of them reads as a map:\n *\n * ● bash npm run check 412 lines\n * ● read packages/tui/src/keys.ts 1451 lines\n * ● grep toolOutputView 27 lines\n * ● edit src/core/keybindings.ts ok\n * ● find src/**.test.ts error\n *\n * Column 1 is the status dot the other views already use. Column 2 is the tool,\n * padded to a fixed width so the verbs line up and a run of reads is a visible\n * block. Column 3 is the call's subject. Flush right is the signal: how much\n * came back, which is what tells you whether a search actually found anything\n * without opening it.\n */\n\n/** Width of the tool-name column. Fits every built-in name (`websearch`, `TodoWrite`). */\nconst VERB_WIDTH = 9;\n\n/** Gap between the verb column and the subject. */\nconst VERB_GAP = 2;\n\n/** Minimum space kept for the flush-right signal before the subject is trimmed. */\nconst MIN_SIGNAL_GAP = 2;\n\n/**\n * Argument names that carry a call's subject, most specific first.\n *\n * Extensions and MCP servers bring their own argument names, so the list is a\n * preference order rather than a contract: anything unmatched falls back to the\n * first string-valued argument.\n */\nconst SUBJECT_KEYS = [\n\t\"command\",\n\t\"file_path\",\n\t// Before `path`: for grep/find the pattern is what identifies the call and\n\t// the path is only the scope it was run over.\n\t\"pattern\",\n\t\"query\",\n\t\"url\",\n\t\"path\",\n\t\"prompt\",\n\t\"description\",\n\t\"task_id\",\n\t\"subagent_type\",\n\t\"name\",\n\t\"id\",\n];\n\nfunction firstStringArg(args: Record<string, unknown>): string | undefined {\n\tfor (const value of Object.values(args)) {\n\t\tif (typeof value === \"string\" && value.trim()) return value;\n\t}\n\treturn undefined;\n}\n\n/** The single most identifying argument of a call, as a one-line string. */\nexport function toolSubject(args: unknown, cwd: string): string {\n\tif (typeof args !== \"object\" || args === null || Array.isArray(args)) return \"\";\n\n\tconst record = args as Record<string, unknown>;\n\tlet subject: string | undefined;\n\tfor (const key of SUBJECT_KEYS) {\n\t\tconst value = record[key];\n\t\tif (typeof value === \"string\" && value.trim()) {\n\t\t\tsubject = value;\n\t\t\tbreak;\n\t\t}\n\t}\n\tsubject ??= firstStringArg(record);\n\tif (!subject) return \"\";\n\n\t// Paths are the most common subject and the least readable in absolute form.\n\tconst relative = getCwdRelativePath(subject, cwd);\n\tconst display = relative && relative.length < subject.length ? relative : subject;\n\n\t// Multi-line commands (heredocs, chained scripts) collapse to their first line.\n\treturn display.replace(/\\s+/g, \" \").trim();\n}\n\nexport interface ToolSignalInput {\n\ttoolName: string;\n\targs: unknown;\n\tcwd: string;\n\tresult?: { content: Array<{ type: string; text?: string; data?: string; mimeType?: string }>; isError: boolean };\n\tisPartial: boolean;\n\tshowImages: boolean;\n}\n\n/**\n * The flush-right signal: how much output came back, or why none did.\n *\n * Line count is the useful magnitude for nearly every tool here — it is what\n * separates a search that found one hit from one that found four hundred. A\n * result with no text (a successful `edit`, a `TodoWrite`) has no magnitude\n * worth printing, so it reports the outcome instead.\n */\nexport function toolSignal(input: ToolSignalInput): { text: string; color: \"success\" | \"error\" | \"warning\" } {\n\tif (!input.result) {\n\t\treturn { text: input.isPartial ? \"running\" : \"\", color: \"warning\" };\n\t}\n\tif (input.result.isError) {\n\t\treturn { text: \"error\", color: \"error\" };\n\t}\n\n\tconst output = getTextOutput(input.result, input.showImages);\n\tif (!output.trim()) {\n\t\treturn { text: \"ok\", color: \"success\" };\n\t}\n\n\tconst lines = output.split(\"\\n\").length;\n\treturn { text: `${lines} ${lines === 1 ? \"line\" : \"lines\"}`, color: \"success\" };\n}\n\n/**\n * Render one radar row, already padded and coloured, without the status dot —\n * the caller prepends that so the dot stays inline with the first line the same\n * way it does in the other two views.\n */\nexport function renderToolSignalLine(input: ToolSignalInput, width: number): string {\n\tconst name = input.toolName.slice(0, VERB_WIDTH);\n\tconst verb = name.padEnd(VERB_WIDTH + VERB_GAP, \" \");\n\tconst signal = toolSignal(input);\n\tconst subject = toolSubject(input.args, input.cwd);\n\n\t// `width` already excludes the caller's status-dot prefix.\n\tconst available = Math.max(0, width);\n\tconst signalWidth = signal.text ? visibleWidth(signal.text) + MIN_SIGNAL_GAP : 0;\n\tconst subjectWidth = Math.max(0, available - visibleWidth(verb) - signalWidth);\n\tconst shownSubject = subjectWidth > 0 ? truncateToWidth(subject, subjectWidth) : \"\";\n\n\tconst left = verb + shownSubject;\n\tconst pad = Math.max(signal.text ? MIN_SIGNAL_GAP : 0, available - visibleWidth(left) - visibleWidth(signal.text));\n\n\treturn (\n\t\ttheme.fg(\"toolTitle\", theme.bold(verb)) +\n\t\ttheme.fg(\"toolOutput\", shownSubject) +\n\t\t\" \".repeat(pad) +\n\t\ttheme.fg(signal.color, signal.text)\n\t);\n}\n\n/**\n * Component wrapper so a radar row slots into the same container the call\n * renderers use, and re-renders on resize like everything else.\n */\nexport class ToolSignalComponent implements Component {\n\tprivate input: ToolSignalInput;\n\tprivate memo?: { width: number; out: string[] };\n\n\tconstructor(input: ToolSignalInput) {\n\t\tthis.input = input;\n\t}\n\n\tsetInput(input: ToolSignalInput): void {\n\t\tthis.input = input;\n\t\tthis.memo = undefined;\n\t}\n\n\tinvalidate(): void {\n\t\tthis.memo = undefined;\n\t}\n\n\trender(width: number): string[] {\n\t\tif (this.memo && this.memo.width === width) return this.memo.out;\n\t\tconst out = [renderToolSignalLine(this.input, width)];\n\t\tthis.memo = { width, out };\n\t\treturn out;\n\t}\n}\n"]}
|
|
@@ -64,7 +64,9 @@ export declare class InteractiveMode {
|
|
|
64
64
|
private readonly scheduleStreamingRender;
|
|
65
65
|
private pendingTools;
|
|
66
66
|
private toolOutputExpanded;
|
|
67
|
-
private
|
|
67
|
+
private toolOutputView;
|
|
68
|
+
private openChain?;
|
|
69
|
+
private sawTextInCurrentMessage;
|
|
68
70
|
private hideThinkingBlock;
|
|
69
71
|
private skillCommands;
|
|
70
72
|
private unsubscribe?;
|
|
@@ -296,8 +298,55 @@ export declare class InteractiveMode {
|
|
|
296
298
|
private updateEditorBorderColor;
|
|
297
299
|
private updateEditorPromptPrefix;
|
|
298
300
|
private cycleThinkingLevel;
|
|
301
|
+
/**
|
|
302
|
+
* Put a tool block into the transcript, extending the open chain or starting
|
|
303
|
+
* one. Every path that renders a tool call comes through here — live
|
|
304
|
+
* streaming, execution start, and rebuilding history — so a chain cannot be
|
|
305
|
+
* assembled correctly in one place and forgotten in another.
|
|
306
|
+
*/
|
|
307
|
+
private attachToolBlock;
|
|
308
|
+
/**
|
|
309
|
+
* Settle the open chain.
|
|
310
|
+
*
|
|
311
|
+
* Called when the agent speaks (the run it was doing is over, whatever comes
|
|
312
|
+
* next is a new one) and when the turn settles. Closing on the agent's next
|
|
313
|
+
* words rather than only at turn end is what keeps the flip cheap: the line
|
|
314
|
+
* is still at the bottom of the screen, so the TUI rewrites it in place
|
|
315
|
+
* instead of taking the full-redraw path that clears terminal scrollback.
|
|
316
|
+
*/
|
|
317
|
+
private closeOpenChain;
|
|
318
|
+
/** Every tool block in the transcript, in order, across all chains. */
|
|
319
|
+
private transcriptToolBlocks;
|
|
320
|
+
/** Every chain in the transcript, in order. */
|
|
321
|
+
private transcriptChains;
|
|
299
322
|
private toggleToolOutputExpansion;
|
|
323
|
+
/**
|
|
324
|
+
* Open or close one tool block instead of all of them.
|
|
325
|
+
*
|
|
326
|
+
* `ctrl+o` is all-or-nothing, which left the ▸ caret on every glance and
|
|
327
|
+
* radar row advertising something no key could do. This is the missing half:
|
|
328
|
+
* unfold walks back from the newest block to the first one still folded,
|
|
329
|
+
* fold walks back to the most recently opened one.
|
|
330
|
+
*
|
|
331
|
+
* Working from the tail is not a shortcut, it is the only thing this view can
|
|
332
|
+
* honestly offer. The transcript is bottom-anchored and has no app-level
|
|
333
|
+
* scrolling — anything far enough up is in the terminal's own scrollback,
|
|
334
|
+
* where this process cannot put a cursor or scroll to a selection. So the
|
|
335
|
+
* key peels backwards through what is actually on screen, and each block it
|
|
336
|
+
* opens is its own marker for where you have got to.
|
|
337
|
+
*/
|
|
338
|
+
private stepToolOutput;
|
|
339
|
+
/**
|
|
340
|
+
* Move the view dial one stop and report where it landed.
|
|
341
|
+
*
|
|
342
|
+
* The dial is the persistent decision ("how much do I ever want to see"),
|
|
343
|
+
* which is why it saves; `app.tools.expand` stays the momentary one ("open
|
|
344
|
+
* what is in front of me"), which is why it does not.
|
|
345
|
+
*/
|
|
346
|
+
private cycleToolOutputView;
|
|
347
|
+
private applyToolOutputView;
|
|
300
348
|
private setToolsExpanded;
|
|
349
|
+
private cycleAgentMode;
|
|
301
350
|
private toggleThinkingBlockVisibility;
|
|
302
351
|
private openExternalEditor;
|
|
303
352
|
clearEditor(): void;
|