@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.
Files changed (134) hide show
  1. package/CHANGELOG.md +234 -0
  2. package/dist/core/agent-selection-eval.d.ts +91 -0
  3. package/dist/core/agent-selection-eval.d.ts.map +1 -0
  4. package/dist/core/agent-selection-eval.js +186 -0
  5. package/dist/core/agent-selection-eval.js.map +1 -0
  6. package/dist/core/agent-session-runtime.d.ts +28 -0
  7. package/dist/core/agent-session-runtime.d.ts.map +1 -1
  8. package/dist/core/agent-session-runtime.js +61 -2
  9. package/dist/core/agent-session-runtime.js.map +1 -1
  10. package/dist/core/builtin-skills.d.ts +61 -0
  11. package/dist/core/builtin-skills.d.ts.map +1 -0
  12. package/dist/core/builtin-skills.js +106 -0
  13. package/dist/core/builtin-skills.js.map +1 -0
  14. package/dist/core/extensions/plugins/default-marketplace/.agents-plugin/marketplace.json +5 -1
  15. package/dist/core/extensions/plugins/trigger-judge.d.ts +42 -0
  16. package/dist/core/extensions/plugins/trigger-judge.d.ts.map +1 -0
  17. package/dist/core/extensions/plugins/trigger-judge.js +121 -0
  18. package/dist/core/extensions/plugins/trigger-judge.js.map +1 -0
  19. package/dist/core/external-tools.d.ts +67 -0
  20. package/dist/core/external-tools.d.ts.map +1 -0
  21. package/dist/core/external-tools.js +120 -0
  22. package/dist/core/external-tools.js.map +1 -0
  23. package/dist/core/keybindings.d.ts +102 -30
  24. package/dist/core/keybindings.d.ts.map +1 -1
  25. package/dist/core/keybindings.js +123 -29
  26. package/dist/core/keybindings.js.map +1 -1
  27. package/dist/core/mode-prompts.d.ts +15 -3
  28. package/dist/core/mode-prompts.d.ts.map +1 -1
  29. package/dist/core/mode-prompts.js +17 -29
  30. package/dist/core/mode-prompts.js.map +1 -1
  31. package/dist/core/settings-defaults.d.ts +1 -1
  32. package/dist/core/settings-defaults.d.ts.map +1 -1
  33. package/dist/core/settings-defaults.js +1 -1
  34. package/dist/core/settings-defaults.js.map +1 -1
  35. package/dist/core/settings-manager.d.ts +10 -3
  36. package/dist/core/settings-manager.d.ts.map +1 -1
  37. package/dist/core/settings-manager.js +19 -8
  38. package/dist/core/settings-manager.js.map +1 -1
  39. package/dist/core/settings-types.d.ts +1 -1
  40. package/dist/core/settings-types.d.ts.map +1 -1
  41. package/dist/core/settings-types.js.map +1 -1
  42. package/dist/core/slash-commands.d.ts.map +1 -1
  43. package/dist/core/slash-commands.js +5 -1
  44. package/dist/core/slash-commands.js.map +1 -1
  45. package/dist/core/tool-output-view.d.ts +33 -0
  46. package/dist/core/tool-output-view.d.ts.map +1 -0
  47. package/dist/core/tool-output-view.js +31 -0
  48. package/dist/core/tool-output-view.js.map +1 -0
  49. package/dist/core/tools/edit.d.ts.map +1 -1
  50. package/dist/core/tools/edit.js +4 -0
  51. package/dist/core/tools/edit.js.map +1 -1
  52. package/dist/core/tools/propose-plugin.d.ts.map +1 -1
  53. package/dist/core/tools/propose-plugin.js +7 -7
  54. package/dist/core/tools/propose-plugin.js.map +1 -1
  55. package/dist/core/tools/subagent.d.ts +7 -1
  56. package/dist/core/tools/subagent.d.ts.map +1 -1
  57. package/dist/core/tools/subagent.js +12 -24
  58. package/dist/core/tools/subagent.js.map +1 -1
  59. package/dist/extensions/core/modes.d.ts.map +1 -1
  60. package/dist/extensions/core/modes.js +21 -23
  61. package/dist/extensions/core/modes.js.map +1 -1
  62. package/dist/extensions/core/scaffold.d.ts.map +1 -1
  63. package/dist/extensions/core/scaffold.js +28 -24
  64. package/dist/extensions/core/scaffold.js.map +1 -1
  65. package/dist/init-templates.generated.d.ts +3 -0
  66. package/dist/init-templates.generated.d.ts.map +1 -1
  67. package/dist/init-templates.generated.js +12 -0
  68. package/dist/init-templates.generated.js.map +1 -1
  69. package/dist/main.d.ts.map +1 -1
  70. package/dist/main.js +14 -1
  71. package/dist/main.js.map +1 -1
  72. package/dist/modes/interactive/command-executor.d.ts +20 -0
  73. package/dist/modes/interactive/command-executor.d.ts.map +1 -1
  74. package/dist/modes/interactive/command-executor.js +138 -9
  75. package/dist/modes/interactive/command-executor.js.map +1 -1
  76. package/dist/modes/interactive/components/footer.d.ts +4 -0
  77. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  78. package/dist/modes/interactive/components/footer.js +31 -2
  79. package/dist/modes/interactive/components/footer.js.map +1 -1
  80. package/dist/modes/interactive/components/scoped-models-selector.d.ts +2 -2
  81. package/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -1
  82. package/dist/modes/interactive/components/scoped-models-selector.js +2 -2
  83. package/dist/modes/interactive/components/scoped-models-selector.js.map +1 -1
  84. package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
  85. package/dist/modes/interactive/components/session-selector.js +2 -2
  86. package/dist/modes/interactive/components/session-selector.js.map +1 -1
  87. package/dist/modes/interactive/components/settings-selector.d.ts +10 -2
  88. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  89. package/dist/modes/interactive/components/settings-selector.js +165 -23
  90. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  91. package/dist/modes/interactive/components/tool-chain-summary.d.ts +65 -0
  92. package/dist/modes/interactive/components/tool-chain-summary.d.ts.map +1 -0
  93. package/dist/modes/interactive/components/tool-chain-summary.js +216 -0
  94. package/dist/modes/interactive/components/tool-chain-summary.js.map +1 -0
  95. package/dist/modes/interactive/components/tool-chain.d.ts +61 -0
  96. package/dist/modes/interactive/components/tool-chain.d.ts.map +1 -0
  97. package/dist/modes/interactive/components/tool-chain.js +156 -0
  98. package/dist/modes/interactive/components/tool-chain.js.map +1 -0
  99. package/dist/modes/interactive/components/tool-execution.d.ts +55 -9
  100. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  101. package/dist/modes/interactive/components/tool-execution.js +173 -33
  102. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  103. package/dist/modes/interactive/components/tool-signal.d.ts +50 -0
  104. package/dist/modes/interactive/components/tool-signal.d.ts.map +1 -0
  105. package/dist/modes/interactive/components/tool-signal.js +151 -0
  106. package/dist/modes/interactive/components/tool-signal.js.map +1 -0
  107. package/dist/modes/interactive/interactive-mode.d.ts +50 -1
  108. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  109. package/dist/modes/interactive/interactive-mode.js +244 -29
  110. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  111. package/dist/modes/interactive/resource-display.d.ts +9 -0
  112. package/dist/modes/interactive/resource-display.d.ts.map +1 -1
  113. package/dist/modes/interactive/resource-display.js +13 -0
  114. package/dist/modes/interactive/resource-display.js.map +1 -1
  115. package/dist/utils/tools-manager.d.ts +30 -0
  116. package/dist/utils/tools-manager.d.ts.map +1 -1
  117. package/dist/utils/tools-manager.js +35 -0
  118. package/dist/utils/tools-manager.js.map +1 -1
  119. package/docs/modes.md +4 -0
  120. package/docs/plugins.md +10 -0
  121. package/docs/settings.md +42 -0
  122. package/docs/skills.md +30 -0
  123. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  124. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  125. package/examples/extensions/sandbox/package.json +1 -1
  126. package/examples/extensions/with-deps/package.json +1 -1
  127. package/package.json +5 -4
  128. package/templates/prompts/grill-bridge.md +1 -0
  129. package/templates/prompts/grill-me.md +7 -0
  130. package/templates/prompts/grill-plan.md +9 -0
  131. package/templates/prompts/task-background-agents.md +2 -0
  132. package/templates/prompts/task-background-none.md +1 -0
  133. package/templates/prompts/task-main.md +19 -0
  134. 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: "ctrl+l";
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: "Toggle tool output";
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: "ctrl+g";
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: "ctrl+r";
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: "ctrl+p";
361
+ readonly defaultKeys: "alt+p";
294
362
  readonly description: "Toggle session path display";
