chainlesschain 0.45.70 → 0.45.75

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 (89) hide show
  1. package/package.json +1 -1
  2. package/src/assets/web-panel/.build-hash +1 -1
  3. package/src/assets/web-panel/assets/Analytics-B4OM8S8X.css +1 -0
  4. package/src/assets/web-panel/assets/Analytics-sBrYoc3A.js +3 -0
  5. package/src/assets/web-panel/assets/AppLayout-2RCrdXxl.js +1 -0
  6. package/src/assets/web-panel/assets/AppLayout-D9pBLPC3.css +1 -0
  7. package/src/assets/web-panel/assets/Backup-D68fenbD.js +1 -0
  8. package/src/assets/web-panel/assets/Backup-fZqtfC1m.css +1 -0
  9. package/src/assets/web-panel/assets/{Chat-DXtvKoM0.js → Chat-B2nB8o_F.js} +1 -1
  10. package/src/assets/web-panel/assets/{Cron-BJ4ODHOy.js → Cron-CNs03iHJ.js} +2 -2
  11. package/src/assets/web-panel/assets/{Dashboard-BZd4wDPQ.js → Dashboard-DanoHPSI.js} +2 -2
  12. package/src/assets/web-panel/assets/Git-CCMVr3Y8.js +2 -0
  13. package/src/assets/web-panel/assets/Git-DGcuBXST.css +1 -0
  14. package/src/assets/web-panel/assets/{Logs-CSeKZEG_.js → Logs-BY6A0UNG.js} +2 -2
  15. package/src/assets/web-panel/assets/{McpTools-BYQAK11r.js → McpTools-CrBVYlg6.js} +2 -2
  16. package/src/assets/web-panel/assets/{Memory-gkUAPyuZ.js → Memory-CWx3SpUt.js} +2 -2
  17. package/src/assets/web-panel/assets/{Notes-bjNrQgAo.js → Notes-1LcGD49x.js} +2 -2
  18. package/src/assets/web-panel/assets/Organization-DdOOM4ic.css +1 -0
  19. package/src/assets/web-panel/assets/Organization-Dx2DhbkM.js +4 -0
  20. package/src/assets/web-panel/assets/P2P-B16fjqfJ.js +2 -0
  21. package/src/assets/web-panel/assets/P2P-OEzOeMZX.css +1 -0
  22. package/src/assets/web-panel/assets/Permissions-BQbC9FzG.js +4 -0
  23. package/src/assets/web-panel/assets/Permissions-C9WlkGl-.css +1 -0
  24. package/src/assets/web-panel/assets/Projects-CjhZbNYm.js +2 -0
  25. package/src/assets/web-panel/assets/Projects-DxKelI5h.css +1 -0
  26. package/src/assets/web-panel/assets/Providers-BEakqcO5.css +1 -0
  27. package/src/assets/web-panel/assets/Providers-ivOAQtHM.js +2 -0
  28. package/src/assets/web-panel/assets/RssFeed-BlFC20eg.css +1 -0
  29. package/src/assets/web-panel/assets/RssFeed-BrsErdrU.js +3 -0
  30. package/src/assets/web-panel/assets/Security-DnEvJU5h.js +4 -0
  31. package/src/assets/web-panel/assets/Security-Dwxw7rfP.css +1 -0
  32. package/src/assets/web-panel/assets/{Services-CS0oMdxh.js → Services-7jQywNbl.js} +2 -2
  33. package/src/assets/web-panel/assets/Skills-CLlblJcG.js +1 -0
  34. package/src/assets/web-panel/assets/{Tasks-qULws8pc.js → Tasks-CmJBC1cf.js} +1 -1
  35. package/src/assets/web-panel/assets/Templates-DOY_oZnm.css +1 -0
  36. package/src/assets/web-panel/assets/Templates-RXT8-DNk.js +1 -0
  37. package/src/assets/web-panel/assets/Wallet-3iYASEx_.js +4 -0
  38. package/src/assets/web-panel/assets/Wallet-DnIumafl.css +1 -0
  39. package/src/assets/web-panel/assets/WebAuthn-CNPl2VQR.css +1 -0
  40. package/src/assets/web-panel/assets/WebAuthn-s3Hzd9db.js +5 -0
  41. package/src/assets/web-panel/assets/{antd-CJSBocer.js → antd-gZyc63Qr.js} +114 -114
  42. package/src/assets/web-panel/assets/chat-DWBA4-cl.js +1 -0
  43. package/src/assets/web-panel/assets/index-CyGtHm63.js +2 -0
  44. package/src/assets/web-panel/assets/{markdown-Bo5cVN4u.js → markdown-Bv7nG63L.js} +1 -1
  45. package/src/assets/web-panel/assets/ws-CU7Gvoom.js +1 -0
  46. package/src/assets/web-panel/index.html +2 -2
  47. package/src/commands/doctor.js +33 -151
  48. package/src/commands/mcp.js +1 -1
  49. package/src/commands/plugin.js +1 -1
  50. package/src/commands/session.js +106 -7
  51. package/src/commands/status.js +39 -69
  52. package/src/gateways/ws/session-protocol.js +1 -1
  53. package/src/gateways/ws/ws-agent-handler.js +484 -0
  54. package/src/gateways/ws/ws-server.js +758 -4
  55. package/src/gateways/ws/ws-session-gateway.js +1432 -1
  56. package/src/harness/mcp-client.js +417 -0
  57. package/src/harness/mock-llm-provider.js +167 -0
  58. package/src/harness/plugin-manager.js +434 -0
  59. package/src/lib/agent-core.js +25 -1902
  60. package/src/lib/hashline.js +208 -0
  61. package/src/lib/jsonl-session-store.js +11 -0
  62. package/src/lib/mcp-client.js +14 -412
  63. package/src/lib/plugin-manager.js +29 -428
  64. package/src/lib/prompt-compressor.js +11 -0
  65. package/src/lib/session-hooks.js +61 -0
  66. package/src/lib/skill-loader.js +4 -0
  67. package/src/lib/skill-mcp.js +190 -0
  68. package/src/lib/workflow-state-reader.js +94 -0
  69. package/src/lib/ws-agent-handler.js +8 -472
  70. package/src/lib/ws-server.js +12 -756
  71. package/src/lib/ws-session-manager.js +8 -1417
  72. package/src/repl/agent-repl.js +27 -3
  73. package/src/runtime/agent-core.js +1760 -0
  74. package/src/runtime/agent-runtime.js +3 -1
  75. package/src/runtime/coding-agent-contract-shared.cjs +496 -0
  76. package/src/runtime/coding-agent-contract.js +49 -229
  77. package/src/runtime/coding-agent-policy.cjs +54 -5
  78. package/src/runtime/diagnostics.js +317 -0
  79. package/src/runtime/index.js +3 -0
  80. package/src/tools/index.js +3 -0
  81. package/src/tools/legacy-agent-tools.js +5 -0
  82. package/src/assets/web-panel/assets/AppLayout-B_tkw3Pn.js +0 -1
  83. package/src/assets/web-panel/assets/AppLayout-CFP4dGIJ.css +0 -1
  84. package/src/assets/web-panel/assets/Providers-Brm-S_hS.css +0 -1
  85. package/src/assets/web-panel/assets/Providers-Dbf57Tbv.js +0 -1
  86. package/src/assets/web-panel/assets/Skills-B2fgruv8.js +0 -1
  87. package/src/assets/web-panel/assets/chat-DnH09sSR.js +0 -1
  88. package/src/assets/web-panel/assets/index-IK-oro0g.js +0 -2
  89. package/src/assets/web-panel/assets/ws-DjelKkD6.js +0 -1
