@kolisachint/hoocode-agent 0.5.21 → 0.5.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/dist/config.d.ts +11 -0
  3. package/dist/config.d.ts.map +1 -1
  4. package/dist/config.js +18 -0
  5. package/dist/config.js.map +1 -1
  6. package/dist/core/agent-log.d.ts +44 -0
  7. package/dist/core/agent-log.d.ts.map +1 -0
  8. package/dist/core/agent-log.js +74 -0
  9. package/dist/core/agent-log.js.map +1 -0
  10. package/dist/core/canvas/discovery.d.ts +60 -0
  11. package/dist/core/canvas/discovery.d.ts.map +1 -0
  12. package/dist/core/canvas/discovery.js +83 -0
  13. package/dist/core/canvas/discovery.js.map +1 -0
  14. package/dist/core/canvas/launch.d.ts +90 -0
  15. package/dist/core/canvas/launch.d.ts.map +1 -0
  16. package/dist/core/canvas/launch.js +176 -0
  17. package/dist/core/canvas/launch.js.map +1 -0
  18. package/dist/core/canvas/protocol.d.ts +229 -0
  19. package/dist/core/canvas/protocol.d.ts.map +1 -0
  20. package/dist/core/canvas/protocol.js +129 -0
  21. package/dist/core/canvas/protocol.js.map +1 -0
  22. package/dist/core/canvas/registry.d.ts +139 -0
  23. package/dist/core/canvas/registry.d.ts.map +1 -0
  24. package/dist/core/canvas/registry.js +275 -0
  25. package/dist/core/canvas/registry.js.map +1 -0
  26. package/dist/core/canvas/resolver.d.ts +26 -0
  27. package/dist/core/canvas/resolver.d.ts.map +1 -0
  28. package/dist/core/canvas/resolver.js +57 -0
  29. package/dist/core/canvas/resolver.js.map +1 -0
  30. package/dist/core/canvas/runner.d.ts +101 -0
  31. package/dist/core/canvas/runner.d.ts.map +1 -0
  32. package/dist/core/canvas/runner.js +196 -0
  33. package/dist/core/canvas/runner.js.map +1 -0
  34. package/dist/core/canvas/sdk-shim/dispatch.test-helpers.d.ts +23 -0
  35. package/dist/core/canvas/sdk-shim/dispatch.test-helpers.d.ts.map +1 -0
  36. package/dist/core/canvas/sdk-shim/dispatch.test-helpers.js +36 -0
  37. package/dist/core/canvas/sdk-shim/dispatch.test-helpers.js.map +1 -0
  38. package/dist/core/canvas/sdk-shim/index.d.ts +113 -0
  39. package/dist/core/canvas/sdk-shim/index.d.ts.map +1 -0
  40. package/dist/core/canvas/sdk-shim/index.js +184 -0
  41. package/dist/core/canvas/sdk-shim/index.js.map +1 -0
  42. package/dist/core/canvas/session.d.ts +112 -0
  43. package/dist/core/canvas/session.d.ts.map +1 -0
  44. package/dist/core/canvas/session.js +197 -0
  45. package/dist/core/canvas/session.js.map +1 -0
  46. package/dist/core/canvas/trust.d.ts +71 -0
  47. package/dist/core/canvas/trust.d.ts.map +1 -0
  48. package/dist/core/canvas/trust.js +88 -0
  49. package/dist/core/canvas/trust.js.map +1 -0
  50. package/dist/core/extensions/loader.d.ts.map +1 -1
  51. package/dist/core/extensions/loader.js +7 -10
  52. package/dist/core/extensions/loader.js.map +1 -1
  53. package/dist/core/extensions/plugins/trust.d.ts +21 -0
  54. package/dist/core/extensions/plugins/trust.d.ts.map +1 -1
  55. package/dist/core/extensions/plugins/trust.js +26 -0
  56. package/dist/core/extensions/plugins/trust.js.map +1 -1
  57. package/dist/core/lifeguard.d.ts.map +1 -1
  58. package/dist/core/lifeguard.js +2 -1
  59. package/dist/core/lifeguard.js.map +1 -1
  60. package/dist/core/subagent-pool.d.ts.map +1 -1
  61. package/dist/core/subagent-pool.js +6 -4
  62. package/dist/core/subagent-pool.js.map +1 -1
  63. package/dist/core/tools/canvas.d.ts +55 -0
  64. package/dist/core/tools/canvas.d.ts.map +1 -0
  65. package/dist/core/tools/canvas.js +159 -0
  66. package/dist/core/tools/canvas.js.map +1 -0
  67. package/dist/core/tools/subagent.d.ts.map +1 -1
  68. package/dist/core/tools/subagent.js +3 -2
  69. package/dist/core/tools/subagent.js.map +1 -1
  70. package/dist/extensions/core/canvas.d.ts +20 -0
  71. package/dist/extensions/core/canvas.d.ts.map +1 -0
  72. package/dist/extensions/core/canvas.js +192 -0
  73. package/dist/extensions/core/canvas.js.map +1 -0
  74. package/dist/extensions/core/hoo-core.d.ts +1 -0
  75. package/dist/extensions/core/hoo-core.d.ts.map +1 -1
  76. package/dist/extensions/core/hoo-core.js +3 -0
  77. package/dist/extensions/core/hoo-core.js.map +1 -1
  78. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  79. package/dist/modes/interactive/interactive-mode.js +7 -0
  80. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  81. package/dist/utils/paths.d.ts +9 -0
  82. package/dist/utils/paths.d.ts.map +1 -1
  83. package/dist/utils/paths.js +16 -0
  84. package/dist/utils/paths.js.map +1 -1
  85. package/docs/routing.md +1 -1
  86. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  87. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  88. package/examples/extensions/sandbox/package.json +1 -1
  89. package/examples/extensions/with-deps/package.json +1 -1
  90. package/package.json +5 -4
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Canvas extension discovery.
3
+ *
4
+ * Design: `docs/canvas-extensions-design.md` §4.1, §4.3. GitHub's
5
+ * `docs/extensions.md` is specific: the CLI "scans `.github/extensions/`
6
+ * (project) and the user's copilot config extensions directory for
7
+ * subdirectories containing `extension.mjs`", the entry file is required and must
8
+ * be named `extension.mjs`, and only ES modules are supported. All 23 extensions
9
+ * in `github/awesome-copilot` comply.
10
+ *
11
+ * So discovery keys off `<dir>/extension.mjs` and nothing else. In particular it
12
+ * does not read `package.json`: 3 of those 23 extensions ship without one, none
13
+ * ships `node_modules`, and where a `package.json` does exist its `main` is
14
+ * always `extension.mjs` anyway. Keying off it would add a failure mode without
15
+ * adding information.
16
+ *
17
+ * The extension id is the directory name, matching how the catalog and the
18
+ * Copilot app refer to extensions (`pr-artifact-explorer`).
19
+ *
20
+ * Some extensions also carry a `copilot-extension.json`, but it is neither
21
+ * required nor informative: 8 of the 23 catalog extensions have one, every
22
+ * instance holds exactly `{ name, version }`, and `name` always equals the
23
+ * directory name. Reading it would add a parse and a disagreement case without
24
+ * telling us anything the directory name does not.
25
+ */
26
+ import { readdirSync, statSync } from "node:fs";
27
+ import * as path from "node:path";
28
+ /** Required entry file name. */
29
+ export const CANVAS_ENTRY_FILE = "extension.mjs";
30
+ /**
31
+ * Search roots in precedence order. `.agents/` first per
32
+ * `docs/plugin-format-mapping.md` §0; the Copilot conventions follow as
33
+ * compatibility inputs.
34
+ *
35
+ * `.github/extensions/` travels with a clone, so anything found there is subject
36
+ * to the workspace-trust gate before it is ever forked (design doc §5). Discovery
37
+ * itself is read-only and always safe to run.
38
+ */
39
+ export function canvasSearchRoots(cwd, homeDir) {
40
+ return [
41
+ { dir: path.join(cwd, ".agents", "extensions"), scope: "agents" },
42
+ { dir: path.join(cwd, ".github", "extensions"), scope: "project" },
43
+ { dir: path.join(homeDir, ".copilot", "extensions"), scope: "user" },
44
+ ];
45
+ }
46
+ function subdirectories(dir) {
47
+ try {
48
+ return readdirSync(dir, { withFileTypes: true })
49
+ .filter((entry) => entry.isDirectory() || entry.isSymbolicLink())
50
+ .map((entry) => entry.name)
51
+ .sort();
52
+ }
53
+ catch {
54
+ return [];
55
+ }
56
+ }
57
+ function hasEntryFile(dir) {
58
+ try {
59
+ return statSync(path.join(dir, CANVAS_ENTRY_FILE)).isFile();
60
+ }
61
+ catch {
62
+ return false;
63
+ }
64
+ }
65
+ /**
66
+ * Discover canvas extensions across `roots`. First root wins on an id collision,
67
+ * so a project-local extension shadows a same-named user-scope one.
68
+ */
69
+ export function discoverCanvasExtensions(roots) {
70
+ const found = new Map();
71
+ for (const root of roots) {
72
+ for (const id of subdirectories(root.dir)) {
73
+ if (found.has(id))
74
+ continue;
75
+ const dir = path.join(root.dir, id);
76
+ if (!hasEntryFile(dir))
77
+ continue;
78
+ found.set(id, { id, dir, entry: path.join(dir, CANVAS_ENTRY_FILE), scope: root.scope });
79
+ }
80
+ }
81
+ return [...found.values()];
82
+ }
83
+ //# sourceMappingURL=discovery.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discovery.js","sourceRoot":"","sources":["../../../src/core/canvas/discovery.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAChD,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAElC,gCAAgC;AAChC,MAAM,CAAC,MAAM,iBAAiB,GAAG,eAAe,CAAC;AAsBjD;;;;;;;;GAQG;AACH,MAAM,UAAU,iBAAiB,CAAC,GAAW,EAAE,OAAe,EAAsB;IACnF,OAAO;QACN,EAAE,GAAG,EAAE,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,EAAE,YAAY,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE;QACjE,EAAE,GAAG,EAAE,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,EAAE,YAAY,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE;QAClE,EAAE,GAAG,EAAE,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,UAAU,EAAE,YAAY,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE;KACpE,CAAC;AAAA,CACF;AAED,SAAS,cAAc,CAAC,GAAW,EAAY;IAC9C,IAAI,CAAC;QACJ,OAAO,WAAW,CAAC,GAAG,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC;aAC9C,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,WAAW,EAAE,IAAI,KAAK,CAAC,cAAc,EAAE,CAAC;aAChE,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC;aAC1B,IAAI,EAAE,CAAC;IACV,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,EAAE,CAAC;IACX,CAAC;AAAA,CACD;AAED,SAAS,YAAY,CAAC,GAAW,EAAW;IAC3C,IAAI,CAAC;QACJ,OAAO,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,iBAAiB,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;IAC7D,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,KAAK,CAAC;IACd,CAAC;AAAA,CACD;AAED;;;GAGG;AACH,MAAM,UAAU,wBAAwB,CAAC,KAAyB,EAA+B;IAChG,MAAM,KAAK,GAAG,IAAI,GAAG,EAAqC,CAAC;IAC3D,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QAC1B,KAAK,MAAM,EAAE,IAAI,cAAc,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;YAC3C,IAAI,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;gBAAE,SAAS;YAC5B,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;YACpC,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC;gBAAE,SAAS;YACjC,KAAK,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,iBAAiB,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC;QACzF,CAAC;IACF,CAAC;IACD,OAAO,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC;AAAA,CAC3B","sourcesContent":["/**\n * Canvas extension discovery.\n *\n * Design: `docs/canvas-extensions-design.md` §4.1, §4.3. GitHub's\n * `docs/extensions.md` is specific: the CLI \"scans `.github/extensions/`\n * (project) and the user's copilot config extensions directory for\n * subdirectories containing `extension.mjs`\", the entry file is required and must\n * be named `extension.mjs`, and only ES modules are supported. All 23 extensions\n * in `github/awesome-copilot` comply.\n *\n * So discovery keys off `<dir>/extension.mjs` and nothing else. In particular it\n * does not read `package.json`: 3 of those 23 extensions ship without one, none\n * ships `node_modules`, and where a `package.json` does exist its `main` is\n * always `extension.mjs` anyway. Keying off it would add a failure mode without\n * adding information.\n *\n * The extension id is the directory name, matching how the catalog and the\n * Copilot app refer to extensions (`pr-artifact-explorer`).\n *\n * Some extensions also carry a `copilot-extension.json`, but it is neither\n * required nor informative: 8 of the 23 catalog extensions have one, every\n * instance holds exactly `{ name, version }`, and `name` always equals the\n * directory name. Reading it would add a parse and a disagreement case without\n * telling us anything the directory name does not.\n */\n\nimport { readdirSync, statSync } from \"node:fs\";\nimport * as path from \"node:path\";\n\n/** Required entry file name. */\nexport const CANVAS_ENTRY_FILE = \"extension.mjs\";\n\n/** Where a canvas extension came from, in precedence order. */\nexport type CanvasScope = \"agents\" | \"project\" | \"user\";\n\n/** Directory searched for canvas extensions. */\nexport interface CanvasSearchRoot {\n\tdir: string;\n\tscope: CanvasScope;\n}\n\n/** One discovered extension. */\nexport interface DiscoveredCanvasExtension {\n\t/** Directory name, used as the provider identifier. */\n\tid: string;\n\t/** Absolute path to the extension directory. */\n\tdir: string;\n\t/** Absolute path to `extension.mjs`. */\n\tentry: string;\n\tscope: CanvasScope;\n}\n\n/**\n * Search roots in precedence order. `.agents/` first per\n * `docs/plugin-format-mapping.md` §0; the Copilot conventions follow as\n * compatibility inputs.\n *\n * `.github/extensions/` travels with a clone, so anything found there is subject\n * to the workspace-trust gate before it is ever forked (design doc §5). Discovery\n * itself is read-only and always safe to run.\n */\nexport function canvasSearchRoots(cwd: string, homeDir: string): CanvasSearchRoot[] {\n\treturn [\n\t\t{ dir: path.join(cwd, \".agents\", \"extensions\"), scope: \"agents\" },\n\t\t{ dir: path.join(cwd, \".github\", \"extensions\"), scope: \"project\" },\n\t\t{ dir: path.join(homeDir, \".copilot\", \"extensions\"), scope: \"user\" },\n\t];\n}\n\nfunction subdirectories(dir: string): string[] {\n\ttry {\n\t\treturn readdirSync(dir, { withFileTypes: true })\n\t\t\t.filter((entry) => entry.isDirectory() || entry.isSymbolicLink())\n\t\t\t.map((entry) => entry.name)\n\t\t\t.sort();\n\t} catch {\n\t\treturn [];\n\t}\n}\n\nfunction hasEntryFile(dir: string): boolean {\n\ttry {\n\t\treturn statSync(path.join(dir, CANVAS_ENTRY_FILE)).isFile();\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * Discover canvas extensions across `roots`. First root wins on an id collision,\n * so a project-local extension shadows a same-named user-scope one.\n */\nexport function discoverCanvasExtensions(roots: CanvasSearchRoot[]): DiscoveredCanvasExtension[] {\n\tconst found = new Map<string, DiscoveredCanvasExtension>();\n\tfor (const root of roots) {\n\t\tfor (const id of subdirectories(root.dir)) {\n\t\t\tif (found.has(id)) continue;\n\t\t\tconst dir = path.join(root.dir, id);\n\t\t\tif (!hasEntryFile(dir)) continue;\n\t\t\tfound.set(id, { id, dir, entry: path.join(dir, CANVAS_ENTRY_FILE), scope: root.scope });\n\t\t}\n\t}\n\treturn [...found.values()];\n}\n"]}
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Whether canvases can run here, and how to fork one.
3
+ *
4
+ * Design: `docs/canvas-extensions-design.md` §11.1. A canvas child must run under
5
+ * **Node ≥ 20.6** — the version that introduced `module.register`, which
6
+ * `resolver.ts` uses to make the SDK specifier resolvable inside the child without
7
+ * writing into the extension directory.
8
+ *
9
+ * The requirement is on the *child*, not on hoocode. An earlier version of this
10
+ * module conflated the two and refused to run canvases at all under the
11
+ * self-contained build; but a Bun-compiled parent forking a Node child is fine, so
12
+ * the question is "is a usable Node reachable?", not "are we Node?". That makes
13
+ * canvases available on every install path where Node exists — npm, bun, or the
14
+ * standalone binary with Node on PATH.
15
+ *
16
+ * Two traps found by running it rather than reasoning about it, both of which fail
17
+ * *silently* if you get them wrong:
18
+ *
19
+ * 1. **Bun exports `module.register` but ignores resolve hooks.** The call
20
+ * succeeds, nothing warns, and the child then resolves the real
21
+ * `@github/copilot-sdk` out of Bun's global install cache instead of the shim.
22
+ * So the child may never be Bun, however the parent was launched.
23
+ * 2. **`process.versions.node` does not identify Node.** Bun reports
24
+ * `process.versions.node = "24.3.0"` next to `process.versions.bun`. Deciding
25
+ * "we can fork ourselves" must therefore require `process.versions.bun` to be
26
+ * absent, never just a satisfying Node version.
27
+ *
28
+ * Resolution is not cached here. It can spawn `node --version`, so the caller should
29
+ * resolve once per session and hold the result rather than asking per open.
30
+ */
31
+ import type { CanvasRuntime } from "./runner.js";
32
+ /** Minimum Node that supports `module.register`, which `resolver.ts` depends on. */
33
+ export declare const CANVAS_MIN_NODE_MAJOR = 20;
34
+ /** Minimum minor within {@link CANVAS_MIN_NODE_MAJOR}. */
35
+ export declare const CANVAS_MIN_NODE_MINOR = 6;
36
+ /** How long to wait for `node --version` before giving up on PATH discovery. */
37
+ export declare const CANVAS_NODE_PROBE_TIMEOUT_MS = 5000;
38
+ /** Either a runtime that can fork canvases, or the reason none is available. */
39
+ export type CanvasAvailability = {
40
+ available: true;
41
+ runtime: CanvasRuntime;
42
+ } | {
43
+ available: false;
44
+ reason: string;
45
+ };
46
+ /**
47
+ * A shim the child could import, paired with whatever argv it needs to do so.
48
+ *
49
+ * The pairing is the point: the built `.js` needs nothing, while the TypeScript source
50
+ * needs a loader. Offering a shim without its prerequisite is how you get an
51
+ * "available" that fails at fork time.
52
+ */
53
+ export interface CanvasShimCandidate {
54
+ /** `file:` URL of the shim module. */
55
+ url: string;
56
+ /** Extra argv prepended for the child, e.g. a TypeScript loader. */
57
+ execArgv: string[];
58
+ }
59
+ /** A Node executable found on PATH. */
60
+ export interface DiscoveredNode {
61
+ /** Passed straight to `spawn`, so a bare name is fine — it resolves via PATH. */
62
+ execPath: string;
63
+ /** Version without the leading `v`. */
64
+ version: string;
65
+ }
66
+ /** Host facts {@link resolveCanvasRuntime} reads. Injectable so tests need no subprocess. */
67
+ export interface CanvasHostProbe {
68
+ /** `process.versions.bun`, or undefined on Node. */
69
+ bunVersion: string | undefined;
70
+ /** `process.versions.node` — meaningless as a Node check; see the module header. */
71
+ nodeVersion: string;
72
+ /** `process.execPath`. */
73
+ execPath: string;
74
+ /** Candidate shims, highest precedence first. */
75
+ shimCandidates: CanvasShimCandidate[];
76
+ /** Whether a `file:` URL exists on disk. */
77
+ exists: (fileUrl: string) => boolean;
78
+ /** Locate a Node on PATH. Resolves undefined when there is none. */
79
+ probePathNode: () => Promise<DiscoveredNode | undefined>;
80
+ }
81
+ /** Read the current process. */
82
+ export declare function currentCanvasHostProbe(): CanvasHostProbe;
83
+ /**
84
+ * Decide whether canvases can run, and produce the runtime if so.
85
+ *
86
+ * Never throws and never guesses: an unavailable result carries a sentence a person
87
+ * can act on, because "no Node here" is a legitimate state rather than a failure.
88
+ */
89
+ export declare function resolveCanvasRuntime(probe?: CanvasHostProbe): Promise<CanvasAvailability>;
90
+ //# sourceMappingURL=launch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"launch.d.ts","sourceRoot":"","sources":["../../../src/core/canvas/launch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AASH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAKjD,oFAAoF;AACpF,eAAO,MAAM,qBAAqB,KAAK,CAAC;AACxC,0DAA0D;AAC1D,eAAO,MAAM,qBAAqB,IAAI,CAAC;AAEvC,gFAAgF;AAChF,eAAO,MAAM,4BAA4B,OAAQ,CAAC;AAElD,gFAAgF;AAChF,MAAM,MAAM,kBAAkB,GAAG;IAAE,SAAS,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,aAAa,CAAA;CAAE,GAAG;IAAE,SAAS,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAEpH;;;;;;GAMG;AACH,MAAM,WAAW,mBAAmB;IACnC,sCAAsC;IACtC,GAAG,EAAE,MAAM,CAAC;IACZ,oEAAoE;IACpE,QAAQ,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,uCAAuC;AACvC,MAAM,WAAW,cAAc;IAC9B,mFAAiF;IACjF,QAAQ,EAAE,MAAM,CAAC;IACjB,uCAAuC;IACvC,OAAO,EAAE,MAAM,CAAC;CAChB;AAED,6FAA6F;AAC7F,MAAM,WAAW,eAAe;IAC/B,oDAAoD;IACpD,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,sFAAoF;IACpF,WAAW,EAAE,MAAM,CAAC;IACpB,0BAA0B;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,iDAAiD;IACjD,cAAc,EAAE,mBAAmB,EAAE,CAAC;IACtC,4CAA4C;IAC5C,MAAM,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC;IACrC,oEAAoE;IACpE,aAAa,EAAE,MAAM,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC,CAAC;CACzD;AA4ED,gCAAgC;AAChC,wBAAgB,sBAAsB,IAAI,eAAe,CASxD;AAWD;;;;;GAKG;AACH,wBAAsB,oBAAoB,CACzC,KAAK,GAAE,eAA0C,GAC/C,OAAO,CAAC,kBAAkB,CAAC,CAmC7B","sourcesContent":["/**\n * Whether canvases can run here, and how to fork one.\n *\n * Design: `docs/canvas-extensions-design.md` §11.1. A canvas child must run under\n * **Node ≥ 20.6** — the version that introduced `module.register`, which\n * `resolver.ts` uses to make the SDK specifier resolvable inside the child without\n * writing into the extension directory.\n *\n * The requirement is on the *child*, not on hoocode. An earlier version of this\n * module conflated the two and refused to run canvases at all under the\n * self-contained build; but a Bun-compiled parent forking a Node child is fine, so\n * the question is \"is a usable Node reachable?\", not \"are we Node?\". That makes\n * canvases available on every install path where Node exists — npm, bun, or the\n * standalone binary with Node on PATH.\n *\n * Two traps found by running it rather than reasoning about it, both of which fail\n * *silently* if you get them wrong:\n *\n * 1. **Bun exports `module.register` but ignores resolve hooks.** The call\n * succeeds, nothing warns, and the child then resolves the real\n * `@github/copilot-sdk` out of Bun's global install cache instead of the shim.\n * So the child may never be Bun, however the parent was launched.\n * 2. **`process.versions.node` does not identify Node.** Bun reports\n * `process.versions.node = \"24.3.0\"` next to `process.versions.bun`. Deciding\n * \"we can fork ourselves\" must therefore require `process.versions.bun` to be\n * absent, never just a satisfying Node version.\n *\n * Resolution is not cached here. It can spawn `node --version`, so the caller should\n * resolve once per session and hold the result rather than asking per open.\n */\n\nimport { execFile } from \"node:child_process\";\nimport { existsSync } from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport * as path from \"node:path\";\nimport { fileURLToPath, pathToFileURL } from \"node:url\";\nimport { promisify } from \"node:util\";\nimport { getCanvasDir } from \"../../config.js\";\nimport type { CanvasRuntime } from \"./runner.js\";\n\nconst execFileAsync = promisify(execFile);\nconst require = createRequire(import.meta.url);\n\n/** Minimum Node that supports `module.register`, which `resolver.ts` depends on. */\nexport const CANVAS_MIN_NODE_MAJOR = 20;\n/** Minimum minor within {@link CANVAS_MIN_NODE_MAJOR}. */\nexport const CANVAS_MIN_NODE_MINOR = 6;\n\n/** How long to wait for `node --version` before giving up on PATH discovery. */\nexport const CANVAS_NODE_PROBE_TIMEOUT_MS = 5_000;\n\n/** Either a runtime that can fork canvases, or the reason none is available. */\nexport type CanvasAvailability = { available: true; runtime: CanvasRuntime } | { available: false; reason: string };\n\n/**\n * A shim the child could import, paired with whatever argv it needs to do so.\n *\n * The pairing is the point: the built `.js` needs nothing, while the TypeScript source\n * needs a loader. Offering a shim without its prerequisite is how you get an\n * \"available\" that fails at fork time.\n */\nexport interface CanvasShimCandidate {\n\t/** `file:` URL of the shim module. */\n\turl: string;\n\t/** Extra argv prepended for the child, e.g. a TypeScript loader. */\n\texecArgv: string[];\n}\n\n/** A Node executable found on PATH. */\nexport interface DiscoveredNode {\n\t/** Passed straight to `spawn`, so a bare name is fine — it resolves via PATH. */\n\texecPath: string;\n\t/** Version without the leading `v`. */\n\tversion: string;\n}\n\n/** Host facts {@link resolveCanvasRuntime} reads. Injectable so tests need no subprocess. */\nexport interface CanvasHostProbe {\n\t/** `process.versions.bun`, or undefined on Node. */\n\tbunVersion: string | undefined;\n\t/** `process.versions.node` — meaningless as a Node check; see the module header. */\n\tnodeVersion: string;\n\t/** `process.execPath`. */\n\texecPath: string;\n\t/** Candidate shims, highest precedence first. */\n\tshimCandidates: CanvasShimCandidate[];\n\t/** Whether a `file:` URL exists on disk. */\n\texists: (fileUrl: string) => boolean;\n\t/** Locate a Node on PATH. Resolves undefined when there is none. */\n\tprobePathNode: () => Promise<DiscoveredNode | undefined>;\n}\n\n/**\n * Where the child looks for the shim, best first.\n *\n * 1. The built `index.js`, via `getCanvasDir()` so the standalone binary's sidecar\n * copy is found the same way themes and the HTML export template are. Needs no\n * extra argv.\n * 2. The TypeScript source, for a checkout run through `tsx` (`hoocode-test.sh`)\n * where no `dist` exists. Offered **only** when `tsx` actually resolves, so this\n * is a verified capability rather than a hopeful one — a forked child cannot\n * import `.ts` on its own, and an \"available\" that fails at fork time is worse\n * than an honest no.\n *\n * Without (2), the people most likely to be writing canvases — contributors running\n * from source — could not open one.\n */\nfunction defaultShimCandidates(): CanvasShimCandidate[] {\n\tconst canvasDir = getCanvasDir();\n\tconst candidates: CanvasShimCandidate[] = [\n\t\t{ url: pathToFileURL(path.join(canvasDir, \"sdk-shim\", \"index.js\")).href, execArgv: [] },\n\t];\n\tconst loader = typescriptLoaderArg();\n\tif (loader) {\n\t\tcandidates.push({\n\t\t\turl: pathToFileURL(path.join(canvasDir, \"sdk-shim\", \"index.ts\")).href,\n\t\t\texecArgv: [\"--import\", loader],\n\t\t});\n\t}\n\treturn candidates;\n}\n\n/**\n * An absolute `--import` argument for `tsx`, or undefined when it is not installed.\n *\n * Absolute on purpose: Node resolves a bare `--import` specifier against the *child's*\n * working directory, so `tsx/esm` would work only while an extension happened to sit\n * inside this repository and fail elsewhere with ERR_MODULE_NOT_FOUND.\n */\nfunction typescriptLoaderArg(): string | undefined {\n\ttry {\n\t\treturn pathToFileURL(require.resolve(\"tsx/esm\")).href;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nfunction fileUrlExists(fileUrl: string): boolean {\n\ttry {\n\t\treturn existsSync(fileURLToPath(fileUrl));\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * Ask PATH for a Node.\n *\n * `spawn` resolves a bare command name through PATH (and PATHEXT on Windows)\n * without a shell, so there is no directory scanning to get wrong here — and unlike\n * `npx`/`npm`, `node` is a real executable rather than a `.cmd` shim, so the Windows\n * caveat in `extensions/core/mcp-loader.ts` does not apply.\n */\nasync function probeNodeOnPath(): Promise<DiscoveredNode | undefined> {\n\ttry {\n\t\tconst { stdout } = await execFileAsync(\"node\", [\"--version\"], {\n\t\t\ttimeout: CANVAS_NODE_PROBE_TIMEOUT_MS,\n\t\t\twindowsHide: true,\n\t\t});\n\t\tconst version = stdout.trim().replace(/^v/, \"\");\n\t\treturn version.length > 0 ? { execPath: \"node\", version } : undefined;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\n/** Read the current process. */\nexport function currentCanvasHostProbe(): CanvasHostProbe {\n\treturn {\n\t\tbunVersion: process.versions.bun,\n\t\tnodeVersion: process.versions.node,\n\t\texecPath: process.execPath,\n\t\tshimCandidates: defaultShimCandidates(),\n\t\texists: fileUrlExists,\n\t\tprobePathNode: probeNodeOnPath,\n\t};\n}\n\nfunction meetsMinimum(nodeVersion: string): boolean {\n\tconst [major, minor] = nodeVersion.split(\".\").map((part) => Number.parseInt(part, 10));\n\tif (!Number.isInteger(major) || !Number.isInteger(minor)) return false;\n\tif (major > CANVAS_MIN_NODE_MAJOR) return true;\n\treturn major === CANVAS_MIN_NODE_MAJOR && minor >= CANVAS_MIN_NODE_MINOR;\n}\n\nconst MIN_LABEL = `${CANVAS_MIN_NODE_MAJOR}.${CANVAS_MIN_NODE_MINOR}`;\n\n/**\n * Decide whether canvases can run, and produce the runtime if so.\n *\n * Never throws and never guesses: an unavailable result carries a sentence a person\n * can act on, because \"no Node here\" is a legitimate state rather than a failure.\n */\nexport async function resolveCanvasRuntime(\n\tprobe: CanvasHostProbe = currentCanvasHostProbe(),\n): Promise<CanvasAvailability> {\n\tconst shim = probe.shimCandidates.find((candidate) => probe.exists(candidate.url));\n\tif (shim === undefined) {\n\t\treturn {\n\t\t\tavailable: false,\n\t\t\treason:\n\t\t\t\t\"Canvas extensions need the built canvas shim, which was not found where it ships. \" +\n\t\t\t\t\"This usually means an incomplete install; reinstalling hoocode should restore it.\",\n\t\t};\n\t}\n\n\t// Forking ourselves is only sound when we are genuinely Node: Bun reports a\n\t// satisfying process.versions.node but ignores the resolve hook the child needs.\n\tif (probe.bunVersion === undefined && meetsMinimum(probe.nodeVersion)) {\n\t\treturn { available: true, runtime: { execPath: probe.execPath, execArgv: shim.execArgv, shimUrl: shim.url } };\n\t}\n\n\tconst found = await probe.probePathNode();\n\tif (!found) {\n\t\treturn {\n\t\t\tavailable: false,\n\t\t\treason:\n\t\t\t\t`Canvas extensions run in a Node child process and no \\`node\\` was found on PATH. ` +\n\t\t\t\t`Install Node ${MIN_LABEL} or newer to use canvases.`,\n\t\t};\n\t}\n\tif (!meetsMinimum(found.version)) {\n\t\treturn {\n\t\t\tavailable: false,\n\t\t\treason:\n\t\t\t\t`Canvas extensions need Node ${MIN_LABEL} or newer for module.register(); ` +\n\t\t\t\t`the \\`node\\` on PATH is ${found.version}.`,\n\t\t};\n\t}\n\treturn { available: true, runtime: { execPath: found.execPath, execArgv: shim.execArgv, shimUrl: shim.url } };\n}\n"]}
@@ -0,0 +1,176 @@
1
+ /**
2
+ * Whether canvases can run here, and how to fork one.
3
+ *
4
+ * Design: `docs/canvas-extensions-design.md` §11.1. A canvas child must run under
5
+ * **Node ≥ 20.6** — the version that introduced `module.register`, which
6
+ * `resolver.ts` uses to make the SDK specifier resolvable inside the child without
7
+ * writing into the extension directory.
8
+ *
9
+ * The requirement is on the *child*, not on hoocode. An earlier version of this
10
+ * module conflated the two and refused to run canvases at all under the
11
+ * self-contained build; but a Bun-compiled parent forking a Node child is fine, so
12
+ * the question is "is a usable Node reachable?", not "are we Node?". That makes
13
+ * canvases available on every install path where Node exists — npm, bun, or the
14
+ * standalone binary with Node on PATH.
15
+ *
16
+ * Two traps found by running it rather than reasoning about it, both of which fail
17
+ * *silently* if you get them wrong:
18
+ *
19
+ * 1. **Bun exports `module.register` but ignores resolve hooks.** The call
20
+ * succeeds, nothing warns, and the child then resolves the real
21
+ * `@github/copilot-sdk` out of Bun's global install cache instead of the shim.
22
+ * So the child may never be Bun, however the parent was launched.
23
+ * 2. **`process.versions.node` does not identify Node.** Bun reports
24
+ * `process.versions.node = "24.3.0"` next to `process.versions.bun`. Deciding
25
+ * "we can fork ourselves" must therefore require `process.versions.bun` to be
26
+ * absent, never just a satisfying Node version.
27
+ *
28
+ * Resolution is not cached here. It can spawn `node --version`, so the caller should
29
+ * resolve once per session and hold the result rather than asking per open.
30
+ */
31
+ import { execFile } from "node:child_process";
32
+ import { existsSync } from "node:fs";
33
+ import { createRequire } from "node:module";
34
+ import * as path from "node:path";
35
+ import { fileURLToPath, pathToFileURL } from "node:url";
36
+ import { promisify } from "node:util";
37
+ import { getCanvasDir } from "../../config.js";
38
+ const execFileAsync = promisify(execFile);
39
+ const require = createRequire(import.meta.url);
40
+ /** Minimum Node that supports `module.register`, which `resolver.ts` depends on. */
41
+ export const CANVAS_MIN_NODE_MAJOR = 20;
42
+ /** Minimum minor within {@link CANVAS_MIN_NODE_MAJOR}. */
43
+ export const CANVAS_MIN_NODE_MINOR = 6;
44
+ /** How long to wait for `node --version` before giving up on PATH discovery. */
45
+ export const CANVAS_NODE_PROBE_TIMEOUT_MS = 5_000;
46
+ /**
47
+ * Where the child looks for the shim, best first.
48
+ *
49
+ * 1. The built `index.js`, via `getCanvasDir()` so the standalone binary's sidecar
50
+ * copy is found the same way themes and the HTML export template are. Needs no
51
+ * extra argv.
52
+ * 2. The TypeScript source, for a checkout run through `tsx` (`hoocode-test.sh`)
53
+ * where no `dist` exists. Offered **only** when `tsx` actually resolves, so this
54
+ * is a verified capability rather than a hopeful one — a forked child cannot
55
+ * import `.ts` on its own, and an "available" that fails at fork time is worse
56
+ * than an honest no.
57
+ *
58
+ * Without (2), the people most likely to be writing canvases — contributors running
59
+ * from source — could not open one.
60
+ */
61
+ function defaultShimCandidates() {
62
+ const canvasDir = getCanvasDir();
63
+ const candidates = [
64
+ { url: pathToFileURL(path.join(canvasDir, "sdk-shim", "index.js")).href, execArgv: [] },
65
+ ];
66
+ const loader = typescriptLoaderArg();
67
+ if (loader) {
68
+ candidates.push({
69
+ url: pathToFileURL(path.join(canvasDir, "sdk-shim", "index.ts")).href,
70
+ execArgv: ["--import", loader],
71
+ });
72
+ }
73
+ return candidates;
74
+ }
75
+ /**
76
+ * An absolute `--import` argument for `tsx`, or undefined when it is not installed.
77
+ *
78
+ * Absolute on purpose: Node resolves a bare `--import` specifier against the *child's*
79
+ * working directory, so `tsx/esm` would work only while an extension happened to sit
80
+ * inside this repository and fail elsewhere with ERR_MODULE_NOT_FOUND.
81
+ */
82
+ function typescriptLoaderArg() {
83
+ try {
84
+ return pathToFileURL(require.resolve("tsx/esm")).href;
85
+ }
86
+ catch {
87
+ return undefined;
88
+ }
89
+ }
90
+ function fileUrlExists(fileUrl) {
91
+ try {
92
+ return existsSync(fileURLToPath(fileUrl));
93
+ }
94
+ catch {
95
+ return false;
96
+ }
97
+ }
98
+ /**
99
+ * Ask PATH for a Node.
100
+ *
101
+ * `spawn` resolves a bare command name through PATH (and PATHEXT on Windows)
102
+ * without a shell, so there is no directory scanning to get wrong here — and unlike
103
+ * `npx`/`npm`, `node` is a real executable rather than a `.cmd` shim, so the Windows
104
+ * caveat in `extensions/core/mcp-loader.ts` does not apply.
105
+ */
106
+ async function probeNodeOnPath() {
107
+ try {
108
+ const { stdout } = await execFileAsync("node", ["--version"], {
109
+ timeout: CANVAS_NODE_PROBE_TIMEOUT_MS,
110
+ windowsHide: true,
111
+ });
112
+ const version = stdout.trim().replace(/^v/, "");
113
+ return version.length > 0 ? { execPath: "node", version } : undefined;
114
+ }
115
+ catch {
116
+ return undefined;
117
+ }
118
+ }
119
+ /** Read the current process. */
120
+ export function currentCanvasHostProbe() {
121
+ return {
122
+ bunVersion: process.versions.bun,
123
+ nodeVersion: process.versions.node,
124
+ execPath: process.execPath,
125
+ shimCandidates: defaultShimCandidates(),
126
+ exists: fileUrlExists,
127
+ probePathNode: probeNodeOnPath,
128
+ };
129
+ }
130
+ function meetsMinimum(nodeVersion) {
131
+ const [major, minor] = nodeVersion.split(".").map((part) => Number.parseInt(part, 10));
132
+ if (!Number.isInteger(major) || !Number.isInteger(minor))
133
+ return false;
134
+ if (major > CANVAS_MIN_NODE_MAJOR)
135
+ return true;
136
+ return major === CANVAS_MIN_NODE_MAJOR && minor >= CANVAS_MIN_NODE_MINOR;
137
+ }
138
+ const MIN_LABEL = `${CANVAS_MIN_NODE_MAJOR}.${CANVAS_MIN_NODE_MINOR}`;
139
+ /**
140
+ * Decide whether canvases can run, and produce the runtime if so.
141
+ *
142
+ * Never throws and never guesses: an unavailable result carries a sentence a person
143
+ * can act on, because "no Node here" is a legitimate state rather than a failure.
144
+ */
145
+ export async function resolveCanvasRuntime(probe = currentCanvasHostProbe()) {
146
+ const shim = probe.shimCandidates.find((candidate) => probe.exists(candidate.url));
147
+ if (shim === undefined) {
148
+ return {
149
+ available: false,
150
+ reason: "Canvas extensions need the built canvas shim, which was not found where it ships. " +
151
+ "This usually means an incomplete install; reinstalling hoocode should restore it.",
152
+ };
153
+ }
154
+ // Forking ourselves is only sound when we are genuinely Node: Bun reports a
155
+ // satisfying process.versions.node but ignores the resolve hook the child needs.
156
+ if (probe.bunVersion === undefined && meetsMinimum(probe.nodeVersion)) {
157
+ return { available: true, runtime: { execPath: probe.execPath, execArgv: shim.execArgv, shimUrl: shim.url } };
158
+ }
159
+ const found = await probe.probePathNode();
160
+ if (!found) {
161
+ return {
162
+ available: false,
163
+ reason: `Canvas extensions run in a Node child process and no \`node\` was found on PATH. ` +
164
+ `Install Node ${MIN_LABEL} or newer to use canvases.`,
165
+ };
166
+ }
167
+ if (!meetsMinimum(found.version)) {
168
+ return {
169
+ available: false,
170
+ reason: `Canvas extensions need Node ${MIN_LABEL} or newer for module.register(); ` +
171
+ `the \`node\` on PATH is ${found.version}.`,
172
+ };
173
+ }
174
+ return { available: true, runtime: { execPath: found.execPath, execArgv: shim.execArgv, shimUrl: shim.url } };
175
+ }
176
+ //# sourceMappingURL=launch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"launch.js","sourceRoot":"","sources":["../../../src/core/canvas/launch.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Whether canvases can run here, and how to fork one.\n *\n * Design: `docs/canvas-extensions-design.md` §11.1. A canvas child must run under\n * **Node ≥ 20.6** — the version that introduced `module.register`, which\n * `resolver.ts` uses to make the SDK specifier resolvable inside the child without\n * writing into the extension directory.\n *\n * The requirement is on the *child*, not on hoocode. An earlier version of this\n * module conflated the two and refused to run canvases at all under the\n * self-contained build; but a Bun-compiled parent forking a Node child is fine, so\n * the question is \"is a usable Node reachable?\", not \"are we Node?\". That makes\n * canvases available on every install path where Node exists — npm, bun, or the\n * standalone binary with Node on PATH.\n *\n * Two traps found by running it rather than reasoning about it, both of which fail\n * *silently* if you get them wrong:\n *\n * 1. **Bun exports `module.register` but ignores resolve hooks.** The call\n * succeeds, nothing warns, and the child then resolves the real\n * `@github/copilot-sdk` out of Bun's global install cache instead of the shim.\n * So the child may never be Bun, however the parent was launched.\n * 2. **`process.versions.node` does not identify Node.** Bun reports\n * `process.versions.node = \"24.3.0\"` next to `process.versions.bun`. Deciding\n * \"we can fork ourselves\" must therefore require `process.versions.bun` to be\n * absent, never just a satisfying Node version.\n *\n * Resolution is not cached here. It can spawn `node --version`, so the caller should\n * resolve once per session and hold the result rather than asking per open.\n */\n\nimport { execFile } from \"node:child_process\";\nimport { existsSync } from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport * as path from \"node:path\";\nimport { fileURLToPath, pathToFileURL } from \"node:url\";\nimport { promisify } from \"node:util\";\nimport { getCanvasDir } from \"../../config.js\";\nimport type { CanvasRuntime } from \"./runner.js\";\n\nconst execFileAsync = promisify(execFile);\nconst require = createRequire(import.meta.url);\n\n/** Minimum Node that supports `module.register`, which `resolver.ts` depends on. */\nexport const CANVAS_MIN_NODE_MAJOR = 20;\n/** Minimum minor within {@link CANVAS_MIN_NODE_MAJOR}. */\nexport const CANVAS_MIN_NODE_MINOR = 6;\n\n/** How long to wait for `node --version` before giving up on PATH discovery. */\nexport const CANVAS_NODE_PROBE_TIMEOUT_MS = 5_000;\n\n/** Either a runtime that can fork canvases, or the reason none is available. */\nexport type CanvasAvailability = { available: true; runtime: CanvasRuntime } | { available: false; reason: string };\n\n/**\n * A shim the child could import, paired with whatever argv it needs to do so.\n *\n * The pairing is the point: the built `.js` needs nothing, while the TypeScript source\n * needs a loader. Offering a shim without its prerequisite is how you get an\n * \"available\" that fails at fork time.\n */\nexport interface CanvasShimCandidate {\n\t/** `file:` URL of the shim module. */\n\turl: string;\n\t/** Extra argv prepended for the child, e.g. a TypeScript loader. */\n\texecArgv: string[];\n}\n\n/** A Node executable found on PATH. */\nexport interface DiscoveredNode {\n\t/** Passed straight to `spawn`, so a bare name is fine — it resolves via PATH. */\n\texecPath: string;\n\t/** Version without the leading `v`. */\n\tversion: string;\n}\n\n/** Host facts {@link resolveCanvasRuntime} reads. Injectable so tests need no subprocess. */\nexport interface CanvasHostProbe {\n\t/** `process.versions.bun`, or undefined on Node. */\n\tbunVersion: string | undefined;\n\t/** `process.versions.node` — meaningless as a Node check; see the module header. */\n\tnodeVersion: string;\n\t/** `process.execPath`. */\n\texecPath: string;\n\t/** Candidate shims, highest precedence first. */\n\tshimCandidates: CanvasShimCandidate[];\n\t/** Whether a `file:` URL exists on disk. */\n\texists: (fileUrl: string) => boolean;\n\t/** Locate a Node on PATH. Resolves undefined when there is none. */\n\tprobePathNode: () => Promise<DiscoveredNode | undefined>;\n}\n\n/**\n * Where the child looks for the shim, best first.\n *\n * 1. The built `index.js`, via `getCanvasDir()` so the standalone binary's sidecar\n * copy is found the same way themes and the HTML export template are. Needs no\n * extra argv.\n * 2. The TypeScript source, for a checkout run through `tsx` (`hoocode-test.sh`)\n * where no `dist` exists. Offered **only** when `tsx` actually resolves, so this\n * is a verified capability rather than a hopeful one — a forked child cannot\n * import `.ts` on its own, and an \"available\" that fails at fork time is worse\n * than an honest no.\n *\n * Without (2), the people most likely to be writing canvases — contributors running\n * from source — could not open one.\n */\nfunction defaultShimCandidates(): CanvasShimCandidate[] {\n\tconst canvasDir = getCanvasDir();\n\tconst candidates: CanvasShimCandidate[] = [\n\t\t{ url: pathToFileURL(path.join(canvasDir, \"sdk-shim\", \"index.js\")).href, execArgv: [] },\n\t];\n\tconst loader = typescriptLoaderArg();\n\tif (loader) {\n\t\tcandidates.push({\n\t\t\turl: pathToFileURL(path.join(canvasDir, \"sdk-shim\", \"index.ts\")).href,\n\t\t\texecArgv: [\"--import\", loader],\n\t\t});\n\t}\n\treturn candidates;\n}\n\n/**\n * An absolute `--import` argument for `tsx`, or undefined when it is not installed.\n *\n * Absolute on purpose: Node resolves a bare `--import` specifier against the *child's*\n * working directory, so `tsx/esm` would work only while an extension happened to sit\n * inside this repository and fail elsewhere with ERR_MODULE_NOT_FOUND.\n */\nfunction typescriptLoaderArg(): string | undefined {\n\ttry {\n\t\treturn pathToFileURL(require.resolve(\"tsx/esm\")).href;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nfunction fileUrlExists(fileUrl: string): boolean {\n\ttry {\n\t\treturn existsSync(fileURLToPath(fileUrl));\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * Ask PATH for a Node.\n *\n * `spawn` resolves a bare command name through PATH (and PATHEXT on Windows)\n * without a shell, so there is no directory scanning to get wrong here — and unlike\n * `npx`/`npm`, `node` is a real executable rather than a `.cmd` shim, so the Windows\n * caveat in `extensions/core/mcp-loader.ts` does not apply.\n */\nasync function probeNodeOnPath(): Promise<DiscoveredNode | undefined> {\n\ttry {\n\t\tconst { stdout } = await execFileAsync(\"node\", [\"--version\"], {\n\t\t\ttimeout: CANVAS_NODE_PROBE_TIMEOUT_MS,\n\t\t\twindowsHide: true,\n\t\t});\n\t\tconst version = stdout.trim().replace(/^v/, \"\");\n\t\treturn version.length > 0 ? { execPath: \"node\", version } : undefined;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\n/** Read the current process. */\nexport function currentCanvasHostProbe(): CanvasHostProbe {\n\treturn {\n\t\tbunVersion: process.versions.bun,\n\t\tnodeVersion: process.versions.node,\n\t\texecPath: process.execPath,\n\t\tshimCandidates: defaultShimCandidates(),\n\t\texists: fileUrlExists,\n\t\tprobePathNode: probeNodeOnPath,\n\t};\n}\n\nfunction meetsMinimum(nodeVersion: string): boolean {\n\tconst [major, minor] = nodeVersion.split(\".\").map((part) => Number.parseInt(part, 10));\n\tif (!Number.isInteger(major) || !Number.isInteger(minor)) return false;\n\tif (major > CANVAS_MIN_NODE_MAJOR) return true;\n\treturn major === CANVAS_MIN_NODE_MAJOR && minor >= CANVAS_MIN_NODE_MINOR;\n}\n\nconst MIN_LABEL = `${CANVAS_MIN_NODE_MAJOR}.${CANVAS_MIN_NODE_MINOR}`;\n\n/**\n * Decide whether canvases can run, and produce the runtime if so.\n *\n * Never throws and never guesses: an unavailable result carries a sentence a person\n * can act on, because \"no Node here\" is a legitimate state rather than a failure.\n */\nexport async function resolveCanvasRuntime(\n\tprobe: CanvasHostProbe = currentCanvasHostProbe(),\n): Promise<CanvasAvailability> {\n\tconst shim = probe.shimCandidates.find((candidate) => probe.exists(candidate.url));\n\tif (shim === undefined) {\n\t\treturn {\n\t\t\tavailable: false,\n\t\t\treason:\n\t\t\t\t\"Canvas extensions need the built canvas shim, which was not found where it ships. \" +\n\t\t\t\t\"This usually means an incomplete install; reinstalling hoocode should restore it.\",\n\t\t};\n\t}\n\n\t// Forking ourselves is only sound when we are genuinely Node: Bun reports a\n\t// satisfying process.versions.node but ignores the resolve hook the child needs.\n\tif (probe.bunVersion === undefined && meetsMinimum(probe.nodeVersion)) {\n\t\treturn { available: true, runtime: { execPath: probe.execPath, execArgv: shim.execArgv, shimUrl: shim.url } };\n\t}\n\n\tconst found = await probe.probePathNode();\n\tif (!found) {\n\t\treturn {\n\t\t\tavailable: false,\n\t\t\treason:\n\t\t\t\t`Canvas extensions run in a Node child process and no \\`node\\` was found on PATH. ` +\n\t\t\t\t`Install Node ${MIN_LABEL} or newer to use canvases.`,\n\t\t};\n\t}\n\tif (!meetsMinimum(found.version)) {\n\t\treturn {\n\t\t\tavailable: false,\n\t\t\treason:\n\t\t\t\t`Canvas extensions need Node ${MIN_LABEL} or newer for module.register(); ` +\n\t\t\t\t`the \\`node\\` on PATH is ${found.version}.`,\n\t\t};\n\t}\n\treturn { available: true, runtime: { execPath: found.execPath, execArgv: shim.execArgv, shimUrl: shim.url } };\n}\n"]}
@@ -0,0 +1,229 @@
1
+ /**
2
+ * Canvas wire protocol — the entire GitHub-facing surface.
3
+ *
4
+ * Design: `docs/canvas-extensions-design.md` §2. The short version: GitHub's own
5
+ * types (`@github/copilot-sdk` `dist/canvas.d.ts`) state that the Node
6
+ * `createCanvas`/`joinSession` API is one of five language wrappers over the same
7
+ * JSON-RPC wire protocol, and that "the divergence is API ergonomics only". So
8
+ * hoocode binds to the wire protocol, not to the Node sugar, and the drift
9
+ * surface is the three provider methods below plus one version integer.
10
+ *
11
+ * Everything a third-party canvas can observe lives in this file. If GitHub
12
+ * moves the protocol, this file and `sdk-shim/` move; nothing else does.
13
+ *
14
+ * Two layers are deliberately separated here:
15
+ *
16
+ * 1. **Provider contract** (`CanvasProvider*`, `CANVAS_METHOD_*`) — GitHub's.
17
+ * These payload shapes reach extension code as the `ctx` argument of
18
+ * `open`, `onClose`, and action handlers, so they must stay structurally
19
+ * identical to the SDK's. `test/canvas-protocol-conformance.test.ts` fails
20
+ * the build if they drift.
21
+ * 2. **Host envelope** (`Canvas*Message`) — ours. It carries provider calls
22
+ * between hoocode's runner and the child-side shim. In the Copilot case the
23
+ * equivalent layer sits between their CLI and their SDK and an extension
24
+ * never sees it, so we are free to keep it simple: newline-delimited JSON.
25
+ */
26
+ /** JSON value, mirroring the SDK's `JsonValue`. */
27
+ export type JsonValue = null | boolean | number | string | JsonValue[] | {
28
+ [key: string]: JsonValue;
29
+ };
30
+ /** JSON Schema carried as data, mirroring the SDK's `CanvasJsonSchema`. */
31
+ export type CanvasJsonSchema = JsonValue;
32
+ /** Result of an action invocation, mirroring the SDK's `CanvasActionInvokeResult`. */
33
+ export type CanvasActionInvokeResult = JsonValue;
34
+ /**
35
+ * SDK protocol version this implementation targets, mirroring the SDK's
36
+ * `SDK_PROTOCOL_VERSION`. The conformance test asserts the two are equal, so a
37
+ * protocol bump surfaces as a failing test rather than a runtime mismatch.
38
+ */
39
+ export declare const CANVAS_SDK_PROTOCOL_VERSION = 3;
40
+ /** Provider callback: open a canvas instance. */
41
+ export declare const CANVAS_METHOD_OPEN = "canvas.open";
42
+ /** Provider callback: a canvas instance was closed. */
43
+ export declare const CANVAS_METHOD_CLOSE = "canvas.close";
44
+ /** Provider callback: invoke an agent-callable action. */
45
+ export declare const CANVAS_METHOD_INVOKE_ACTION = "canvas.action.invoke";
46
+ /** The three provider callbacks, in the order the design doc lists them. */
47
+ export declare const CANVAS_PROVIDER_METHODS: readonly ["canvas.open", "canvas.close", "canvas.action.invoke"];
48
+ /** One of the three provider callback names. */
49
+ export type CanvasProviderMethod = (typeof CANVAS_PROVIDER_METHODS)[number];
50
+ /** Host capabilities advertised to the provider. */
51
+ export interface CanvasHostContextCapabilities {
52
+ /** Whether canvas rendering is supported. */
53
+ canvases?: boolean;
54
+ }
55
+ /** Host context supplied by the runtime. */
56
+ export interface CanvasHostContext {
57
+ capabilities?: CanvasHostContextCapabilities;
58
+ }
59
+ /** Session context supplied by the runtime. */
60
+ export interface CanvasSessionContext {
61
+ /** Active session working directory, when known. */
62
+ workingDirectory?: string;
63
+ }
64
+ /** Fields shared by every provider callback payload. */
65
+ interface CanvasProviderRequestBase {
66
+ /** Target session identifier. */
67
+ sessionId: string;
68
+ /** Owning provider identifier. */
69
+ extensionId: string;
70
+ /** Provider-local canvas identifier. */
71
+ canvasId: string;
72
+ /** Canvas instance identifier. */
73
+ instanceId: string;
74
+ host?: CanvasHostContext;
75
+ session?: CanvasSessionContext;
76
+ }
77
+ /** `canvas.open` payload. */
78
+ export interface CanvasProviderOpenRequest extends CanvasProviderRequestBase {
79
+ /** Canvas open input. */
80
+ input?: JsonValue;
81
+ }
82
+ /** `canvas.open` result. A web-rendered canvas returns the URL the host loads. */
83
+ export interface CanvasProviderOpenResult {
84
+ /** URL for web-rendered canvases. */
85
+ url?: string;
86
+ /** Provider-supplied title. */
87
+ title?: string;
88
+ /** Provider-supplied status text. */
89
+ status?: string;
90
+ }
91
+ /** `canvas.close` payload. */
92
+ export interface CanvasProviderCloseRequest extends CanvasProviderRequestBase {
93
+ }
94
+ /** `canvas.action.invoke` payload. */
95
+ export interface CanvasProviderInvokeActionRequest extends CanvasProviderRequestBase {
96
+ /** Action name to invoke. */
97
+ actionName: string;
98
+ /** Action input. */
99
+ input?: JsonValue;
100
+ }
101
+ /**
102
+ * Action metadata as it crosses the wire. The SDK strips each action's `handler`
103
+ * closure before sending the declaration, so this is `CanvasAction` minus the
104
+ * handler.
105
+ */
106
+ export interface CanvasActionDeclaration {
107
+ /** Action identifier, unique within the canvas. */
108
+ name: string;
109
+ /** Description shown to the model when picking an action. */
110
+ description?: string;
111
+ /** Optional JSON Schema for the action's `input` payload. */
112
+ inputSchema?: CanvasJsonSchema;
113
+ }
114
+ /** Declarative metadata for a single canvas. */
115
+ export interface CanvasDeclaration {
116
+ /** Canvas id, unique within the declaring connection. */
117
+ id: string;
118
+ /** Human-readable label shown in discovery and host UI chrome. */
119
+ displayName: string;
120
+ /** Short, single-sentence description shown to the agent in canvas catalogs. */
121
+ description: string;
122
+ /** Optional JSON Schema for the `input` payload accepted by `canvas.open`. */
123
+ inputSchema?: CanvasJsonSchema;
124
+ /** Agent-invocable actions. */
125
+ actions?: CanvasActionDeclaration[];
126
+ }
127
+ /**
128
+ * Reserved action-name prefix. The SDK is explicit: "Names MUST NOT start with
129
+ * `canvas.` — that prefix is reserved for lifecycle verbs."
130
+ */
131
+ export declare const CANVAS_RESERVED_ACTION_PREFIX = "canvas.";
132
+ /** Log levels accepted by `session.log`. */
133
+ export type CanvasLogLevel = "info" | "warning" | "error";
134
+ /**
135
+ * Host envelope version. Ours, not GitHub's — bumped only when the
136
+ * runner↔shim framing changes.
137
+ */
138
+ export declare const CANVAS_ENVELOPE_VERSION = 1;
139
+ /** Child announces itself and its canvases. Always the first message. */
140
+ export interface CanvasReadyMessage {
141
+ envelope: typeof CANVAS_ENVELOPE_VERSION;
142
+ type: "ready";
143
+ /** The `CANVAS_SDK_PROTOCOL_VERSION` the child was built against. */
144
+ protocolVersion: number;
145
+ /** Provider identifier, derived by the runner from the extension directory. */
146
+ extensionId: string;
147
+ canvases: CanvasDeclaration[];
148
+ /**
149
+ * Surfaces declared by the extension that this shim does not implement, so the
150
+ * runner can warn once instead of letting the extension half-work
151
+ * (design doc §6.2).
152
+ */
153
+ unsupported?: string[];
154
+ }
155
+ /** Child forwards a `session.log` call. */
156
+ export interface CanvasLogMessage {
157
+ envelope: typeof CANVAS_ENVELOPE_VERSION;
158
+ type: "log";
159
+ message: string;
160
+ level?: CanvasLogLevel;
161
+ ephemeral?: boolean;
162
+ }
163
+ /** Host asks the child to run one provider callback. */
164
+ export interface CanvasRequestMessage {
165
+ envelope: typeof CANVAS_ENVELOPE_VERSION;
166
+ type: "request";
167
+ id: number;
168
+ method: CanvasProviderMethod;
169
+ params: JsonValue;
170
+ }
171
+ /** Child returns a provider callback result. */
172
+ export interface CanvasResponseMessage {
173
+ envelope: typeof CANVAS_ENVELOPE_VERSION;
174
+ type: "response";
175
+ id: number;
176
+ result: JsonValue;
177
+ }
178
+ /**
179
+ * Child returns a failure. `code` carries `CanvasError.code` when the handler threw
180
+ * one, so the agent gets a machine-readable code rather than a string to parse.
181
+ */
182
+ export interface CanvasErrorMessage {
183
+ envelope: typeof CANVAS_ENVELOPE_VERSION;
184
+ type: "error";
185
+ id: number;
186
+ code: string;
187
+ message: string;
188
+ }
189
+ /** Anything the host may send to a child. */
190
+ export type CanvasHostToChildMessage = CanvasRequestMessage;
191
+ /** Anything a child may send to the host. */
192
+ export type CanvasChildToHostMessage = CanvasReadyMessage | CanvasLogMessage | CanvasResponseMessage | CanvasErrorMessage;
193
+ /** Error code used when a handler throws something that is not a `CanvasError`. */
194
+ export declare const CANVAS_ERROR_CODE_INTERNAL = "internal_error";
195
+ /** Error code used when the host asks for a canvas or action the child does not declare. */
196
+ export declare const CANVAS_ERROR_CODE_UNKNOWN_TARGET = "unknown_target";
197
+ /** Whether `value` is a provider callback name. */
198
+ export declare function isCanvasProviderMethod(value: unknown): value is CanvasProviderMethod;
199
+ /** Narrow a decoded value to a host→child message. */
200
+ export declare function isCanvasHostToChildMessage(value: unknown): value is CanvasHostToChildMessage;
201
+ /** Narrow a decoded value to a child→host message. */
202
+ export declare function isCanvasChildToHostMessage(value: unknown): value is CanvasChildToHostMessage;
203
+ /** Serialize one message as a single NDJSON line, newline included. */
204
+ export declare function encodeCanvasMessage(message: CanvasHostToChildMessage | CanvasChildToHostMessage): string;
205
+ /** One decode pass: parsed JSON values plus any lines that were not JSON at all. */
206
+ export interface CanvasDecodeResult {
207
+ values: unknown[];
208
+ /**
209
+ * Lines that failed to parse. In the Copilot CLI stdout is the JSON-RPC channel,
210
+ * so a stray `console.log` corrupts it; we keep the same discipline but report
211
+ * the stray text so the runner can tell the author to use `session.log`
212
+ * instead of leaving them with a silent protocol error.
213
+ */
214
+ strays: string[];
215
+ }
216
+ /**
217
+ * Incremental NDJSON decoder. Chunk boundaries do not respect line boundaries, so
218
+ * a partial trailing line is held until the rest arrives.
219
+ */
220
+ export declare class CanvasMessageDecoder {
221
+ private buffer;
222
+ /** Decode everything complete in `chunk`, buffering any partial trailing line. */
223
+ push(chunk: string): CanvasDecodeResult;
224
+ /** Decode whatever is left, for use when the stream ends without a trailing newline. */
225
+ flush(): CanvasDecodeResult;
226
+ private take;
227
+ }
228
+ export {};
229
+ //# sourceMappingURL=protocol.d.ts.map