@kolisachint/hoocode-agent 0.5.30 → 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 +234 -0
- package/dist/core/agent-selection-eval.d.ts +91 -0
- package/dist/core/agent-selection-eval.d.ts.map +1 -0
- package/dist/core/agent-selection-eval.js +186 -0
- package/dist/core/agent-selection-eval.js.map +1 -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/builtin-skills.d.ts +61 -0
- package/dist/core/builtin-skills.d.ts.map +1 -0
- package/dist/core/builtin-skills.js +106 -0
- package/dist/core/builtin-skills.js.map +1 -0
- package/dist/core/extensions/plugins/default-marketplace/.agents-plugin/marketplace.json +5 -1
- package/dist/core/extensions/plugins/trigger-judge.d.ts +42 -0
- package/dist/core/extensions/plugins/trigger-judge.d.ts.map +1 -0
- package/dist/core/extensions/plugins/trigger-judge.js +121 -0
- package/dist/core/extensions/plugins/trigger-judge.js.map +1 -0
- package/dist/core/external-tools.d.ts +67 -0
- package/dist/core/external-tools.d.ts.map +1 -0
- package/dist/core/external-tools.js +120 -0
- package/dist/core/external-tools.js.map +1 -0
- 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/mode-prompts.d.ts +15 -3
- package/dist/core/mode-prompts.d.ts.map +1 -1
- package/dist/core/mode-prompts.js +17 -29
- package/dist/core/mode-prompts.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/core/tools/propose-plugin.d.ts.map +1 -1
- package/dist/core/tools/propose-plugin.js +7 -7
- package/dist/core/tools/propose-plugin.js.map +1 -1
- package/dist/core/tools/subagent.d.ts +7 -1
- package/dist/core/tools/subagent.d.ts.map +1 -1
- package/dist/core/tools/subagent.js +12 -24
- package/dist/core/tools/subagent.js.map +1 -1
- package/dist/extensions/core/modes.d.ts.map +1 -1
- package/dist/extensions/core/modes.js +21 -23
- package/dist/extensions/core/modes.js.map +1 -1
- package/dist/extensions/core/scaffold.d.ts.map +1 -1
- package/dist/extensions/core/scaffold.js +28 -24
- package/dist/extensions/core/scaffold.js.map +1 -1
- package/dist/init-templates.generated.d.ts +3 -0
- package/dist/init-templates.generated.d.ts.map +1 -1
- package/dist/init-templates.generated.js +12 -0
- package/dist/init-templates.generated.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +14 -1
- package/dist/main.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 +10 -2
- package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +165 -23
- 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 +244 -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/dist/utils/tools-manager.d.ts +30 -0
- package/dist/utils/tools-manager.d.ts.map +1 -1
- package/dist/utils/tools-manager.js +35 -0
- package/dist/utils/tools-manager.js.map +1 -1
- package/docs/modes.md +4 -0
- package/docs/plugins.md +10 -0
- package/docs/settings.md +42 -0
- package/docs/skills.md +30 -0
- 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 +5 -4
- package/templates/prompts/grill-bridge.md +1 -0
- package/templates/prompts/grill-me.md +7 -0
- package/templates/prompts/grill-plan.md +9 -0
- package/templates/prompts/task-background-agents.md +2 -0
- package/templates/prompts/task-background-none.md +1 -0
- package/templates/prompts/task-main.md +19 -0
- package/templates/skills/plugin-authoring/SKILL.md +81 -0
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The model call G4 was designed around and never had.
|
|
3
|
+
*
|
|
4
|
+
* `trigger-eval.ts` takes its judge as a parameter so scoring stays testable
|
|
5
|
+
* without a model, and nothing in the tree ever passed one — so every G4 run
|
|
6
|
+
* has reported `not-run` since it was written. This is that judge.
|
|
7
|
+
*
|
|
8
|
+
* It is deliberately in its own module rather than inside `trigger-eval.ts`:
|
|
9
|
+
* the eval is pure and testable, this reaches the network, and the same
|
|
10
|
+
* separation lets the agent-selection eval reuse the judge without pulling in
|
|
11
|
+
* the plugin gate machinery.
|
|
12
|
+
*
|
|
13
|
+
* ## Why one call for all prompts
|
|
14
|
+
*
|
|
15
|
+
* The judge sees every candidate and every prompt at once. Per-prompt calls
|
|
16
|
+
* would be cleaner to reason about, but the question being scored is
|
|
17
|
+
* comparative — "which of these fires" — and batching keeps the candidate list
|
|
18
|
+
* identical across prompts, which is the thing that must not vary. It also
|
|
19
|
+
* makes the cost proportional to the corpus rather than to the case count.
|
|
20
|
+
*/
|
|
21
|
+
import { completeSimple } from "@kolisachint/hoocode-ai";
|
|
22
|
+
/** Description characters per candidate. The opening states the trigger; past that it is filler. */
|
|
23
|
+
const DESCRIPTION_CHARS = 600;
|
|
24
|
+
const MAX_RESPONSE_TOKENS = 4_000;
|
|
25
|
+
const JUDGE_SYSTEM_PROMPT = `You simulate how a coding agent picks a capability.
|
|
26
|
+
|
|
27
|
+
You are given numbered CAPABILITIES (name and description) and numbered PROMPTS. For each prompt, answer with the ONE capability an agent would reach for, or null when none of them fits and the agent should just do the work itself.
|
|
28
|
+
|
|
29
|
+
Rules:
|
|
30
|
+
- Judge ONLY from the descriptions. Do not use knowledge about what these names usually mean elsewhere.
|
|
31
|
+
- Pick the single best fit. When two fit, pick the one whose description names the prompt's situation more specifically.
|
|
32
|
+
- Answer null when no description covers the prompt. Do not stretch a description to make it fit; a wrong pick and a null are both wrong, but pretending coverage hides the real failure.
|
|
33
|
+
- Answer every prompt exactly once, in the order given.
|
|
34
|
+
|
|
35
|
+
Output STRICT JSON, no markdown fence, no prose:
|
|
36
|
+
{"verdicts":[{"prompt":0,"capability":"explore"},{"prompt":1,"capability":null}]}`;
|
|
37
|
+
function buildPrompt(candidates, prompts) {
|
|
38
|
+
const lines = ["CAPABILITIES:"];
|
|
39
|
+
for (const [i, candidate] of candidates.entries()) {
|
|
40
|
+
lines.push(`${i}. ${candidate.name} — ${candidate.description.slice(0, DESCRIPTION_CHARS)}`);
|
|
41
|
+
}
|
|
42
|
+
lines.push("", "PROMPTS:");
|
|
43
|
+
for (const [i, prompt] of prompts.entries()) {
|
|
44
|
+
lines.push(`${i}. ${prompt}`);
|
|
45
|
+
}
|
|
46
|
+
return lines.join("\n");
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Read the verdict list back, positionally.
|
|
50
|
+
*
|
|
51
|
+
* Returns one entry per prompt no matter what came back: a missing or
|
|
52
|
+
* unparseable row becomes null (read as "nothing fired") rather than shifting
|
|
53
|
+
* every later verdict onto the wrong prompt. `runTriggerEval` rejects a
|
|
54
|
+
* length mismatch outright, so the alternative to filling the gaps is
|
|
55
|
+
* discarding the whole run — and a hallucinated capability name is a real
|
|
56
|
+
* signal about the candidate list, not a reason to throw the batch away.
|
|
57
|
+
*/
|
|
58
|
+
export function parseTriggerVerdicts(response, candidates, promptCount) {
|
|
59
|
+
const verdicts = new Array(promptCount).fill(null);
|
|
60
|
+
const start = response.indexOf("{");
|
|
61
|
+
const end = response.lastIndexOf("}");
|
|
62
|
+
if (start < 0 || end <= start)
|
|
63
|
+
return verdicts;
|
|
64
|
+
let parsed;
|
|
65
|
+
try {
|
|
66
|
+
parsed = JSON.parse(response.slice(start, end + 1));
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return verdicts;
|
|
70
|
+
}
|
|
71
|
+
const rows = parsed?.verdicts;
|
|
72
|
+
if (!Array.isArray(rows))
|
|
73
|
+
return verdicts;
|
|
74
|
+
const known = new Set(candidates.map((c) => c.name));
|
|
75
|
+
for (const row of rows) {
|
|
76
|
+
if (!row || typeof row !== "object")
|
|
77
|
+
continue;
|
|
78
|
+
const entry = row;
|
|
79
|
+
const index = typeof entry.prompt === "number" ? entry.prompt : Number.NaN;
|
|
80
|
+
if (!Number.isInteger(index) || index < 0 || index >= promptCount)
|
|
81
|
+
continue;
|
|
82
|
+
const capability = entry.capability;
|
|
83
|
+
if (capability === null || capability === undefined) {
|
|
84
|
+
verdicts[index] = null;
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
// A name that is not on the candidate list is a hallucination. Recording it
|
|
88
|
+
// as null keeps it wrong without letting it masquerade as a real pick.
|
|
89
|
+
verdicts[index] = typeof capability === "string" && known.has(capability) ? capability : null;
|
|
90
|
+
}
|
|
91
|
+
return verdicts;
|
|
92
|
+
}
|
|
93
|
+
/** A {@link TriggerJudge} backed by a real model. */
|
|
94
|
+
export function createLlmTriggerJudge(deps) {
|
|
95
|
+
return async ({ candidates, prompts }) => {
|
|
96
|
+
if (candidates.length === 0 || prompts.length === 0)
|
|
97
|
+
return prompts.map(() => null);
|
|
98
|
+
const response = await completeSimple(deps.model, {
|
|
99
|
+
systemPrompt: JUDGE_SYSTEM_PROMPT,
|
|
100
|
+
messages: [
|
|
101
|
+
{
|
|
102
|
+
role: "user",
|
|
103
|
+
content: [{ type: "text", text: buildPrompt(candidates, prompts) }],
|
|
104
|
+
timestamp: Date.now(),
|
|
105
|
+
},
|
|
106
|
+
],
|
|
107
|
+
}, { maxTokens: MAX_RESPONSE_TOKENS, signal: deps.signal, apiKey: deps.apiKey, headers: deps.headers });
|
|
108
|
+
if (response.stopReason === "error") {
|
|
109
|
+
// Thrown, not swallowed: runTriggerEval turns this into `not-run` with
|
|
110
|
+
// the reason attached, which is the honest outcome. Returning nulls
|
|
111
|
+
// would score every case as "nothing fired" and read like a real result.
|
|
112
|
+
throw new Error(response.errorMessage || "trigger judge call failed");
|
|
113
|
+
}
|
|
114
|
+
const text = response.content
|
|
115
|
+
.filter((c) => c.type === "text")
|
|
116
|
+
.map((c) => c.text)
|
|
117
|
+
.join("\n");
|
|
118
|
+
return parseTriggerVerdicts(text, candidates, prompts.length);
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
//# sourceMappingURL=trigger-judge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"trigger-judge.js","sourceRoot":"","sources":["../../../../src/core/extensions/plugins/trigger-judge.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The model call G4 was designed around and never had.\n *\n * `trigger-eval.ts` takes its judge as a parameter so scoring stays testable\n * without a model, and nothing in the tree ever passed one — so every G4 run\n * has reported `not-run` since it was written. This is that judge.\n *\n * It is deliberately in its own module rather than inside `trigger-eval.ts`:\n * the eval is pure and testable, this reaches the network, and the same\n * separation lets the agent-selection eval reuse the judge without pulling in\n * the plugin gate machinery.\n *\n * ## Why one call for all prompts\n *\n * The judge sees every candidate and every prompt at once. Per-prompt calls\n * would be cleaner to reason about, but the question being scored is\n * comparative — \"which of these fires\" — and batching keeps the candidate list\n * identical across prompts, which is the thing that must not vary. It also\n * makes the cost proportional to the corpus rather than to the case count.\n */\n\nimport { completeSimple, type Model } from \"@kolisachint/hoocode-ai\";\nimport type { TriggerCandidate, TriggerJudge, TriggerJudgeVerdict } from \"./trigger-eval.js\";\n\n/** Description characters per candidate. The opening states the trigger; past that it is filler. */\nconst DESCRIPTION_CHARS = 600;\nconst MAX_RESPONSE_TOKENS = 4_000;\n\nconst JUDGE_SYSTEM_PROMPT = `You simulate how a coding agent picks a capability.\n\nYou are given numbered CAPABILITIES (name and description) and numbered PROMPTS. For each prompt, answer with the ONE capability an agent would reach for, or null when none of them fits and the agent should just do the work itself.\n\nRules:\n- Judge ONLY from the descriptions. Do not use knowledge about what these names usually mean elsewhere.\n- Pick the single best fit. When two fit, pick the one whose description names the prompt's situation more specifically.\n- Answer null when no description covers the prompt. Do not stretch a description to make it fit; a wrong pick and a null are both wrong, but pretending coverage hides the real failure.\n- Answer every prompt exactly once, in the order given.\n\nOutput STRICT JSON, no markdown fence, no prose:\n{\"verdicts\":[{\"prompt\":0,\"capability\":\"explore\"},{\"prompt\":1,\"capability\":null}]}`;\n\nfunction buildPrompt(candidates: readonly TriggerCandidate[], prompts: readonly string[]): string {\n\tconst lines: string[] = [\"CAPABILITIES:\"];\n\tfor (const [i, candidate] of candidates.entries()) {\n\t\tlines.push(`${i}. ${candidate.name} — ${candidate.description.slice(0, DESCRIPTION_CHARS)}`);\n\t}\n\tlines.push(\"\", \"PROMPTS:\");\n\tfor (const [i, prompt] of prompts.entries()) {\n\t\tlines.push(`${i}. ${prompt}`);\n\t}\n\treturn lines.join(\"\\n\");\n}\n\n/**\n * Read the verdict list back, positionally.\n *\n * Returns one entry per prompt no matter what came back: a missing or\n * unparseable row becomes null (read as \"nothing fired\") rather than shifting\n * every later verdict onto the wrong prompt. `runTriggerEval` rejects a\n * length mismatch outright, so the alternative to filling the gaps is\n * discarding the whole run — and a hallucinated capability name is a real\n * signal about the candidate list, not a reason to throw the batch away.\n */\nexport function parseTriggerVerdicts(\n\tresponse: string,\n\tcandidates: readonly TriggerCandidate[],\n\tpromptCount: number,\n): TriggerJudgeVerdict[] {\n\tconst verdicts: TriggerJudgeVerdict[] = new Array(promptCount).fill(null);\n\tconst start = response.indexOf(\"{\");\n\tconst end = response.lastIndexOf(\"}\");\n\tif (start < 0 || end <= start) return verdicts;\n\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(response.slice(start, end + 1));\n\t} catch {\n\t\treturn verdicts;\n\t}\n\n\tconst rows = (parsed as { verdicts?: unknown })?.verdicts;\n\tif (!Array.isArray(rows)) return verdicts;\n\n\tconst known = new Set(candidates.map((c) => c.name));\n\tfor (const row of rows) {\n\t\tif (!row || typeof row !== \"object\") continue;\n\t\tconst entry = row as Record<string, unknown>;\n\t\tconst index = typeof entry.prompt === \"number\" ? entry.prompt : Number.NaN;\n\t\tif (!Number.isInteger(index) || index < 0 || index >= promptCount) continue;\n\t\tconst capability = entry.capability;\n\t\tif (capability === null || capability === undefined) {\n\t\t\tverdicts[index] = null;\n\t\t\tcontinue;\n\t\t}\n\t\t// A name that is not on the candidate list is a hallucination. Recording it\n\t\t// as null keeps it wrong without letting it masquerade as a real pick.\n\t\tverdicts[index] = typeof capability === \"string\" && known.has(capability) ? capability : null;\n\t}\n\treturn verdicts;\n}\n\nexport interface TriggerJudgeDeps {\n\tmodel: Model<any>;\n\tapiKey?: string;\n\theaders?: Record<string, string>;\n\tsignal?: AbortSignal;\n}\n\n/** A {@link TriggerJudge} backed by a real model. */\nexport function createLlmTriggerJudge(deps: TriggerJudgeDeps): TriggerJudge {\n\treturn async ({ candidates, prompts }) => {\n\t\tif (candidates.length === 0 || prompts.length === 0) return prompts.map(() => null);\n\n\t\tconst response = await completeSimple(\n\t\t\tdeps.model,\n\t\t\t{\n\t\t\t\tsystemPrompt: JUDGE_SYSTEM_PROMPT,\n\t\t\t\tmessages: [\n\t\t\t\t\t{\n\t\t\t\t\t\trole: \"user\",\n\t\t\t\t\t\tcontent: [{ type: \"text\", text: buildPrompt(candidates, prompts) }],\n\t\t\t\t\t\ttimestamp: Date.now(),\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t},\n\t\t\t{ maxTokens: MAX_RESPONSE_TOKENS, signal: deps.signal, apiKey: deps.apiKey, headers: deps.headers },\n\t\t);\n\n\t\tif (response.stopReason === \"error\") {\n\t\t\t// Thrown, not swallowed: runTriggerEval turns this into `not-run` with\n\t\t\t// the reason attached, which is the honest outcome. Returning nulls\n\t\t\t// would score every case as \"nothing fired\" and read like a real result.\n\t\t\tthrow new Error(response.errorMessage || \"trigger judge call failed\");\n\t\t}\n\n\t\tconst text = response.content\n\t\t\t.filter((c): c is { type: \"text\"; text: string } => c.type === \"text\")\n\t\t\t.map((c) => c.text)\n\t\t\t.join(\"\\n\");\n\t\treturn parseTriggerVerdicts(text, candidates, prompts.length);\n\t};\n}\n"]}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The external (Rust) binaries hoocode can use, and what each one is worth.
|
|
3
|
+
*
|
|
4
|
+
* hoocode is self-sufficient without any of them: `grep`/`find`/`search` fall
|
|
5
|
+
* back to pure-JS implementations, and the features that have no fallback
|
|
6
|
+
* (web, voice) are off or inert rather than broken. These binaries are an
|
|
7
|
+
* *expansion* layer, which is exactly why they were invisible - nothing failed
|
|
8
|
+
* loudly enough to tell anyone they existed.
|
|
9
|
+
*
|
|
10
|
+
* This module is the single description of that layer. The `/settings` pane
|
|
11
|
+
* renders it; the same table says which settings rows are gated on a binary, so
|
|
12
|
+
* a row that cannot do anything today can say so instead of lying.
|
|
13
|
+
*/
|
|
14
|
+
import { type ManagedTool, type ManagedToolStatus } from "../utils/tools-manager.js";
|
|
15
|
+
/** How hoocode gets a binary it does not already have. */
|
|
16
|
+
export type Acquisition =
|
|
17
|
+
/** Fetched in the background at startup. */
|
|
18
|
+
"startup"
|
|
19
|
+
/** Fetched the first time the feature is actually used. */
|
|
20
|
+
| "on-demand"
|
|
21
|
+
/** Never fetched implicitly; install it yourself or point the env var at it. */
|
|
22
|
+
| "manual";
|
|
23
|
+
export interface ExternalToolDoc {
|
|
24
|
+
tool: ManagedTool;
|
|
25
|
+
/** Row label in the pane. */
|
|
26
|
+
label: string;
|
|
27
|
+
/** What the binary does for hoocode, in one line. */
|
|
28
|
+
summary: string;
|
|
29
|
+
/** What it turns on, feature by feature. */
|
|
30
|
+
enables: string[];
|
|
31
|
+
/** What hoocode does instead when it is missing. Never "nothing works". */
|
|
32
|
+
fallback: string;
|
|
33
|
+
acquisition: Acquisition;
|
|
34
|
+
/** Env vars that change how this binary is resolved or driven. */
|
|
35
|
+
env: string[];
|
|
36
|
+
/**
|
|
37
|
+
* `/settings` row ids whose effect depends on this binary. A row listed here
|
|
38
|
+
* is annotated (not hidden) while the binary is missing: hiding it would
|
|
39
|
+
* recreate the discoverability hole this whole surface exists to close.
|
|
40
|
+
*/
|
|
41
|
+
dependentRows: string[];
|
|
42
|
+
/** settings.json keys this binary gates, for docs and search. */
|
|
43
|
+
settingsKeys: string[];
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Ordered so the two that silently make everything faster come first, then the
|
|
47
|
+
* three that add capability hoocode does not otherwise have.
|
|
48
|
+
*/
|
|
49
|
+
export declare const EXTERNAL_TOOLS: readonly ExternalToolDoc[];
|
|
50
|
+
export interface ExternalToolStatus extends ExternalToolDoc, ManagedToolStatus {
|
|
51
|
+
installed: boolean;
|
|
52
|
+
/**
|
|
53
|
+
* Whether hoocode would fetch it if the feature were used right now. False in
|
|
54
|
+
* offline mode, and on Android where the published Linux builds do not run.
|
|
55
|
+
*/
|
|
56
|
+
downloadable: boolean;
|
|
57
|
+
}
|
|
58
|
+
/** Short, fixed-width-ish status word for the pane's value column. */
|
|
59
|
+
export declare function statusLabel(status: ExternalToolStatus): string;
|
|
60
|
+
/**
|
|
61
|
+
* Resolve every external tool. Never downloads: this is what the pane shows
|
|
62
|
+
* *before* the user opts into anything.
|
|
63
|
+
*/
|
|
64
|
+
export declare function describeExternalTools(): ExternalToolStatus[];
|
|
65
|
+
/** Index of pane-row id -> the tool it needs, for annotating gated rows. */
|
|
66
|
+
export declare function buildRowGates(statuses: readonly ExternalToolStatus[]): Map<string, ExternalToolStatus>;
|
|
67
|
+
//# sourceMappingURL=external-tools.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"external-tools.d.ts","sourceRoot":"","sources":["../../src/core/external-tools.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAgC,KAAK,WAAW,EAAE,KAAK,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAEnH,0DAA0D;AAC1D,MAAM,MAAM,WAAW;AACtB,4CAA4C;AAC1C,SAAS;AACX,2DAA2D;GACzD,WAAW;AACb,gFAAgF;GAC9E,QAAQ,CAAC;AAEZ,MAAM,WAAW,eAAe;IAC/B,IAAI,EAAE,WAAW,CAAC;IAClB,6BAA6B;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,qDAAqD;IACrD,OAAO,EAAE,MAAM,CAAC;IAChB,4CAA4C;IAC5C,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,2EAA2E;IAC3E,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,WAAW,CAAC;IACzB,kEAAkE;IAClE,GAAG,EAAE,MAAM,EAAE,CAAC;IACd;;;;OAIG;IACH,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,iEAAiE;IACjE,YAAY,EAAE,MAAM,EAAE,CAAC;CACvB;AAED;;;GAGG;AACH,eAAO,MAAM,cAAc,EAAE,SAAS,eAAe,EA+DpD,CAAC;AAEF,MAAM,WAAW,kBAAmB,SAAQ,eAAe,EAAE,iBAAiB;IAC7E,SAAS,EAAE,OAAO,CAAC;IACnB;;;OAGG;IACH,YAAY,EAAE,OAAO,CAAC;CACtB;AAED,sEAAsE;AACtE,wBAAgB,WAAW,CAAC,MAAM,EAAE,kBAAkB,GAAG,MAAM,CAY9D;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,IAAI,kBAAkB,EAAE,CAY5D;AAED,4EAA4E;AAC5E,wBAAgB,aAAa,CAAC,QAAQ,EAAE,SAAS,kBAAkB,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAMtG","sourcesContent":["/**\n * The external (Rust) binaries hoocode can use, and what each one is worth.\n *\n * hoocode is self-sufficient without any of them: `grep`/`find`/`search` fall\n * back to pure-JS implementations, and the features that have no fallback\n * (web, voice) are off or inert rather than broken. These binaries are an\n * *expansion* layer, which is exactly why they were invisible - nothing failed\n * loudly enough to tell anyone they existed.\n *\n * This module is the single description of that layer. The `/settings` pane\n * renders it; the same table says which settings rows are gated on a binary, so\n * a row that cannot do anything today can say so instead of lying.\n */\n\nimport { getToolStatus, isOfflineMode, type ManagedTool, type ManagedToolStatus } from \"../utils/tools-manager.js\";\n\n/** How hoocode gets a binary it does not already have. */\nexport type Acquisition =\n\t/** Fetched in the background at startup. */\n\t| \"startup\"\n\t/** Fetched the first time the feature is actually used. */\n\t| \"on-demand\"\n\t/** Never fetched implicitly; install it yourself or point the env var at it. */\n\t| \"manual\";\n\nexport interface ExternalToolDoc {\n\ttool: ManagedTool;\n\t/** Row label in the pane. */\n\tlabel: string;\n\t/** What the binary does for hoocode, in one line. */\n\tsummary: string;\n\t/** What it turns on, feature by feature. */\n\tenables: string[];\n\t/** What hoocode does instead when it is missing. Never \"nothing works\". */\n\tfallback: string;\n\tacquisition: Acquisition;\n\t/** Env vars that change how this binary is resolved or driven. */\n\tenv: string[];\n\t/**\n\t * `/settings` row ids whose effect depends on this binary. A row listed here\n\t * is annotated (not hidden) while the binary is missing: hiding it would\n\t * recreate the discoverability hole this whole surface exists to close.\n\t */\n\tdependentRows: string[];\n\t/** settings.json keys this binary gates, for docs and search. */\n\tsettingsKeys: string[];\n}\n\n/**\n * Ordered so the two that silently make everything faster come first, then the\n * three that add capability hoocode does not otherwise have.\n */\nexport const EXTERNAL_TOOLS: readonly ExternalToolDoc[] = [\n\t{\n\t\ttool: \"rg\",\n\t\tlabel: \"ripgrep (rg)\",\n\t\tsummary: \"Fast path for content search.\",\n\t\tenables: [\"grep runs rg instead of the JS scanner\", \"the lexical half of search runs rg\"],\n\t\tfallback:\n\t\t\t\"A pure-JS scanner produces the same match shape, so results are identical - it is materially slower on large trees and respects fewer ignore-file edge cases.\",\n\t\tacquisition: \"startup\",\n\t\tenv: [\"HOOCODE_RG_BINARY\", \"HOOCODE_NATIVE_SEARCH=1 forces the JS path even when rg is present\"],\n\t\tdependentRows: [],\n\t\tsettingsKeys: [],\n\t},\n\t{\n\t\ttool: \"fd\",\n\t\tlabel: \"fd\",\n\t\tsummary: \"Fast path for filename search.\",\n\t\tenables: [\"find runs fd instead of the JS directory walker\"],\n\t\tfallback:\n\t\t\t\"A JS walker produces the same result shape - slower on large trees, and glob/ignore handling is the JS approximation rather than fd's.\",\n\t\tacquisition: \"startup\",\n\t\tenv: [\"HOOCODE_FD_BINARY\", \"HOOCODE_NATIVE_SEARCH=1 forces the JS path even when fd is present\"],\n\t\tdependentRows: [],\n\t\tsettingsKeys: [],\n\t},\n\t{\n\t\ttool: \"embsearch\",\n\t\tlabel: \"embsearch (semantic index)\",\n\t\tsummary: \"Local embedding index. The only source of semantic ranking in hoocode.\",\n\t\tenables: [\n\t\t\t\"search fuses semantic hits with its lexical hits\",\n\t\t\t\"MCP/capability deferral ranks tools by meaning rather than keyword\",\n\t\t],\n\t\tfallback:\n\t\t\t\"search is lexical-only and capability lookup ranks lexically. Nothing errors; queries phrased by intent rather than by token simply rank worse. Requires the ONNX build - the mock build is rejected on purpose, because it would rank at random while looking healthy.\",\n\t\tacquisition: \"on-demand\",\n\t\tenv: [\"HOOCODE_EMBSEARCH_BINARY\"],\n\t\tdependentRows: [\"group:embsearch\"],\n\t\tsettingsKeys: [\"enableEmbsearchTools\", \"embsearchBinaryPath\", \"embsearchThresholdBytes\"],\n\t},\n\t{\n\t\ttool: \"webtools\",\n\t\tlabel: \"webtools (webfetch/websearch)\",\n\t\tsummary: \"The network layer. Without it hoocode has no way to reach the internet.\",\n\t\tenables: [\"the webfetch tool\", \"the websearch tool\"],\n\t\tfallback:\n\t\t\t\"Both tools return an error when called. The web tool group is off by default, so a missing binary is invisible until you turn the group on.\",\n\t\tacquisition: \"on-demand\",\n\t\tenv: [\"HOOCODE_WEBTOOLS_BINARY\", \"HOOCODE_WEBTOOLS_TIMEOUT\"],\n\t\tdependentRows: [\"group:web\", \"webtools-timeout-secs\"],\n\t\tsettingsKeys: [\"enableWebTools\", \"webtools.timeoutSecs\"],\n\t},\n\t{\n\t\ttool: \"voicetools\",\n\t\tlabel: \"voicetools (voice input)\",\n\t\tsummary: \"Microphone capture and transcription for the TUI.\",\n\t\tenables: [\"push-to-talk voice input in the editor\"],\n\t\tfallback: \"Voice capture reports an error and never starts. Typing is unaffected.\",\n\t\tacquisition: \"on-demand\",\n\t\tenv: [\"VOICETOOLS_BIN\", \"HOOCODE_VOICETOOLS_BINARY\", \"VOICETOOLS_SILENCE_MS\"],\n\t\tdependentRows: [\"voice-silence-ms\"],\n\t\tsettingsKeys: [\"voice.silenceMs\"],\n\t},\n];\n\nexport interface ExternalToolStatus extends ExternalToolDoc, ManagedToolStatus {\n\tinstalled: boolean;\n\t/**\n\t * Whether hoocode would fetch it if the feature were used right now. False in\n\t * offline mode, and on Android where the published Linux builds do not run.\n\t */\n\tdownloadable: boolean;\n}\n\n/** Short, fixed-width-ish status word for the pane's value column. */\nexport function statusLabel(status: ExternalToolStatus): string {\n\tif (!status.installed) return status.downloadable ? \"not installed\" : \"unavailable\";\n\tswitch (status.source) {\n\t\tcase \"override\":\n\t\t\treturn \"env override\";\n\t\tcase \"managed\":\n\t\t\treturn \"installed\";\n\t\tcase \"path\":\n\t\t\treturn \"system\";\n\t\tdefault:\n\t\t\treturn \"installed\";\n\t}\n}\n\n/**\n * Resolve every external tool. Never downloads: this is what the pane shows\n * *before* the user opts into anything.\n */\nexport function describeExternalTools(): ExternalToolStatus[] {\n\tconst offline = isOfflineMode();\n\tconst android = process.platform === \"android\";\n\treturn EXTERNAL_TOOLS.map((doc) => {\n\t\tconst status = getToolStatus(doc.tool);\n\t\treturn {\n\t\t\t...doc,\n\t\t\t...status,\n\t\t\tinstalled: status.path !== null,\n\t\t\tdownloadable: !offline && !android && doc.acquisition !== \"manual\",\n\t\t};\n\t});\n}\n\n/** Index of pane-row id -> the tool it needs, for annotating gated rows. */\nexport function buildRowGates(statuses: readonly ExternalToolStatus[]): Map<string, ExternalToolStatus> {\n\tconst gates = new Map<string, ExternalToolStatus>();\n\tfor (const status of statuses) {\n\t\tfor (const rowId of status.dependentRows) gates.set(rowId, status);\n\t}\n\treturn gates;\n}\n"]}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The external (Rust) binaries hoocode can use, and what each one is worth.
|
|
3
|
+
*
|
|
4
|
+
* hoocode is self-sufficient without any of them: `grep`/`find`/`search` fall
|
|
5
|
+
* back to pure-JS implementations, and the features that have no fallback
|
|
6
|
+
* (web, voice) are off or inert rather than broken. These binaries are an
|
|
7
|
+
* *expansion* layer, which is exactly why they were invisible - nothing failed
|
|
8
|
+
* loudly enough to tell anyone they existed.
|
|
9
|
+
*
|
|
10
|
+
* This module is the single description of that layer. The `/settings` pane
|
|
11
|
+
* renders it; the same table says which settings rows are gated on a binary, so
|
|
12
|
+
* a row that cannot do anything today can say so instead of lying.
|
|
13
|
+
*/
|
|
14
|
+
import { getToolStatus, isOfflineMode } from "../utils/tools-manager.js";
|
|
15
|
+
/**
|
|
16
|
+
* Ordered so the two that silently make everything faster come first, then the
|
|
17
|
+
* three that add capability hoocode does not otherwise have.
|
|
18
|
+
*/
|
|
19
|
+
export const EXTERNAL_TOOLS = [
|
|
20
|
+
{
|
|
21
|
+
tool: "rg",
|
|
22
|
+
label: "ripgrep (rg)",
|
|
23
|
+
summary: "Fast path for content search.",
|
|
24
|
+
enables: ["grep runs rg instead of the JS scanner", "the lexical half of search runs rg"],
|
|
25
|
+
fallback: "A pure-JS scanner produces the same match shape, so results are identical - it is materially slower on large trees and respects fewer ignore-file edge cases.",
|
|
26
|
+
acquisition: "startup",
|
|
27
|
+
env: ["HOOCODE_RG_BINARY", "HOOCODE_NATIVE_SEARCH=1 forces the JS path even when rg is present"],
|
|
28
|
+
dependentRows: [],
|
|
29
|
+
settingsKeys: [],
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
tool: "fd",
|
|
33
|
+
label: "fd",
|
|
34
|
+
summary: "Fast path for filename search.",
|
|
35
|
+
enables: ["find runs fd instead of the JS directory walker"],
|
|
36
|
+
fallback: "A JS walker produces the same result shape - slower on large trees, and glob/ignore handling is the JS approximation rather than fd's.",
|
|
37
|
+
acquisition: "startup",
|
|
38
|
+
env: ["HOOCODE_FD_BINARY", "HOOCODE_NATIVE_SEARCH=1 forces the JS path even when fd is present"],
|
|
39
|
+
dependentRows: [],
|
|
40
|
+
settingsKeys: [],
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
tool: "embsearch",
|
|
44
|
+
label: "embsearch (semantic index)",
|
|
45
|
+
summary: "Local embedding index. The only source of semantic ranking in hoocode.",
|
|
46
|
+
enables: [
|
|
47
|
+
"search fuses semantic hits with its lexical hits",
|
|
48
|
+
"MCP/capability deferral ranks tools by meaning rather than keyword",
|
|
49
|
+
],
|
|
50
|
+
fallback: "search is lexical-only and capability lookup ranks lexically. Nothing errors; queries phrased by intent rather than by token simply rank worse. Requires the ONNX build - the mock build is rejected on purpose, because it would rank at random while looking healthy.",
|
|
51
|
+
acquisition: "on-demand",
|
|
52
|
+
env: ["HOOCODE_EMBSEARCH_BINARY"],
|
|
53
|
+
dependentRows: ["group:embsearch"],
|
|
54
|
+
settingsKeys: ["enableEmbsearchTools", "embsearchBinaryPath", "embsearchThresholdBytes"],
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
tool: "webtools",
|
|
58
|
+
label: "webtools (webfetch/websearch)",
|
|
59
|
+
summary: "The network layer. Without it hoocode has no way to reach the internet.",
|
|
60
|
+
enables: ["the webfetch tool", "the websearch tool"],
|
|
61
|
+
fallback: "Both tools return an error when called. The web tool group is off by default, so a missing binary is invisible until you turn the group on.",
|
|
62
|
+
acquisition: "on-demand",
|
|
63
|
+
env: ["HOOCODE_WEBTOOLS_BINARY", "HOOCODE_WEBTOOLS_TIMEOUT"],
|
|
64
|
+
dependentRows: ["group:web", "webtools-timeout-secs"],
|
|
65
|
+
settingsKeys: ["enableWebTools", "webtools.timeoutSecs"],
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
tool: "voicetools",
|
|
69
|
+
label: "voicetools (voice input)",
|
|
70
|
+
summary: "Microphone capture and transcription for the TUI.",
|
|
71
|
+
enables: ["push-to-talk voice input in the editor"],
|
|
72
|
+
fallback: "Voice capture reports an error and never starts. Typing is unaffected.",
|
|
73
|
+
acquisition: "on-demand",
|
|
74
|
+
env: ["VOICETOOLS_BIN", "HOOCODE_VOICETOOLS_BINARY", "VOICETOOLS_SILENCE_MS"],
|
|
75
|
+
dependentRows: ["voice-silence-ms"],
|
|
76
|
+
settingsKeys: ["voice.silenceMs"],
|
|
77
|
+
},
|
|
78
|
+
];
|
|
79
|
+
/** Short, fixed-width-ish status word for the pane's value column. */
|
|
80
|
+
export function statusLabel(status) {
|
|
81
|
+
if (!status.installed)
|
|
82
|
+
return status.downloadable ? "not installed" : "unavailable";
|
|
83
|
+
switch (status.source) {
|
|
84
|
+
case "override":
|
|
85
|
+
return "env override";
|
|
86
|
+
case "managed":
|
|
87
|
+
return "installed";
|
|
88
|
+
case "path":
|
|
89
|
+
return "system";
|
|
90
|
+
default:
|
|
91
|
+
return "installed";
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Resolve every external tool. Never downloads: this is what the pane shows
|
|
96
|
+
* *before* the user opts into anything.
|
|
97
|
+
*/
|
|
98
|
+
export function describeExternalTools() {
|
|
99
|
+
const offline = isOfflineMode();
|
|
100
|
+
const android = process.platform === "android";
|
|
101
|
+
return EXTERNAL_TOOLS.map((doc) => {
|
|
102
|
+
const status = getToolStatus(doc.tool);
|
|
103
|
+
return {
|
|
104
|
+
...doc,
|
|
105
|
+
...status,
|
|
106
|
+
installed: status.path !== null,
|
|
107
|
+
downloadable: !offline && !android && doc.acquisition !== "manual",
|
|
108
|
+
};
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
/** Index of pane-row id -> the tool it needs, for annotating gated rows. */
|
|
112
|
+
export function buildRowGates(statuses) {
|
|
113
|
+
const gates = new Map();
|
|
114
|
+
for (const status of statuses) {
|
|
115
|
+
for (const rowId of status.dependentRows)
|
|
116
|
+
gates.set(rowId, status);
|
|
117
|
+
}
|
|
118
|
+
return gates;
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=external-tools.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"external-tools.js","sourceRoot":"","sources":["../../src/core/external-tools.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The external (Rust) binaries hoocode can use, and what each one is worth.\n *\n * hoocode is self-sufficient without any of them: `grep`/`find`/`search` fall\n * back to pure-JS implementations, and the features that have no fallback\n * (web, voice) are off or inert rather than broken. These binaries are an\n * *expansion* layer, which is exactly why they were invisible - nothing failed\n * loudly enough to tell anyone they existed.\n *\n * This module is the single description of that layer. The `/settings` pane\n * renders it; the same table says which settings rows are gated on a binary, so\n * a row that cannot do anything today can say so instead of lying.\n */\n\nimport { getToolStatus, isOfflineMode, type ManagedTool, type ManagedToolStatus } from \"../utils/tools-manager.js\";\n\n/** How hoocode gets a binary it does not already have. */\nexport type Acquisition =\n\t/** Fetched in the background at startup. */\n\t| \"startup\"\n\t/** Fetched the first time the feature is actually used. */\n\t| \"on-demand\"\n\t/** Never fetched implicitly; install it yourself or point the env var at it. */\n\t| \"manual\";\n\nexport interface ExternalToolDoc {\n\ttool: ManagedTool;\n\t/** Row label in the pane. */\n\tlabel: string;\n\t/** What the binary does for hoocode, in one line. */\n\tsummary: string;\n\t/** What it turns on, feature by feature. */\n\tenables: string[];\n\t/** What hoocode does instead when it is missing. Never \"nothing works\". */\n\tfallback: string;\n\tacquisition: Acquisition;\n\t/** Env vars that change how this binary is resolved or driven. */\n\tenv: string[];\n\t/**\n\t * `/settings` row ids whose effect depends on this binary. A row listed here\n\t * is annotated (not hidden) while the binary is missing: hiding it would\n\t * recreate the discoverability hole this whole surface exists to close.\n\t */\n\tdependentRows: string[];\n\t/** settings.json keys this binary gates, for docs and search. */\n\tsettingsKeys: string[];\n}\n\n/**\n * Ordered so the two that silently make everything faster come first, then the\n * three that add capability hoocode does not otherwise have.\n */\nexport const EXTERNAL_TOOLS: readonly ExternalToolDoc[] = [\n\t{\n\t\ttool: \"rg\",\n\t\tlabel: \"ripgrep (rg)\",\n\t\tsummary: \"Fast path for content search.\",\n\t\tenables: [\"grep runs rg instead of the JS scanner\", \"the lexical half of search runs rg\"],\n\t\tfallback:\n\t\t\t\"A pure-JS scanner produces the same match shape, so results are identical - it is materially slower on large trees and respects fewer ignore-file edge cases.\",\n\t\tacquisition: \"startup\",\n\t\tenv: [\"HOOCODE_RG_BINARY\", \"HOOCODE_NATIVE_SEARCH=1 forces the JS path even when rg is present\"],\n\t\tdependentRows: [],\n\t\tsettingsKeys: [],\n\t},\n\t{\n\t\ttool: \"fd\",\n\t\tlabel: \"fd\",\n\t\tsummary: \"Fast path for filename search.\",\n\t\tenables: [\"find runs fd instead of the JS directory walker\"],\n\t\tfallback:\n\t\t\t\"A JS walker produces the same result shape - slower on large trees, and glob/ignore handling is the JS approximation rather than fd's.\",\n\t\tacquisition: \"startup\",\n\t\tenv: [\"HOOCODE_FD_BINARY\", \"HOOCODE_NATIVE_SEARCH=1 forces the JS path even when fd is present\"],\n\t\tdependentRows: [],\n\t\tsettingsKeys: [],\n\t},\n\t{\n\t\ttool: \"embsearch\",\n\t\tlabel: \"embsearch (semantic index)\",\n\t\tsummary: \"Local embedding index. The only source of semantic ranking in hoocode.\",\n\t\tenables: [\n\t\t\t\"search fuses semantic hits with its lexical hits\",\n\t\t\t\"MCP/capability deferral ranks tools by meaning rather than keyword\",\n\t\t],\n\t\tfallback:\n\t\t\t\"search is lexical-only and capability lookup ranks lexically. Nothing errors; queries phrased by intent rather than by token simply rank worse. Requires the ONNX build - the mock build is rejected on purpose, because it would rank at random while looking healthy.\",\n\t\tacquisition: \"on-demand\",\n\t\tenv: [\"HOOCODE_EMBSEARCH_BINARY\"],\n\t\tdependentRows: [\"group:embsearch\"],\n\t\tsettingsKeys: [\"enableEmbsearchTools\", \"embsearchBinaryPath\", \"embsearchThresholdBytes\"],\n\t},\n\t{\n\t\ttool: \"webtools\",\n\t\tlabel: \"webtools (webfetch/websearch)\",\n\t\tsummary: \"The network layer. Without it hoocode has no way to reach the internet.\",\n\t\tenables: [\"the webfetch tool\", \"the websearch tool\"],\n\t\tfallback:\n\t\t\t\"Both tools return an error when called. The web tool group is off by default, so a missing binary is invisible until you turn the group on.\",\n\t\tacquisition: \"on-demand\",\n\t\tenv: [\"HOOCODE_WEBTOOLS_BINARY\", \"HOOCODE_WEBTOOLS_TIMEOUT\"],\n\t\tdependentRows: [\"group:web\", \"webtools-timeout-secs\"],\n\t\tsettingsKeys: [\"enableWebTools\", \"webtools.timeoutSecs\"],\n\t},\n\t{\n\t\ttool: \"voicetools\",\n\t\tlabel: \"voicetools (voice input)\",\n\t\tsummary: \"Microphone capture and transcription for the TUI.\",\n\t\tenables: [\"push-to-talk voice input in the editor\"],\n\t\tfallback: \"Voice capture reports an error and never starts. Typing is unaffected.\",\n\t\tacquisition: \"on-demand\",\n\t\tenv: [\"VOICETOOLS_BIN\", \"HOOCODE_VOICETOOLS_BINARY\", \"VOICETOOLS_SILENCE_MS\"],\n\t\tdependentRows: [\"voice-silence-ms\"],\n\t\tsettingsKeys: [\"voice.silenceMs\"],\n\t},\n];\n\nexport interface ExternalToolStatus extends ExternalToolDoc, ManagedToolStatus {\n\tinstalled: boolean;\n\t/**\n\t * Whether hoocode would fetch it if the feature were used right now. False in\n\t * offline mode, and on Android where the published Linux builds do not run.\n\t */\n\tdownloadable: boolean;\n}\n\n/** Short, fixed-width-ish status word for the pane's value column. */\nexport function statusLabel(status: ExternalToolStatus): string {\n\tif (!status.installed) return status.downloadable ? \"not installed\" : \"unavailable\";\n\tswitch (status.source) {\n\t\tcase \"override\":\n\t\t\treturn \"env override\";\n\t\tcase \"managed\":\n\t\t\treturn \"installed\";\n\t\tcase \"path\":\n\t\t\treturn \"system\";\n\t\tdefault:\n\t\t\treturn \"installed\";\n\t}\n}\n\n/**\n * Resolve every external tool. Never downloads: this is what the pane shows\n * *before* the user opts into anything.\n */\nexport function describeExternalTools(): ExternalToolStatus[] {\n\tconst offline = isOfflineMode();\n\tconst android = process.platform === \"android\";\n\treturn EXTERNAL_TOOLS.map((doc) => {\n\t\tconst status = getToolStatus(doc.tool);\n\t\treturn {\n\t\t\t...doc,\n\t\t\t...status,\n\t\t\tinstalled: status.path !== null,\n\t\t\tdownloadable: !offline && !android && doc.acquisition !== \"manual\",\n\t\t};\n\t});\n}\n\n/** Index of pane-row id -> the tool it needs, for annotating gated rows. */\nexport function buildRowGates(statuses: readonly ExternalToolStatus[]): Map<string, ExternalToolStatus> {\n\tconst gates = new Map<string, ExternalToolStatus>();\n\tfor (const status of statuses) {\n\t\tfor (const rowId of status.dependentRows) gates.set(rowId, status);\n\t}\n\treturn gates;\n}\n"]}
|
|
@@ -9,6 +9,10 @@ interface AppKeybindings {
|
|
|
9
9
|
"app.model.cycleBackward": true;
|
|
10
10
|
"app.model.select": true;
|
|
11
11
|
"app.tools.expand": true;
|
|
12
|
+
"app.view.cycleForward": true;
|
|
13
|
+
"app.view.cycleBackward": true;
|
|
14
|
+
"app.tools.unfoldOne": true;
|
|
15
|
+
"app.tools.foldOne": true;
|
|
12
16
|
"app.thinking.toggle": true;
|
|
13
17
|
"app.tasks.cycleView": true;
|
|
14
18
|
"app.team.focus": true;
|
|
@@ -24,6 +28,10 @@ interface AppKeybindings {
|
|
|
24
28
|
"app.session.tree": true;
|
|
25
29
|
"app.session.fork": true;
|
|
26
30
|
"app.session.resume": true;
|
|
31
|
+
"app.session.changeDirectory": true;
|
|
32
|
+
"app.settings.open": true;
|
|
33
|
+
"app.hotkeys.open": true;
|
|
34
|
+
"app.mode.cycle": true;
|
|
27
35
|
"app.tree.foldOrUp": true;
|
|
28
36
|
"app.tree.unfoldOrDown": true;
|
|
29
37
|
"app.tree.editLabel": true;
|
|
@@ -52,6 +60,38 @@ declare module "@kolisachint/hoocode-tui" {
|
|
|
52
60
|
interface Keybindings extends AppKeybindings {
|
|
53
61
|
}
|
|
54
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* The cockpit layout.
|
|
65
|
+
*
|
|
66
|
+
* Three rings, and which ring a key belongs to is decided by its modifier:
|
|
67
|
+
*
|
|
68
|
+
* - **ctrl — the view.** What is on screen right now: expand, thinking blocks,
|
|
69
|
+
* task panel, model cycling. Pressed many times a minute, so they sit under
|
|
70
|
+
* the hand and never require a second modifier.
|
|
71
|
+
* - **alt — the cockpit.** What the agent *is* and where it works: model,
|
|
72
|
+
* mode, working directory, settings, sessions. Pressed a few times a session.
|
|
73
|
+
* - **overlays.** Inside a picker, the query line is a text field, so every
|
|
74
|
+
* `ctrl+<letter>` there belongs to the *text* (ctrl+a start of line, ctrl+u
|
|
75
|
+
* kill line, ctrl+w kill word). A picker's own verbs are therefore all on
|
|
76
|
+
* `alt+<letter>`, mnemonic to that picker; the picker captures keys while it
|
|
77
|
+
* is open, so reusing a global letter there is unambiguous.
|
|
78
|
+
*
|
|
79
|
+
* Two rules keep the set learnable: shift reverses whatever the unshifted key
|
|
80
|
+
* does, and no binding takes a key the editor or the terminal already owns
|
|
81
|
+
* (ctrl+a/e/b/f/k/u/w/y/d/l/r/g, ctrl+s XOFF, ctrl+m == enter, ctrl+i == tab).
|
|
82
|
+
*
|
|
83
|
+
* Two constraints from the key parser shape which alt keys are usable at all,
|
|
84
|
+
* and `test/keybinding-layout.test.ts` holds both:
|
|
85
|
+
*
|
|
86
|
+
* - Only `alt+<letter>` and `alt+<digit>` survive a terminal without the Kitty
|
|
87
|
+
* keyboard protocol. `alt+[` and `alt+]` arrive as the CSI and OSC
|
|
88
|
+
* introducers and are not parsed as keys; nor is any shift-modified key, so a
|
|
89
|
+
* `shift+…` default is a Kitty-only convenience on top of an unshifted key
|
|
90
|
+
* that works everywhere (as `shift+ctrl+p` has always been).
|
|
91
|
+
* - Legacy `alt+p` and `alt+n` are also accepted as `alt+up` and `alt+down`
|
|
92
|
+
* (the emacs previous/next aliases), so no scope may bind both halves of
|
|
93
|
+
* either pair.
|
|
94
|
+
*/
|
|
55
95
|
export declare const KEYBINDINGS: {
|
|
56
96
|
readonly "tui.editor.cursorUp": {
|
|
57
97
|
readonly defaultKeys: "up";
|
|
@@ -206,12 +246,28 @@ export declare const KEYBINDINGS: {
|
|
|
206
246
|
readonly description: "Cycle to previous model";
|
|
207
247
|
};
|
|
208
248
|
readonly "app.model.select": {
|
|
209
|
-
readonly defaultKeys: "
|
|
249
|
+
readonly defaultKeys: "alt+m";
|
|
210
250
|
readonly description: "Open model selector";
|
|
211
251
|
};
|
|
212
252
|
readonly "app.tools.expand": {
|
|
213
253
|
readonly defaultKeys: "ctrl+o";
|
|
214
|
-
readonly description: "
|
|
254
|
+
readonly description: "Expand or collapse what is in front of you (tool bodies, header, summaries)";
|
|
255
|
+
};
|
|
256
|
+
readonly "app.view.cycleForward": {
|
|
257
|
+
readonly defaultKeys: "alt+o";
|
|
258
|
+
readonly description: "Cycle tool output view (radar → glance → full)";
|
|
259
|
+
};
|
|
260
|
+
readonly "app.view.cycleBackward": {
|
|
261
|
+
readonly defaultKeys: "shift+alt+o";
|
|
262
|
+
readonly description: "Cycle tool output view backward";
|
|
263
|
+
};
|
|
264
|
+
readonly "app.tools.unfoldOne": {
|
|
265
|
+
readonly defaultKeys: "alt+u";
|
|
266
|
+
readonly description: "Open the newest folded thing — a chain in radar, a tool body otherwise";
|
|
267
|
+
};
|
|
268
|
+
readonly "app.tools.foldOne": {
|
|
269
|
+
readonly defaultKeys: "shift+alt+u";
|
|
270
|
+
readonly description: "Re-fold the most recently opened chain or tool block";
|
|
215
271
|
};
|
|
216
272
|
readonly "app.thinking.toggle": {
|
|
217
273
|
readonly defaultKeys: "ctrl+t";
|
|
@@ -221,10 +277,6 @@ export declare const KEYBINDINGS: {
|
|
|
221
277
|
readonly defaultKeys: "ctrl+n";
|
|
222
278
|
readonly description: "Cycle task panel view (tasks → subagents → teams, skips empty lenses)";
|
|
223
279
|
};
|
|
224
|
-
readonly "app.session.toggleNamedFilter": {
|
|
225
|
-
readonly defaultKeys: "ctrl+n";
|
|
226
|
-
readonly description: "Toggle named session filter";
|
|
227
|
-
};
|
|
228
280
|
readonly "app.team.focus": {
|
|
229
281
|
readonly defaultKeys: "alt+n";
|
|
230
282
|
readonly description: "Focus the team roster (navigate roles, n nudge, a attach)";
|
|
@@ -238,7 +290,7 @@ export declare const KEYBINDINGS: {
|
|
|
238
290
|
readonly description: "Attach to the selected team role (team focus mode)";
|
|
239
291
|
};
|
|
240
292
|
readonly "app.editor.external": {
|
|
241
|
-
readonly defaultKeys: "
|
|
293
|
+
readonly defaultKeys: "alt+e";
|
|
242
294
|
readonly description: "Open external editor";
|
|
243
295
|
};
|
|
244
296
|
readonly "app.message.followUp": {
|
|
@@ -254,24 +306,40 @@ export declare const KEYBINDINGS: {
|
|
|
254
306
|
readonly description: "Paste image from clipboard";
|
|
255
307
|
};
|
|
256
308
|
readonly "app.input.voiceTranscribe": {
|
|
257
|
-
readonly defaultKeys: "
|
|
309
|
+
readonly defaultKeys: "alt+r";
|
|
258
310
|
readonly description: "Record voice and transcribe into the editor";
|
|
259
311
|
};
|
|
260
312
|
readonly "app.session.new": {
|
|
261
313
|
readonly defaultKeys: [];
|
|
262
314
|
readonly description: "Start a new session";
|
|
263
315
|
};
|
|
264
|
-
readonly "app.session.tree": {
|
|
265
|
-
readonly defaultKeys: [];
|
|
266
|
-
readonly description: "Open session tree";
|
|
267
|
-
};
|
|
268
316
|
readonly "app.session.fork": {
|
|
269
317
|
readonly defaultKeys: [];
|
|
270
318
|
readonly description: "Fork current session";
|
|
271
319
|
};
|
|
320
|
+
readonly "app.session.tree": {
|
|
321
|
+
readonly defaultKeys: "alt+t";
|
|
322
|
+
readonly description: "Open session tree";
|
|
323
|
+
};
|
|
272
324
|
readonly "app.session.resume": {
|
|
273
|
-
readonly defaultKeys:
|
|
274
|
-
readonly description: "Resume a session";
|
|
325
|
+
readonly defaultKeys: "alt+h";
|
|
326
|
+
readonly description: "Resume a session from history";
|
|
327
|
+
};
|
|
328
|
+
readonly "app.session.changeDirectory": {
|
|
329
|
+
readonly defaultKeys: "alt+w";
|
|
330
|
+
readonly description: "Change working directory (move to another repo without quitting)";
|
|
331
|
+
};
|
|
332
|
+
readonly "app.settings.open": {
|
|
333
|
+
readonly defaultKeys: "alt+s";
|
|
334
|
+
readonly description: "Open settings";
|
|
335
|
+
};
|
|
336
|
+
readonly "app.hotkeys.open": {
|
|
337
|
+
readonly defaultKeys: "alt+k";
|
|
338
|
+
readonly description: "Show keyboard shortcuts";
|
|
339
|
+
};
|
|
340
|
+
readonly "app.mode.cycle": {
|
|
341
|
+
readonly defaultKeys: "alt+g";
|
|
342
|
+
readonly description: "Cycle agent mode (ask → plan → build → debug)";
|
|
275
343
|
};
|
|
276
344
|
readonly "app.tree.foldOrUp": {
|
|
277
345
|
readonly defaultKeys: ["ctrl+left", "alt+left"];
|
|
@@ -290,19 +358,23 @@ export declare const KEYBINDINGS: {
|
|
|
290
358
|
readonly description: "Toggle tree label timestamps";
|
|
291
359
|
};
|
|
292
360
|
readonly "app.session.togglePath": {
|
|
293
|
-
readonly defaultKeys: "
|
|
361
|
+
readonly defaultKeys: "alt+p";
|
|
294
362
|
readonly description: "Toggle session path display";
|
|
295
363
|
};
|
|
296
364
|
readonly "app.session.toggleSort": {
|
|
297
|
-
readonly defaultKeys: "
|
|
298
|
-
readonly description: "Toggle session sort
|
|
365
|
+
readonly defaultKeys: "alt+o";
|
|
366
|
+
readonly description: "Toggle session sort order";
|
|
299
367
|
};
|
|
300
368
|
readonly "app.session.rename": {
|
|
301
|
-
readonly defaultKeys: "
|
|
369
|
+
readonly defaultKeys: "alt+r";
|
|
302
370
|
readonly description: "Rename session";
|
|
303
371
|
};
|
|
372
|
+
readonly "app.session.toggleNamedFilter": {
|
|
373
|
+
readonly defaultKeys: "alt+n";
|
|
374
|
+
readonly description: "Toggle named session filter";
|
|
375
|
+
};
|
|
304
376
|
readonly "app.session.delete": {
|
|
305
|
-
readonly defaultKeys: "
|
|
377
|
+
readonly defaultKeys: "alt+x";
|
|
306
378
|
readonly description: "Delete session";
|
|
307
379
|
};
|
|
308
380
|
readonly "app.session.deleteNoninvasive": {
|
|
@@ -310,19 +382,19 @@ export declare const KEYBINDINGS: {
|
|
|
310
382
|
readonly description: "Delete session when query is empty";
|
|
311
383
|
};
|
|
312
384
|
readonly "app.models.save": {
|
|
313
|
-
readonly defaultKeys: "
|
|
385
|
+
readonly defaultKeys: "alt+s";
|
|
314
386
|
readonly description: "Save model selection";
|
|
315
387
|
};
|
|
316
388
|
readonly "app.models.enableAll": {
|
|
317
|
-
readonly defaultKeys: "
|
|
389
|
+
readonly defaultKeys: "alt+a";
|
|
318
390
|
readonly description: "Enable all models";
|
|
319
391
|
};
|
|
320
392
|
readonly "app.models.clearAll": {
|
|
321
|
-
readonly defaultKeys: "
|
|
393
|
+
readonly defaultKeys: "alt+x";
|
|
322
394
|
readonly description: "Clear all models";
|
|
323
395
|
};
|
|
324
396
|
readonly "app.models.toggleProvider": {
|
|
325
|
-
readonly defaultKeys: "
|
|
397
|
+
readonly defaultKeys: "alt+g";
|
|
326
398
|
readonly description: "Toggle all models for provider";
|
|
327
399
|
};
|
|
328
400
|
readonly "app.models.reorderUp": {
|
|
@@ -342,31 +414,31 @@ export declare const KEYBINDINGS: {
|
|
|
342
414
|
readonly description: "Go back to the previous question";
|
|
343
415
|
};
|
|
344
416
|
readonly "app.tree.filter.default": {
|
|
345
|
-
readonly defaultKeys: "
|
|
417
|
+
readonly defaultKeys: "alt+1";
|
|
346
418
|
readonly description: "Tree filter: default view";
|
|
347
419
|
};
|
|
348
420
|
readonly "app.tree.filter.noTools": {
|
|
349
|
-
readonly defaultKeys: "
|
|
421
|
+
readonly defaultKeys: "alt+2";
|
|
350
422
|
readonly description: "Tree filter: hide tool results";
|
|
351
423
|
};
|
|
352
424
|
readonly "app.tree.filter.userOnly": {
|
|
353
|
-
readonly defaultKeys: "
|
|
425
|
+
readonly defaultKeys: "alt+3";
|
|
354
426
|
readonly description: "Tree filter: user messages only";
|
|
355
427
|
};
|
|
356
428
|
readonly "app.tree.filter.labeledOnly": {
|
|
357
|
-
readonly defaultKeys: "
|
|
429
|
+
readonly defaultKeys: "alt+4";
|
|
358
430
|
readonly description: "Tree filter: labeled entries only";
|
|
359
431
|
};
|
|
360
432
|
readonly "app.tree.filter.all": {
|
|
361
|
-
readonly defaultKeys: "
|
|
433
|
+
readonly defaultKeys: "alt+5";
|
|
362
434
|
readonly description: "Tree filter: show all entries";
|
|
363
435
|
};
|
|
364
436
|
readonly "app.tree.filter.cycleForward": {
|
|
365
|
-
readonly defaultKeys: "
|
|
437
|
+
readonly defaultKeys: "alt+c";
|
|
366
438
|
readonly description: "Tree filter: cycle forward";
|
|
367
439
|
};
|
|
368
440
|
readonly "app.tree.filter.cycleBackward": {
|
|
369
|
-
readonly defaultKeys: "shift+
|
|
441
|
+
readonly defaultKeys: "shift+alt+c";
|
|
370
442
|
readonly description: "Tree filter: cycle backward";
|
|
371
443
|
};
|
|
372
444
|
};
|