chainlesschain 0.45.70 → 0.45.74

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-BhJ3YFWt.js +1 -0
  6. package/src/assets/web-panel/assets/AppLayout-Cr2lWhF-.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-DaxTP3x8.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-CjlX4CrX.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-BCvgBkD3.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-BmwHBi9M.js +1 -0
  43. package/src/assets/web-panel/assets/index-DrmEk9S3.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
@@ -0,0 +1,434 @@
1
+ /**
2
+ * Plugin Manager — Plugin installation, management, and marketplace for CLI.
3
+ * Manages plugin lifecycle: install, enable, disable, remove, update.
4
+ *
5
+ * Canonical location (moved from src/lib/plugin-manager.js as part of the
6
+ * CLI Runtime Convergence roadmap, Phase 4). src/lib/plugin-manager.js is
7
+ * now a thin re-export shim for backwards compatibility.
8
+ */
9
+
10
+ import crypto from "crypto";
11
+ import fs from "fs";
12
+ import path from "path";
13
+ import { getElectronUserDataDir } from "../lib/paths.js";
14
+
15
+ /**
16
+ * Ensure plugin tables exist.
17
+ */
18
+ export function ensurePluginTables(db) {
19
+ db.exec(`
20
+ CREATE TABLE IF NOT EXISTS plugins (
21
+ id TEXT PRIMARY KEY,
22
+ name TEXT NOT NULL UNIQUE,
23
+ version TEXT NOT NULL,
24
+ description TEXT,
25
+ author TEXT,
26
+ homepage TEXT,
27
+ entry_point TEXT,
28
+ permissions TEXT,
29
+ status TEXT DEFAULT 'installed',
30
+ enabled INTEGER DEFAULT 1,
31
+ install_path TEXT,
32
+ installed_at TEXT DEFAULT (datetime('now')),
33
+ updated_at TEXT DEFAULT (datetime('now'))
34
+ )
35
+ `);
36
+ db.exec(`
37
+ CREATE TABLE IF NOT EXISTS plugin_settings (
38
+ id TEXT PRIMARY KEY,
39
+ plugin_id TEXT NOT NULL,
40
+ key TEXT NOT NULL,
41
+ value TEXT
42
+ )
43
+ `);
44
+ db.exec(`
45
+ CREATE TABLE IF NOT EXISTS plugin_skills (
46
+ id TEXT PRIMARY KEY,
47
+ plugin_name TEXT NOT NULL,
48
+ skill_name TEXT NOT NULL,
49
+ skill_path TEXT NOT NULL,
50
+ installed_at TEXT DEFAULT (datetime('now'))
51
+ )
52
+ `);
53
+ db.exec(`
54
+ CREATE TABLE IF NOT EXISTS plugin_registry (
55
+ name TEXT PRIMARY KEY,
56
+ latest_version TEXT,
57
+ description TEXT,
58
+ author TEXT,
59
+ downloads INTEGER DEFAULT 0,
60
+ rating REAL DEFAULT 0,
61
+ tags TEXT,
62
+ updated_at TEXT DEFAULT (datetime('now'))
63
+ )
64
+ `);
65
+ }
66
+
67
+ /**
68
+ * Install a plugin (record in DB).
69
+ */
70
+ export function installPlugin(db, pluginInfo) {
71
+ ensurePluginTables(db);
72
+ const {
73
+ name,
74
+ version,
75
+ description,
76
+ author,
77
+ homepage,
78
+ entryPoint,
79
+ permissions,
80
+ installPath,
81
+ } = pluginInfo;
82
+
83
+ if (!name || !version) {
84
+ throw new Error("Plugin name and version are required");
85
+ }
86
+
87
+ // Check if already installed
88
+ const existing = getPlugin(db, name);
89
+ if (existing) {
90
+ throw new Error(`Plugin already installed: ${name}`);
91
+ }
92
+
93
+ const id = `plugin-${crypto.randomBytes(8).toString("hex")}`;
94
+
95
+ db.prepare(
96
+ `INSERT INTO plugins (id, name, version, description, author, homepage, entry_point, permissions, status, enabled, install_path)
97
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
98
+ ).run(
99
+ id,
100
+ name,
101
+ version,
102
+ description || null,
103
+ author || null,
104
+ homepage || null,
105
+ entryPoint || null,
106
+ permissions ? JSON.stringify(permissions) : null,
107
+ "installed",
108
+ 1,
109
+ installPath || null,
110
+ );
111
+
112
+ return {
113
+ id,
114
+ name,
115
+ version,
116
+ description,
117
+ author,
118
+ status: "installed",
119
+ enabled: true,
120
+ };
121
+ }
122
+
123
+ /**
124
+ * Get a plugin by name.
125
+ */
126
+ export function getPlugin(db, name) {
127
+ ensurePluginTables(db);
128
+ return db.prepare("SELECT * FROM plugins WHERE name = ?").get(name);
129
+ }
130
+
131
+ /**
132
+ * Get a plugin by ID.
133
+ */
134
+ export function getPluginById(db, pluginId) {
135
+ ensurePluginTables(db);
136
+ return db.prepare("SELECT * FROM plugins WHERE id = ?").get(pluginId);
137
+ }
138
+
139
+ /**
140
+ * List all installed plugins.
141
+ */
142
+ export function listPlugins(db, options = {}) {
143
+ ensurePluginTables(db);
144
+ const { enabledOnly = false } = options;
145
+
146
+ if (enabledOnly) {
147
+ return db
148
+ .prepare("SELECT * FROM plugins WHERE enabled = 1 ORDER BY name ASC")
149
+ .all();
150
+ }
151
+ return db.prepare("SELECT * FROM plugins ORDER BY name ASC").all();
152
+ }
153
+
154
+ /**
155
+ * Enable a plugin.
156
+ */
157
+ export function enablePlugin(db, name) {
158
+ ensurePluginTables(db);
159
+ const result = db
160
+ .prepare("UPDATE plugins SET enabled = ?, status = ? WHERE name = ?")
161
+ .run(1, "installed", name);
162
+ return result.changes > 0;
163
+ }
164
+
165
+ /**
166
+ * Disable a plugin.
167
+ */
168
+ export function disablePlugin(db, name) {
169
+ ensurePluginTables(db);
170
+ const result = db
171
+ .prepare("UPDATE plugins SET enabled = ?, status = ? WHERE name = ?")
172
+ .run(0, "disabled", name);
173
+ return result.changes > 0;
174
+ }
175
+
176
+ /**
177
+ * Remove (uninstall) a plugin.
178
+ */
179
+ export function removePlugin(db, name) {
180
+ ensurePluginTables(db);
181
+ // Remove settings first
182
+ const plugin = getPlugin(db, name);
183
+ if (!plugin) return false;
184
+
185
+ db.prepare("DELETE FROM plugin_settings WHERE plugin_id = ?").run(plugin.id);
186
+ const result = db.prepare("DELETE FROM plugins WHERE name = ?").run(name);
187
+ return result.changes > 0;
188
+ }
189
+
190
+ /**
191
+ * Update a plugin version.
192
+ */
193
+ export function updatePlugin(db, name, newVersion) {
194
+ ensurePluginTables(db);
195
+ const result = db
196
+ .prepare(
197
+ "UPDATE plugins SET version = ?, updated_at = datetime('now') WHERE name = ?",
198
+ )
199
+ .run(newVersion, name);
200
+ return result.changes > 0;
201
+ }
202
+
203
+ /**
204
+ * Set a plugin setting.
205
+ */
206
+ export function setPluginSetting(db, pluginName, key, value) {
207
+ ensurePluginTables(db);
208
+ const plugin = getPlugin(db, pluginName);
209
+ if (!plugin) throw new Error(`Plugin not found: ${pluginName}`);
210
+
211
+ // Remove existing setting for this key
212
+ db.prepare("DELETE FROM plugin_settings WHERE plugin_id = ? AND key = ?").run(
213
+ plugin.id,
214
+ key,
215
+ );
216
+ const settingId = `ps-${crypto.randomBytes(4).toString("hex")}`;
217
+ db.prepare(
218
+ `INSERT INTO plugin_settings (id, plugin_id, key, value) VALUES (?, ?, ?, ?)`,
219
+ ).run(
220
+ settingId,
221
+ plugin.id,
222
+ key,
223
+ typeof value === "string" ? value : JSON.stringify(value),
224
+ );
225
+
226
+ return true;
227
+ }
228
+
229
+ /**
230
+ * Get a plugin setting.
231
+ */
232
+ export function getPluginSetting(db, pluginName, key) {
233
+ ensurePluginTables(db);
234
+ const plugin = getPlugin(db, pluginName);
235
+ if (!plugin) return null;
236
+
237
+ const row = db
238
+ .prepare(
239
+ "SELECT value FROM plugin_settings WHERE plugin_id = ? AND key = ?",
240
+ )
241
+ .get(plugin.id, key);
242
+ return row ? row.value : null;
243
+ }
244
+
245
+ /**
246
+ * Get all settings for a plugin.
247
+ */
248
+ export function getPluginSettings(db, pluginName) {
249
+ ensurePluginTables(db);
250
+ const plugin = getPlugin(db, pluginName);
251
+ if (!plugin) return {};
252
+
253
+ const rows = db
254
+ .prepare("SELECT key, value FROM plugin_settings WHERE plugin_id = ?")
255
+ .all(plugin.id);
256
+ const settings = {};
257
+ for (const row of rows) {
258
+ settings[row.key] = row.value;
259
+ }
260
+ return settings;
261
+ }
262
+
263
+ // ─── Registry / Marketplace ─────────────────────────────
264
+
265
+ /**
266
+ * Add/update a plugin in the registry.
267
+ */
268
+ export function registerInMarketplace(db, pluginInfo) {
269
+ ensurePluginTables(db);
270
+ const { name, latestVersion, description, author, tags } = pluginInfo;
271
+
272
+ db.prepare(
273
+ `INSERT OR REPLACE INTO plugin_registry (name, latest_version, description, author, tags)
274
+ VALUES (?, ?, ?, ?, ?)`,
275
+ ).run(
276
+ name,
277
+ latestVersion,
278
+ description || null,
279
+ author || null,
280
+ tags ? JSON.stringify(tags) : null,
281
+ );
282
+
283
+ return { name, latestVersion, description, author };
284
+ }
285
+
286
+ /**
287
+ * Search plugins in registry.
288
+ */
289
+ export function searchRegistry(db, query) {
290
+ ensurePluginTables(db);
291
+ return db
292
+ .prepare(
293
+ "SELECT * FROM plugin_registry WHERE name LIKE ? OR description LIKE ? ORDER BY downloads DESC",
294
+ )
295
+ .all(`%${query}%`, `%${query}%`);
296
+ }
297
+
298
+ /**
299
+ * List all registry plugins.
300
+ */
301
+ export function listRegistry(db) {
302
+ ensurePluginTables(db);
303
+ return db
304
+ .prepare("SELECT * FROM plugin_registry ORDER BY downloads DESC")
305
+ .all();
306
+ }
307
+
308
+ /**
309
+ * Get plugin summary.
310
+ */
311
+ export function getPluginSummary(db) {
312
+ ensurePluginTables(db);
313
+ const total = db.prepare("SELECT COUNT(*) as c FROM plugins").get();
314
+ const enabled = db
315
+ .prepare("SELECT COUNT(*) as c FROM plugins WHERE enabled = ?")
316
+ .get(1);
317
+ const registry = db
318
+ .prepare("SELECT COUNT(*) as c FROM plugin_registry")
319
+ .get();
320
+
321
+ return {
322
+ installed: total?.c || 0,
323
+ enabled: enabled?.c || 0,
324
+ registryCount: registry?.c || 0,
325
+ };
326
+ }
327
+
328
+ // ─── Plugin Skills ──────────────────────────────────────
329
+
330
+ /**
331
+ * Get the marketplace skills directory
332
+ */
333
+ function getMarketplaceSkillsDir() {
334
+ return path.join(getElectronUserDataDir(), "marketplace", "skills");
335
+ }
336
+
337
+ /**
338
+ * Install skills from a plugin manifest.
339
+ * Copies skill directories to the marketplace/skills/ layer.
340
+ *
341
+ * @param {object} db - Database instance
342
+ * @param {string} pluginName - Plugin name
343
+ * @param {string} pluginPath - Root path of the plugin package
344
+ * @param {{ name: string, path: string }[]} skills - Skills declared in manifest
345
+ * @returns {{ installed: string[] }} Names of installed skills
346
+ */
347
+ export function installPluginSkills(db, pluginName, pluginPath, skills) {
348
+ ensurePluginTables(db);
349
+ if (!skills || skills.length === 0) return { installed: [] };
350
+
351
+ const marketplaceDir = getMarketplaceSkillsDir();
352
+ const installed = [];
353
+
354
+ for (const skill of skills) {
355
+ const srcDir = path.resolve(pluginPath, skill.path);
356
+ if (!fs.existsSync(srcDir)) continue;
357
+
358
+ const destDir = path.join(marketplaceDir, skill.name);
359
+ fs.mkdirSync(destDir, { recursive: true });
360
+
361
+ // Copy skill files
362
+ _copyDirSync(srcDir, destDir);
363
+
364
+ // Record in DB
365
+ const id = `ps-${crypto.randomBytes(6).toString("hex")}`;
366
+ db.prepare(
367
+ `INSERT OR REPLACE INTO plugin_skills (id, plugin_name, skill_name, skill_path)
368
+ VALUES (?, ?, ?, ?)`,
369
+ ).run(id, pluginName, skill.name, destDir);
370
+
371
+ installed.push(skill.name);
372
+ }
373
+
374
+ return { installed };
375
+ }
376
+
377
+ /**
378
+ * Remove all skills installed by a plugin.
379
+ *
380
+ * @param {object} db - Database instance
381
+ * @param {string} pluginName - Plugin name
382
+ * @returns {{ removed: string[] }} Names of removed skills
383
+ */
384
+ export function removePluginSkills(db, pluginName) {
385
+ ensurePluginTables(db);
386
+ const rows = db
387
+ .prepare("SELECT * FROM plugin_skills WHERE plugin_name = ?")
388
+ .all(pluginName);
389
+
390
+ const removed = [];
391
+ for (const row of rows) {
392
+ // Remove the skill directory
393
+ if (fs.existsSync(row.skill_path)) {
394
+ fs.rmSync(row.skill_path, { recursive: true, force: true });
395
+ }
396
+ removed.push(row.skill_name);
397
+ }
398
+
399
+ db.prepare("DELETE FROM plugin_skills WHERE plugin_name = ?").run(pluginName);
400
+ return { removed };
401
+ }
402
+
403
+ /**
404
+ * List skills installed by a specific plugin.
405
+ *
406
+ * @param {object} db - Database instance
407
+ * @param {string} pluginName - Plugin name
408
+ * @returns {{ skill_name: string, skill_path: string }[]}
409
+ */
410
+ export function getPluginSkills(db, pluginName) {
411
+ ensurePluginTables(db);
412
+ return db
413
+ .prepare(
414
+ "SELECT skill_name, skill_path FROM plugin_skills WHERE plugin_name = ?",
415
+ )
416
+ .all(pluginName);
417
+ }
418
+
419
+ /**
420
+ * Recursively copy a directory
421
+ */
422
+ function _copyDirSync(src, dest) {
423
+ fs.mkdirSync(dest, { recursive: true });
424
+ const entries = fs.readdirSync(src, { withFileTypes: true });
425
+ for (const entry of entries) {
426
+ const srcPath = path.join(src, entry.name);
427
+ const destPath = path.join(dest, entry.name);
428
+ if (entry.isDirectory()) {
429
+ _copyDirSync(srcPath, destPath);
430
+ } else {
431
+ fs.copyFileSync(srcPath, destPath);
432
+ }
433
+ }
434
+ }