@clinebot/core 0.0.11 → 0.0.13

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 (68) hide show
  1. package/README.md +1 -1
  2. package/dist/agents/agent-config-loader.d.ts +1 -1
  3. package/dist/agents/agent-config-parser.d.ts +5 -2
  4. package/dist/agents/index.d.ts +1 -1
  5. package/dist/agents/plugin-config-loader.d.ts +4 -0
  6. package/dist/agents/plugin-loader.d.ts +1 -0
  7. package/dist/agents/plugin-sandbox-bootstrap.js +446 -0
  8. package/dist/agents/plugin-sandbox.d.ts +4 -0
  9. package/dist/index.node.d.ts +5 -0
  10. package/dist/index.node.js +685 -413
  11. package/dist/runtime/commands.d.ts +11 -0
  12. package/dist/runtime/sandbox/subprocess-sandbox.d.ts +8 -1
  13. package/dist/runtime/skills.d.ts +13 -0
  14. package/dist/session/default-session-manager.d.ts +5 -0
  15. package/dist/session/session-config-builder.d.ts +4 -1
  16. package/dist/session/session-manager.d.ts +1 -0
  17. package/dist/session/session-service.d.ts +22 -22
  18. package/dist/session/unified-session-persistence-service.d.ts +12 -6
  19. package/dist/session/utils/helpers.d.ts +2 -2
  20. package/dist/session/utils/types.d.ts +9 -0
  21. package/dist/tools/definitions.d.ts +2 -2
  22. package/dist/tools/presets.d.ts +3 -3
  23. package/dist/tools/schemas.d.ts +15 -14
  24. package/dist/types/config.d.ts +5 -0
  25. package/dist/types/events.d.ts +22 -0
  26. package/package.json +5 -4
  27. package/src/agents/agent-config-loader.test.ts +2 -0
  28. package/src/agents/agent-config-loader.ts +1 -0
  29. package/src/agents/agent-config-parser.ts +12 -5
  30. package/src/agents/index.ts +1 -0
  31. package/src/agents/plugin-config-loader.test.ts +49 -0
  32. package/src/agents/plugin-config-loader.ts +10 -73
  33. package/src/agents/plugin-loader.test.ts +127 -1
  34. package/src/agents/plugin-loader.ts +72 -5
  35. package/src/agents/plugin-sandbox-bootstrap.ts +445 -0
  36. package/src/agents/plugin-sandbox.test.ts +198 -1
  37. package/src/agents/plugin-sandbox.ts +223 -353
  38. package/src/index.node.ts +14 -0
  39. package/src/runtime/commands.test.ts +98 -0
  40. package/src/runtime/commands.ts +83 -0
  41. package/src/runtime/hook-file-hooks.test.ts +1 -1
  42. package/src/runtime/hook-file-hooks.ts +16 -6
  43. package/src/runtime/index.ts +10 -0
  44. package/src/runtime/runtime-builder.test.ts +67 -0
  45. package/src/runtime/runtime-builder.ts +70 -16
  46. package/src/runtime/sandbox/subprocess-sandbox.ts +35 -11
  47. package/src/runtime/skills.ts +44 -0
  48. package/src/runtime/workflows.ts +20 -29
  49. package/src/session/default-session-manager.e2e.test.ts +52 -33
  50. package/src/session/default-session-manager.test.ts +453 -1
  51. package/src/session/default-session-manager.ts +210 -12
  52. package/src/session/rpc-session-service.ts +14 -96
  53. package/src/session/session-config-builder.ts +2 -0
  54. package/src/session/session-manager.ts +1 -0
  55. package/src/session/session-service.ts +127 -64
  56. package/src/session/session-team-coordination.ts +30 -0
  57. package/src/session/unified-session-persistence-service.test.ts +3 -3
  58. package/src/session/unified-session-persistence-service.ts +159 -141
  59. package/src/session/utils/helpers.ts +22 -41
  60. package/src/session/utils/types.ts +10 -0
  61. package/src/storage/sqlite-team-store.ts +16 -5
  62. package/src/tools/definitions.test.ts +137 -8
  63. package/src/tools/definitions.ts +115 -70
  64. package/src/tools/presets.test.ts +2 -3
  65. package/src/tools/presets.ts +3 -3
  66. package/src/tools/schemas.ts +28 -28
  67. package/src/types/config.ts +5 -0
  68. package/src/types/events.ts +23 -0
