@kolisachint/hoocode-agent 0.5.22 → 0.5.24

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 (73) hide show
  1. package/CHANGELOG.md +47 -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/canvas/discovery.d.ts +60 -0
  7. package/dist/core/canvas/discovery.d.ts.map +1 -0
  8. package/dist/core/canvas/discovery.js +83 -0
  9. package/dist/core/canvas/discovery.js.map +1 -0
  10. package/dist/core/canvas/launch.d.ts +90 -0
  11. package/dist/core/canvas/launch.d.ts.map +1 -0
  12. package/dist/core/canvas/launch.js +176 -0
  13. package/dist/core/canvas/launch.js.map +1 -0
  14. package/dist/core/canvas/protocol.d.ts +229 -0
  15. package/dist/core/canvas/protocol.d.ts.map +1 -0
  16. package/dist/core/canvas/protocol.js +129 -0
  17. package/dist/core/canvas/protocol.js.map +1 -0
  18. package/dist/core/canvas/registry.d.ts +139 -0
  19. package/dist/core/canvas/registry.d.ts.map +1 -0
  20. package/dist/core/canvas/registry.js +275 -0
  21. package/dist/core/canvas/registry.js.map +1 -0
  22. package/dist/core/canvas/resolver.d.ts +26 -0
  23. package/dist/core/canvas/resolver.d.ts.map +1 -0
  24. package/dist/core/canvas/resolver.js +57 -0
  25. package/dist/core/canvas/resolver.js.map +1 -0
  26. package/dist/core/canvas/runner.d.ts +101 -0
  27. package/dist/core/canvas/runner.d.ts.map +1 -0
  28. package/dist/core/canvas/runner.js +196 -0
  29. package/dist/core/canvas/runner.js.map +1 -0
  30. package/dist/core/canvas/sdk-shim/dispatch.test-helpers.d.ts +23 -0
  31. package/dist/core/canvas/sdk-shim/dispatch.test-helpers.d.ts.map +1 -0
  32. package/dist/core/canvas/sdk-shim/dispatch.test-helpers.js +36 -0
  33. package/dist/core/canvas/sdk-shim/dispatch.test-helpers.js.map +1 -0
  34. package/dist/core/canvas/sdk-shim/index.d.ts +113 -0
  35. package/dist/core/canvas/sdk-shim/index.d.ts.map +1 -0
  36. package/dist/core/canvas/sdk-shim/index.js +184 -0
  37. package/dist/core/canvas/sdk-shim/index.js.map +1 -0
  38. package/dist/core/canvas/session.d.ts +112 -0
  39. package/dist/core/canvas/session.d.ts.map +1 -0
  40. package/dist/core/canvas/session.js +197 -0
  41. package/dist/core/canvas/session.js.map +1 -0
  42. package/dist/core/canvas/trust.d.ts +71 -0
  43. package/dist/core/canvas/trust.d.ts.map +1 -0
  44. package/dist/core/canvas/trust.js +88 -0
  45. package/dist/core/canvas/trust.js.map +1 -0
  46. package/dist/core/extensions/loader.d.ts.map +1 -1
  47. package/dist/core/extensions/loader.js +32 -21
  48. package/dist/core/extensions/loader.js.map +1 -1
  49. package/dist/core/extensions/plugins/trust.d.ts +21 -0
  50. package/dist/core/extensions/plugins/trust.d.ts.map +1 -1
  51. package/dist/core/extensions/plugins/trust.js +26 -0
  52. package/dist/core/extensions/plugins/trust.js.map +1 -1
  53. package/dist/core/tools/canvas.d.ts +55 -0
  54. package/dist/core/tools/canvas.d.ts.map +1 -0
  55. package/dist/core/tools/canvas.js +159 -0
  56. package/dist/core/tools/canvas.js.map +1 -0
  57. package/dist/extensions/core/canvas.d.ts +20 -0
  58. package/dist/extensions/core/canvas.d.ts.map +1 -0
  59. package/dist/extensions/core/canvas.js +192 -0
  60. package/dist/extensions/core/canvas.js.map +1 -0
  61. package/dist/extensions/core/hoo-core.d.ts +1 -0
  62. package/dist/extensions/core/hoo-core.d.ts.map +1 -1
  63. package/dist/extensions/core/hoo-core.js +3 -0
  64. package/dist/extensions/core/hoo-core.js.map +1 -1
  65. package/dist/utils/paths.d.ts +9 -0
  66. package/dist/utils/paths.d.ts.map +1 -1
  67. package/dist/utils/paths.js +16 -0
  68. package/dist/utils/paths.js.map +1 -1
  69. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  70. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  71. package/examples/extensions/sandbox/package.json +1 -1
  72. package/examples/extensions/with-deps/package.json +1 -1
  73. package/package.json +5 -4
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Child-side `@github/copilot-sdk/extension` implementation.
3
+ *
4
+ * Design: `docs/canvas-extensions-design.md` §4, §4.1. A canvas extension from
5
+ * `github/awesome-copilot` is a directory of plain `.mjs` files whose only
6
+ * external import is `@github/copilot-sdk/extension` — GitHub's own
7
+ * `docs/extensions.md` states the import "is resolved automatically — you don't
8
+ * install it", and none of the 23 catalog extensions ships `node_modules`. This
9
+ * module is what hoocode resolves that specifier to inside the forked child, so a
10
+ * catalog canvas runs byte-identical to upstream with nothing written into its
11
+ * directory.
12
+ *
13
+ * What this is not: the SDK's `joinSession` resolves to a full `CopilotSession`
14
+ * (messaging, events, RPC passthrough, factories). This shim implements the
15
+ * canvas surface and `log` only. `tools`, `hooks`, and `factories` are accepted
16
+ * and reported as unsupported so an extension that uses them fails visibly rather
17
+ * than half-working (design doc §6.2). The canvas types themselves are held
18
+ * structurally identical to the SDK's by
19
+ * `test/canvas-protocol-conformance.test.ts`.
20
+ */
21
+ import { type CanvasDeclaration, type CanvasJsonSchema, type CanvasLogLevel, type CanvasProviderCloseRequest, type CanvasProviderInvokeActionRequest, type CanvasProviderOpenRequest, type CanvasProviderOpenResult } from "../protocol.js";
22
+ /**
23
+ * A single agent-callable action contributed by a canvas. Mirrors the SDK's
24
+ * `CanvasAction`: the `handler` closure is stripped before the declaration is
25
+ * sent and dispatched in-process here.
26
+ */
27
+ export interface CanvasAction {
28
+ /** Action identifier, unique within the canvas. */
29
+ name: string;
30
+ /** Description shown to the model when picking an action. */
31
+ description?: string;
32
+ /** Optional JSON Schema for the action's `input` payload. */
33
+ inputSchema?: CanvasJsonSchema;
34
+ /** Required per-action dispatch handler. */
35
+ handler: (ctx: CanvasProviderInvokeActionRequest) => Promise<unknown> | unknown;
36
+ }
37
+ /** Options accepted by {@link createCanvas}. Mirrors the SDK's `CanvasOptions`. */
38
+ export interface CanvasOptions {
39
+ /** Canvas id, unique within the declaring connection. */
40
+ id: string;
41
+ /** Human-readable label shown in discovery and host UI chrome. */
42
+ displayName: string;
43
+ /** Short, single-sentence description shown to the agent in canvas catalogs. */
44
+ description: string;
45
+ /** Optional JSON Schema for the `input` payload accepted by `canvas.open`. */
46
+ inputSchema?: CanvasJsonSchema;
47
+ /** Agent-invocable actions, each carrying its own required handler. */
48
+ actions?: CanvasAction[];
49
+ /** Required. Open a new canvas instance. */
50
+ open: (ctx: CanvasProviderOpenRequest) => Promise<CanvasProviderOpenResult> | CanvasProviderOpenResult;
51
+ /**
52
+ * Optional. Notified when a canvas instance is closed. Fire-and-forget: the
53
+ * return value is ignored and errors are logged but not surfaced.
54
+ */
55
+ onClose?: (ctx: CanvasProviderCloseRequest) => Promise<void> | void;
56
+ }
57
+ /** Structured error returned from canvas handlers. Mirrors the SDK's `CanvasError`. */
58
+ export declare class CanvasError extends Error {
59
+ readonly code: string;
60
+ constructor(code: string, message: string);
61
+ /** Default error when an action is declared but no `handler` is wired. */
62
+ static noHandler(): CanvasError;
63
+ }
64
+ /** A registered canvas: declarative metadata plus in-process handler closures. */
65
+ export declare class Canvas {
66
+ readonly declaration: CanvasDeclaration;
67
+ readonly open: NonNullable<CanvasOptions["open"]>;
68
+ readonly onClose?: CanvasOptions["onClose"];
69
+ /** Handlers by action name, kept out of the declaration that crosses the wire. */
70
+ readonly handlers: ReadonlyMap<string, CanvasAction["handler"]>;
71
+ constructor(options: CanvasOptions);
72
+ }
73
+ /** Create a canvas declaration with bound in-process handlers. */
74
+ export declare function createCanvas(options: CanvasOptions): Canvas;
75
+ /** Configuration accepted by {@link joinSession}. */
76
+ export interface JoinSessionConfig {
77
+ canvases?: Canvas[];
78
+ /** Accepted and reported as unsupported (design doc §6.2). */
79
+ tools?: unknown;
80
+ /** Accepted and reported as unsupported (design doc §6.2). */
81
+ hooks?: unknown;
82
+ /** Accepted and reported as unsupported (design doc §6.2). */
83
+ factories?: unknown;
84
+ /** Accepted and reported as unsupported (design doc §6.2). */
85
+ onPermissionRequest?: unknown;
86
+ }
87
+ /**
88
+ * The subset of the SDK's `CopilotSession` this shim provides. Deliberately small:
89
+ * see the module header.
90
+ */
91
+ export interface CanvasShimSession {
92
+ /** Log a message to the session timeline. */
93
+ log(message: string, options?: {
94
+ level?: CanvasLogLevel;
95
+ ephemeral?: boolean;
96
+ }): Promise<void>;
97
+ }
98
+ /** Streams the shim reads from and writes to. Injectable so tests need no child process. */
99
+ export interface CanvasShimStreams {
100
+ input: {
101
+ setEncoding(encoding: "utf8"): unknown;
102
+ on(event: "data", listener: (chunk: string) => void): unknown;
103
+ };
104
+ write(chunk: string): unknown;
105
+ /** Provider identifier; the runner passes it in the environment. */
106
+ extensionId: string;
107
+ }
108
+ /**
109
+ * Join the host session: announce the declared canvases, then serve provider
110
+ * callbacks until the input stream ends.
111
+ */
112
+ export declare function joinSession(config?: JoinSessionConfig, streams?: CanvasShimStreams): Promise<CanvasShimSession>;
113
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/core/canvas/sdk-shim/index.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Child-side `@github/copilot-sdk/extension` implementation.\n *\n * Design: `docs/canvas-extensions-design.md` §4, §4.1. A canvas extension from\n * `github/awesome-copilot` is a directory of plain `.mjs` files whose only\n * external import is `@github/copilot-sdk/extension` — GitHub's own\n * `docs/extensions.md` states the import \"is resolved automatically — you don't\n * install it\", and none of the 23 catalog extensions ships `node_modules`. This\n * module is what hoocode resolves that specifier to inside the forked child, so a\n * catalog canvas runs byte-identical to upstream with nothing written into its\n * directory.\n *\n * What this is not: the SDK's `joinSession` resolves to a full `CopilotSession`\n * (messaging, events, RPC passthrough, factories). This shim implements the\n * canvas surface and `log` only. `tools`, `hooks`, and `factories` are accepted\n * and reported as unsupported so an extension that uses them fails visibly rather\n * than half-working (design doc §6.2). The canvas types themselves are held\n * structurally identical to the SDK's by\n * `test/canvas-protocol-conformance.test.ts`.\n */\n\nimport {\n\tCANVAS_ENVELOPE_VERSION,\n\tCANVAS_ERROR_CODE_INTERNAL,\n\tCANVAS_ERROR_CODE_UNKNOWN_TARGET,\n\tCANVAS_METHOD_CLOSE,\n\tCANVAS_METHOD_INVOKE_ACTION,\n\tCANVAS_METHOD_OPEN,\n\tCANVAS_RESERVED_ACTION_PREFIX,\n\tCANVAS_SDK_PROTOCOL_VERSION,\n\ttype CanvasActionDeclaration,\n\ttype CanvasActionInvokeResult,\n\ttype CanvasChildToHostMessage,\n\ttype CanvasDeclaration,\n\ttype CanvasJsonSchema,\n\ttype CanvasLogLevel,\n\tCanvasMessageDecoder,\n\ttype CanvasProviderCloseRequest,\n\ttype CanvasProviderInvokeActionRequest,\n\ttype CanvasProviderOpenRequest,\n\ttype CanvasProviderOpenResult,\n\tencodeCanvasMessage,\n\tisCanvasHostToChildMessage,\n\ttype JsonValue,\n} from \"../protocol.js\";\n\n/**\n * A single agent-callable action contributed by a canvas. Mirrors the SDK's\n * `CanvasAction`: the `handler` closure is stripped before the declaration is\n * sent and dispatched in-process here.\n */\nexport interface CanvasAction {\n\t/** Action identifier, unique within the canvas. */\n\tname: string;\n\t/** Description shown to the model when picking an action. */\n\tdescription?: string;\n\t/** Optional JSON Schema for the action's `input` payload. */\n\tinputSchema?: CanvasJsonSchema;\n\t/** Required per-action dispatch handler. */\n\thandler: (ctx: CanvasProviderInvokeActionRequest) => Promise<unknown> | unknown;\n}\n\n/** Options accepted by {@link createCanvas}. Mirrors the SDK's `CanvasOptions`. */\nexport interface CanvasOptions {\n\t/** Canvas id, unique within the declaring connection. */\n\tid: string;\n\t/** Human-readable label shown in discovery and host UI chrome. */\n\tdisplayName: string;\n\t/** Short, single-sentence description shown to the agent in canvas catalogs. */\n\tdescription: string;\n\t/** Optional JSON Schema for the `input` payload accepted by `canvas.open`. */\n\tinputSchema?: CanvasJsonSchema;\n\t/** Agent-invocable actions, each carrying its own required handler. */\n\tactions?: CanvasAction[];\n\t/** Required. Open a new canvas instance. */\n\topen: (ctx: CanvasProviderOpenRequest) => Promise<CanvasProviderOpenResult> | CanvasProviderOpenResult;\n\t/**\n\t * Optional. Notified when a canvas instance is closed. Fire-and-forget: the\n\t * return value is ignored and errors are logged but not surfaced.\n\t */\n\tonClose?: (ctx: CanvasProviderCloseRequest) => Promise<void> | void;\n}\n\n/** Structured error returned from canvas handlers. Mirrors the SDK's `CanvasError`. */\nexport class CanvasError extends Error {\n\treadonly code: string;\n\n\tconstructor(code: string, message: string) {\n\t\tsuper(message);\n\t\tthis.name = \"CanvasError\";\n\t\tthis.code = code;\n\t}\n\n\t/** Default error when an action is declared but no `handler` is wired. */\n\tstatic noHandler(): CanvasError {\n\t\treturn new CanvasError(\"no_handler\", \"Canvas action has no handler.\");\n\t}\n}\n\n/** A registered canvas: declarative metadata plus in-process handler closures. */\nexport class Canvas {\n\treadonly declaration: CanvasDeclaration;\n\treadonly open: NonNullable<CanvasOptions[\"open\"]>;\n\treadonly onClose?: CanvasOptions[\"onClose\"];\n\t/** Handlers by action name, kept out of the declaration that crosses the wire. */\n\treadonly handlers: ReadonlyMap<string, CanvasAction[\"handler\"]>;\n\n\tconstructor(options: CanvasOptions) {\n\t\tconst actions: CanvasActionDeclaration[] = [];\n\t\tconst handlers = new Map<string, CanvasAction[\"handler\"]>();\n\t\tfor (const action of options.actions ?? []) {\n\t\t\tif (action.name.startsWith(CANVAS_RESERVED_ACTION_PREFIX)) {\n\t\t\t\tthrow new CanvasError(\n\t\t\t\t\t\"invalid_action_name\",\n\t\t\t\t\t`Action \"${action.name}\" must not start with \"${CANVAS_RESERVED_ACTION_PREFIX}\" — that prefix is reserved for lifecycle verbs.`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (handlers.has(action.name)) {\n\t\t\t\tthrow new CanvasError(\"duplicate_action\", `Action \"${action.name}\" is declared more than once.`);\n\t\t\t}\n\t\t\tactions.push({ name: action.name, description: action.description, inputSchema: action.inputSchema });\n\t\t\thandlers.set(action.name, action.handler);\n\t\t}\n\t\tthis.declaration = {\n\t\t\tid: options.id,\n\t\t\tdisplayName: options.displayName,\n\t\t\tdescription: options.description,\n\t\t\tinputSchema: options.inputSchema,\n\t\t\tactions: actions.length > 0 ? actions : undefined,\n\t\t};\n\t\tthis.open = options.open;\n\t\tthis.onClose = options.onClose;\n\t\tthis.handlers = handlers;\n\t}\n}\n\n/** Create a canvas declaration with bound in-process handlers. */\nexport function createCanvas(options: CanvasOptions): Canvas {\n\treturn new Canvas(options);\n}\n\n/** Surfaces a Copilot extension may declare that this shim does not implement. */\nconst UNSUPPORTED_KEYS = [\"tools\", \"hooks\", \"factories\", \"onPermissionRequest\"] as const;\n\n/** Configuration accepted by {@link joinSession}. */\nexport interface JoinSessionConfig {\n\tcanvases?: Canvas[];\n\t/** Accepted and reported as unsupported (design doc §6.2). */\n\ttools?: unknown;\n\t/** Accepted and reported as unsupported (design doc §6.2). */\n\thooks?: unknown;\n\t/** Accepted and reported as unsupported (design doc §6.2). */\n\tfactories?: unknown;\n\t/** Accepted and reported as unsupported (design doc §6.2). */\n\tonPermissionRequest?: unknown;\n}\n\n/**\n * The subset of the SDK's `CopilotSession` this shim provides. Deliberately small:\n * see the module header.\n */\nexport interface CanvasShimSession {\n\t/** Log a message to the session timeline. */\n\tlog(message: string, options?: { level?: CanvasLogLevel; ephemeral?: boolean }): Promise<void>;\n}\n\n/** Streams the shim reads from and writes to. Injectable so tests need no child process. */\nexport interface CanvasShimStreams {\n\tinput: { setEncoding(encoding: \"utf8\"): unknown; on(event: \"data\", listener: (chunk: string) => void): unknown };\n\twrite(chunk: string): unknown;\n\t/** Provider identifier; the runner passes it in the environment. */\n\textensionId: string;\n}\n\nfunction defaultStreams(): CanvasShimStreams {\n\treturn {\n\t\tinput: process.stdin,\n\t\twrite: (chunk: string) => process.stdout.write(chunk),\n\t\textensionId: process.env.HOOCODE_CANVAS_EXTENSION_ID ?? \"unknown-extension\",\n\t};\n}\n\nfunction toJsonValue(value: unknown): JsonValue {\n\t// Handler return values are author-supplied. Round-tripping through JSON is what\n\t// the wire does anyway, so do it here where a cycle or a BigInt fails loudly with\n\t// the action's name attached rather than mid-write.\n\treturn value === undefined ? null : (JSON.parse(JSON.stringify(value)) as JsonValue);\n}\n\nfunction errorOf(cause: unknown): { code: string; message: string } {\n\tif (cause instanceof CanvasError) return { code: cause.code, message: cause.message };\n\treturn {\n\t\tcode: CANVAS_ERROR_CODE_INTERNAL,\n\t\tmessage: cause instanceof Error ? cause.message : String(cause),\n\t};\n}\n\n/**\n * Join the host session: announce the declared canvases, then serve provider\n * callbacks until the input stream ends.\n */\nexport async function joinSession(\n\tconfig: JoinSessionConfig = {},\n\tstreams: CanvasShimStreams = defaultStreams(),\n): Promise<CanvasShimSession> {\n\tconst canvases = new Map<string, Canvas>();\n\tfor (const canvas of config.canvases ?? []) {\n\t\tif (canvases.has(canvas.declaration.id)) {\n\t\t\tthrow new CanvasError(\"duplicate_canvas\", `Canvas \"${canvas.declaration.id}\" is declared more than once.`);\n\t\t}\n\t\tcanvases.set(canvas.declaration.id, canvas);\n\t}\n\n\tconst send = (message: CanvasChildToHostMessage): void => {\n\t\tstreams.write(encodeCanvasMessage(message));\n\t};\n\n\tsend({\n\t\tenvelope: CANVAS_ENVELOPE_VERSION,\n\t\ttype: \"ready\",\n\t\tprotocolVersion: CANVAS_SDK_PROTOCOL_VERSION,\n\t\textensionId: streams.extensionId,\n\t\tcanvases: [...canvases.values()].map((canvas) => canvas.declaration),\n\t\tunsupported: UNSUPPORTED_KEYS.filter((key) => config[key] !== undefined),\n\t});\n\n\tconst decoder = new CanvasMessageDecoder();\n\tstreams.input.setEncoding(\"utf8\");\n\tstreams.input.on(\"data\", (chunk: string) => {\n\t\tfor (const value of decoder.push(chunk).values) {\n\t\t\tif (!isCanvasHostToChildMessage(value)) continue;\n\t\t\tvoid dispatch(canvases, value.id, value.method, value.params, send);\n\t\t}\n\t});\n\n\treturn {\n\t\tlog: async (message, options) => {\n\t\t\tsend({\n\t\t\t\tenvelope: CANVAS_ENVELOPE_VERSION,\n\t\t\t\ttype: \"log\",\n\t\t\t\tmessage,\n\t\t\t\tlevel: options?.level,\n\t\t\t\tephemeral: options?.ephemeral,\n\t\t\t});\n\t\t},\n\t};\n}\n\nasync function dispatch(\n\tcanvases: ReadonlyMap<string, Canvas>,\n\tid: number,\n\tmethod: string,\n\tparams: JsonValue,\n\tsend: (message: CanvasChildToHostMessage) => void,\n): Promise<void> {\n\ttry {\n\t\tsend({\n\t\t\tenvelope: CANVAS_ENVELOPE_VERSION,\n\t\t\ttype: \"response\",\n\t\t\tid,\n\t\t\tresult: await invoke(canvases, method, params),\n\t\t});\n\t} catch (cause) {\n\t\tconst { code, message } = errorOf(cause);\n\t\tsend({ envelope: CANVAS_ENVELOPE_VERSION, type: \"error\", id, code, message });\n\t}\n}\n\nasync function invoke(canvases: ReadonlyMap<string, Canvas>, method: string, params: JsonValue): Promise<JsonValue> {\n\tif (method === CANVAS_METHOD_OPEN) {\n\t\tconst ctx = params as unknown as CanvasProviderOpenRequest;\n\t\treturn toJsonValue(await resolve(canvases, ctx.canvasId).open(ctx));\n\t}\n\tif (method === CANVAS_METHOD_CLOSE) {\n\t\tconst ctx = params as unknown as CanvasProviderCloseRequest;\n\t\t// Fire-and-forget per the SDK contract: a failing onClose must not fail the close.\n\t\tawait resolve(canvases, ctx.canvasId).onClose?.(ctx);\n\t\treturn null;\n\t}\n\tif (method === CANVAS_METHOD_INVOKE_ACTION) {\n\t\tconst ctx = params as unknown as CanvasProviderInvokeActionRequest;\n\t\tconst canvas = resolve(canvases, ctx.canvasId);\n\t\tconst handler = canvas.handlers.get(ctx.actionName);\n\t\tif (!handler) {\n\t\t\tthrow new CanvasError(\n\t\t\t\tCANVAS_ERROR_CODE_UNKNOWN_TARGET,\n\t\t\t\t`Canvas \"${ctx.canvasId}\" does not declare an action named \"${ctx.actionName}\".`,\n\t\t\t);\n\t\t}\n\t\tconst result: CanvasActionInvokeResult = toJsonValue(await handler(ctx));\n\t\treturn result;\n\t}\n\t// Unreachable while the host only sends methods `isCanvasHostToChildMessage`\n\t// accepts, but naming it keeps this function total if the protocol grows.\n\tthrow new CanvasError(CANVAS_ERROR_CODE_UNKNOWN_TARGET, `Unsupported provider method \"${method}\".`);\n}\n\nfunction resolve(canvases: ReadonlyMap<string, Canvas>, canvasId: string): Canvas {\n\tconst canvas = canvases.get(canvasId);\n\tif (!canvas) {\n\t\tthrow new CanvasError(CANVAS_ERROR_CODE_UNKNOWN_TARGET, `No canvas is registered with id \"${canvasId}\".`);\n\t}\n\treturn canvas;\n}\n"]}
@@ -0,0 +1,184 @@
1
+ /**
2
+ * Child-side `@github/copilot-sdk/extension` implementation.
3
+ *
4
+ * Design: `docs/canvas-extensions-design.md` §4, §4.1. A canvas extension from
5
+ * `github/awesome-copilot` is a directory of plain `.mjs` files whose only
6
+ * external import is `@github/copilot-sdk/extension` — GitHub's own
7
+ * `docs/extensions.md` states the import "is resolved automatically — you don't
8
+ * install it", and none of the 23 catalog extensions ships `node_modules`. This
9
+ * module is what hoocode resolves that specifier to inside the forked child, so a
10
+ * catalog canvas runs byte-identical to upstream with nothing written into its
11
+ * directory.
12
+ *
13
+ * What this is not: the SDK's `joinSession` resolves to a full `CopilotSession`
14
+ * (messaging, events, RPC passthrough, factories). This shim implements the
15
+ * canvas surface and `log` only. `tools`, `hooks`, and `factories` are accepted
16
+ * and reported as unsupported so an extension that uses them fails visibly rather
17
+ * than half-working (design doc §6.2). The canvas types themselves are held
18
+ * structurally identical to the SDK's by
19
+ * `test/canvas-protocol-conformance.test.ts`.
20
+ */
21
+ import { CANVAS_ENVELOPE_VERSION, CANVAS_ERROR_CODE_INTERNAL, CANVAS_ERROR_CODE_UNKNOWN_TARGET, CANVAS_METHOD_CLOSE, CANVAS_METHOD_INVOKE_ACTION, CANVAS_METHOD_OPEN, CANVAS_RESERVED_ACTION_PREFIX, CANVAS_SDK_PROTOCOL_VERSION, CanvasMessageDecoder, encodeCanvasMessage, isCanvasHostToChildMessage, } from "../protocol.js";
22
+ /** Structured error returned from canvas handlers. Mirrors the SDK's `CanvasError`. */
23
+ export class CanvasError extends Error {
24
+ code;
25
+ constructor(code, message) {
26
+ super(message);
27
+ this.name = "CanvasError";
28
+ this.code = code;
29
+ }
30
+ /** Default error when an action is declared but no `handler` is wired. */
31
+ static noHandler() {
32
+ return new CanvasError("no_handler", "Canvas action has no handler.");
33
+ }
34
+ }
35
+ /** A registered canvas: declarative metadata plus in-process handler closures. */
36
+ export class Canvas {
37
+ declaration;
38
+ open;
39
+ onClose;
40
+ /** Handlers by action name, kept out of the declaration that crosses the wire. */
41
+ handlers;
42
+ constructor(options) {
43
+ const actions = [];
44
+ const handlers = new Map();
45
+ for (const action of options.actions ?? []) {
46
+ if (action.name.startsWith(CANVAS_RESERVED_ACTION_PREFIX)) {
47
+ throw new CanvasError("invalid_action_name", `Action "${action.name}" must not start with "${CANVAS_RESERVED_ACTION_PREFIX}" — that prefix is reserved for lifecycle verbs.`);
48
+ }
49
+ if (handlers.has(action.name)) {
50
+ throw new CanvasError("duplicate_action", `Action "${action.name}" is declared more than once.`);
51
+ }
52
+ actions.push({ name: action.name, description: action.description, inputSchema: action.inputSchema });
53
+ handlers.set(action.name, action.handler);
54
+ }
55
+ this.declaration = {
56
+ id: options.id,
57
+ displayName: options.displayName,
58
+ description: options.description,
59
+ inputSchema: options.inputSchema,
60
+ actions: actions.length > 0 ? actions : undefined,
61
+ };
62
+ this.open = options.open;
63
+ this.onClose = options.onClose;
64
+ this.handlers = handlers;
65
+ }
66
+ }
67
+ /** Create a canvas declaration with bound in-process handlers. */
68
+ export function createCanvas(options) {
69
+ return new Canvas(options);
70
+ }
71
+ /** Surfaces a Copilot extension may declare that this shim does not implement. */
72
+ const UNSUPPORTED_KEYS = ["tools", "hooks", "factories", "onPermissionRequest"];
73
+ function defaultStreams() {
74
+ return {
75
+ input: process.stdin,
76
+ write: (chunk) => process.stdout.write(chunk),
77
+ extensionId: process.env.HOOCODE_CANVAS_EXTENSION_ID ?? "unknown-extension",
78
+ };
79
+ }
80
+ function toJsonValue(value) {
81
+ // Handler return values are author-supplied. Round-tripping through JSON is what
82
+ // the wire does anyway, so do it here where a cycle or a BigInt fails loudly with
83
+ // the action's name attached rather than mid-write.
84
+ return value === undefined ? null : JSON.parse(JSON.stringify(value));
85
+ }
86
+ function errorOf(cause) {
87
+ if (cause instanceof CanvasError)
88
+ return { code: cause.code, message: cause.message };
89
+ return {
90
+ code: CANVAS_ERROR_CODE_INTERNAL,
91
+ message: cause instanceof Error ? cause.message : String(cause),
92
+ };
93
+ }
94
+ /**
95
+ * Join the host session: announce the declared canvases, then serve provider
96
+ * callbacks until the input stream ends.
97
+ */
98
+ export async function joinSession(config = {}, streams = defaultStreams()) {
99
+ const canvases = new Map();
100
+ for (const canvas of config.canvases ?? []) {
101
+ if (canvases.has(canvas.declaration.id)) {
102
+ throw new CanvasError("duplicate_canvas", `Canvas "${canvas.declaration.id}" is declared more than once.`);
103
+ }
104
+ canvases.set(canvas.declaration.id, canvas);
105
+ }
106
+ const send = (message) => {
107
+ streams.write(encodeCanvasMessage(message));
108
+ };
109
+ send({
110
+ envelope: CANVAS_ENVELOPE_VERSION,
111
+ type: "ready",
112
+ protocolVersion: CANVAS_SDK_PROTOCOL_VERSION,
113
+ extensionId: streams.extensionId,
114
+ canvases: [...canvases.values()].map((canvas) => canvas.declaration),
115
+ unsupported: UNSUPPORTED_KEYS.filter((key) => config[key] !== undefined),
116
+ });
117
+ const decoder = new CanvasMessageDecoder();
118
+ streams.input.setEncoding("utf8");
119
+ streams.input.on("data", (chunk) => {
120
+ for (const value of decoder.push(chunk).values) {
121
+ if (!isCanvasHostToChildMessage(value))
122
+ continue;
123
+ void dispatch(canvases, value.id, value.method, value.params, send);
124
+ }
125
+ });
126
+ return {
127
+ log: async (message, options) => {
128
+ send({
129
+ envelope: CANVAS_ENVELOPE_VERSION,
130
+ type: "log",
131
+ message,
132
+ level: options?.level,
133
+ ephemeral: options?.ephemeral,
134
+ });
135
+ },
136
+ };
137
+ }
138
+ async function dispatch(canvases, id, method, params, send) {
139
+ try {
140
+ send({
141
+ envelope: CANVAS_ENVELOPE_VERSION,
142
+ type: "response",
143
+ id,
144
+ result: await invoke(canvases, method, params),
145
+ });
146
+ }
147
+ catch (cause) {
148
+ const { code, message } = errorOf(cause);
149
+ send({ envelope: CANVAS_ENVELOPE_VERSION, type: "error", id, code, message });
150
+ }
151
+ }
152
+ async function invoke(canvases, method, params) {
153
+ if (method === CANVAS_METHOD_OPEN) {
154
+ const ctx = params;
155
+ return toJsonValue(await resolve(canvases, ctx.canvasId).open(ctx));
156
+ }
157
+ if (method === CANVAS_METHOD_CLOSE) {
158
+ const ctx = params;
159
+ // Fire-and-forget per the SDK contract: a failing onClose must not fail the close.
160
+ await resolve(canvases, ctx.canvasId).onClose?.(ctx);
161
+ return null;
162
+ }
163
+ if (method === CANVAS_METHOD_INVOKE_ACTION) {
164
+ const ctx = params;
165
+ const canvas = resolve(canvases, ctx.canvasId);
166
+ const handler = canvas.handlers.get(ctx.actionName);
167
+ if (!handler) {
168
+ throw new CanvasError(CANVAS_ERROR_CODE_UNKNOWN_TARGET, `Canvas "${ctx.canvasId}" does not declare an action named "${ctx.actionName}".`);
169
+ }
170
+ const result = toJsonValue(await handler(ctx));
171
+ return result;
172
+ }
173
+ // Unreachable while the host only sends methods `isCanvasHostToChildMessage`
174
+ // accepts, but naming it keeps this function total if the protocol grows.
175
+ throw new CanvasError(CANVAS_ERROR_CODE_UNKNOWN_TARGET, `Unsupported provider method "${method}".`);
176
+ }
177
+ function resolve(canvases, canvasId) {
178
+ const canvas = canvases.get(canvasId);
179
+ if (!canvas) {
180
+ throw new CanvasError(CANVAS_ERROR_CODE_UNKNOWN_TARGET, `No canvas is registered with id "${canvasId}".`);
181
+ }
182
+ return canvas;
183
+ }
184
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/core/canvas/sdk-shim/index.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Child-side `@github/copilot-sdk/extension` implementation.\n *\n * Design: `docs/canvas-extensions-design.md` §4, §4.1. A canvas extension from\n * `github/awesome-copilot` is a directory of plain `.mjs` files whose only\n * external import is `@github/copilot-sdk/extension` — GitHub's own\n * `docs/extensions.md` states the import \"is resolved automatically — you don't\n * install it\", and none of the 23 catalog extensions ships `node_modules`. This\n * module is what hoocode resolves that specifier to inside the forked child, so a\n * catalog canvas runs byte-identical to upstream with nothing written into its\n * directory.\n *\n * What this is not: the SDK's `joinSession` resolves to a full `CopilotSession`\n * (messaging, events, RPC passthrough, factories). This shim implements the\n * canvas surface and `log` only. `tools`, `hooks`, and `factories` are accepted\n * and reported as unsupported so an extension that uses them fails visibly rather\n * than half-working (design doc §6.2). The canvas types themselves are held\n * structurally identical to the SDK's by\n * `test/canvas-protocol-conformance.test.ts`.\n */\n\nimport {\n\tCANVAS_ENVELOPE_VERSION,\n\tCANVAS_ERROR_CODE_INTERNAL,\n\tCANVAS_ERROR_CODE_UNKNOWN_TARGET,\n\tCANVAS_METHOD_CLOSE,\n\tCANVAS_METHOD_INVOKE_ACTION,\n\tCANVAS_METHOD_OPEN,\n\tCANVAS_RESERVED_ACTION_PREFIX,\n\tCANVAS_SDK_PROTOCOL_VERSION,\n\ttype CanvasActionDeclaration,\n\ttype CanvasActionInvokeResult,\n\ttype CanvasChildToHostMessage,\n\ttype CanvasDeclaration,\n\ttype CanvasJsonSchema,\n\ttype CanvasLogLevel,\n\tCanvasMessageDecoder,\n\ttype CanvasProviderCloseRequest,\n\ttype CanvasProviderInvokeActionRequest,\n\ttype CanvasProviderOpenRequest,\n\ttype CanvasProviderOpenResult,\n\tencodeCanvasMessage,\n\tisCanvasHostToChildMessage,\n\ttype JsonValue,\n} from \"../protocol.js\";\n\n/**\n * A single agent-callable action contributed by a canvas. Mirrors the SDK's\n * `CanvasAction`: the `handler` closure is stripped before the declaration is\n * sent and dispatched in-process here.\n */\nexport interface CanvasAction {\n\t/** Action identifier, unique within the canvas. */\n\tname: string;\n\t/** Description shown to the model when picking an action. */\n\tdescription?: string;\n\t/** Optional JSON Schema for the action's `input` payload. */\n\tinputSchema?: CanvasJsonSchema;\n\t/** Required per-action dispatch handler. */\n\thandler: (ctx: CanvasProviderInvokeActionRequest) => Promise<unknown> | unknown;\n}\n\n/** Options accepted by {@link createCanvas}. Mirrors the SDK's `CanvasOptions`. */\nexport interface CanvasOptions {\n\t/** Canvas id, unique within the declaring connection. */\n\tid: string;\n\t/** Human-readable label shown in discovery and host UI chrome. */\n\tdisplayName: string;\n\t/** Short, single-sentence description shown to the agent in canvas catalogs. */\n\tdescription: string;\n\t/** Optional JSON Schema for the `input` payload accepted by `canvas.open`. */\n\tinputSchema?: CanvasJsonSchema;\n\t/** Agent-invocable actions, each carrying its own required handler. */\n\tactions?: CanvasAction[];\n\t/** Required. Open a new canvas instance. */\n\topen: (ctx: CanvasProviderOpenRequest) => Promise<CanvasProviderOpenResult> | CanvasProviderOpenResult;\n\t/**\n\t * Optional. Notified when a canvas instance is closed. Fire-and-forget: the\n\t * return value is ignored and errors are logged but not surfaced.\n\t */\n\tonClose?: (ctx: CanvasProviderCloseRequest) => Promise<void> | void;\n}\n\n/** Structured error returned from canvas handlers. Mirrors the SDK's `CanvasError`. */\nexport class CanvasError extends Error {\n\treadonly code: string;\n\n\tconstructor(code: string, message: string) {\n\t\tsuper(message);\n\t\tthis.name = \"CanvasError\";\n\t\tthis.code = code;\n\t}\n\n\t/** Default error when an action is declared but no `handler` is wired. */\n\tstatic noHandler(): CanvasError {\n\t\treturn new CanvasError(\"no_handler\", \"Canvas action has no handler.\");\n\t}\n}\n\n/** A registered canvas: declarative metadata plus in-process handler closures. */\nexport class Canvas {\n\treadonly declaration: CanvasDeclaration;\n\treadonly open: NonNullable<CanvasOptions[\"open\"]>;\n\treadonly onClose?: CanvasOptions[\"onClose\"];\n\t/** Handlers by action name, kept out of the declaration that crosses the wire. */\n\treadonly handlers: ReadonlyMap<string, CanvasAction[\"handler\"]>;\n\n\tconstructor(options: CanvasOptions) {\n\t\tconst actions: CanvasActionDeclaration[] = [];\n\t\tconst handlers = new Map<string, CanvasAction[\"handler\"]>();\n\t\tfor (const action of options.actions ?? []) {\n\t\t\tif (action.name.startsWith(CANVAS_RESERVED_ACTION_PREFIX)) {\n\t\t\t\tthrow new CanvasError(\n\t\t\t\t\t\"invalid_action_name\",\n\t\t\t\t\t`Action \"${action.name}\" must not start with \"${CANVAS_RESERVED_ACTION_PREFIX}\" — that prefix is reserved for lifecycle verbs.`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (handlers.has(action.name)) {\n\t\t\t\tthrow new CanvasError(\"duplicate_action\", `Action \"${action.name}\" is declared more than once.`);\n\t\t\t}\n\t\t\tactions.push({ name: action.name, description: action.description, inputSchema: action.inputSchema });\n\t\t\thandlers.set(action.name, action.handler);\n\t\t}\n\t\tthis.declaration = {\n\t\t\tid: options.id,\n\t\t\tdisplayName: options.displayName,\n\t\t\tdescription: options.description,\n\t\t\tinputSchema: options.inputSchema,\n\t\t\tactions: actions.length > 0 ? actions : undefined,\n\t\t};\n\t\tthis.open = options.open;\n\t\tthis.onClose = options.onClose;\n\t\tthis.handlers = handlers;\n\t}\n}\n\n/** Create a canvas declaration with bound in-process handlers. */\nexport function createCanvas(options: CanvasOptions): Canvas {\n\treturn new Canvas(options);\n}\n\n/** Surfaces a Copilot extension may declare that this shim does not implement. */\nconst UNSUPPORTED_KEYS = [\"tools\", \"hooks\", \"factories\", \"onPermissionRequest\"] as const;\n\n/** Configuration accepted by {@link joinSession}. */\nexport interface JoinSessionConfig {\n\tcanvases?: Canvas[];\n\t/** Accepted and reported as unsupported (design doc §6.2). */\n\ttools?: unknown;\n\t/** Accepted and reported as unsupported (design doc §6.2). */\n\thooks?: unknown;\n\t/** Accepted and reported as unsupported (design doc §6.2). */\n\tfactories?: unknown;\n\t/** Accepted and reported as unsupported (design doc §6.2). */\n\tonPermissionRequest?: unknown;\n}\n\n/**\n * The subset of the SDK's `CopilotSession` this shim provides. Deliberately small:\n * see the module header.\n */\nexport interface CanvasShimSession {\n\t/** Log a message to the session timeline. */\n\tlog(message: string, options?: { level?: CanvasLogLevel; ephemeral?: boolean }): Promise<void>;\n}\n\n/** Streams the shim reads from and writes to. Injectable so tests need no child process. */\nexport interface CanvasShimStreams {\n\tinput: { setEncoding(encoding: \"utf8\"): unknown; on(event: \"data\", listener: (chunk: string) => void): unknown };\n\twrite(chunk: string): unknown;\n\t/** Provider identifier; the runner passes it in the environment. */\n\textensionId: string;\n}\n\nfunction defaultStreams(): CanvasShimStreams {\n\treturn {\n\t\tinput: process.stdin,\n\t\twrite: (chunk: string) => process.stdout.write(chunk),\n\t\textensionId: process.env.HOOCODE_CANVAS_EXTENSION_ID ?? \"unknown-extension\",\n\t};\n}\n\nfunction toJsonValue(value: unknown): JsonValue {\n\t// Handler return values are author-supplied. Round-tripping through JSON is what\n\t// the wire does anyway, so do it here where a cycle or a BigInt fails loudly with\n\t// the action's name attached rather than mid-write.\n\treturn value === undefined ? null : (JSON.parse(JSON.stringify(value)) as JsonValue);\n}\n\nfunction errorOf(cause: unknown): { code: string; message: string } {\n\tif (cause instanceof CanvasError) return { code: cause.code, message: cause.message };\n\treturn {\n\t\tcode: CANVAS_ERROR_CODE_INTERNAL,\n\t\tmessage: cause instanceof Error ? cause.message : String(cause),\n\t};\n}\n\n/**\n * Join the host session: announce the declared canvases, then serve provider\n * callbacks until the input stream ends.\n */\nexport async function joinSession(\n\tconfig: JoinSessionConfig = {},\n\tstreams: CanvasShimStreams = defaultStreams(),\n): Promise<CanvasShimSession> {\n\tconst canvases = new Map<string, Canvas>();\n\tfor (const canvas of config.canvases ?? []) {\n\t\tif (canvases.has(canvas.declaration.id)) {\n\t\t\tthrow new CanvasError(\"duplicate_canvas\", `Canvas \"${canvas.declaration.id}\" is declared more than once.`);\n\t\t}\n\t\tcanvases.set(canvas.declaration.id, canvas);\n\t}\n\n\tconst send = (message: CanvasChildToHostMessage): void => {\n\t\tstreams.write(encodeCanvasMessage(message));\n\t};\n\n\tsend({\n\t\tenvelope: CANVAS_ENVELOPE_VERSION,\n\t\ttype: \"ready\",\n\t\tprotocolVersion: CANVAS_SDK_PROTOCOL_VERSION,\n\t\textensionId: streams.extensionId,\n\t\tcanvases: [...canvases.values()].map((canvas) => canvas.declaration),\n\t\tunsupported: UNSUPPORTED_KEYS.filter((key) => config[key] !== undefined),\n\t});\n\n\tconst decoder = new CanvasMessageDecoder();\n\tstreams.input.setEncoding(\"utf8\");\n\tstreams.input.on(\"data\", (chunk: string) => {\n\t\tfor (const value of decoder.push(chunk).values) {\n\t\t\tif (!isCanvasHostToChildMessage(value)) continue;\n\t\t\tvoid dispatch(canvases, value.id, value.method, value.params, send);\n\t\t}\n\t});\n\n\treturn {\n\t\tlog: async (message, options) => {\n\t\t\tsend({\n\t\t\t\tenvelope: CANVAS_ENVELOPE_VERSION,\n\t\t\t\ttype: \"log\",\n\t\t\t\tmessage,\n\t\t\t\tlevel: options?.level,\n\t\t\t\tephemeral: options?.ephemeral,\n\t\t\t});\n\t\t},\n\t};\n}\n\nasync function dispatch(\n\tcanvases: ReadonlyMap<string, Canvas>,\n\tid: number,\n\tmethod: string,\n\tparams: JsonValue,\n\tsend: (message: CanvasChildToHostMessage) => void,\n): Promise<void> {\n\ttry {\n\t\tsend({\n\t\t\tenvelope: CANVAS_ENVELOPE_VERSION,\n\t\t\ttype: \"response\",\n\t\t\tid,\n\t\t\tresult: await invoke(canvases, method, params),\n\t\t});\n\t} catch (cause) {\n\t\tconst { code, message } = errorOf(cause);\n\t\tsend({ envelope: CANVAS_ENVELOPE_VERSION, type: \"error\", id, code, message });\n\t}\n}\n\nasync function invoke(canvases: ReadonlyMap<string, Canvas>, method: string, params: JsonValue): Promise<JsonValue> {\n\tif (method === CANVAS_METHOD_OPEN) {\n\t\tconst ctx = params as unknown as CanvasProviderOpenRequest;\n\t\treturn toJsonValue(await resolve(canvases, ctx.canvasId).open(ctx));\n\t}\n\tif (method === CANVAS_METHOD_CLOSE) {\n\t\tconst ctx = params as unknown as CanvasProviderCloseRequest;\n\t\t// Fire-and-forget per the SDK contract: a failing onClose must not fail the close.\n\t\tawait resolve(canvases, ctx.canvasId).onClose?.(ctx);\n\t\treturn null;\n\t}\n\tif (method === CANVAS_METHOD_INVOKE_ACTION) {\n\t\tconst ctx = params as unknown as CanvasProviderInvokeActionRequest;\n\t\tconst canvas = resolve(canvases, ctx.canvasId);\n\t\tconst handler = canvas.handlers.get(ctx.actionName);\n\t\tif (!handler) {\n\t\t\tthrow new CanvasError(\n\t\t\t\tCANVAS_ERROR_CODE_UNKNOWN_TARGET,\n\t\t\t\t`Canvas \"${ctx.canvasId}\" does not declare an action named \"${ctx.actionName}\".`,\n\t\t\t);\n\t\t}\n\t\tconst result: CanvasActionInvokeResult = toJsonValue(await handler(ctx));\n\t\treturn result;\n\t}\n\t// Unreachable while the host only sends methods `isCanvasHostToChildMessage`\n\t// accepts, but naming it keeps this function total if the protocol grows.\n\tthrow new CanvasError(CANVAS_ERROR_CODE_UNKNOWN_TARGET, `Unsupported provider method \"${method}\".`);\n}\n\nfunction resolve(canvases: ReadonlyMap<string, Canvas>, canvasId: string): Canvas {\n\tconst canvas = canvases.get(canvasId);\n\tif (!canvas) {\n\t\tthrow new CanvasError(CANVAS_ERROR_CODE_UNKNOWN_TARGET, `No canvas is registered with id \"${canvasId}\".`);\n\t}\n\treturn canvas;\n}\n"]}
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Session-scoped canvas facade: everything the TUI needs, with no TUI in it.
3
+ *
4
+ * Design: `docs/canvas-extensions-design.md` §11. The pieces underneath — discovery,
5
+ * the trust gate, availability, the registry — are each small and separately tested.
6
+ * This is what stitches them into the four questions a user surface actually asks:
7
+ * what is there, can it run, open this one, close that one.
8
+ *
9
+ * It holds no TUI types on purpose. `extensions/core/canvas.ts` renders and supplies
10
+ * an `AbortSignal` from a cancellable loader; everything decided here stays testable
11
+ * without a terminal.
12
+ *
13
+ * Availability is resolved once and cached, because resolving can spawn
14
+ * `node --version` (§11.1) and the answer cannot change within a session.
15
+ */
16
+ import type { DiscoveredCanvasExtension } from "./discovery.js";
17
+ import { type CanvasSearchRoot } from "./discovery.js";
18
+ import { type CanvasAvailability } from "./launch.js";
19
+ import { type CanvasInstance, CanvasRegistry, type CanvasRegistryEvents } from "./registry.js";
20
+ import type { CanvasCallOptions } from "./runner.js";
21
+ /** One canvas a person could open, or has open. */
22
+ export interface CanvasListing {
23
+ extensionId: string;
24
+ /** Undefined until the extension has been forked, since declarations come from it. */
25
+ canvasId: string | undefined;
26
+ displayName: string | undefined;
27
+ scope: DiscoveredCanvasExtension["scope"];
28
+ /** Why it cannot be opened, if it cannot. */
29
+ withheld: "untrusted-workspace" | undefined;
30
+ /** Instances of this canvas that are currently open. */
31
+ open: CanvasInstance[];
32
+ }
33
+ /** What `list()` reports. */
34
+ export interface CanvasOverview {
35
+ /** Absent `reason` means canvases can run here. */
36
+ availability: CanvasAvailability;
37
+ listings: CanvasListing[];
38
+ /** Extensions withheld by the trust gate — surfaced, never hidden (§5.1). */
39
+ withheldCount: number;
40
+ }
41
+ /** Configuration for a session's canvas facade. */
42
+ export interface CanvasSessionOptions extends CanvasRegistryEvents {
43
+ cwd: string;
44
+ homeDir: string;
45
+ agentDir?: string;
46
+ /** Override the search roots; defaults to {@link canvasSearchRoots}. */
47
+ roots?: CanvasSearchRoot[];
48
+ /** Override availability resolution, for tests and for hosts that already know. */
49
+ resolveRuntime?: () => Promise<CanvasAvailability>;
50
+ }
51
+ /** Reference to a canvas: an extension id, optionally narrowed to one of its canvases. */
52
+ export interface CanvasRef {
53
+ extensionId: string;
54
+ canvasId?: string;
55
+ }
56
+ /**
57
+ * Parse `extension` or `extension:canvas`.
58
+ *
59
+ * Extension ids are directory names and canvas ids are provider-local, so a single
60
+ * colon is unambiguous and needs no quoting.
61
+ */
62
+ export declare function parseCanvasRef(input: string): CanvasRef | undefined;
63
+ export declare class CanvasSession {
64
+ private readonly options;
65
+ private readonly roots;
66
+ /** Cached because resolving can spawn `node --version` and cannot change mid-session. */
67
+ private availabilityPromise;
68
+ /** Created on first successful open, not at construction: listing must not fork. */
69
+ private registry;
70
+ constructor(options: CanvasSessionOptions);
71
+ /** Discovered extensions, partitioned by the trust gate. Read-only and always safe. */
72
+ discover(): {
73
+ runnable: DiscoveredCanvasExtension[];
74
+ withheld: DiscoveredCanvasExtension[];
75
+ };
76
+ /** Whether canvases can run here. Resolved once per session and cached. */
77
+ availability(): Promise<CanvasAvailability>;
78
+ /**
79
+ * What is installed, what is open, and what is being withheld.
80
+ *
81
+ * Deliberately does not fork anything: listing must stay free and safe, so a
82
+ * `canvasId` is only known for extensions already running. That is the visible
83
+ * consequence of a canvas having no passive half (§5.1) — even its name comes from
84
+ * running its code.
85
+ */
86
+ list(): Promise<CanvasOverview>;
87
+ /**
88
+ * Open a canvas.
89
+ *
90
+ * `options.signal` comes from the caller's cancellable loader, so a person's Esc
91
+ * reaches the registry's abandon path (§11.6) rather than merely hiding a spinner.
92
+ */
93
+ open(ref: CanvasRef, options?: CanvasCallOptions): Promise<CanvasInstance>;
94
+ /** Close one open instance. Unknown ids are a no-op, so closing twice is harmless. */
95
+ close(instanceId: string): Promise<CanvasInstance | undefined>;
96
+ /** Every open instance. */
97
+ instances(): CanvasInstance[];
98
+ /**
99
+ * The live registry, or undefined if nothing has been opened yet.
100
+ *
101
+ * Exposed so the host can hand it to the canvas tools, which read
102
+ * `listInstances()` and `activeActions()` from it.
103
+ */
104
+ registryOrUndefined(): CanvasRegistry | undefined;
105
+ /** Advisory cleanup, driven by whoever owns the session clock. */
106
+ reapIdle(): Promise<string[]>;
107
+ /** Close everything and stop every child. Safe to call twice. */
108
+ dispose(): Promise<void>;
109
+ private ensureRegistry;
110
+ private soleCanvasId;
111
+ }
112
+ //# sourceMappingURL=session.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../../src/core/canvas/session.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Session-scoped canvas facade: everything the TUI needs, with no TUI in it.\n *\n * Design: `docs/canvas-extensions-design.md` §11. The pieces underneath — discovery,\n * the trust gate, availability, the registry — are each small and separately tested.\n * This is what stitches them into the four questions a user surface actually asks:\n * what is there, can it run, open this one, close that one.\n *\n * It holds no TUI types on purpose. `extensions/core/canvas.ts` renders and supplies\n * an `AbortSignal` from a cancellable loader; everything decided here stays testable\n * without a terminal.\n *\n * Availability is resolved once and cached, because resolving can spawn\n * `node --version` (§11.1) and the answer cannot change within a session.\n */\n\nimport { getAgentDir } from \"../../config.js\";\nimport type { DiscoveredCanvasExtension } from \"./discovery.js\";\nimport { type CanvasSearchRoot, canvasSearchRoots, discoverCanvasExtensions } from \"./discovery.js\";\nimport { type CanvasAvailability, resolveCanvasRuntime } from \"./launch.js\";\nimport { type CanvasInstance, CanvasRegistry, type CanvasRegistryEvents } from \"./registry.js\";\nimport type { CanvasCallOptions } from \"./runner.js\";\nimport { gateCanvasExtensions } from \"./trust.js\";\n\n/** One canvas a person could open, or has open. */\nexport interface CanvasListing {\n\textensionId: string;\n\t/** Undefined until the extension has been forked, since declarations come from it. */\n\tcanvasId: string | undefined;\n\tdisplayName: string | undefined;\n\tscope: DiscoveredCanvasExtension[\"scope\"];\n\t/** Why it cannot be opened, if it cannot. */\n\twithheld: \"untrusted-workspace\" | undefined;\n\t/** Instances of this canvas that are currently open. */\n\topen: CanvasInstance[];\n}\n\n/** What `list()` reports. */\nexport interface CanvasOverview {\n\t/** Absent `reason` means canvases can run here. */\n\tavailability: CanvasAvailability;\n\tlistings: CanvasListing[];\n\t/** Extensions withheld by the trust gate — surfaced, never hidden (§5.1). */\n\twithheldCount: number;\n}\n\n/** Configuration for a session's canvas facade. */\nexport interface CanvasSessionOptions extends CanvasRegistryEvents {\n\tcwd: string;\n\thomeDir: string;\n\tagentDir?: string;\n\t/** Override the search roots; defaults to {@link canvasSearchRoots}. */\n\troots?: CanvasSearchRoot[];\n\t/** Override availability resolution, for tests and for hosts that already know. */\n\tresolveRuntime?: () => Promise<CanvasAvailability>;\n}\n\n/** Reference to a canvas: an extension id, optionally narrowed to one of its canvases. */\nexport interface CanvasRef {\n\textensionId: string;\n\tcanvasId?: string;\n}\n\n/**\n * Parse `extension` or `extension:canvas`.\n *\n * Extension ids are directory names and canvas ids are provider-local, so a single\n * colon is unambiguous and needs no quoting.\n */\nexport function parseCanvasRef(input: string): CanvasRef | undefined {\n\tconst trimmed = input.trim();\n\tif (trimmed.length === 0) return undefined;\n\tconst colon = trimmed.indexOf(\":\");\n\tif (colon === -1) return { extensionId: trimmed };\n\tconst extensionId = trimmed.slice(0, colon).trim();\n\tconst canvasId = trimmed.slice(colon + 1).trim();\n\tif (extensionId.length === 0 || canvasId.length === 0) return undefined;\n\treturn { extensionId, canvasId };\n}\n\nexport class CanvasSession {\n\tprivate readonly options: CanvasSessionOptions;\n\tprivate readonly roots: CanvasSearchRoot[];\n\t/** Cached because resolving can spawn `node --version` and cannot change mid-session. */\n\tprivate availabilityPromise: Promise<CanvasAvailability> | undefined;\n\t/** Created on first successful open, not at construction: listing must not fork. */\n\tprivate registry: CanvasRegistry | undefined;\n\n\tconstructor(options: CanvasSessionOptions) {\n\t\tthis.options = options;\n\t\tthis.roots = options.roots ?? canvasSearchRoots(options.cwd, options.homeDir);\n\t}\n\n\t/** Discovered extensions, partitioned by the trust gate. Read-only and always safe. */\n\tdiscover(): { runnable: DiscoveredCanvasExtension[]; withheld: DiscoveredCanvasExtension[] } {\n\t\tconst gated = gateCanvasExtensions(\n\t\t\tdiscoverCanvasExtensions(this.roots),\n\t\t\tthis.options.cwd,\n\t\t\tthis.options.agentDir ?? getAgentDir(),\n\t\t);\n\t\treturn { runnable: gated.runnable, withheld: gated.withheld.map((entry) => entry.extension) };\n\t}\n\n\t/** Whether canvases can run here. Resolved once per session and cached. */\n\tasync availability(): Promise<CanvasAvailability> {\n\t\tthis.availabilityPromise ??= (this.options.resolveRuntime ?? resolveCanvasRuntime)();\n\t\treturn this.availabilityPromise;\n\t}\n\n\t/**\n\t * What is installed, what is open, and what is being withheld.\n\t *\n\t * Deliberately does not fork anything: listing must stay free and safe, so a\n\t * `canvasId` is only known for extensions already running. That is the visible\n\t * consequence of a canvas having no passive half (§5.1) — even its name comes from\n\t * running its code.\n\t */\n\tasync list(): Promise<CanvasOverview> {\n\t\tconst availability = await this.availability();\n\t\tconst { runnable, withheld } = this.discover();\n\t\tconst open = this.registryOrUndefined()?.listInstances() ?? [];\n\n\t\tconst listings: CanvasListing[] = [];\n\t\tfor (const extension of runnable) {\n\t\t\tconst instances = open.filter((instance) => instance.extensionId === extension.id);\n\t\t\tif (instances.length === 0) {\n\t\t\t\tlistings.push({\n\t\t\t\t\textensionId: extension.id,\n\t\t\t\t\tcanvasId: undefined,\n\t\t\t\t\tdisplayName: undefined,\n\t\t\t\t\tscope: extension.scope,\n\t\t\t\t\twithheld: undefined,\n\t\t\t\t\topen: [],\n\t\t\t\t});\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tfor (const canvasId of new Set(instances.map((instance) => instance.canvasId))) {\n\t\t\t\tconst forCanvas = instances.filter((instance) => instance.canvasId === canvasId);\n\t\t\t\tlistings.push({\n\t\t\t\t\textensionId: extension.id,\n\t\t\t\t\tcanvasId,\n\t\t\t\t\tdisplayName: forCanvas[0]?.title,\n\t\t\t\t\tscope: extension.scope,\n\t\t\t\t\twithheld: undefined,\n\t\t\t\t\topen: forCanvas,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t\tfor (const extension of withheld) {\n\t\t\tlistings.push({\n\t\t\t\textensionId: extension.id,\n\t\t\t\tcanvasId: undefined,\n\t\t\t\tdisplayName: undefined,\n\t\t\t\tscope: extension.scope,\n\t\t\t\twithheld: \"untrusted-workspace\",\n\t\t\t\topen: [],\n\t\t\t});\n\t\t}\n\t\treturn { availability, listings, withheldCount: withheld.length };\n\t}\n\n\t/**\n\t * Open a canvas.\n\t *\n\t * `options.signal` comes from the caller's cancellable loader, so a person's Esc\n\t * reaches the registry's abandon path (§11.6) rather than merely hiding a spinner.\n\t */\n\tasync open(ref: CanvasRef, options?: CanvasCallOptions): Promise<CanvasInstance> {\n\t\tconst availability = await this.availability();\n\t\tif (!availability.available) throw new Error(availability.reason);\n\n\t\tconst { runnable, withheld } = this.discover();\n\t\tconst extension = runnable.find((candidate) => candidate.id === ref.extensionId);\n\t\tif (!extension) {\n\t\t\tif (withheld.some((candidate) => candidate.id === ref.extensionId)) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Canvas extension \"${ref.extensionId}\" came with this repository, which is not a trusted workspace. ` +\n\t\t\t\t\t\t\"Run /plugin trust to allow this directory to run code it ships.\",\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst known = runnable.map((candidate) => candidate.id).join(\", \") || \"none\";\n\t\t\tthrow new Error(`No canvas extension \"${ref.extensionId}\" (found: ${known}).`);\n\t\t}\n\n\t\tconst registry = this.ensureRegistry(availability);\n\t\tconst canvasId = ref.canvasId ?? (await this.soleCanvasId(registry, extension));\n\t\treturn registry.open(extension, canvasId, undefined, options);\n\t}\n\n\t/** Close one open instance. Unknown ids are a no-op, so closing twice is harmless. */\n\tasync close(instanceId: string): Promise<CanvasInstance | undefined> {\n\t\tconst registry = this.registryOrUndefined();\n\t\tconst instance = registry?.listInstances().find((open) => open.instanceId === instanceId);\n\t\tif (!registry || !instance) return undefined;\n\t\tawait registry.close(instance);\n\t\treturn instance;\n\t}\n\n\t/** Every open instance. */\n\tinstances(): CanvasInstance[] {\n\t\treturn this.registryOrUndefined()?.listInstances() ?? [];\n\t}\n\n\t/**\n\t * The live registry, or undefined if nothing has been opened yet.\n\t *\n\t * Exposed so the host can hand it to the canvas tools, which read\n\t * `listInstances()` and `activeActions()` from it.\n\t */\n\tregistryOrUndefined(): CanvasRegistry | undefined {\n\t\treturn this.registry;\n\t}\n\n\t/** Advisory cleanup, driven by whoever owns the session clock. */\n\tasync reapIdle(): Promise<string[]> {\n\t\treturn (await this.registryOrUndefined()?.reapIdle()) ?? [];\n\t}\n\n\t/** Close everything and stop every child. Safe to call twice. */\n\tasync dispose(): Promise<void> {\n\t\tawait this.registry?.shutdown();\n\t\tthis.registry = undefined;\n\t}\n\n\tprivate ensureRegistry(availability: Extract<CanvasAvailability, { available: true }>): CanvasRegistry {\n\t\tif (!this.registry) {\n\t\t\tthis.registry = new CanvasRegistry({\n\t\t\t\truntime: availability.runtime,\n\t\t\t\tcwd: this.options.cwd,\n\t\t\t\tagentDir: this.options.agentDir,\n\t\t\t\tonLog: this.options.onLog,\n\t\t\t\tonStray: this.options.onStray,\n\t\t\t\tonStderr: this.options.onStderr,\n\t\t\t\tonDiagnostic: this.options.onDiagnostic,\n\t\t\t});\n\t\t}\n\t\treturn this.registry;\n\t}\n\n\t/**\n\t * Pick the canvas when the caller named only an extension.\n\t *\n\t * Forking to read the declarations is unavoidable: they arrive in the child's\n\t * `ready` message. A multi-canvas extension must be named explicitly rather than\n\t * guessed at.\n\t */\n\tprivate async soleCanvasId(registry: CanvasRegistry, extension: DiscoveredCanvasExtension): Promise<string> {\n\t\tconst declarations = await registry.declarations(extension);\n\t\tif (declarations.length === 1) return declarations[0]?.id as string;\n\t\tif (declarations.length === 0) throw new Error(`Canvas extension \"${extension.id}\" declares no canvases.`);\n\t\tconst ids = declarations.map((declaration) => `${extension.id}:${declaration.id}`).join(\", \");\n\t\tthrow new Error(`Canvas extension \"${extension.id}\" declares several canvases; name one of: ${ids}.`);\n\t}\n}\n"]}