@kolisachint/hoocode-agent 0.5.25 → 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 +128 -0
- package/dist/core/canvas/lifecycle.d.ts +93 -0
- package/dist/core/canvas/lifecycle.d.ts.map +1 -0
- package/dist/core/canvas/lifecycle.js +165 -0
- package/dist/core/canvas/lifecycle.js.map +1 -0
- package/dist/core/canvas/registry.d.ts +89 -0
- package/dist/core/canvas/registry.d.ts.map +1 -1
- package/dist/core/canvas/registry.js +205 -10
- package/dist/core/canvas/registry.js.map +1 -1
- package/dist/core/canvas/scaffold.d.ts +123 -0
- package/dist/core/canvas/scaffold.d.ts.map +1 -0
- package/dist/core/canvas/scaffold.js +376 -0
- package/dist/core/canvas/scaffold.js.map +1 -0
- package/dist/core/canvas/session.d.ts +39 -1
- package/dist/core/canvas/session.d.ts.map +1 -1
- package/dist/core/canvas/session.js +83 -1
- package/dist/core/canvas/session.js.map +1 -1
- 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/core/tools/canvas.d.ts +23 -3
- package/dist/core/tools/canvas.d.ts.map +1 -1
- package/dist/core/tools/canvas.js +99 -4
- package/dist/core/tools/canvas.js.map +1 -1
- package/dist/extensions/core/canvas.d.ts +20 -2
- package/dist/extensions/core/canvas.d.ts.map +1 -1
- package/dist/extensions/core/canvas.js +279 -36
- package/dist/extensions/core/canvas.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/scaffold.d.ts +7 -1
- package/dist/extensions/core/scaffold.d.ts.map +1 -1
- package/dist/extensions/core/scaffold.js +7 -185
- package/dist/extensions/core/scaffold.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
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scaffold.js","sourceRoot":"","sources":["../../../src/extensions/core/scaffold.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Scaffold commands — /new-skill, /new-agent, /new-command, and /new-canvas.\n *\n * Without `--platform`, each creates a ready-to-edit resource file\n * under `.hoocode/` (hoocode's private surface), picked up on the next /reload.\n *\n * With `--platform` (or the `platform` setting), the scaffold\n * instead lands in each target platform's *workspace* conventions via the\n * format registry's per-adapter {@link WorkspaceLayout} — e.g.\n * `--platform copilot` writes `.github/skills/<name>/SKILL.md`,\n * `.github/agents/<name>.agent.md`, and `.github/prompts/<name>.prompt.md`,\n * while `claude` writes `.claude/skills|agents|commands/`. hoocode reads all\n * of these back, so the scaffold is live after /reload either way.\n */\n\nimport { existsSync, mkdirSync, writeFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { getAgentDir } from \"../../config.js\";\nimport { CANVAS_ENTRY_FILE } from \"../../core/canvas/discovery.js\";\nimport { getFormatByPlatform } from \"../../core/extensions/plugins/formats/index.js\";\nimport { getWorkspacePlatforms } from \"../../core/extensions/plugins/formats/platform-targets.js\";\nimport type { EmittedFile, MarketplacePlatform, WorkspaceLayout } from \"../../core/extensions/plugins/formats/types.js\";\nimport { isWorkspaceTrusted, trustWorkspace } from \"../../core/extensions/plugins/trust.js\";\nimport type { ExtensionAPI, ExtensionCommandContext } from \"../../core/extensions/types.js\";\n\n/** Validates a resource name: lowercase a-z, 0-9, hyphens, no leading/trailing/double hyphens. */\nfunction validateResourceName(name: string): string | null {\n\tif (!name) return \"name is required\";\n\tif (!/^[a-z0-9-]+$/.test(name)) return \"name must be lowercase a-z, 0-9, and hyphens only\";\n\tif (name.startsWith(\"-\") || name.endsWith(\"-\")) return \"name must not start or end with a hyphen\";\n\tif (name.includes(\"--\")) return \"name must not contain consecutive hyphens\";\n\treturn null;\n}\n\n/**\n * Write one scaffolded artifact into every `--platform` target's\n * workspace layout. Existing files are never clobbered — they are reported and\n * skipped. Returns true when the platform-targeted path handled the command.\n */\nfunction scaffoldForPlatforms(\n\tctx: ExtensionCommandContext,\n\tcommand: string,\n\tplatforms: MarketplacePlatform[],\n\temit: (workspace: WorkspaceLayout) => EmittedFile,\n): void {\n\tconst created: string[] = [];\n\tconst skipped: string[] = [];\n\tfor (const platform of platforms) {\n\t\tconst adapter = getFormatByPlatform(platform);\n\t\tif (!adapter) continue;\n\t\tconst file = emit(adapter.workspace);\n\t\tconst abs = join(ctx.cwd, file.path);\n\t\tif (existsSync(abs)) {\n\t\t\tskipped.push(file.path);\n\t\t\tcontinue;\n\t\t}\n\t\tmkdirSync(dirname(abs), { recursive: true });\n\t\twriteFileSync(abs, file.content, \"utf8\");\n\t\tcreated.push(file.path);\n\t}\n\n\tconst lines: string[] = [];\n\tif (created.length > 0) {\n\t\tlines.push(`Created (${platforms.join(\", \")}):`, ...created.map((f) => ` ${f}`));\n\t\tlines.push(\"Edit the file(s), then run /reload to activate.\");\n\t}\n\tif (skipped.length > 0) {\n\t\tlines.push(`Skipped (already exist):`, ...skipped.map((f) => ` ${f}`));\n\t}\n\tif (lines.length === 0) {\n\t\tlines.push(`/${command}: no writable platform targets resolved`);\n\t}\n\tctx.ui.notify(lines.join(\"\\n\"), created.length > 0 ? \"info\" : \"warning\");\n}\n\nconst SKILL_BODY_TEMPLATE = (name: string) =>\n\t[\n\t\t`# ${name}`,\n\t\t\"\",\n\t\t\"TODO: write the skill instructions here.\",\n\t\t\"\",\n\t\t\"When relative paths appear below, they are resolved from this file's directory.\",\n\t\t\"\",\n\t].join(\"\\n\");\n\nconst AGENT_BODY_TEMPLATE = (name: string) =>\n\t[\n\t\t`You are a ${name} subagent.`,\n\t\t\"You run in an isolated context and cannot see the parent conversation.\",\n\t\t\"\",\n\t\t\"TODO: write the system prompt here.\",\n\t\t\"\",\n\t\t\"Your final message must contain ONLY your answer — it is the only output\",\n\t\t\"the caller receives. Do not include intermediate reasoning or tool logs.\",\n\t\t\"\",\n\t].join(\"\\n\");\n\nconst COMMAND_BODY_TEMPLATE = (name: string) =>\n\t[\n\t\t`Run the /${name} command with arguments: **$ARGUMENTS**.`,\n\t\t\"\",\n\t\t\"TODO: write the instructions here. Placeholders you can use:\",\n\t\t\"- $1, $2, ... for positional arguments\",\n\t\t\"- $@ or $ARGUMENTS for all arguments\",\n\t\t\"\",\n\t].join(\"\\n\");\n\n/**\n * A working single-canvas extension.\n *\n * Deliberately complete rather than a stub: a canvas has no passive half — its\n * name, its actions and its UI all come from running its code — so a scaffold\n * that does not run teaches nothing and cannot be checked with `/canvas open`.\n * This one opens, serves a page, answers an action, and closes cleanly, which is\n * the whole contract; everything past that is the author's.\n *\n * Shaped like the catalog extensions hoocode already hosts: the only import is\n * `@github/copilot-sdk/extension` (host-resolved — never installed, see\n * `docs/canvas-extensions-design.md` §4.1) plus `node:` builtins, and the UI is\n * served from an ephemeral loopback port behind a per-instance token so nothing\n * else on the machine can read it.\n */\nconst CANVAS_ENTRY_TEMPLATE = (name: string): string => `\\\n// ${name} — a canvas extension. Run it with: /canvas open ${name}\n//\n// The \"@github/copilot-sdk/extension\" import is resolved by the host at fork\n// time. Do not install it, and do not add a node_modules here.\nimport { createCanvas, CanvasError, joinSession } from \"@github/copilot-sdk/extension\";\nimport { randomBytes } from \"node:crypto\";\nimport { createServer } from \"node:http\";\n\n/** Per-instance state. A canvas can be opened more than once at a time. */\nconst instances = new Map();\n\nconst session = await joinSession({\n\tcanvases: [\n\t\tcreateCanvas({\n\t\t\tid: \"${name}\",\n\t\t\tdisplayName: \"${name}\",\n\t\t\tdescription: \"TODO: one sentence — the agent reads this to decide whether to open it.\",\n\t\t\tactions: [\n\t\t\t\t{\n\t\t\t\t\tname: \"add_note\",\n\t\t\t\t\tdescription: \"TODO: describe what the agent can do to this canvas.\",\n\t\t\t\t\tinputSchema: {\n\t\t\t\t\t\ttype: \"object\",\n\t\t\t\t\t\tproperties: { text: { type: \"string\" } },\n\t\t\t\t\t\trequired: [\"text\"],\n\t\t\t\t\t},\n\t\t\t\t\thandler: (ctx) => {\n\t\t\t\t\t\tconst entry = instances.get(ctx.instanceId);\n\t\t\t\t\t\t// CanvasError carries a code the host shows verbatim; a bare\n\t\t\t\t\t\t// throw arrives as an opaque internal error instead.\n\t\t\t\t\t\tif (!entry) throw new CanvasError(\"no_instance\", \\`Instance \"\\${ctx.instanceId}\" is not open.\\`);\n\t\t\t\t\t\tentry.notes.push(ctx.input.text);\n\t\t\t\t\t\treturn { notes: entry.notes.length };\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t],\n\t\t\topen: async (ctx) => {\n\t\t\t\tconst token = randomBytes(32).toString(\"base64url\");\n\t\t\t\tconst notes = [];\n\t\t\t\tconst server = createServer((req, res) => {\n\t\t\t\t\tconst url = new URL(req.url ?? \"/\", \"http://127.0.0.1\");\n\t\t\t\t\tif (url.searchParams.get(\"token\") !== token) {\n\t\t\t\t\t\tres.writeHead(403, { \"Content-Type\": \"text/plain\" });\n\t\t\t\t\t\tres.end(\"forbidden\");\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tres.writeHead(200, { \"Content-Type\": \"text/html; charset=utf-8\" });\n\t\t\t\t\tres.end(\n\t\t\t\t\t\t\\`<!doctype html><meta charset=\"utf-8\"><title>${name}</title>\\` +\n\t\t\t\t\t\t\t\\`<p>\\${notes.length} note(s). TODO: build the UI.\\`,\n\t\t\t\t\t);\n\t\t\t\t});\n\t\t\t\t// Port 0 on 127.0.0.1: an ephemeral port, reachable only from this machine.\n\t\t\t\tawait new Promise((resolve) => server.listen(0, \"127.0.0.1\", resolve));\n\t\t\t\tconst { port } = server.address();\n\t\t\t\tinstances.set(ctx.instanceId, { server, notes });\n\t\t\t\treturn {\n\t\t\t\t\turl: \\`http://127.0.0.1:\\${port}/?token=\\${token}\\`,\n\t\t\t\t\ttitle: \"${name}\",\n\t\t\t\t};\n\t\t\t},\n\t\t\tonClose: async (ctx) => {\n\t\t\t\tconst entry = instances.get(ctx.instanceId);\n\t\t\t\t// Called for an abandoned open too, so the instance may be unknown.\n\t\t\t\tif (!entry) return;\n\t\t\t\tinstances.delete(ctx.instanceId);\n\t\t\t\tawait new Promise((resolve) => entry.server.close(resolve));\n\t\t\t},\n\t\t}),\n\t],\n});\n\n// stdout is the protocol channel. Use session.log, never console.log.\nawait session.log(\"${name} ready\");\n`;\n\n/**\n * Where a canvas extension lives, per platform.\n *\n * This does not go through {@link WorkspaceLayout} like the other scaffolds, and\n * the reason is that a canvas has no Claude convention to emit into: the surface\n * exists in Copilot and in hoocode's own `.agents/` tree and nowhere else. A\n * layout method returning nothing for one adapter would be a worse lie than\n * naming the two real homes here — these are exactly the roots\n * `core/canvas/discovery.ts` searches, which is what makes a scaffold live on\n * the next /canvas.\n */\nconst CANVAS_HOMES: Partial<Record<MarketplacePlatform, string[]>> = {\n\tagents: [\".agents\", \"extensions\"],\n\tgithub: [\".github\", \"extensions\"],\n};\n\nexport function setupScaffold(pi: ExtensionAPI): void {\n\t// ── /new-skill <name> ─────────────────────────────────────────────────────\n\t// Creates a SKILL.md with valid Agent Skills frontmatter — under .hoocode/ by\n\t// default, or under each --platform target's skills directory.\n\n\tpi.registerCommand(\"new-skill\", {\n\t\tdescription: \"Scaffold a new skill. Usage: /new-skill <name>\",\n\t\tgetArgumentCompletions: () => [],\n\t\thandler: async (args: string, ctx: ExtensionCommandContext): Promise<void> => {\n\t\t\tconst name = args.trim();\n\t\t\tconst error = validateResourceName(name);\n\t\t\tif (error) {\n\t\t\t\tctx.ui.notify(`/new-skill: ${error}. Usage: /new-skill <name>`, \"warning\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst platforms = getWorkspacePlatforms();\n\t\t\tif (platforms) {\n\t\t\t\tscaffoldForPlatforms(ctx, \"new-skill\", platforms, (ws) =>\n\t\t\t\t\tws.emitSkill({\n\t\t\t\t\t\tname,\n\t\t\t\t\t\tdescription:\n\t\t\t\t\t\t\t\"TODO: describe when to use this skill — the agent reads this to decide whether to load it.\",\n\t\t\t\t\t\tbody: SKILL_BODY_TEMPLATE(name),\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst skillDir = join(ctx.cwd, \".hoocode\", \"skills\", name);\n\t\t\tconst skillFile = join(skillDir, \"SKILL.md\");\n\n\t\t\tif (existsSync(skillFile)) {\n\t\t\t\tctx.ui.notify(`/new-skill: ${skillFile} already exists`, \"warning\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tmkdirSync(skillDir, { recursive: true });\n\t\t\twriteFileSync(\n\t\t\t\tskillFile,\n\t\t\t\t[\n\t\t\t\t\t\"---\",\n\t\t\t\t\t`name: ${name}`,\n\t\t\t\t\t\"description: |\",\n\t\t\t\t\t\" TODO: describe when to use this skill — one clear sentence per bullet.\",\n\t\t\t\t\t\" The model reads this to decide whether to load the skill.\",\n\t\t\t\t\t\"allowed-tools: read, bash\",\n\t\t\t\t\t\"---\",\n\t\t\t\t\t\"\",\n\t\t\t\t\tSKILL_BODY_TEMPLATE(name),\n\t\t\t\t].join(\"\\n\"),\n\t\t\t\t\"utf8\",\n\t\t\t);\n\n\t\t\tctx.ui.notify(\n\t\t\t\t`Skill created: ${join(\".hoocode\", \"skills\", name, \"SKILL.md\")}\\nEdit the file, then run /reload to activate it.`,\n\t\t\t\t\"info\",\n\t\t\t);\n\t\t},\n\t});\n\n\t// ── /new-agent <name> ─────────────────────────────────────────────────────\n\t// Creates a subagent definition — .hoocode/agents/<name>.md by default, or\n\t// each platform's convention (.claude/agents/<name>.md,\n\t// .github/agents/<name>.agent.md with a YAML-list tools grant, ...).\n\n\tpi.registerCommand(\"new-agent\", {\n\t\tdescription: \"Scaffold a new subagent. Usage: /new-agent <name>\",\n\t\tgetArgumentCompletions: () => [],\n\t\thandler: async (args: string, ctx: ExtensionCommandContext): Promise<void> => {\n\t\t\tconst name = args.trim();\n\t\t\tconst error = validateResourceName(name);\n\t\t\tif (error) {\n\t\t\t\tctx.ui.notify(`/new-agent: ${error}. Usage: /new-agent <name>`, \"warning\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst platforms = getWorkspacePlatforms();\n\t\t\tif (platforms) {\n\t\t\t\tscaffoldForPlatforms(ctx, \"new-agent\", platforms, (ws) =>\n\t\t\t\t\tws.emitAgent({\n\t\t\t\t\t\tname,\n\t\t\t\t\t\tdescription: \"TODO: describe the task(s) to delegate to this agent.\",\n\t\t\t\t\t\ttools: \"read, bash\",\n\t\t\t\t\t\tbody: AGENT_BODY_TEMPLATE(name),\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst agentsDir = join(ctx.cwd, \".hoocode\", \"agents\");\n\t\t\tconst agentFile = join(agentsDir, `${name}.md`);\n\n\t\t\tif (existsSync(agentFile)) {\n\t\t\t\tctx.ui.notify(`/new-agent: ${agentFile} already exists`, \"warning\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tmkdirSync(agentsDir, { recursive: true });\n\t\t\twriteFileSync(\n\t\t\t\tagentFile,\n\t\t\t\t[\n\t\t\t\t\t\"---\",\n\t\t\t\t\t`name: ${name}`,\n\t\t\t\t\t\"description: |\",\n\t\t\t\t\t\" Use this subagent ONLY when:\",\n\t\t\t\t\t\" - TODO: describe the task(s) to delegate here\",\n\t\t\t\t\t\"\",\n\t\t\t\t\t\" DO NOT use for:\",\n\t\t\t\t\t\" - TODO: describe what this agent should NOT handle\",\n\t\t\t\t\t\"tools: read, bash\",\n\t\t\t\t\t\"model: sonnet\",\n\t\t\t\t\t\"---\",\n\t\t\t\t\t`You are a ${name} subagent running inside hoocode.`,\n\t\t\t\t\t\"You run in an isolated context and cannot see the parent conversation.\",\n\t\t\t\t\t\"\",\n\t\t\t\t\t\"TODO: write the system prompt here.\",\n\t\t\t\t\t\"\",\n\t\t\t\t\t\"Your final message must contain ONLY your answer — it is the only output\",\n\t\t\t\t\t\"the caller receives. Do not include intermediate reasoning or tool logs.\",\n\t\t\t\t\t\"\",\n\t\t\t\t].join(\"\\n\"),\n\t\t\t\t\"utf8\",\n\t\t\t);\n\n\t\t\tctx.ui.notify(\n\t\t\t\t`Agent created: ${join(\".hoocode\", \"agents\", `${name}.md`)}\\nEdit the file, then run /reload to activate it.`,\n\t\t\t\t\"info\",\n\t\t\t);\n\t\t},\n\t});\n\n\t// ── /new-command <name> ───────────────────────────────────────────────────\n\t// Creates a slash-command prompt template — .hoocode/commands/<name>.md by\n\t// default, or each platform's convention (.claude/commands/<name>.md,\n\t// .github/prompts/<name>.prompt.md, ...).\n\n\tpi.registerCommand(\"new-command\", {\n\t\tdescription: \"Scaffold a new slash command. Usage: /new-command <name>\",\n\t\tgetArgumentCompletions: () => [],\n\t\thandler: async (args: string, ctx: ExtensionCommandContext): Promise<void> => {\n\t\t\tconst name = args.trim();\n\t\t\tconst error = validateResourceName(name);\n\t\t\tif (error) {\n\t\t\t\tctx.ui.notify(`/new-command: ${error}. Usage: /new-command <name>`, \"warning\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst platforms = getWorkspacePlatforms();\n\t\t\tif (platforms) {\n\t\t\t\tscaffoldForPlatforms(ctx, \"new-command\", platforms, (ws) =>\n\t\t\t\t\tws.emitCommand({\n\t\t\t\t\t\tname,\n\t\t\t\t\t\tdescription: `TODO: describe what /${name} does and when to use it.`,\n\t\t\t\t\t\tbody: COMMAND_BODY_TEMPLATE(name),\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst commandsDir = join(ctx.cwd, \".hoocode\", \"commands\");\n\t\t\tconst commandFile = join(commandsDir, `${name}.md`);\n\n\t\t\tif (existsSync(commandFile)) {\n\t\t\t\tctx.ui.notify(`/new-command: ${commandFile} already exists`, \"warning\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tmkdirSync(commandsDir, { recursive: true });\n\t\t\twriteFileSync(\n\t\t\t\tcommandFile,\n\t\t\t\t[\n\t\t\t\t\t\"---\",\n\t\t\t\t\t`name: ${name}`,\n\t\t\t\t\t\"description: |\",\n\t\t\t\t\t` TODO: describe what /${name} does and when to use it.`,\n\t\t\t\t\t` Usage: /${name} <args>`,\n\t\t\t\t\t\"argument-hint: <args>\",\n\t\t\t\t\t\"---\",\n\t\t\t\t\t`Run the /${name} command with arguments: **$ARGUMENTS**.`,\n\t\t\t\t\t\"\",\n\t\t\t\t\t\"TODO: write the instructions here. Placeholders you can use:\",\n\t\t\t\t\t\"- $1, $2, ... for positional arguments\",\n\t\t\t\t\t\"- $@ or $ARGUMENTS for all arguments\",\n\t\t\t\t\t`- $${\"{\"}@:N} / $${\"{\"}@:N:L} for bash-style slices`,\n\t\t\t\t\t\"\",\n\t\t\t\t].join(\"\\n\"),\n\t\t\t\t\"utf8\",\n\t\t\t);\n\n\t\t\tctx.ui.notify(\n\t\t\t\t`Command created: ${join(\".hoocode\", \"commands\", `${name}.md`)}\\nEdit the file, then run /reload to activate it.`,\n\t\t\t\t\"info\",\n\t\t\t);\n\t\t},\n\t});\n\n\t// ── /new-canvas <name> ────────────────────────────────────────────────────\n\t// Creates a canvas extension — .agents/extensions/<name>/extension.mjs by\n\t// default, or .github/extensions/<name>/ with --platform github (Copilot's\n\t// project scope). Design: docs/canvas-extensions-design.md §9, Phase 3.\n\t//\n\t// Unlike the other scaffolds this one needs no /reload: canvases are\n\t// discovered when /canvas runs, not loaded at session start, so the new\n\t// extension is openable immediately.\n\n\tpi.registerCommand(\"new-canvas\", {\n\t\tdescription: \"Scaffold a new canvas extension. Usage: /new-canvas <name>\",\n\t\tgetArgumentCompletions: () => [],\n\t\thandler: async (args: string, ctx: ExtensionCommandContext): Promise<void> => {\n\t\t\tconst name = args.trim();\n\t\t\tconst error = validateResourceName(name);\n\t\t\tif (error) {\n\t\t\t\tctx.ui.notify(`/new-canvas: ${error}. Usage: /new-canvas <name>`, \"warning\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// `--platform claude` is dropped rather than redirected: Claude has no\n\t\t\t// canvas convention, and silently writing into someone else's marker\n\t\t\t// directory would put an extension where that vendor will never look.\n\t\t\tconst requested = getWorkspacePlatforms() ?? [\"agents\"];\n\t\t\tconst targets = requested.filter((platform) => CANVAS_HOMES[platform] !== undefined);\n\t\t\tif (targets.length === 0) {\n\t\t\t\tctx.ui.notify(\n\t\t\t\t\t`/new-canvas: no canvas home for platform \"${requested.join(\", \")}\". ` +\n\t\t\t\t\t\t\"Canvas extensions exist under .agents/extensions (agents) and .github/extensions (github) only.\",\n\t\t\t\t\t\"warning\",\n\t\t\t\t);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst created: string[] = [];\n\t\t\tconst skipped: string[] = [];\n\t\t\tfor (const platform of targets) {\n\t\t\t\tconst home = CANVAS_HOMES[platform];\n\t\t\t\tif (!home) continue;\n\t\t\t\tconst relative = join(...home, name, CANVAS_ENTRY_FILE);\n\t\t\t\tconst absolute = join(ctx.cwd, relative);\n\t\t\t\tif (existsSync(absolute)) {\n\t\t\t\t\tskipped.push(relative);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tmkdirSync(dirname(absolute), { recursive: true });\n\t\t\t\twriteFileSync(absolute, CANVAS_ENTRY_TEMPLATE(name), \"utf8\");\n\t\t\t\tcreated.push(relative);\n\t\t\t}\n\n\t\t\t// A canvas lands in the working tree, which is where the trust gate looks\n\t\t\t// (`core/canvas/trust.ts`) — so without this the canvas you just asked for\n\t\t\t// is withheld the moment you try to open it, and refused with \"came with\n\t\t\t// this repository\", which is not true of a file created seconds ago.\n\t\t\t//\n\t\t\t// Granting is the same call `/plugin install --scope project` makes, for\n\t\t\t// the same stated reason: a person typing this command in this directory\n\t\t\t// is the human act workspace trust asks for. It is deliberately wider than\n\t\t\t// this one canvas — it also lets plugins already committed here run their\n\t\t\t// hooks and MCP servers — so it is said out loud and pointed at its\n\t\t\t// reverse, never done silently.\n\t\t\tlet trustNote = \"\";\n\t\t\tif (created.length > 0 && !isWorkspaceTrusted(ctx.cwd, getAgentDir())) {\n\t\t\t\ttrustWorkspace(ctx.cwd, getAgentDir());\n\t\t\t\ttrustNote =\n\t\t\t\t\t`Trusted this workspace so the canvas can run. Plugins committed here may now run hooks ` +\n\t\t\t\t\t`and MCP servers too; \\`/plugin untrust\\` reverses it.`;\n\t\t\t}\n\n\t\t\tconst lines: string[] = [];\n\t\t\tif (created.length > 0) {\n\t\t\t\tlines.push(\"Canvas created:\", ...created.map((file) => ` ${file}`));\n\t\t\t\tlines.push(\"\", `Open it now with /canvas open ${name} — no /reload needed.`);\n\t\t\t\tif (trustNote) lines.push(\"\", trustNote);\n\t\t\t}\n\t\t\tif (skipped.length > 0) {\n\t\t\t\tlines.push(\"Skipped (already exist):\", ...skipped.map((file) => ` ${file}`));\n\t\t\t}\n\t\t\tctx.ui.notify(lines.join(\"\\n\"), created.length > 0 ? \"info\" : \"warning\");\n\t\t},\n\t});\n}\n"]}
|
|
1
|
+
{"version":3,"file":"scaffold.js","sourceRoot":"","sources":["../../../src/extensions/core/scaffold.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Scaffold commands — /new-skill, /new-agent, and /new-command.\n *\n * `/new-canvas` was their fourth sibling and is not here any more. It stopped\n * being a file-writing command when it grew Copilot's `/create-canvas` shape:\n * it opens what it writes and hands the agent a brief to build it, so it needs\n * the canvas session and the agent loop, neither of which belongs in a scaffold.\n * It lives in `extensions/core/canvas.ts` over `core/canvas/scaffold.ts`.\n *\n * Without `--platform`, each creates a ready-to-edit resource file\n * under `.hoocode/` (hoocode's private surface), picked up on the next /reload.\n *\n * With `--platform` (or the `platform` setting), the scaffold\n * instead lands in each target platform's *workspace* conventions via the\n * format registry's per-adapter {@link WorkspaceLayout} — e.g.\n * `--platform copilot` writes `.github/skills/<name>/SKILL.md`,\n * `.github/agents/<name>.agent.md`, and `.github/prompts/<name>.prompt.md`,\n * while `claude` writes `.claude/skills|agents|commands/`. hoocode reads all\n * of these back, so the scaffold is live after /reload either way.\n */\n\nimport { existsSync, mkdirSync, writeFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { getFormatByPlatform } from \"../../core/extensions/plugins/formats/index.js\";\nimport { getWorkspacePlatforms } from \"../../core/extensions/plugins/formats/platform-targets.js\";\nimport type { EmittedFile, MarketplacePlatform, WorkspaceLayout } from \"../../core/extensions/plugins/formats/types.js\";\nimport type { ExtensionAPI, ExtensionCommandContext } from \"../../core/extensions/types.js\";\n\n/** Validates a resource name: lowercase a-z, 0-9, hyphens, no leading/trailing/double hyphens. */\nfunction validateResourceName(name: string): string | null {\n\tif (!name) return \"name is required\";\n\tif (!/^[a-z0-9-]+$/.test(name)) return \"name must be lowercase a-z, 0-9, and hyphens only\";\n\tif (name.startsWith(\"-\") || name.endsWith(\"-\")) return \"name must not start or end with a hyphen\";\n\tif (name.includes(\"--\")) return \"name must not contain consecutive hyphens\";\n\treturn null;\n}\n\n/**\n * Write one scaffolded artifact into every `--platform` target's\n * workspace layout. Existing files are never clobbered — they are reported and\n * skipped. Returns true when the platform-targeted path handled the command.\n */\nfunction scaffoldForPlatforms(\n\tctx: ExtensionCommandContext,\n\tcommand: string,\n\tplatforms: MarketplacePlatform[],\n\temit: (workspace: WorkspaceLayout) => EmittedFile,\n): void {\n\tconst created: string[] = [];\n\tconst skipped: string[] = [];\n\tfor (const platform of platforms) {\n\t\tconst adapter = getFormatByPlatform(platform);\n\t\tif (!adapter) continue;\n\t\tconst file = emit(adapter.workspace);\n\t\tconst abs = join(ctx.cwd, file.path);\n\t\tif (existsSync(abs)) {\n\t\t\tskipped.push(file.path);\n\t\t\tcontinue;\n\t\t}\n\t\tmkdirSync(dirname(abs), { recursive: true });\n\t\twriteFileSync(abs, file.content, \"utf8\");\n\t\tcreated.push(file.path);\n\t}\n\n\tconst lines: string[] = [];\n\tif (created.length > 0) {\n\t\tlines.push(`Created (${platforms.join(\", \")}):`, ...created.map((f) => ` ${f}`));\n\t\tlines.push(\"Edit the file(s), then run /reload to activate.\");\n\t}\n\tif (skipped.length > 0) {\n\t\tlines.push(`Skipped (already exist):`, ...skipped.map((f) => ` ${f}`));\n\t}\n\tif (lines.length === 0) {\n\t\tlines.push(`/${command}: no writable platform targets resolved`);\n\t}\n\tctx.ui.notify(lines.join(\"\\n\"), created.length > 0 ? \"info\" : \"warning\");\n}\n\nconst SKILL_BODY_TEMPLATE = (name: string) =>\n\t[\n\t\t`# ${name}`,\n\t\t\"\",\n\t\t\"TODO: write the skill instructions here.\",\n\t\t\"\",\n\t\t\"When relative paths appear below, they are resolved from this file's directory.\",\n\t\t\"\",\n\t].join(\"\\n\");\n\nconst AGENT_BODY_TEMPLATE = (name: string) =>\n\t[\n\t\t`You are a ${name} subagent.`,\n\t\t\"You run in an isolated context and cannot see the parent conversation.\",\n\t\t\"\",\n\t\t\"TODO: write the system prompt here.\",\n\t\t\"\",\n\t\t\"Your final message must contain ONLY your answer — it is the only output\",\n\t\t\"the caller receives. Do not include intermediate reasoning or tool logs.\",\n\t\t\"\",\n\t].join(\"\\n\");\n\nconst COMMAND_BODY_TEMPLATE = (name: string) =>\n\t[\n\t\t`Run the /${name} command with arguments: **$ARGUMENTS**.`,\n\t\t\"\",\n\t\t\"TODO: write the instructions here. Placeholders you can use:\",\n\t\t\"- $1, $2, ... for positional arguments\",\n\t\t\"- $@ or $ARGUMENTS for all arguments\",\n\t\t\"\",\n\t].join(\"\\n\");\n\nexport function setupScaffold(pi: ExtensionAPI): void {\n\t// ── /new-skill <name> ─────────────────────────────────────────────────────\n\t// Creates a SKILL.md with valid Agent Skills frontmatter — under .hoocode/ by\n\t// default, or under each --platform target's skills directory.\n\n\tpi.registerCommand(\"new-skill\", {\n\t\tdescription: \"Scaffold a new skill. Usage: /new-skill <name>\",\n\t\tgetArgumentCompletions: () => [],\n\t\thandler: async (args: string, ctx: ExtensionCommandContext): Promise<void> => {\n\t\t\tconst name = args.trim();\n\t\t\tconst error = validateResourceName(name);\n\t\t\tif (error) {\n\t\t\t\tctx.ui.notify(`/new-skill: ${error}. Usage: /new-skill <name>`, \"warning\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst platforms = getWorkspacePlatforms();\n\t\t\tif (platforms) {\n\t\t\t\tscaffoldForPlatforms(ctx, \"new-skill\", platforms, (ws) =>\n\t\t\t\t\tws.emitSkill({\n\t\t\t\t\t\tname,\n\t\t\t\t\t\tdescription:\n\t\t\t\t\t\t\t\"TODO: describe when to use this skill — the agent reads this to decide whether to load it.\",\n\t\t\t\t\t\tbody: SKILL_BODY_TEMPLATE(name),\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst skillDir = join(ctx.cwd, \".hoocode\", \"skills\", name);\n\t\t\tconst skillFile = join(skillDir, \"SKILL.md\");\n\n\t\t\tif (existsSync(skillFile)) {\n\t\t\t\tctx.ui.notify(`/new-skill: ${skillFile} already exists`, \"warning\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tmkdirSync(skillDir, { recursive: true });\n\t\t\twriteFileSync(\n\t\t\t\tskillFile,\n\t\t\t\t[\n\t\t\t\t\t\"---\",\n\t\t\t\t\t`name: ${name}`,\n\t\t\t\t\t\"description: |\",\n\t\t\t\t\t\" TODO: describe when to use this skill — one clear sentence per bullet.\",\n\t\t\t\t\t\" The model reads this to decide whether to load the skill.\",\n\t\t\t\t\t\"allowed-tools: read, bash\",\n\t\t\t\t\t\"---\",\n\t\t\t\t\t\"\",\n\t\t\t\t\tSKILL_BODY_TEMPLATE(name),\n\t\t\t\t].join(\"\\n\"),\n\t\t\t\t\"utf8\",\n\t\t\t);\n\n\t\t\tctx.ui.notify(\n\t\t\t\t`Skill created: ${join(\".hoocode\", \"skills\", name, \"SKILL.md\")}\\nEdit the file, then run /reload to activate it.`,\n\t\t\t\t\"info\",\n\t\t\t);\n\t\t},\n\t});\n\n\t// ── /new-agent <name> ─────────────────────────────────────────────────────\n\t// Creates a subagent definition — .hoocode/agents/<name>.md by default, or\n\t// each platform's convention (.claude/agents/<name>.md,\n\t// .github/agents/<name>.agent.md with a YAML-list tools grant, ...).\n\n\tpi.registerCommand(\"new-agent\", {\n\t\tdescription: \"Scaffold a new subagent. Usage: /new-agent <name>\",\n\t\tgetArgumentCompletions: () => [],\n\t\thandler: async (args: string, ctx: ExtensionCommandContext): Promise<void> => {\n\t\t\tconst name = args.trim();\n\t\t\tconst error = validateResourceName(name);\n\t\t\tif (error) {\n\t\t\t\tctx.ui.notify(`/new-agent: ${error}. Usage: /new-agent <name>`, \"warning\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst platforms = getWorkspacePlatforms();\n\t\t\tif (platforms) {\n\t\t\t\tscaffoldForPlatforms(ctx, \"new-agent\", platforms, (ws) =>\n\t\t\t\t\tws.emitAgent({\n\t\t\t\t\t\tname,\n\t\t\t\t\t\tdescription: \"TODO: describe the task(s) to delegate to this agent.\",\n\t\t\t\t\t\ttools: \"read, bash\",\n\t\t\t\t\t\tbody: AGENT_BODY_TEMPLATE(name),\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst agentsDir = join(ctx.cwd, \".hoocode\", \"agents\");\n\t\t\tconst agentFile = join(agentsDir, `${name}.md`);\n\n\t\t\tif (existsSync(agentFile)) {\n\t\t\t\tctx.ui.notify(`/new-agent: ${agentFile} already exists`, \"warning\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tmkdirSync(agentsDir, { recursive: true });\n\t\t\twriteFileSync(\n\t\t\t\tagentFile,\n\t\t\t\t[\n\t\t\t\t\t\"---\",\n\t\t\t\t\t`name: ${name}`,\n\t\t\t\t\t\"description: |\",\n\t\t\t\t\t\" Use this subagent ONLY when:\",\n\t\t\t\t\t\" - TODO: describe the task(s) to delegate here\",\n\t\t\t\t\t\"\",\n\t\t\t\t\t\" DO NOT use for:\",\n\t\t\t\t\t\" - TODO: describe what this agent should NOT handle\",\n\t\t\t\t\t\"tools: read, bash\",\n\t\t\t\t\t\"model: sonnet\",\n\t\t\t\t\t\"---\",\n\t\t\t\t\t`You are a ${name} subagent running inside hoocode.`,\n\t\t\t\t\t\"You run in an isolated context and cannot see the parent conversation.\",\n\t\t\t\t\t\"\",\n\t\t\t\t\t\"TODO: write the system prompt here.\",\n\t\t\t\t\t\"\",\n\t\t\t\t\t\"Your final message must contain ONLY your answer — it is the only output\",\n\t\t\t\t\t\"the caller receives. Do not include intermediate reasoning or tool logs.\",\n\t\t\t\t\t\"\",\n\t\t\t\t].join(\"\\n\"),\n\t\t\t\t\"utf8\",\n\t\t\t);\n\n\t\t\tctx.ui.notify(\n\t\t\t\t`Agent created: ${join(\".hoocode\", \"agents\", `${name}.md`)}\\nEdit the file, then run /reload to activate it.`,\n\t\t\t\t\"info\",\n\t\t\t);\n\t\t},\n\t});\n\n\t// ── /new-command <name> ───────────────────────────────────────────────────\n\t// Creates a slash-command prompt template — .hoocode/commands/<name>.md by\n\t// default, or each platform's convention (.claude/commands/<name>.md,\n\t// .github/prompts/<name>.prompt.md, ...).\n\n\tpi.registerCommand(\"new-command\", {\n\t\tdescription: \"Scaffold a new slash command. Usage: /new-command <name>\",\n\t\tgetArgumentCompletions: () => [],\n\t\thandler: async (args: string, ctx: ExtensionCommandContext): Promise<void> => {\n\t\t\tconst name = args.trim();\n\t\t\tconst error = validateResourceName(name);\n\t\t\tif (error) {\n\t\t\t\tctx.ui.notify(`/new-command: ${error}. Usage: /new-command <name>`, \"warning\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst platforms = getWorkspacePlatforms();\n\t\t\tif (platforms) {\n\t\t\t\tscaffoldForPlatforms(ctx, \"new-command\", platforms, (ws) =>\n\t\t\t\t\tws.emitCommand({\n\t\t\t\t\t\tname,\n\t\t\t\t\t\tdescription: `TODO: describe what /${name} does and when to use it.`,\n\t\t\t\t\t\tbody: COMMAND_BODY_TEMPLATE(name),\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst commandsDir = join(ctx.cwd, \".hoocode\", \"commands\");\n\t\t\tconst commandFile = join(commandsDir, `${name}.md`);\n\n\t\t\tif (existsSync(commandFile)) {\n\t\t\t\tctx.ui.notify(`/new-command: ${commandFile} already exists`, \"warning\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tmkdirSync(commandsDir, { recursive: true });\n\t\t\twriteFileSync(\n\t\t\t\tcommandFile,\n\t\t\t\t[\n\t\t\t\t\t\"---\",\n\t\t\t\t\t`name: ${name}`,\n\t\t\t\t\t\"description: |\",\n\t\t\t\t\t` TODO: describe what /${name} does and when to use it.`,\n\t\t\t\t\t` Usage: /${name} <args>`,\n\t\t\t\t\t\"argument-hint: <args>\",\n\t\t\t\t\t\"---\",\n\t\t\t\t\t`Run the /${name} command with arguments: **$ARGUMENTS**.`,\n\t\t\t\t\t\"\",\n\t\t\t\t\t\"TODO: write the instructions here. Placeholders you can use:\",\n\t\t\t\t\t\"- $1, $2, ... for positional arguments\",\n\t\t\t\t\t\"- $@ or $ARGUMENTS for all arguments\",\n\t\t\t\t\t`- $${\"{\"}@:N} / $${\"{\"}@:N:L} for bash-style slices`,\n\t\t\t\t\t\"\",\n\t\t\t\t].join(\"\\n\"),\n\t\t\t\t\"utf8\",\n\t\t\t);\n\n\t\t\tctx.ui.notify(\n\t\t\t\t`Command created: ${join(\".hoocode\", \"commands\", `${name}.md`)}\\nEdit the file, then run /reload to activate it.`,\n\t\t\t\t\"info\",\n\t\t\t);\n\t\t},\n\t});\n}\n"]}
|
|
@@ -0,0 +1,28 @@
|
|
|
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 { ExtensionAPI } from "../../core/extensions/types.js";
|
|
25
|
+
export declare function setupSelfKnowledge(pi: ExtensionAPI): void;
|
|
26
|
+
/** Tests, and anything that relocates the package root mid-process. */
|
|
27
|
+
export declare function resetSelfKnowledge(): void;
|
|
28
|
+
//# sourceMappingURL=self-knowledge.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"self-knowledge.d.ts","sourceRoot":"","sources":["../../../src/extensions/core/self-knowledge.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAOH,OAAO,KAAK,EAAyB,YAAY,EAAkB,MAAM,gCAAgC,CAAC;AAyI1G,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CAsEzD;AAED,uEAAuE;AACvE,wBAAgB,kBAAkB,IAAI,IAAI,CAGzC","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"]}
|
|
@@ -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
|
|