@@ -1,430 +1,31 @@
1
1
  /**
2
- * Plugin Manager Plugin installation, management, and marketplace for CLI.
3
- * Manages plugin lifecycle: install, enable, disable, remove, update.
4
- */
5
-
6
- import crypto from "crypto";
7
- import fs from "fs";
8
- import path from "path";
9
- import { getElectronUserDataDir } from "./paths.js";
10
-
11
- /**
12
- * Ensure plugin tables exist.
13
- */
14
- export function ensurePluginTables(db) {
15
- db.exec(`
16
- CREATE TABLE IF NOT EXISTS plugins (
17
- id TEXT PRIMARY KEY,
18
- name TEXT NOT NULL UNIQUE,
19
- version TEXT NOT NULL,
20
- description TEXT,
21
- author TEXT,
22
- homepage TEXT,
23
- entry_point TEXT,
24
- permissions TEXT,
25
- status TEXT DEFAULT 'installed',
26
- enabled INTEGER DEFAULT 1,
27
- install_path TEXT,
28
- installed_at TEXT DEFAULT (datetime('now')),
29
- updated_at TEXT DEFAULT (datetime('now'))
30
- )
31
- `);
32
- db.exec(`
33
- CREATE TABLE IF NOT EXISTS plugin_settings (
34
- id TEXT PRIMARY KEY,
35
- plugin_id TEXT NOT NULL,
36
- key TEXT NOT NULL,
37
- value TEXT
38
- )
39
- `);
40
- db.exec(`
41
- CREATE TABLE IF NOT EXISTS plugin_skills (
42
- id TEXT PRIMARY KEY,
43
- plugin_name TEXT NOT NULL,
44
- skill_name TEXT NOT NULL,
45
- skill_path TEXT NOT NULL,
46
- installed_at TEXT DEFAULT (datetime('now'))
47
- )
48
- `);
49
- db.exec(`
50
- CREATE TABLE IF NOT EXISTS plugin_registry (
51
- name TEXT PRIMARY KEY,
52
- latest_version TEXT,
53
- description TEXT,
54
- author TEXT,
55
- downloads INTEGER DEFAULT 0,
56
- rating REAL DEFAULT 0,
57
- tags TEXT,
58
- updated_at TEXT DEFAULT (datetime('now'))
59
- )
60
- `);
61
- }
62
-
63
- /**
64
- * Install a plugin (record in DB).
65
- */
66
- export function installPlugin(db, pluginInfo) {
67
- ensurePluginTables(db);
68
- const {
69
- name,
70
- version,
71
- description,
72
- author,
73
- homepage,
74
- entryPoint,
75
- permissions,
76
- installPath,
77
- } = pluginInfo;
78
-
79
- if (!name || !version) {
80
- throw new Error("Plugin name and version are required");
81
- }
82
-
83
- // Check if already installed
84
- const existing = getPlugin(db, name);
85
- if (existing) {
86
- throw new Error(`Plugin already installed: ${name}`);
87
- }
88
-
89
- const id = `plugin-${crypto.randomBytes(8).toString("hex")}`;
90
-
91
- db.prepare(
92
- `INSERT INTO plugins (id, name, version, description, author, homepage, entry_point, permissions, status, enabled, install_path)
93
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
94
- ).run(
95
- id,
96
- name,
97
- version,
98
- description || null,
99
- author || null,
100
- homepage || null,
101
- entryPoint || null,
102
- permissions ? JSON.stringify(permissions) : null,
103
- "installed",
104
- 1,
105
- installPath || null,
106
- );
107
-
108
- return {
109
- id,
110
- name,
111
- version,
112
- description,
113
- author,
114
- status: "installed",
115
- enabled: true,
116
- };
117
- }
118
-
119
- /**
120
- * Get a plugin by name.
121
- */
122
- export function getPlugin(db, name) {
123
- ensurePluginTables(db);
124
- return db.prepare("SELECT * FROM plugins WHERE name = ?").get(name);
125
- }
126
-
127
- /**
128
- * Get a plugin by ID.
129
- */
130
- export function getPluginById(db, pluginId) {
131
- ensurePluginTables(db);
132
- return db.prepare("SELECT * FROM plugins WHERE id = ?").get(pluginId);
133
- }
134
-
135
- /**
136
- * List all installed plugins.
137
- */
138
- export function listPlugins(db, options = {}) {
139
- ensurePluginTables(db);
140
- const { enabledOnly = false } = options;
141
-
142
- if (enabledOnly) {
143
- return db
144
- .prepare("SELECT * FROM plugins WHERE enabled = 1 ORDER BY name ASC")
145
- .all();
146
- }
147
- return db.prepare("SELECT * FROM plugins ORDER BY name ASC").all();
148
- }
149
-
150
- /**
151
- * Enable a plugin.
152
- */
153
- export function enablePlugin(db, name) {
154
- ensurePluginTables(db);
155
- const result = db
156
- .prepare("UPDATE plugins SET enabled = ?, status = ? WHERE name = ?")
157
- .run(1, "installed", name);
158
- return result.changes > 0;
159
- }
160
-
161
- /**
162
- * Disable a plugin.
163
- */
164
- export function disablePlugin(db, name) {
165
- ensurePluginTables(db);
166
- const result = db
167
- .prepare("UPDATE plugins SET enabled = ?, status = ? WHERE name = ?")
168
- .run(0, "disabled", name);
169
- return result.changes > 0;
170
- }
171
-
172
- /**
173
- * Remove (uninstall) a plugin.
174
- */
175
- export function removePlugin(db, name) {
176
- ensurePluginTables(db);
177
- // Remove settings first
178
- const plugin = getPlugin(db, name);
179
- if (!plugin) return false;
180
-
181
- db.prepare("DELETE FROM plugin_settings WHERE plugin_id = ?").run(plugin.id);
182
- const result = db.prepare("DELETE FROM plugins WHERE name = ?").run(name);
183
- return result.changes > 0;
184
- }
185
-
186
- /**
187
- * Update a plugin version.
188
- */
189
- export function updatePlugin(db, name, newVersion) {
190
- ensurePluginTables(db);
191
- const result = db
192
- .prepare(
193
- "UPDATE plugins SET version = ?, updated_at = datetime('now') WHERE name = ?",
194
- )
195
- .run(newVersion, name);
196
- return result.changes > 0;
197
- }
198
-
199
- /**
200
- * Set a plugin setting.
201
- */
202
- export function setPluginSetting(db, pluginName, key, value) {
203
- ensurePluginTables(db);
204
- const plugin = getPlugin(db, pluginName);
205
- if (!plugin) throw new Error(`Plugin not found: ${pluginName}`);
206
-
207
- // Remove existing setting for this key
208
- db.prepare("DELETE FROM plugin_settings WHERE plugin_id = ? AND key = ?").run(
209
- plugin.id,
210
- key,
211
- );
212
- const settingId = `ps-${crypto.randomBytes(4).toString("hex")}`;
213
- db.prepare(
214
- `INSERT INTO plugin_settings (id, plugin_id, key, value) VALUES (?, ?, ?, ?)`,
215
- ).run(
216
- settingId,
217
- plugin.id,
218
- key,
219
- typeof value === "string" ? value : JSON.stringify(value),
220
- );
221
-
222
- return true;
223
- }
224
-
225
- /**
226
- * Get a plugin setting.
227
- */
228
- export function getPluginSetting(db, pluginName, key) {
229
- ensurePluginTables(db);
230
- const plugin = getPlugin(db, pluginName);
231
- if (!plugin) return null;
232
-
233
- const row = db
234
- .prepare(
235
- "SELECT value FROM plugin_settings WHERE plugin_id = ? AND key = ?",
236
- )
237
- .get(plugin.id, key);
238
- return row ? row.value : null;
239
- }
240
-
241
- /**
242
- * Get all settings for a plugin.
243
- */
244
- export function getPluginSettings(db, pluginName) {
245
- ensurePluginTables(db);
246
- const plugin = getPlugin(db, pluginName);
247
- if (!plugin) return {};
248
-
249
- const rows = db
250
- .prepare("SELECT key, value FROM plugin_settings WHERE plugin_id = ?")
251
- .all(plugin.id);
252
- const settings = {};
253
- for (const row of rows) {
254
- settings[row.key] = row.value;
255
- }
256
- return settings;
257
- }
258
-
259
- // ─── Registry / Marketplace ─────────────────────────────
260
-
261
- /**
262
- * Add/update a plugin in the registry.
263
- */
264
- export function registerInMarketplace(db, pluginInfo) {
265
- ensurePluginTables(db);
266
- const { name, latestVersion, description, author, tags } = pluginInfo;
267
-
268
- db.prepare(
269
- `INSERT OR REPLACE INTO plugin_registry (name, latest_version, description, author, tags)
270
- VALUES (?, ?, ?, ?, ?)`,
271
- ).run(
272
- name,
273
- latestVersion,
274
- description || null,
275
- author || null,
276
- tags ? JSON.stringify(tags) : null,
277
- );
278
-
279
- return { name, latestVersion, description, author };
280
- }
281
-
282
- /**
283
- * Search plugins in registry.
284
- */
285
- export function searchRegistry(db, query) {
286
- ensurePluginTables(db);
287
- return db
288
- .prepare(
289
- "SELECT * FROM plugin_registry WHERE name LIKE ? OR description LIKE ? ORDER BY downloads DESC",
290
- )
291
- .all(`%${query}%`, `%${query}%`);
292
- }
293
-
294
- /**
295
- * List all registry plugins.
296
- */
297
- export function listRegistry(db) {
298
- ensurePluginTables(db);
299
- return db
300
- .prepare("SELECT * FROM plugin_registry ORDER BY downloads DESC")
301
- .all();
302
- }
303
-
304
- /**
305
- * Get plugin summary.
306
- */
307
- export function getPluginSummary(db) {
308
- ensurePluginTables(db);
309
- const total = db.prepare("SELECT COUNT(*) as c FROM plugins").get();
310
- const enabled = db
311
- .prepare("SELECT COUNT(*) as c FROM plugins WHERE enabled = ?")
312
- .get(1);
313
- const registry = db
314
- .prepare("SELECT COUNT(*) as c FROM plugin_registry")
315
- .get();
316
-
317
- return {
318
- installed: total?.c || 0,
319
- enabled: enabled?.c || 0,
320
- registryCount: registry?.c || 0,
321
- };
322
- }
323
-
324
- // ─── Plugin Skills ──────────────────────────────────────
325
-
326
- /**
327
- * Get the marketplace skills directory
328
- */
329
- function getMarketplaceSkillsDir() {
330
- return path.join(getElectronUserDataDir(), "marketplace", "skills");
331
- }
332
-
333
- /**
334
- * Install skills from a plugin manifest.
335
- * Copies skill directories to the marketplace/skills/ layer.
2
+ * @deprecatedcanonical implementation lives in `../harness/plugin-manager.js`
3
+ * as of the CLI Runtime Convergence roadmap (Phase 4, 2026-04-09).
4
+ * This file is retained as a re-export shim for backwards compatibility
5
+ * and will be removed once all external consumers have migrated.
336
6
  *
337
- * @param {object} db - Database instance
338
- * @param {string} pluginName - Plugin name
339
- * @param {string} pluginPath - Root path of the plugin package
340
- * @param {{ name: string, path: string }[]} skills - Skills declared in manifest
341
- * @returns {{ installed: string[] }} Names of installed skills
342
- */
343
- export function installPluginSkills(db, pluginName, pluginPath, skills) {
344
- ensurePluginTables(db);
345
- if (!skills || skills.length === 0) return { installed: [] };
346
-
347
- const marketplaceDir = getMarketplaceSkillsDir();
348
- const installed = [];
349
-
350
- for (const skill of skills) {
351
- const srcDir = path.resolve(pluginPath, skill.path);
352
- if (!fs.existsSync(srcDir)) continue;
353
-
354
- const destDir = path.join(marketplaceDir, skill.name);
355
- fs.mkdirSync(destDir, { recursive: true });
356
-
357
- // Copy skill files
358
- _copyDirSync(srcDir, destDir);
359
-
360
- // Record in DB
361
- const id = `ps-${crypto.randomBytes(6).toString("hex")}`;
362
- db.prepare(
363
- `INSERT OR REPLACE INTO plugin_skills (id, plugin_name, skill_name, skill_path)
364
- VALUES (?, ?, ?, ?)`,
365
- ).run(id, pluginName, skill.name, destDir);
366
-
367
- installed.push(skill.name);
368
- }
369
-
370
- return { installed };
371
- }
372
-
373
- /**
374
- * Remove all skills installed by a plugin.
375
- *
376
- * @param {object} db - Database instance
377
- * @param {string} pluginName - Plugin name
378
- * @returns {{ removed: string[] }} Names of removed skills
379
- */
380
- export function removePluginSkills(db, pluginName) {
381
- ensurePluginTables(db);
382
- const rows = db
383
- .prepare("SELECT * FROM plugin_skills WHERE plugin_name = ?")
384
- .all(pluginName);
385
-
386
- const removed = [];
387
- for (const row of rows) {
388
- // Remove the skill directory
389
- if (fs.existsSync(row.skill_path)) {
390
- fs.rmSync(row.skill_path, { recursive: true, force: true });
391
- }
392
- removed.push(row.skill_name);
393
- }
394
-
395
- db.prepare("DELETE FROM plugin_skills WHERE plugin_name = ?").run(pluginName);
396
- return { removed };
397
- }
398
-
399
- /**
400
- * List skills installed by a specific plugin.
401
- *
402
- * @param {object} db - Database instance
403
- * @param {string} pluginName - Plugin name
404
- * @returns {{ skill_name: string, skill_path: string }[]}
405
- */
406
- export function getPluginSkills(db, pluginName) {
407
- ensurePluginTables(db);
408
- return db
409
- .prepare(
410
- "SELECT skill_name, skill_path FROM plugin_skills WHERE plugin_name = ?",
411
- )
412
- .all(pluginName);
413
- }
414
-
415
- /**
416
- * Recursively copy a directory
417
- */
418
- function _copyDirSync(src, dest) {
419
- fs.mkdirSync(dest, { recursive: true });
420
- const entries = fs.readdirSync(src, { withFileTypes: true });
421
- for (const entry of entries) {
422
- const srcPath = path.join(src, entry.name);
423
- const destPath = path.join(dest, entry.name);
424
- if (entry.isDirectory()) {
425
- _copyDirSync(srcPath, destPath);
426
- } else {
427
- fs.copyFileSync(srcPath, destPath);
428
- }
429
- }
430
- }
7
+ * Please import plugin manager functions directly from
8
+ * `packages/cli/src/harness/plugin-manager.js` in new code.
9
+ */
10
+
11
+ export {
12
+ ensurePluginTables,
13
+ installPlugin,
14
+ getPlugin,
15
+ getPluginById,
16
+ listPlugins,
17
+ enablePlugin,
18
+ disablePlugin,
19
+ removePlugin,
20
+ updatePlugin,
21
+ setPluginSetting,
22
+ getPluginSetting,
23
+ getPluginSettings,
24
+ registerInMarketplace,
25
+ searchRegistry,
26
+ listRegistry,
27
+ getPluginSummary,
28
+ installPluginSkills,
29
+ removePluginSkills,
30
+ getPluginSkills,
31
+ } from "../harness/plugin-manager.js";
@@ -1,3 +1,14 @@
1
+ /**
2
+ * @deprecated — canonical implementation lives in
3
+ * `../harness/prompt-compressor.js` as of the CLI Runtime Convergence
4
+ * roadmap. This file is retained as a re-export shim for backwards
5
+ * compatibility and will be removed once all external consumers have
6
+ * migrated.
7
+ *
8
+ * Please import from `packages/cli/src/harness/prompt-compressor.js`
9
+ * in new code.
10
+ */
11
+
1
12
  export {
2
13
  estimateTokens,
3
14
  estimateMessagesTokens,
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Session-level hook firing helpers — the "三件套" that complements
3
+ * the tool-level PreToolUse/PostToolUse hooks already wired in
4
+ * `runtime/agent-core.js`.
5
+ *
6
+ * These three events are defined in hook-manager.js but were never
7
+ * actually fired anywhere in the CLI. This module is the canonical
8
+ * fire site, consumed by `repl/agent-repl.js`.
9
+ *
10
+ * - SessionStart — once, after sessionId is established
11
+ * - UserPromptSubmit — per user line, before agentLoop()
12
+ * - SessionEnd — once, on rl.close() before shutdown
13
+ *
14
+ * Semantics (matches existing PreToolUse convention):
15
+ * - Fire-and-forget: hook failures NEVER break the host flow
16
+ * - Observational only: no abort, no rewrite (can be added later)
17
+ * - No-op when hookDb is null (REPL without DB)
18
+ */
19
+
20
+ import { executeHooks, HookEvents } from "./hook-manager.js";
21
+
22
+ /**
23
+ * Events this helper is allowed to fire. Guards against typos that
24
+ * would otherwise silently no-op inside executeHooks' event filter.
25
+ */
26
+ export const SESSION_HOOK_EVENTS = Object.freeze([
27
+ HookEvents.SessionStart,
28
+ HookEvents.UserPromptSubmit,
29
+ HookEvents.SessionEnd,
30
+ ]);
31
+
32
+ /**
33
+ * Fire a session-level hook. Returns the raw results from executeHooks
34
+ * (array of {hookId, hookName, success, ...}) or an empty array if
35
+ * hookDb is missing, event is not allowed, or execution throws.
36
+ *
37
+ * @param {object|null} hookDb better-sqlite3 handle, or null to no-op
38
+ * @param {string} eventName one of SESSION_HOOK_EVENTS
39
+ * @param {object} [context] forwarded to hook matcher + handler env
40
+ * @returns {Promise<Array>}
41
+ */
42
+ export async function fireSessionHook(hookDb, eventName, context = {}) {
43
+ if (!hookDb) return [];
44
+ if (!SESSION_HOOK_EVENTS.includes(eventName)) {
45
+ throw new Error(
46
+ `fireSessionHook: event "${eventName}" is not a session hook. ` +
47
+ `Use one of: ${SESSION_HOOK_EVENTS.join(", ")}`,
48
+ );
49
+ }
50
+
51
+ try {
52
+ const enriched = {
53
+ timestamp: new Date().toISOString(),
54
+ ...context,
55
+ };
56
+ return await executeHooks(hookDb, eventName, enriched);
57
+ } catch (_err) {
58
+ // Hook failures must never break the REPL
59
+ return [];
60
+ }
61
+ }
@@ -13,6 +13,7 @@ import path from "path";
13
13
  import { fileURLToPath } from "url";
14
14
  import { getElectronUserDataDir } from "./paths.js";
15
15
  import { findProjectRoot } from "./project-detector.js";
16
+ import { parseSkillMcpServers } from "./skill-mcp.js";
16
17
 
17
18
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
18
19
 
@@ -225,6 +226,9 @@ export class CLISkillLoader {
225
226
  dirName: entry.name,
226
227
  hasHandler: fs.existsSync(path.join(dir, entry.name, "handler.js")),
227
228
  body,
229
+ // Skill-Embedded MCP: inline server declarations in a
230
+ // ```mcp-servers fenced code block. Empty array if absent.
231
+ mcpServers: parseSkillMcpServers(body),
228
232
  source: layer,
229
233
  skillDir: path.join(dir, entry.name),
230
234
  });