295
363
  };
296
364
  readonly "app.session.toggleSort": {
297
- readonly defaultKeys: "ctrl+s";
298
- readonly description: "Toggle session sort mode";
365
+ readonly defaultKeys: "alt+o";
366
+ readonly description: "Toggle session sort order";
299
367
  };
300
368
  readonly "app.session.rename": {
301
- readonly defaultKeys: "ctrl+r";
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: "ctrl+d";
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: "ctrl+s";
385
+ readonly defaultKeys: "alt+s";
314
386
  readonly description: "Save model selection";
315
387
  };
316
388
  readonly "app.models.enableAll": {
317
- readonly defaultKeys: "ctrl+a";
389
+ readonly defaultKeys: "alt+a";
318
390
  readonly description: "Enable all models";
319
391
  };
320
392
  readonly "app.models.clearAll": {
321
- readonly defaultKeys: "ctrl+x";
393
+ readonly defaultKeys: "alt+x";
322
394
  readonly description: "Clear all models";
323
395
  };
324
396
  readonly "app.models.toggleProvider": {
325
- readonly defaultKeys: "ctrl+p";
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: "ctrl+d";
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: "ctrl+t";
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: "ctrl+u";
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: "ctrl+l";
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: "ctrl+a";
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: "ctrl+o";
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+ctrl+o";
441
+ readonly defaultKeys: "shift+alt+c";
370
442
  readonly description: "Tree filter: cycle backward";
371
443
  };
372
444
  };