@@ -1,3 +1,6 @@
1
+ import { existsSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { fileURLToPath } from "node:url";
1
4
  import type { AgentConfig, Tool } from "@clinebot/agents";
2
5
  import { SubprocessSandbox } from "../runtime/sandbox/subprocess-sandbox";
3
6
 
@@ -7,6 +10,7 @@ export interface PluginSandboxOptions {
7
10
  importTimeoutMs?: number;
8
11
  hookTimeoutMs?: number;
9
12
  contributionTimeoutMs?: number;
13
+ onEvent?: (event: { name: string; payload?: unknown }) => void;
10
14
  }
11
15
 
12
16
  type AgentExtension = NonNullable<AgentConfig["extensions"]>[number];
@@ -48,192 +52,87 @@ type SandboxedPluginDescriptor = {
48
52
  };
49
53
  };
50
54
 
51
- const PLUGIN_SANDBOX_BOOTSTRAP = `
52
- const { pathToFileURL } = require("node:url");
53
- let pluginCounter = 0;
54
- const pluginState = new Map();
55
-
56
- function toErrorPayload(error) {
57
- const message = error instanceof Error ? error.message : String(error);
58
- const stack = error instanceof Error ? error.stack : undefined;
59
- return { message, stack };
60
- }
61
-
62
- function sendResponse(id, ok, result, error) {
63
- if (!process.send) return;
64
- process.send({ type: "response", id, ok, result, error });
65
- }
66
-
67
- function sanitizeObject(value) {
68
- if (!value || typeof value !== "object") return {};
69
- return value;
70
- }
71
-
72
- async function initialize(args) {
73
- const descriptors = [];
74
- const exportName = (args && args.exportName) || "plugin";
75
- for (const pluginPath of args.pluginPaths || []) {
76
- const moduleExports = await import(pathToFileURL(pluginPath).href);
77
- const plugin = moduleExports.default || moduleExports[exportName];
78
- if (!plugin || typeof plugin !== "object") {
79
- throw new Error(\`Invalid plugin module: \${pluginPath}\`);
80
- }
81
- if (typeof plugin.name !== "string" || !plugin.name) {
82
- throw new Error(\`Invalid plugin name: \${pluginPath}\`);
83
- }
84
- if (!plugin.manifest || typeof plugin.manifest !== "object") {
85
- throw new Error(\`Invalid plugin manifest: \${pluginPath}\`);
86
- }
87
-
88
- const pluginId = \`plugin_\${++pluginCounter}\`;
89
- const contributions = {
90
- tools: [],
91
- commands: [],
92
- shortcuts: [],
93
- flags: [],
94
- messageRenderers: [],
95
- providers: [],
96
- };
97
- const handlers = {
98
- tools: new Map(),
99
- commands: new Map(),
100
- messageRenderers: new Map(),
101
- };
102
-
103
- const makeId = (prefix) => \`\${pluginId}_\${prefix}_\${Math.random().toString(36).slice(2, 10)}\`;
104
- const api = {
105
- registerTool: (tool) => {
106
- const id = makeId("tool");
107
- handlers.tools.set(id, tool.execute);
108
- contributions.tools.push({
109
- id,
110
- name: tool.name,
111
- description: tool.description,
112
- inputSchema: tool.inputSchema,
113
- timeoutMs: tool.timeoutMs,
114
- retryable: tool.retryable,
115
- });
116
- },
117
- registerCommand: (command) => {
118
- const id = makeId("command");
119
- if (typeof command.handler === "function") {
120
- handlers.commands.set(id, command.handler);
121
- }
122
- contributions.commands.push({
123
- id,
124
- name: command.name,
125
- description: command.description,
126
- });
127
- },
128
- registerShortcut: (shortcut) => {
129
- contributions.shortcuts.push({
130
- id: makeId("shortcut"),
131
- name: shortcut.name,
132
- value: shortcut.value,
133
- description: shortcut.description,
134
- });
135
- },
136
- registerFlag: (flag) => {
137
- contributions.flags.push({
138
- id: makeId("flag"),
139
- name: flag.name,
140
- description: flag.description,
141
- defaultValue: flag.defaultValue,
142
- });
143
- },
144
- registerMessageRenderer: (renderer) => {
145
- const id = makeId("renderer");
146
- handlers.messageRenderers.set(id, renderer.render);
147
- contributions.messageRenderers.push({ id, name: renderer.name });
148
- },
149
- registerProvider: (provider) => {
150
- contributions.providers.push({
151
- id: makeId("provider"),
152
- name: provider.name,
153
- description: provider.description,
154
- metadata: sanitizeObject(provider.metadata),
155
- });
156
- },
157
- };
158
-
159
- if (typeof plugin.setup === "function") {
160
- await plugin.setup(api);
161
- }
162
-
163
- pluginState.set(pluginId, { plugin, handlers });
164
- descriptors.push({
165
- pluginId,
166
- name: plugin.name,
167
- manifest: plugin.manifest,
168
- contributions,
169
- });
170
- }
171
- return descriptors;
172
- }
173
-
174
- function getPlugin(pluginId) {
175
- const state = pluginState.get(pluginId);
176
- if (!state) {
177
- throw new Error(\`Unknown sandbox plugin id: \${pluginId}\`);
178
- }
179
- return state;
180
- }
181
-
182
- async function invokeHook(args) {
183
- const state = getPlugin(args.pluginId);
184
- const handler = state.plugin[args.hookName];
185
- if (typeof handler !== "function") {
186
- return undefined;
187
- }
188
- return await handler(args.payload);
189
- }
190
-
191
- async function executeTool(args) {
192
- const state = getPlugin(args.pluginId);
193
- const handler = state.handlers.tools.get(args.contributionId);
194
- if (typeof handler !== "function") {
195
- throw new Error("Unknown sandbox tool contribution");
196
- }
197
- return await handler(args.input, args.context);
198
- }
199
-
200
- async function executeCommand(args) {
201
- const state = getPlugin(args.pluginId);
202
- const handler = state.handlers.commands.get(args.contributionId);
203
- if (typeof handler !== "function") {
204
- return "";
205
- }
206
- return await handler(args.input);
207
- }
208
-
209
- async function renderMessage(args) {
210
- const state = getPlugin(args.pluginId);
211
- const handler = state.handlers.messageRenderers.get(args.contributionId);
212
- if (typeof handler !== "function") {
213
- return "";
214
- }
215
- return await handler(args.message);
55
+ /**
56
+ * Resolve the bootstrap for the sandbox subprocess.
57
+ *
58
+ * In production (bundled), the compiled `.js` file lives next to this module
59
+ * and can be passed directly as a file to spawn. In development/test
60
+ * (unbundled, where only the `.ts` source exists), we load the TypeScript
61
+ * bootstrap through jiti from an inline script.
62
+ */
63
+ function resolveBootstrap(): { file: string } | { script: string } {
64
+ const dir = dirname(fileURLToPath(import.meta.url));
65
+ // In dev, the bootstrap sits next to this file in src/agents/.
66
+ // In production, the main bundle is at dist/ but bootstrap is at dist/agents/.
67
+ const candidates = [
68
+ join(dir, "plugin-sandbox-bootstrap.js"),
69
+ join(dir, "agents", "plugin-sandbox-bootstrap.js"),
70
+ ];
71
+ for (const candidate of candidates) {
72
+ if (existsSync(candidate)) return { file: candidate };
73
+ }
74
+ const tsPath = join(dir, "plugin-sandbox-bootstrap.ts");
75
+ return {
76
+ script: [
77
+ "const createJiti = require('jiti');",
78
+ `const jiti = createJiti(${JSON.stringify(tsPath)}, { cache: false, requireCache: false, esmResolve: true, interopDefault: false });`,
79
+ `Promise.resolve(jiti.import(${JSON.stringify(tsPath)}, {})).catch((error) => {`,
80
+ " console.error(error);",
81
+ " process.exitCode = 1;",
82
+ "});",
83
+ ].join("\n"),
84
+ };
216
85
  }
217
86
 
218
- const methods = { initialize, invokeHook, executeTool, executeCommand, renderMessage };
219
-
220
- process.on("message", async (message) => {
221
- if (!message || message.type !== "call") {
222
- return;
223
- }
224
- const method = methods[message.method];
225
- if (!method) {
226
- sendResponse(message.id, false, undefined, { message: \`Unknown method: \${String(message.method)}\` });
227
- return;
228
- }
229
- try {
230
- const result = await method(message.args || {});
231
- sendResponse(message.id, true, result);
232
- } catch (error) {
233
- sendResponse(message.id, false, undefined, toErrorPayload(error));
234
- }
235
- });
236
- `;
87
+ const BOOTSTRAP = resolveBootstrap();
88
+
89
+ /**
90
+ * Map from hook stage names in the manifest to the property name on AgentExtension
91
+ * and the corresponding hook method name inside the sandbox subprocess.
92
+ */
93
+ const HOOK_BINDINGS: Array<{
94
+ stage: HookStage;
95
+ extensionKey: keyof AgentExtension;
96
+ sandboxHookName: string;
97
+ }> = [
98
+ { stage: "input", extensionKey: "onInput", sandboxHookName: "onInput" },
99
+ {
100
+ stage: "session_start",
101
+ extensionKey: "onSessionStart",
102
+ sandboxHookName: "onSessionStart",
103
+ },
104
+ {
105
+ stage: "before_agent_start",
106
+ extensionKey: "onBeforeAgentStart",
107
+ sandboxHookName: "onBeforeAgentStart",
108
+ },
109
+ {
110
+ stage: "tool_call_before",
111
+ extensionKey: "onToolCall",
112
+ sandboxHookName: "onToolCall",
113
+ },
114
+ {
115
+ stage: "tool_call_after",
116
+ extensionKey: "onToolResult",
117
+ sandboxHookName: "onToolResult",
118
+ },
119
+ {
120
+ stage: "turn_end",
121
+ extensionKey: "onAgentEnd",
122
+ sandboxHookName: "onAgentEnd",
123
+ },
124
+ {
125
+ stage: "session_shutdown",
126
+ extensionKey: "onSessionShutdown",
127
+ sandboxHookName: "onSessionShutdown",
128
+ },
129
+ {
130
+ stage: "runtime_event",
131
+ extensionKey: "onRuntimeEvent",
132
+ sandboxHookName: "onRuntimeEvent",
133
+ },
134
+ { stage: "error", extensionKey: "onError", sandboxHookName: "onError" },
135
+ ];
237
136
 
238
137
  function hasHookStage(extension: AgentExtension, stage: HookStage): boolean {
239
138
  return extension.manifest.hookStages?.includes(stage) === true;
@@ -254,7 +153,10 @@ export async function loadSandboxedPlugins(
254
153
  }> {
255
154
  const sandbox = new SubprocessSandbox({
256
155
  name: "plugin-sandbox",
257
- bootstrapScript: PLUGIN_SANDBOX_BOOTSTRAP,
156
+ ...("file" in BOOTSTRAP
157
+ ? { bootstrapFile: BOOTSTRAP.file }
158
+ : { bootstrapScript: BOOTSTRAP.script }),
159
+ onEvent: options.onEvent,
258
160
  });
259
161
  const importTimeoutMs = withTimeoutFallback(options.importTimeoutMs, 4000);
260
162
  const hookTimeoutMs = withTimeoutFallback(options.hookTimeoutMs, 3000);
@@ -286,177 +188,13 @@ export async function loadSandboxedPlugins(
286
188
  name: descriptor.name,
287
189
  manifest: descriptor.manifest,
288
190
  setup: (api: AgentExtensionApi) => {
289
- for (const toolDescriptor of descriptor.contributions.tools) {
290
- const tool: Tool = {
291
- name: toolDescriptor.name,
292
- description: toolDescriptor.description ?? "",
293
- inputSchema: (toolDescriptor.inputSchema ?? {
294
- type: "object",
295
- properties: {},
296
- }) as Tool["inputSchema"],
297
- timeoutMs: toolDescriptor.timeoutMs,
298
- retryable: toolDescriptor.retryable,
299
- execute: async (input: unknown, context: unknown) =>
300
- await sandbox.call(
301
- "executeTool",
302
- {
303
- pluginId: descriptor.pluginId,
304
- contributionId: toolDescriptor.id,
305
- input,
306
- context,
307
- },
308
- { timeoutMs: contributionTimeoutMs },
309
- ),
310
- };
311
- api.registerTool(tool);
312
- }
313
-
314
- for (const commandDescriptor of descriptor.contributions.commands) {
315
- api.registerCommand({
316
- name: commandDescriptor.name,
317
- description: commandDescriptor.description,
318
- handler: async (input: string) =>
319
- await sandbox.call<string>(
320
- "executeCommand",
321
- {
322
- pluginId: descriptor.pluginId,
323
- contributionId: commandDescriptor.id,
324
- input,
325
- },
326
- { timeoutMs: contributionTimeoutMs },
327
- ),
328
- });
329
- }
330
-
331
- for (const shortcutDescriptor of descriptor.contributions.shortcuts) {
332
- api.registerShortcut({
333
- name: shortcutDescriptor.name,
334
- value: shortcutDescriptor.value ?? "",
335
- description: shortcutDescriptor.description,
336
- });
337
- }
338
-
339
- for (const flagDescriptor of descriptor.contributions.flags) {
340
- api.registerFlag({
341
- name: flagDescriptor.name,
342
- description: flagDescriptor.description,
343
- defaultValue: flagDescriptor.defaultValue,
344
- });
345
- }
346
-
347
- for (const rendererDescriptor of descriptor.contributions
348
- .messageRenderers) {
349
- api.registerMessageRenderer({
350
- name: rendererDescriptor.name,
351
- render: () =>
352
- `[sandbox renderer ${rendererDescriptor.name} requires async bridge]`,
353
- });
354
- }
355
-
356
- for (const providerDescriptor of descriptor.contributions.providers) {
357
- api.registerProvider({
358
- name: providerDescriptor.name,
359
- description: providerDescriptor.description,
360
- metadata: providerDescriptor.metadata,
361
- });
362
- }
191
+ registerTools(api, sandbox, descriptor, contributionTimeoutMs);
192
+ registerCommands(api, sandbox, descriptor, contributionTimeoutMs);
193
+ registerSimpleContributions(api, descriptor);
363
194
  },
364
195
  };
365
196
 
366
- if (hasHookStage(extension, "input")) {
367
- extension.onInput = async (payload: unknown) =>
368
- await sandbox.call(
369
- "invokeHook",
370
- { pluginId: descriptor.pluginId, hookName: "onInput", payload },
371
- { timeoutMs: hookTimeoutMs },
372
- );
373
- }
374
- if (hasHookStage(extension, "session_start")) {
375
- extension.onSessionStart = async (payload: unknown) =>
376
- await sandbox.call(
377
- "invokeHook",
378
- {
379
- pluginId: descriptor.pluginId,
380
- hookName: "onSessionStart",
381
- payload,
382
- },
383
- { timeoutMs: hookTimeoutMs },
384
- );
385
- }
386
- if (hasHookStage(extension, "before_agent_start")) {
387
- extension.onBeforeAgentStart = async (payload: unknown) =>
388
- await sandbox.call(
389
- "invokeHook",
390
- {
391
- pluginId: descriptor.pluginId,
392
- hookName: "onBeforeAgentStart",
393
- payload,
394
- },
395
- { timeoutMs: hookTimeoutMs },
396
- );
397
- }
398
- if (hasHookStage(extension, "tool_call_before")) {
399
- extension.onToolCall = async (payload: unknown) =>
400
- await sandbox.call(
401
- "invokeHook",
402
- { pluginId: descriptor.pluginId, hookName: "onToolCall", payload },
403
- { timeoutMs: hookTimeoutMs },
404
- );
405
- }
406
- if (hasHookStage(extension, "tool_call_after")) {
407
- extension.onToolResult = async (payload: unknown) =>
408
- await sandbox.call(
409
- "invokeHook",
410
- {
411
- pluginId: descriptor.pluginId,
412
- hookName: "onToolResult",
413
- payload,
414
- },
415
- { timeoutMs: hookTimeoutMs },
416
- );
417
- }
418
- if (hasHookStage(extension, "turn_end")) {
419
- extension.onAgentEnd = async (payload: unknown) =>
420
- await sandbox.call(
421
- "invokeHook",
422
- { pluginId: descriptor.pluginId, hookName: "onAgentEnd", payload },
423
- { timeoutMs: hookTimeoutMs },
424
- );
425
- }
426
- if (hasHookStage(extension, "session_shutdown")) {
427
- extension.onSessionShutdown = async (payload: unknown) =>
428
- await sandbox.call(
429
- "invokeHook",
430
- {
431
- pluginId: descriptor.pluginId,
432
- hookName: "onSessionShutdown",
433
- payload,
434
- },
435
- { timeoutMs: hookTimeoutMs },
436
- );
437
- }
438
- if (hasHookStage(extension, "runtime_event")) {
439
- extension.onRuntimeEvent = async (payload: unknown) => {
440
- await sandbox.call(
441
- "invokeHook",
442
- {
443
- pluginId: descriptor.pluginId,
444
- hookName: "onRuntimeEvent",
445
- payload,
446
- },
447
- { timeoutMs: hookTimeoutMs },
448
- );
449
- };
450
- }
451
- if (hasHookStage(extension, "error")) {
452
- extension.onError = async (payload: unknown) => {
453
- await sandbox.call(
454
- "invokeHook",
455
- { pluginId: descriptor.pluginId, hookName: "onError", payload },
456
- { timeoutMs: hookTimeoutMs },
457
- );
458
- };
459
- }
197
+ bindHooks(extension, sandbox, descriptor.pluginId, hookTimeoutMs);
460
198
 
461
199
  return extension;
462
200
  },
@@ -469,3 +207,135 @@ export async function loadSandboxedPlugins(
469
207
  },
470
208
  };
471
209
  }
210
+
211
+ // ---------------------------------------------------------------------------
212
+ // Contribution registration helpers
213
+ // ---------------------------------------------------------------------------
214
+
215
+ function registerTools(
216
+ api: AgentExtensionApi,
217
+ sandbox: SubprocessSandbox,
218
+ descriptor: SandboxedPluginDescriptor,
219
+ timeoutMs: number,
220
+ ): void {
221
+ for (const td of descriptor.contributions.tools) {
222
+ const tool: Tool = {
223
+ name: td.name,
224
+ description: td.description ?? "",
225
+ inputSchema: (td.inputSchema ?? {
226
+ type: "object",
227
+ properties: {},
228
+ }) as Tool["inputSchema"],
229
+ timeoutMs: td.timeoutMs,
230
+ retryable: td.retryable,
231
+ execute: async (input: unknown, context: unknown) =>
232
+ await sandbox.call(
233
+ "executeTool",
234
+ {
235
+ pluginId: descriptor.pluginId,
236
+ contributionId: td.id,
237
+ input,
238
+ context,
239
+ },
240
+ { timeoutMs },
241
+ ),
242
+ };
243
+ api.registerTool(tool);
244
+ }
245
+ }
246
+
247
+ function registerCommands(
248
+ api: AgentExtensionApi,
249
+ sandbox: SubprocessSandbox,
250
+ descriptor: SandboxedPluginDescriptor,
251
+ timeoutMs: number,
252
+ ): void {
253
+ for (const cd of descriptor.contributions.commands) {
254
+ api.registerCommand({
255
+ name: cd.name,
256
+ description: cd.description,
257
+ handler: async (input: string) =>
258
+ await sandbox.call<string>(
259
+ "executeCommand",
260
+ {
261
+ pluginId: descriptor.pluginId,
262
+ contributionId: cd.id,
263
+ input,
264
+ },
265
+ { timeoutMs },
266
+ ),
267
+ });
268
+ }
269
+ }
270
+
271
+ function registerSimpleContributions(
272
+ api: AgentExtensionApi,
273
+ descriptor: SandboxedPluginDescriptor,
274
+ ): void {
275
+ for (const sd of descriptor.contributions.shortcuts) {
276
+ api.registerShortcut({
277
+ name: sd.name,
278
+ value: sd.value ?? "",
279
+ description: sd.description,
280
+ });
281
+ }
282
+
283
+ for (const fd of descriptor.contributions.flags) {
284
+ api.registerFlag({
285
+ name: fd.name,
286
+ description: fd.description,
287
+ defaultValue: fd.defaultValue,
288
+ });
289
+ }
290
+
291
+ for (const rd of descriptor.contributions.messageRenderers) {
292
+ api.registerMessageRenderer({
293
+ name: rd.name,
294
+ render: () => `[sandbox renderer ${rd.name} requires async bridge]`,
295
+ });
296
+ }
297
+
298
+ for (const pd of descriptor.contributions.providers) {
299
+ api.registerProvider({
300
+ name: pd.name,
301
+ description: pd.description,
302
+ metadata: pd.metadata,
303
+ });
304
+ }
305
+ }
306
+
307
+ function makeHookHandler(
308
+ sandbox: SubprocessSandbox,
309
+ pluginId: string,
310
+ hookName: string,
311
+ timeoutMs: number,
312
+ ): (payload: unknown) => Promise<unknown> {
313
+ return async (payload: unknown) =>
314
+ await sandbox.call(
315
+ "invokeHook",
316
+ { pluginId, hookName, payload },
317
+ { timeoutMs },
318
+ );
319
+ }
320
+
321
+ function bindHooks(
322
+ extension: AgentExtension,
323
+ sandbox: SubprocessSandbox,
324
+ pluginId: string,
325
+ hookTimeoutMs: number,
326
+ ): void {
327
+ for (const { stage, extensionKey, sandboxHookName } of HOOK_BINDINGS) {
328
+ if (hasHookStage(extension, stage)) {
329
+ const handler = makeHookHandler(
330
+ sandbox,
331
+ pluginId,
332
+ sandboxHookName,
333
+ hookTimeoutMs,
334
+ );
335
+ // Each hook property on AgentExtension accepts (payload: unknown) => Promise<unknown>.
336
+ // TypeScript cannot narrow a union of optional callback keys via computed access,
337
+ // so we use Object.assign to set the property safely.
338
+ Object.assign(extension, { [extensionKey]: handler });
339
+ }
340
+ }
341
+ }
package/src/index.node.ts CHANGED
@@ -138,6 +138,11 @@ export {
138
138
  saveLocalProviderOAuthCredentials,
139
139
  saveLocalProviderSettings,
140
140
  } from "./providers/local-provider-service";
141
+ export type { AvailableRuntimeCommand } from "./runtime/commands";
142
+ export {
143
+ listAvailableRuntimeCommandsFromWatcher,
144
+ resolveRuntimeSlashCommandFromWatcher,
145
+ } from "./runtime/commands";
141
146
  export {
142
147
  formatRulesForSystemPrompt,
143
148
  isRuleEnabled,
@@ -159,6 +164,11 @@ export type {
159
164
  RuntimeBuilderInput,
160
165
  SessionRuntime,
161
166
  } from "./runtime/session-runtime";
167
+ export type { AvailableSkill } from "./runtime/skills";
168
+ export {
169
+ listAvailableSkillsFromWatcher,
170
+ resolveSkillsSlashCommandFromWatcher,
171
+ } from "./runtime/skills";
162
172
  export {
163
173
  type DesktopToolApprovalOptions,
164
174
  requestDesktopToolApproval,
@@ -207,6 +217,10 @@ export {
207
217
  SqliteRpcSessionBackend,
208
218
  type SqliteRpcSessionBackendOptions,
209
219
  } from "./session/sqlite-rpc-session-backend";
220
+ export {
221
+ accumulateUsageTotals,
222
+ createInitialAccumulatedUsage,
223
+ } from "./session/utils/usage";
210
224
  export type {
211
225
  WorkspaceManager,
212
226
  WorkspaceManagerEvent,