@kolisachint/hoocode-agent 0.5.26 → 0.5.27
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 +49 -0
- package/dist/core/capabilities/lexical.d.ts +4 -0
- package/dist/core/capabilities/lexical.d.ts.map +1 -1
- package/dist/core/capabilities/lexical.js +104 -4
- package/dist/core/capabilities/lexical.js.map +1 -1
- package/dist/core/capabilities/registry.d.ts +3 -1
- package/dist/core/capabilities/registry.d.ts.map +1 -1
- package/dist/core/capabilities/registry.js.map +1 -1
- package/dist/core/self-docs.d.ts +103 -0
- package/dist/core/self-docs.d.ts.map +1 -0
- package/dist/core/self-docs.js +351 -0
- package/dist/core/self-docs.js.map +1 -0
- package/dist/core/system-prompt.d.ts +12 -0
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +11 -1
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/extensions/core/hoo-core.d.ts +1 -0
- package/dist/extensions/core/hoo-core.d.ts.map +1 -1
- package/dist/extensions/core/hoo-core.js +3 -0
- package/dist/extensions/core/hoo-core.js.map +1 -1
- package/dist/extensions/core/mcp-loader.d.ts.map +1 -1
- package/dist/extensions/core/mcp-loader.js +8 -2
- package/dist/extensions/core/mcp-loader.js.map +1 -1
- package/dist/extensions/core/self-knowledge.d.ts +28 -0
- package/dist/extensions/core/self-knowledge.d.ts.map +1 -0
- package/dist/extensions/core/self-knowledge.js +199 -0
- package/dist/extensions/core/self-knowledge.js.map +1 -0
- package/docs/canvas.md +117 -0
- package/docs/compaction.md +4 -4
- package/docs/custom-provider.md +1 -1
- package/docs/development.md +1 -1
- package/docs/docs.json +27 -2
- package/docs/extensions.md +12 -12
- package/docs/index.md +8 -0
- package/docs/keybindings.md +2 -2
- package/docs/mcp.md +97 -0
- package/docs/models.md +1 -1
- package/docs/modes.md +87 -0
- package/docs/packages.md +4 -4
- package/docs/plugins.md +124 -0
- package/docs/prompt-templates.md +1 -1
- package/docs/providers.md +2 -2
- package/docs/quickstart.md +2 -2
- package/docs/rpc.md +5 -5
- package/docs/sdk.md +5 -5
- package/docs/session-format.md +3 -3
- package/docs/sessions.md +1 -1
- package/docs/settings.md +3 -3
- package/docs/shell-aliases.md +1 -1
- package/docs/skills.md +2 -2
- package/docs/terminal-setup.md +1 -1
- package/docs/termux.md +2 -2
- package/docs/themes.md +3 -3
- package/docs/usage.md +93 -4
- package/docs/windows.md +1 -1
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package.json +1 -1
- package/package.json +4 -4
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SearchHooCode — retrieval over hoocode's own docs and the capabilities this
|
|
3
|
+
* session actually has.
|
|
4
|
+
*
|
|
5
|
+
* The system prompt already lists every shipped doc with a one-line summary
|
|
6
|
+
* (see `core/self-docs.ts`), which answers "which file covers extensions?".
|
|
7
|
+
* What it cannot answer is anything *inside* a file: `extensions.md` alone is
|
|
8
|
+
* over a thousand lines, and reading it whole to find one heading costs more
|
|
9
|
+
* context than the rest of the prompt put together. This indexes docs at the
|
|
10
|
+
* heading level so a question lands on a section and a line number.
|
|
11
|
+
*
|
|
12
|
+
* It also registers the capability kinds the registry has always declared but
|
|
13
|
+
* nobody ever filled — `skill`, `command`, `agent`, `plugin-installed`
|
|
14
|
+
* (registry.ts notes §6.4 left them eager). Those are eager in the prompt, so
|
|
15
|
+
* indexing them is not about reachability; it is about "what can you do?"
|
|
16
|
+
* having one place that answers it rather than the model reciting whichever
|
|
17
|
+
* list happens to be in front of it.
|
|
18
|
+
*
|
|
19
|
+
* `mcp-tool` is deliberately out of scope: ResolveMcpTools owns that kind
|
|
20
|
+
* because finding an MCP tool and making it callable are the same action
|
|
21
|
+
* there, and a second searcher that returns un-resolvable names would be a
|
|
22
|
+
* worse answer, not an extra one.
|
|
23
|
+
*/
|
|
24
|
+
import { Type } from "typebox";
|
|
25
|
+
import { ensureDenseIndex } from "../../core/capabilities/dense.js";
|
|
26
|
+
import { getCapabilities, registerCapabilities } from "../../core/capabilities/registry.js";
|
|
27
|
+
import { resetCapabilitySearch, searchCapabilities } from "../../core/capabilities/search.js";
|
|
28
|
+
import { listSelfDocSections, sectionLabel } from "../../core/self-docs.js";
|
|
29
|
+
const SEARCH_HOOCODE_TOOL_NAME = "SearchHooCode";
|
|
30
|
+
/** Kinds this tool searches. `mcp-tool` belongs to ResolveMcpTools. */
|
|
31
|
+
const SEARCHABLE = ["doc", "skill", "command", "agent", "plugin-installed"];
|
|
32
|
+
const DEFAULT_LIMIT = 8;
|
|
33
|
+
const MAX_LIMIT = 25;
|
|
34
|
+
/** Registered once per process; the docs are read-only install content. */
|
|
35
|
+
let docsRegistered = false;
|
|
36
|
+
/**
|
|
37
|
+
* Index the shipped docs, the first time anyone asks.
|
|
38
|
+
*
|
|
39
|
+
* Lazy because it reads thirty files and produces roughly a thousand sections.
|
|
40
|
+
* A session that never asks hoocode about itself should not pay for that at
|
|
41
|
+
* startup, and the cost is invisible once paid — `listSelfDocSections` caches.
|
|
42
|
+
*/
|
|
43
|
+
function ensureDocsRegistered() {
|
|
44
|
+
if (docsRegistered)
|
|
45
|
+
return;
|
|
46
|
+
docsRegistered = true;
|
|
47
|
+
const sections = listSelfDocSections();
|
|
48
|
+
if (sections.length === 0)
|
|
49
|
+
return;
|
|
50
|
+
registerCapabilities("doc", sections.map((section) => ({
|
|
51
|
+
id: `doc:${section.id}`,
|
|
52
|
+
kind: "doc",
|
|
53
|
+
name: sectionLabel(section),
|
|
54
|
+
description: section.excerpt,
|
|
55
|
+
source: section.path,
|
|
56
|
+
// The body is withheld until the model reads the file, which is
|
|
57
|
+
// exactly the condition this flag records.
|
|
58
|
+
deferred: true,
|
|
59
|
+
})));
|
|
60
|
+
resetCapabilitySearch();
|
|
61
|
+
}
|
|
62
|
+
/** Line-number lookup for rendering a hit, keyed by the id we registered under. */
|
|
63
|
+
function sectionIndex() {
|
|
64
|
+
const index = new Map();
|
|
65
|
+
for (const section of listSelfDocSections()) {
|
|
66
|
+
index.set(`doc:${section.id}`, { path: section.path, line: section.line });
|
|
67
|
+
}
|
|
68
|
+
return index;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Mirror the session's skills, commands, and subagents into the registry.
|
|
72
|
+
*
|
|
73
|
+
* Driven from `before_agent_start` because that is where the assembled
|
|
74
|
+
* `systemPromptOptions` carries the skills and agents the session actually
|
|
75
|
+
* loaded — re-discovering them from disk here would risk disagreeing with what
|
|
76
|
+
* the model was told. Guarded by a signature so a steady session registers once
|
|
77
|
+
* instead of dropping the lexical index on every turn.
|
|
78
|
+
*/
|
|
79
|
+
let lastSignature = "";
|
|
80
|
+
function registerSessionCapabilities(event, pi) {
|
|
81
|
+
const skills = event.systemPromptOptions.skills ?? [];
|
|
82
|
+
const agents = event.systemPromptOptions.agents ?? [];
|
|
83
|
+
let commands = [];
|
|
84
|
+
try {
|
|
85
|
+
commands = pi.getCommands();
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
// Commands are an interactive-mode concern; a headless session may not
|
|
89
|
+
// have them. Missing commands is not a reason to skip skills and agents.
|
|
90
|
+
commands = [];
|
|
91
|
+
}
|
|
92
|
+
const signature = [
|
|
93
|
+
skills.map((s) => s.name).join(","),
|
|
94
|
+
agents.map((a) => a.name).join(","),
|
|
95
|
+
commands.map((c) => c.name).join(","),
|
|
96
|
+
].join("|");
|
|
97
|
+
if (signature === lastSignature)
|
|
98
|
+
return;
|
|
99
|
+
lastSignature = signature;
|
|
100
|
+
registerCapabilities("skill", skills.map((skill) => ({
|
|
101
|
+
id: `skill:${skill.name}`,
|
|
102
|
+
kind: "skill",
|
|
103
|
+
name: skill.name,
|
|
104
|
+
description: skill.description ?? "",
|
|
105
|
+
source: skill.filePath,
|
|
106
|
+
// Eager: the prompt already lists these, so a hit is a convenience.
|
|
107
|
+
deferred: false,
|
|
108
|
+
})));
|
|
109
|
+
registerCapabilities("agent", agents.map((agent) => ({
|
|
110
|
+
id: `agent:${agent.name}`,
|
|
111
|
+
kind: "agent",
|
|
112
|
+
name: agent.name,
|
|
113
|
+
description: agent.description ?? "",
|
|
114
|
+
deferred: false,
|
|
115
|
+
})));
|
|
116
|
+
registerCapabilities("command", commands.map((command) => ({
|
|
117
|
+
id: `command:${command.name}`,
|
|
118
|
+
kind: "command",
|
|
119
|
+
name: `/${command.name}`,
|
|
120
|
+
description: command.description ?? "",
|
|
121
|
+
deferred: false,
|
|
122
|
+
})));
|
|
123
|
+
resetCapabilitySearch();
|
|
124
|
+
}
|
|
125
|
+
function describeHit(doc, sections) {
|
|
126
|
+
if (doc.kind === "doc") {
|
|
127
|
+
const where = sections.get(doc.id);
|
|
128
|
+
const location = where ? `${where.path}:${where.line}` : (doc.source ?? "");
|
|
129
|
+
return `- [doc] ${doc.name}\n read ${location}\n ${doc.description}`;
|
|
130
|
+
}
|
|
131
|
+
const summary = doc.description ? ` — ${doc.description}` : "";
|
|
132
|
+
return `- [${doc.kind}] ${doc.name}${summary}`;
|
|
133
|
+
}
|
|
134
|
+
export function setupSelfKnowledge(pi) {
|
|
135
|
+
pi.on("before_agent_start", (event) => {
|
|
136
|
+
registerSessionCapabilities(event, pi);
|
|
137
|
+
});
|
|
138
|
+
const params = Type.Object({
|
|
139
|
+
query: Type.String({
|
|
140
|
+
description: "What you want to know about hoocode, in your own words — 'how do I write an extension', " +
|
|
141
|
+
"'where are sessions stored', 'can it run subagents'.",
|
|
142
|
+
}),
|
|
143
|
+
limit: Type.Optional(Type.Number({ description: `Maximum results. Default ${DEFAULT_LIMIT}.` })),
|
|
144
|
+
}, { additionalProperties: false });
|
|
145
|
+
pi.registerTool({
|
|
146
|
+
name: SEARCH_HOOCODE_TOOL_NAME,
|
|
147
|
+
label: SEARCH_HOOCODE_TOOL_NAME,
|
|
148
|
+
description: "Search hoocode's own documentation and the capabilities loaded in this session (skills, slash " +
|
|
149
|
+
"commands, subagents, installed plugins). Use it when the user asks what hoocode can do, how one of " +
|
|
150
|
+
"its features works, or how to configure or extend it. Doc results come back as a file path and line " +
|
|
151
|
+
"number — read that range for the answer rather than replying from the excerpt alone. " +
|
|
152
|
+
"MCP tools are not covered here; find those with ResolveMcpTools.",
|
|
153
|
+
promptSnippet: "Search hoocode's own docs and this session's capabilities by describing what you need.",
|
|
154
|
+
promptGuidelines: [
|
|
155
|
+
"For questions about hoocode itself — its features, configuration, or how to extend it — use SearchHooCode and read the section it points at instead of answering from memory.",
|
|
156
|
+
],
|
|
157
|
+
parameters: params,
|
|
158
|
+
executionMode: "parallel",
|
|
159
|
+
async execute(_toolCallId, args) {
|
|
160
|
+
const query = args.query?.trim();
|
|
161
|
+
if (!query) {
|
|
162
|
+
return { content: [{ type: "text", text: "Provide a query." }], details: undefined };
|
|
163
|
+
}
|
|
164
|
+
ensureDocsRegistered();
|
|
165
|
+
const limit = Math.min(Math.max(1, Math.trunc(args.limit ?? DEFAULT_LIMIT)), MAX_LIMIT);
|
|
166
|
+
// Fire-and-forget over everything registered, not just the docs: the
|
|
167
|
+
// dense store is shared, so indexing a subset here would evict whatever
|
|
168
|
+
// the MCP resolver indexed. The lexical leg answers this call either way.
|
|
169
|
+
void ensureDenseIndex(getCapabilities()).catch(() => { });
|
|
170
|
+
const { hits, legs } = await searchCapabilities(query, { kinds: [...SEARCHABLE], limit });
|
|
171
|
+
if (hits.length === 0) {
|
|
172
|
+
return {
|
|
173
|
+
content: [
|
|
174
|
+
{
|
|
175
|
+
type: "text",
|
|
176
|
+
text: `Nothing matched "${query}". The docs are listed in the system prompt under "About hoocode itself" — read the most likely file directly.`,
|
|
177
|
+
},
|
|
178
|
+
],
|
|
179
|
+
details: undefined,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
const sections = sectionIndex();
|
|
183
|
+
const body = hits.map((hit) => describeHit(hit.doc, sections)).join("\n");
|
|
184
|
+
// Say which legs answered: lexical-only on a conceptual query is a
|
|
185
|
+
// weaker result, and the caller should be able to see that.
|
|
186
|
+
const note = legs.includes("dense") ? "" : "\n(Lexical match only — try naming the feature if this missed.)";
|
|
187
|
+
return {
|
|
188
|
+
content: [{ type: "text", text: `Results for "${query}":\n${body}${note}` }],
|
|
189
|
+
details: undefined,
|
|
190
|
+
};
|
|
191
|
+
},
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
/** Tests, and anything that relocates the package root mid-process. */
|
|
195
|
+
export function resetSelfKnowledge() {
|
|
196
|
+
docsRegistered = false;
|
|
197
|
+
lastSignature = "";
|
|
198
|
+
}
|
|
199
|
+
//# sourceMappingURL=self-knowledge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"self-knowledge.js","sourceRoot":"","sources":["../../../src/extensions/core/self-knowledge.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * SearchHooCode — retrieval over hoocode's own docs and the capabilities this\n * session actually has.\n *\n * The system prompt already lists every shipped doc with a one-line summary\n * (see `core/self-docs.ts`), which answers \"which file covers extensions?\".\n * What it cannot answer is anything *inside* a file: `extensions.md` alone is\n * over a thousand lines, and reading it whole to find one heading costs more\n * context than the rest of the prompt put together. This indexes docs at the\n * heading level so a question lands on a section and a line number.\n *\n * It also registers the capability kinds the registry has always declared but\n * nobody ever filled — `skill`, `command`, `agent`, `plugin-installed`\n * (registry.ts notes §6.4 left them eager). Those are eager in the prompt, so\n * indexing them is not about reachability; it is about \"what can you do?\"\n * having one place that answers it rather than the model reciting whichever\n * list happens to be in front of it.\n *\n * `mcp-tool` is deliberately out of scope: ResolveMcpTools owns that kind\n * because finding an MCP tool and making it callable are the same action\n * there, and a second searcher that returns un-resolvable names would be a\n * worse answer, not an extra one.\n */\n\nimport type { Static } from \"typebox\";\nimport { Type } from \"typebox\";\nimport { ensureDenseIndex } from \"../../core/capabilities/dense.js\";\nimport { type CapabilityDoc, getCapabilities, registerCapabilities } from \"../../core/capabilities/registry.js\";\nimport { resetCapabilitySearch, searchCapabilities } from \"../../core/capabilities/search.js\";\nimport type { BeforeAgentStartEvent, ExtensionAPI, ToolDefinition } from \"../../core/extensions/types.js\";\nimport { listSelfDocSections, sectionLabel } from \"../../core/self-docs.js\";\n\nconst SEARCH_HOOCODE_TOOL_NAME = \"SearchHooCode\";\n\n/** Kinds this tool searches. `mcp-tool` belongs to ResolveMcpTools. */\nconst SEARCHABLE = [\"doc\", \"skill\", \"command\", \"agent\", \"plugin-installed\"] as const;\n\nconst DEFAULT_LIMIT = 8;\nconst MAX_LIMIT = 25;\n\n/** Registered once per process; the docs are read-only install content. */\nlet docsRegistered = false;\n\n/**\n * Index the shipped docs, the first time anyone asks.\n *\n * Lazy because it reads thirty files and produces roughly a thousand sections.\n * A session that never asks hoocode about itself should not pay for that at\n * startup, and the cost is invisible once paid — `listSelfDocSections` caches.\n */\nfunction ensureDocsRegistered(): void {\n\tif (docsRegistered) return;\n\tdocsRegistered = true;\n\n\tconst sections = listSelfDocSections();\n\tif (sections.length === 0) return;\n\n\tregisterCapabilities(\n\t\t\"doc\",\n\t\tsections.map(\n\t\t\t(section): CapabilityDoc => ({\n\t\t\t\tid: `doc:${section.id}`,\n\t\t\t\tkind: \"doc\",\n\t\t\t\tname: sectionLabel(section),\n\t\t\t\tdescription: section.excerpt,\n\t\t\t\tsource: section.path,\n\t\t\t\t// The body is withheld until the model reads the file, which is\n\t\t\t\t// exactly the condition this flag records.\n\t\t\t\tdeferred: true,\n\t\t\t}),\n\t\t),\n\t);\n\tresetCapabilitySearch();\n}\n\n/** Line-number lookup for rendering a hit, keyed by the id we registered under. */\nfunction sectionIndex(): Map<string, { path: string; line: number }> {\n\tconst index = new Map<string, { path: string; line: number }>();\n\tfor (const section of listSelfDocSections()) {\n\t\tindex.set(`doc:${section.id}`, { path: section.path, line: section.line });\n\t}\n\treturn index;\n}\n\n/**\n * Mirror the session's skills, commands, and subagents into the registry.\n *\n * Driven from `before_agent_start` because that is where the assembled\n * `systemPromptOptions` carries the skills and agents the session actually\n * loaded — re-discovering them from disk here would risk disagreeing with what\n * the model was told. Guarded by a signature so a steady session registers once\n * instead of dropping the lexical index on every turn.\n */\nlet lastSignature = \"\";\n\nfunction registerSessionCapabilities(event: BeforeAgentStartEvent, pi: ExtensionAPI): void {\n\tconst skills = event.systemPromptOptions.skills ?? [];\n\tconst agents = event.systemPromptOptions.agents ?? [];\n\tlet commands: Array<{ name: string; description?: string }> = [];\n\ttry {\n\t\tcommands = pi.getCommands();\n\t} catch {\n\t\t// Commands are an interactive-mode concern; a headless session may not\n\t\t// have them. Missing commands is not a reason to skip skills and agents.\n\t\tcommands = [];\n\t}\n\n\tconst signature = [\n\t\tskills.map((s) => s.name).join(\",\"),\n\t\tagents.map((a) => a.name).join(\",\"),\n\t\tcommands.map((c) => c.name).join(\",\"),\n\t].join(\"|\");\n\tif (signature === lastSignature) return;\n\tlastSignature = signature;\n\n\tregisterCapabilities(\n\t\t\"skill\",\n\t\tskills.map(\n\t\t\t(skill): CapabilityDoc => ({\n\t\t\t\tid: `skill:${skill.name}`,\n\t\t\t\tkind: \"skill\",\n\t\t\t\tname: skill.name,\n\t\t\t\tdescription: skill.description ?? \"\",\n\t\t\t\tsource: skill.filePath,\n\t\t\t\t// Eager: the prompt already lists these, so a hit is a convenience.\n\t\t\t\tdeferred: false,\n\t\t\t}),\n\t\t),\n\t);\n\tregisterCapabilities(\n\t\t\"agent\",\n\t\tagents.map(\n\t\t\t(agent): CapabilityDoc => ({\n\t\t\t\tid: `agent:${agent.name}`,\n\t\t\t\tkind: \"agent\",\n\t\t\t\tname: agent.name,\n\t\t\t\tdescription: agent.description ?? \"\",\n\t\t\t\tdeferred: false,\n\t\t\t}),\n\t\t),\n\t);\n\tregisterCapabilities(\n\t\t\"command\",\n\t\tcommands.map(\n\t\t\t(command): CapabilityDoc => ({\n\t\t\t\tid: `command:${command.name}`,\n\t\t\t\tkind: \"command\",\n\t\t\t\tname: `/${command.name}`,\n\t\t\t\tdescription: command.description ?? \"\",\n\t\t\t\tdeferred: false,\n\t\t\t}),\n\t\t),\n\t);\n\tresetCapabilitySearch();\n}\n\nfunction describeHit(doc: CapabilityDoc, sections: Map<string, { path: string; line: number }>): string {\n\tif (doc.kind === \"doc\") {\n\t\tconst where = sections.get(doc.id);\n\t\tconst location = where ? `${where.path}:${where.line}` : (doc.source ?? \"\");\n\t\treturn `- [doc] ${doc.name}\\n read ${location}\\n ${doc.description}`;\n\t}\n\tconst summary = doc.description ? ` — ${doc.description}` : \"\";\n\treturn `- [${doc.kind}] ${doc.name}${summary}`;\n}\n\nexport function setupSelfKnowledge(pi: ExtensionAPI): void {\n\tpi.on(\"before_agent_start\", (event: BeforeAgentStartEvent) => {\n\t\tregisterSessionCapabilities(event, pi);\n\t});\n\n\tconst params = Type.Object(\n\t\t{\n\t\t\tquery: Type.String({\n\t\t\t\tdescription:\n\t\t\t\t\t\"What you want to know about hoocode, in your own words — 'how do I write an extension', \" +\n\t\t\t\t\t\"'where are sessions stored', 'can it run subagents'.\",\n\t\t\t}),\n\t\t\tlimit: Type.Optional(Type.Number({ description: `Maximum results. Default ${DEFAULT_LIMIT}.` })),\n\t\t},\n\t\t{ additionalProperties: false },\n\t);\n\n\tpi.registerTool({\n\t\tname: SEARCH_HOOCODE_TOOL_NAME,\n\t\tlabel: SEARCH_HOOCODE_TOOL_NAME,\n\t\tdescription:\n\t\t\t\"Search hoocode's own documentation and the capabilities loaded in this session (skills, slash \" +\n\t\t\t\"commands, subagents, installed plugins). Use it when the user asks what hoocode can do, how one of \" +\n\t\t\t\"its features works, or how to configure or extend it. Doc results come back as a file path and line \" +\n\t\t\t\"number — read that range for the answer rather than replying from the excerpt alone. \" +\n\t\t\t\"MCP tools are not covered here; find those with ResolveMcpTools.\",\n\t\tpromptSnippet: \"Search hoocode's own docs and this session's capabilities by describing what you need.\",\n\t\tpromptGuidelines: [\n\t\t\t\"For questions about hoocode itself — its features, configuration, or how to extend it — use SearchHooCode and read the section it points at instead of answering from memory.\",\n\t\t],\n\t\tparameters: params,\n\t\texecutionMode: \"parallel\",\n\t\tasync execute(_toolCallId: string, args: Static<typeof params>) {\n\t\t\tconst query = args.query?.trim();\n\t\t\tif (!query) {\n\t\t\t\treturn { content: [{ type: \"text\" as const, text: \"Provide a query.\" }], details: undefined };\n\t\t\t}\n\n\t\t\tensureDocsRegistered();\n\t\t\tconst limit = Math.min(Math.max(1, Math.trunc(args.limit ?? DEFAULT_LIMIT)), MAX_LIMIT);\n\n\t\t\t// Fire-and-forget over everything registered, not just the docs: the\n\t\t\t// dense store is shared, so indexing a subset here would evict whatever\n\t\t\t// the MCP resolver indexed. The lexical leg answers this call either way.\n\t\t\tvoid ensureDenseIndex(getCapabilities()).catch(() => {});\n\n\t\t\tconst { hits, legs } = await searchCapabilities(query, { kinds: [...SEARCHABLE], limit });\n\t\t\tif (hits.length === 0) {\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttype: \"text\" as const,\n\t\t\t\t\t\t\ttext: `Nothing matched \"${query}\". The docs are listed in the system prompt under \"About hoocode itself\" — read the most likely file directly.`,\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t\tdetails: undefined,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tconst sections = sectionIndex();\n\t\t\tconst body = hits.map((hit) => describeHit(hit.doc, sections)).join(\"\\n\");\n\t\t\t// Say which legs answered: lexical-only on a conceptual query is a\n\t\t\t// weaker result, and the caller should be able to see that.\n\t\t\tconst note = legs.includes(\"dense\") ? \"\" : \"\\n(Lexical match only — try naming the feature if this missed.)\";\n\t\t\treturn {\n\t\t\t\tcontent: [{ type: \"text\" as const, text: `Results for \"${query}\":\\n${body}${note}` }],\n\t\t\t\tdetails: undefined,\n\t\t\t};\n\t\t},\n\t} as ToolDefinition);\n}\n\n/** Tests, and anything that relocates the package root mid-process. */\nexport function resetSelfKnowledge(): void {\n\tdocsRegistered = false;\n\tlastSignature = \"\";\n}\n"]}
|
package/docs/canvas.md
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# Canvas extensions
|
|
2
|
+
|
|
3
|
+
A canvas extension is a separate process that serves an interactive surface —
|
|
4
|
+
a web UI the agent can also drive — while the session keeps running. Canvases
|
|
5
|
+
are how a task that needs a real interface (a diff explorer, a chart, a form)
|
|
6
|
+
gets one without leaving the terminal.
|
|
7
|
+
|
|
8
|
+
hoocode implements GitHub's canvas wire protocol, so extensions written for
|
|
9
|
+
GitHub Copilot work unchanged.
|
|
10
|
+
|
|
11
|
+
## Using canvases
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
/canvas list # what the loaded extensions provide
|
|
15
|
+
/canvas open <extension>[:<canvas>] # open one
|
|
16
|
+
/canvas reload [extension] # pick up code changes without restarting
|
|
17
|
+
/canvas close <instanceId> # close a running one
|
|
18
|
+
/canvas rename <extension> <new-name> # rename everywhere the name appears
|
|
19
|
+
/canvas remove <extension> # delete it, after confirming
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Esc during an open cancels it, and the extension is told to release any port it
|
|
23
|
+
had already bound — the spinner disappearing is not the whole story.
|
|
24
|
+
|
|
25
|
+
`rename` matters more than it looks: a canvas's name lives in four places — the
|
|
26
|
+
directory (which *is* the extension id), the canvas's own `id`, its
|
|
27
|
+
`displayName`, and its header comment. Getting the `id` wrong by hand drops the
|
|
28
|
+
canvas you are looking at on the next reload. `rename` does all four at once,
|
|
29
|
+
closes what was open first, and prints every line it rewrote; it only touches a
|
|
30
|
+
string that is *entirely* the old name, so a sentence merely mentioning the
|
|
31
|
+
canvas is reported rather than rewritten.
|
|
32
|
+
|
|
33
|
+
`rename` and `remove` both refuse a canvas that came from a plugin, and point at
|
|
34
|
+
`/plugin` instead.
|
|
35
|
+
|
|
36
|
+
## Reloading
|
|
37
|
+
|
|
38
|
+
Editing an extension's code while it is open used to do nothing: the running
|
|
39
|
+
process was forked from the old code, so neither the open page nor a newly
|
|
40
|
+
opened second instance saw the change, and only restarting the session helped.
|
|
41
|
+
|
|
42
|
+
`/canvas reload` (and the `reload_canvas` tool) forks the new code and asks it
|
|
43
|
+
for its declarations **before** stopping the old process, so an edit that does
|
|
44
|
+
not run leaves the canvas you are looking at exactly as it was and reports the
|
|
45
|
+
error instead.
|
|
46
|
+
|
|
47
|
+
Instances keep their ids and the input they were opened with, but each gets a
|
|
48
|
+
**new url** — the extension binds a new port and mints a new token on every
|
|
49
|
+
open — so the previous browser tab is dead and the replacement url is printed.
|
|
50
|
+
|
|
51
|
+
## Discovery
|
|
52
|
+
|
|
53
|
+
An extension is a directory containing an `extension.mjs` entry file. That file
|
|
54
|
+
is the entire detection contract: no `package.json` is read, and the directory
|
|
55
|
+
name is the extension id.
|
|
56
|
+
|
|
57
|
+
Search roots, in precedence order:
|
|
58
|
+
|
|
59
|
+
| Directory | Scope |
|
|
60
|
+
|-----------|-------|
|
|
61
|
+
| `./.agents/extensions/` | Project (hoocode convention) |
|
|
62
|
+
| `./.github/extensions/` | Project (Copilot convention) |
|
|
63
|
+
| `~/.copilot/extensions/` | User |
|
|
64
|
+
|
|
65
|
+
Only ES modules are supported.
|
|
66
|
+
|
|
67
|
+
Plugins can also ship canvases; see [Plugins](plugins.md).
|
|
68
|
+
|
|
69
|
+
## Trust
|
|
70
|
+
|
|
71
|
+
`.github/extensions/` travels with a clone, so a canvas extension found there is
|
|
72
|
+
repository-supplied code. A canvas extension is a process **that also opens a
|
|
73
|
+
listening socket**, so it sits behind the same workspace-trust record as plugin
|
|
74
|
+
hooks and MCP servers — granted with `/plugin trust`, revoked with
|
|
75
|
+
`/plugin untrust`, and stored outside the repository so repository content
|
|
76
|
+
cannot forge it. See [Plugins → Trust](plugins.md#trust).
|
|
77
|
+
|
|
78
|
+
Discovery itself is read-only and always runs; the gate applies before an
|
|
79
|
+
extension is forked.
|
|
80
|
+
|
|
81
|
+
## Agent-facing tools
|
|
82
|
+
|
|
83
|
+
These register on the **first successful open** and stay for the session:
|
|
84
|
+
|
|
85
|
+
| Tool | Purpose |
|
|
86
|
+
|------|---------|
|
|
87
|
+
| `list_canvas_capabilities` | What is open and which actions each instance declares |
|
|
88
|
+
| `invoke_canvas_action` | Call an action on an instance, with input matching its declared schema |
|
|
89
|
+
| `reload_canvas` | Re-fork an extension after its code changed, keeping instance ids |
|
|
90
|
+
|
|
91
|
+
A session that never opens a canvas pays nothing for them, and they answer
|
|
92
|
+
honestly when nothing is open.
|
|
93
|
+
|
|
94
|
+
## Authoring
|
|
95
|
+
|
|
96
|
+
```
|
|
97
|
+
/new-canvas <what it should do> # scaffold, open, and build it
|
|
98
|
+
/new-canvas <name> # scaffold the template only
|
|
99
|
+
/new-canvas <name>: <what it should do> # name it yourself, then build
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Given a description, hoocode scaffolds the extension, derives and reports a
|
|
103
|
+
directory name, opens the canvas, and hands the agent a brief to build it —
|
|
104
|
+
which you then steer like any other turn, with `reload_canvas` picking up each
|
|
105
|
+
edit. Given a bare name, you get the template to edit by hand and no build
|
|
106
|
+
starts.
|
|
107
|
+
|
|
108
|
+
Scaffolding into the project grants workspace trust, since you are demonstrably
|
|
109
|
+
working in the directory on purpose.
|
|
110
|
+
|
|
111
|
+
hoocode ships one canvas of its own at `.agents/extensions/arrow-key-games/`
|
|
112
|
+
(`/canvas open arrow-key-games`), built this way.
|
|
113
|
+
|
|
114
|
+
## Related
|
|
115
|
+
|
|
116
|
+
- [Extensions](extensions.md) — in-process TypeScript extensions, which canvases are not
|
|
117
|
+
- [Plugins](plugins.md) — distribution and the shared trust model
|
package/docs/compaction.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
LLMs have limited context windows. When conversations grow too long, hoocode uses compaction to summarize older content while preserving recent work. This page covers both auto-compaction and branch summarization.
|
|
4
4
|
|
|
5
|
-
**Source files** ([
|
|
5
|
+
**Source files** ([hoocode](https://github.com/kolisachint/hoocode)):
|
|
6
6
|
- [`packages/coding-agent/src/core/compaction/compaction.ts`](https://github.com/kolisachint/hoocode/blob/main/packages/coding-agent/src/core/compaction/compaction.ts) - Auto-compaction logic
|
|
7
7
|
- [`packages/coding-agent/src/core/compaction/branch-summarization.ts`](https://github.com/kolisachint/hoocode/blob/main/packages/coding-agent/src/core/compaction/branch-summarization.ts) - Branch summarization
|
|
8
8
|
- [`packages/coding-agent/src/core/compaction/utils.ts`](https://github.com/kolisachint/hoocode/blob/main/packages/coding-agent/src/core/compaction/utils.ts) - Shared utilities (file tracking, serialization)
|
|
@@ -32,13 +32,13 @@ Auto-compaction triggers when:
|
|
|
32
32
|
contextTokens > contextWindow - reserveTokens
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
-
By default, `reserveTokens` is 16384 tokens (configurable in `~/.hoocode/
|
|
35
|
+
By default, `reserveTokens` is 16384 tokens (configurable in `~/.hoocode/settings.json` or `<project-dir>/.hoocode/settings.json`). This leaves room for the LLM's response.
|
|
36
36
|
|
|
37
37
|
You can also trigger manually with `/compact [instructions]`, where optional instructions focus the summary.
|
|
38
38
|
|
|
39
39
|
### How It Works
|
|
40
40
|
|
|
41
|
-
1. **Find cut point**: Walk backwards from newest message, accumulating token estimates until `keepRecentTokens` (default 20k, configurable in `~/.hoocode/
|
|
41
|
+
1. **Find cut point**: Walk backwards from newest message, accumulating token estimates until `keepRecentTokens` (default 20k, configurable in `~/.hoocode/settings.json` or `<project-dir>/.hoocode/settings.json`) is reached
|
|
42
42
|
2. **Extract messages**: Collect messages from the previous kept boundary (or session start) up to the cut point
|
|
43
43
|
3. **Generate summary**: Call LLM to summarize with structured format, passing the previous summary as iterative context when present
|
|
44
44
|
4. **Append entry**: Save `CompactionEntry` with summary and `firstKeptEntryId`
|
|
@@ -373,7 +373,7 @@ See `SessionBeforeTreeEvent` and `TreePreparation` in the types file.
|
|
|
373
373
|
|
|
374
374
|
## Settings
|
|
375
375
|
|
|
376
|
-
Configure compaction in `~/.hoocode/
|
|
376
|
+
Configure compaction in `~/.hoocode/settings.json` or `<project-dir>/.hoocode/settings.json`:
|
|
377
377
|
|
|
378
378
|
```json
|
|
379
379
|
{
|
package/docs/custom-provider.md
CHANGED
package/docs/development.md
CHANGED
|
@@ -48,7 +48,7 @@ Never use `__dirname` directly for package assets.
|
|
|
48
48
|
|
|
49
49
|
## Debug Command
|
|
50
50
|
|
|
51
|
-
`/debug` (hidden) writes to `~/.hoocode/
|
|
51
|
+
`/debug` (hidden) writes to `~/.hoocode/hoocode-debug.log`:
|
|
52
52
|
- Rendered TUI lines with ANSI codes
|
|
53
53
|
- Last messages sent to the LLM
|
|
54
54
|
|
package/docs/docs.json
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"path": "quickstart.md"
|
|
13
13
|
},
|
|
14
14
|
{
|
|
15
|
-
"title": "Using
|
|
15
|
+
"title": "Using HooCode",
|
|
16
16
|
"path": "usage.md"
|
|
17
17
|
},
|
|
18
18
|
{
|
|
@@ -34,6 +34,31 @@
|
|
|
34
34
|
{
|
|
35
35
|
"title": "Compaction",
|
|
36
36
|
"path": "compaction.md"
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"title": "Modes",
|
|
40
|
+
"path": "modes.md"
|
|
41
|
+
}
|
|
42
|
+
]
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"title": "Extending hoocode",
|
|
46
|
+
"items": [
|
|
47
|
+
{
|
|
48
|
+
"title": "MCP",
|
|
49
|
+
"path": "mcp.md"
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"title": "Plugins",
|
|
53
|
+
"path": "plugins.md"
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"title": "Canvas",
|
|
57
|
+
"path": "canvas.md"
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"title": "Subagent delegation",
|
|
61
|
+
"path": "routing.md"
|
|
37
62
|
}
|
|
38
63
|
]
|
|
39
64
|
},
|
|
@@ -57,7 +82,7 @@
|
|
|
57
82
|
"path": "themes.md"
|
|
58
83
|
},
|
|
59
84
|
{
|
|
60
|
-
"title": "
|
|
85
|
+
"title": "HooCode Packages",
|
|
61
86
|
"path": "packages.md"
|
|
62
87
|
},
|
|
63
88
|
{
|
package/docs/extensions.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
Extensions are TypeScript modules that extend hoocode's behavior. They can subscribe to lifecycle events, register custom tools callable by the LLM, add commands, and more.
|
|
6
6
|
|
|
7
|
-
> **Placement for /reload:** Put extensions in `~/.hoocode/
|
|
7
|
+
> **Placement for /reload:** Put extensions in `~/.hoocode/extensions/` (global) or `.hoocode/extensions/` (project-local) for auto-discovery. Use `hoocode -e ./path.ts` only for quick tests. Extensions in auto-discovered locations can be hot-reloaded with `/reload`.
|
|
8
8
|
|
|
9
9
|
**Key capabilities:**
|
|
10
10
|
- **Custom tools** - Register tools the LLM can call via `pi.registerTool()`
|
|
@@ -54,7 +54,7 @@ See [examples/extensions/](../examples/extensions/) for working implementations.
|
|
|
54
54
|
|
|
55
55
|
## Quick Start
|
|
56
56
|
|
|
57
|
-
Create `~/.
|
|
57
|
+
Create `~/.hoocode/extensions/my-extension.ts`:
|
|
58
58
|
|
|
59
59
|
```typescript
|
|
60
60
|
import type { ExtensionAPI } from "@kolisachint/hoocode-agent";
|
|
@@ -113,10 +113,10 @@ Extensions are auto-discovered from:
|
|
|
113
113
|
|
|
114
114
|
| Location | Scope |
|
|
115
115
|
|----------|-------|
|
|
116
|
-
| `~/.
|
|
117
|
-
| `~/.
|
|
118
|
-
| `.
|
|
119
|
-
| `.
|
|
116
|
+
| `~/.hoocode/extensions/*.ts` | Global (all projects) |
|
|
117
|
+
| `~/.hoocode/extensions/*/index.ts` | Global (subdirectory) |
|
|
118
|
+
| `.hoocode/extensions/*.ts` | Project-local |
|
|
119
|
+
| `.hoocode/extensions/*/index.ts` | Project-local (subdirectory) |
|
|
120
120
|
|
|
121
121
|
Additional paths via `settings.json`:
|
|
122
122
|
|
|
@@ -221,14 +221,14 @@ This pattern makes the fetched models available during normal startup and to `ho
|
|
|
221
221
|
**Single file** - simplest, for small extensions:
|
|
222
222
|
|
|
223
223
|
```
|
|
224
|
-
~/.
|
|
224
|
+
~/.hoocode/extensions/
|
|
225
225
|
└── my-extension.ts
|
|
226
226
|
```
|
|
227
227
|
|
|
228
228
|
**Directory with index.ts** - for multi-file extensions:
|
|
229
229
|
|
|
230
230
|
```
|
|
231
|
-
~/.
|
|
231
|
+
~/.hoocode/extensions/
|
|
232
232
|
└── my-extension/
|
|
233
233
|
├── index.ts # Entry point (exports default function)
|
|
234
234
|
├── tools.ts # Helper module
|
|
@@ -238,7 +238,7 @@ This pattern makes the fetched models available during normal startup and to `ho
|
|
|
238
238
|
**Package with dependencies** - for extensions that need npm packages:
|
|
239
239
|
|
|
240
240
|
```
|
|
241
|
-
~/.
|
|
241
|
+
~/.hoocode/extensions/
|
|
242
242
|
└── my-extension/
|
|
243
243
|
├── package.json # Declares dependencies and entry points
|
|
244
244
|
├── package-lock.json
|
|
@@ -496,7 +496,7 @@ pi.on("before_agent_start", async (event, ctx) => {
|
|
|
496
496
|
});
|
|
497
497
|
```
|
|
498
498
|
|
|
499
|
-
The `systemPromptOptions` field gives extensions access to the same structured data
|
|
499
|
+
The `systemPromptOptions` field gives extensions access to the same structured data HooCode uses to build the system prompt. This lets you inspect what HooCode has loaded — custom prompts, guidelines, tool snippets, context files, skills — without re-discovering resources or re-parsing flags. Use it when your extension needs to make deep, informed changes to the system prompt while respecting user-provided configuration.
|
|
500
500
|
|
|
501
501
|
Inside `before_agent_start`, `event.systemPrompt` and `ctx.getSystemPrompt()` both reflect the chained system prompt as of the current handler. Later `before_agent_start` handlers can still modify it again.
|
|
502
502
|
|
|
@@ -602,7 +602,7 @@ pi.on("context", async (event, ctx) => {
|
|
|
602
602
|
|
|
603
603
|
Fired after the provider-specific payload is built, right before the request is sent. Handlers run in extension load order. Returning `undefined` keeps the payload unchanged. Returning any other value replaces the payload for later handlers and for the actual request.
|
|
604
604
|
|
|
605
|
-
This hook can rewrite provider-level system instructions or remove them entirely. Those payload-level changes are not reflected by `ctx.getSystemPrompt()`, which reports
|
|
605
|
+
This hook can rewrite provider-level system instructions or remove them entirely. Those payload-level changes are not reflected by `ctx.getSystemPrompt()`, which reports HooCode's system prompt string rather than the final serialized provider payload.
|
|
606
606
|
|
|
607
607
|
```typescript
|
|
608
608
|
pi.on("before_provider_request", (event, ctx) => {
|
|
@@ -957,7 +957,7 @@ ctx.compact({
|
|
|
957
957
|
|
|
958
958
|
### ctx.getSystemPrompt()
|
|
959
959
|
|
|
960
|
-
Returns
|
|
960
|
+
Returns HooCode's current system prompt string.
|
|
961
961
|
|
|
962
962
|
- During `before_agent_start`, this reflects chained system-prompt changes made so far for the current turn.
|
|
963
963
|
- It does not include later `context` message mutations.
|
package/docs/index.md
CHANGED
|
@@ -35,6 +35,14 @@ For the full first-run flow, see [Quickstart](quickstart.md).
|
|
|
35
35
|
- [Keybindings](keybindings.md) - default shortcuts and custom keybindings.
|
|
36
36
|
- [Sessions](sessions.md) - session management, branching, and tree navigation.
|
|
37
37
|
- [Compaction](compaction.md) - context compaction and branch summarization.
|
|
38
|
+
- [Modes](modes.md) - ask, plan, build, and debug, and the planning workflow.
|
|
39
|
+
|
|
40
|
+
## Extending hoocode
|
|
41
|
+
|
|
42
|
+
- [MCP](mcp.md) - connect Model Context Protocol servers and their tools.
|
|
43
|
+
- [Plugins](plugins.md) - install capabilities from marketplaces, and the trust model.
|
|
44
|
+
- [Canvas](canvas.md) - interactive surfaces the agent can drive.
|
|
45
|
+
- [Subagent delegation](routing.md) - hand focused work to a separate agent.
|
|
38
46
|
|
|
39
47
|
## Customization
|
|
40
48
|
|
package/docs/keybindings.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Keybindings
|
|
2
2
|
|
|
3
|
-
All keyboard shortcuts can be customized via `~/.hoocode/
|
|
3
|
+
All keyboard shortcuts can be customized via `~/.hoocode/keybindings.json`. Each action can be bound to one or more keys.
|
|
4
4
|
|
|
5
5
|
The config file uses the same namespaced keybinding ids that hoocode uses internally and that extension authors use in `keyHint()` and injected `keybindings` managers.
|
|
6
6
|
|
|
@@ -153,7 +153,7 @@ Used inside the scoped models selector (opened via `/scoped-models`).
|
|
|
153
153
|
|
|
154
154
|
## Custom Configuration
|
|
155
155
|
|
|
156
|
-
Create `~/.hoocode/
|
|
156
|
+
Create `~/.hoocode/keybindings.json`:
|
|
157
157
|
|
|
158
158
|
```json
|
|
159
159
|
{
|