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
@@ -0,0 +1,190 @@
1
+ /**
2
+ * Skill-Embedded MCP — skills declare their MCP servers inline (v5.0.2.9)
3
+ *
4
+ * Inspired by oh-my-openagent's "Skill-Embedded MCPs" design: instead of
5
+ * forcing users to pre-register MCP servers in a DB, a skill can declare
6
+ * the MCP servers it needs directly in its SKILL.md body. When the skill
7
+ * is activated, its servers are mounted; when the skill exits, they are
8
+ * unmounted. This keeps the agent's tool list scoped and avoids tool
9
+ * explosion across 130+ skills.
10
+ *
11
+ * Declaration format — a fenced code block tagged `mcp-servers` in the
12
+ * SKILL.md body (NOT frontmatter, so no YAML parser changes are needed):
13
+ *
14
+ * ```mcp-servers
15
+ * [
16
+ * {
17
+ * "name": "weather",
18
+ * "command": "npx",
19
+ * "args": ["-y", "@modelcontextprotocol/server-weather"]
20
+ * }
21
+ * ]
22
+ * ```
23
+ *
24
+ * The block content must be a JSON array of server configs. Each config
25
+ * requires `name` and `command`; `args`, `env`, and `cwd` are optional.
26
+ *
27
+ * Pure functions only (except mount/unmount which take an MCPClient dep).
28
+ */
29
+
30
+ /**
31
+ * Parse MCP server declarations from a SKILL.md body.
32
+ * Returns an empty array if no `mcp-servers` code block is present.
33
+ *
34
+ * @param {string} body - The markdown body after YAML frontmatter
35
+ * @returns {Array<object>} Array of validated server configs (may be empty)
36
+ */
37
+ export function parseSkillMcpServers(body) {
38
+ if (typeof body !== "string" || body.length === 0) return [];
39
+
40
+ // Match fenced code blocks with info string "mcp-servers" or
41
+ // "json mcp-servers". We accept both because some users default to
42
+ // json-tagged blocks for editor highlighting.
43
+ const fenceRegex = /```(?:json\s+)?mcp-servers\s*\n([\s\S]*?)\n```/i;
44
+ const match = body.match(fenceRegex);
45
+ if (!match) return [];
46
+
47
+ let parsed;
48
+ try {
49
+ parsed = JSON.parse(match[1]);
50
+ } catch {
51
+ // Malformed JSON — return empty, let caller decide whether to warn
52
+ return [];
53
+ }
54
+
55
+ if (!Array.isArray(parsed)) return [];
56
+
57
+ const validated = [];
58
+ for (const entry of parsed) {
59
+ const normalized = validateMcpServerConfig(entry);
60
+ if (normalized) validated.push(normalized);
61
+ }
62
+ return validated;
63
+ }
64
+
65
+ /**
66
+ * Validate a single server config. Returns a normalized frozen object on
67
+ * success, or null on validation failure.
68
+ *
69
+ * @param {object} entry
70
+ * @returns {object | null}
71
+ */
72
+ export function validateMcpServerConfig(entry) {
73
+ if (!entry || typeof entry !== "object") return null;
74
+ if (typeof entry.name !== "string" || entry.name.trim().length === 0) {
75
+ return null;
76
+ }
77
+ if (typeof entry.command !== "string" || entry.command.trim().length === 0) {
78
+ return null;
79
+ }
80
+
81
+ const normalized = {
82
+ name: entry.name.trim(),
83
+ command: entry.command.trim(),
84
+ args: Array.isArray(entry.args)
85
+ ? entry.args.filter((a) => typeof a === "string")
86
+ : [],
87
+ };
88
+ if (entry.env && typeof entry.env === "object" && !Array.isArray(entry.env)) {
89
+ normalized.env = { ...entry.env };
90
+ }
91
+ if (typeof entry.cwd === "string" && entry.cwd.length > 0) {
92
+ normalized.cwd = entry.cwd;
93
+ }
94
+ return Object.freeze(normalized);
95
+ }
96
+
97
+ /**
98
+ * Mount a skill's declared MCP servers on an existing MCPClient.
99
+ * Servers that fail to connect are skipped (errors collected in the
100
+ * result). Returns a handle that can be passed to unmountSkillMcpServers.
101
+ *
102
+ * @param {object} mcpClient - An instance of MCPClient (must expose .connect)
103
+ * @param {object} skill - Skill metadata with `mcpServers` array
104
+ * @param {object} [opts]
105
+ * @param {(msg: string, err?: Error) => void} [opts.onWarn] - Warning hook
106
+ * @returns {Promise<{ mounted: string[], skipped: Array<{ name: string, error: string }> }>}
107
+ */
108
+ export async function mountSkillMcpServers(mcpClient, skill, opts = {}) {
109
+ const declared = Array.isArray(skill?.mcpServers) ? skill.mcpServers : [];
110
+ const mounted = [];
111
+ const skipped = [];
112
+
113
+ if (declared.length === 0) return { mounted, skipped };
114
+ if (!mcpClient || typeof mcpClient.connect !== "function") {
115
+ throw new Error("mountSkillMcpServers requires an MCPClient with .connect");
116
+ }
117
+
118
+ for (const server of declared) {
119
+ const normalized = validateMcpServerConfig(server);
120
+ if (!normalized) {
121
+ skipped.push({
122
+ name: server?.name || "(invalid)",
123
+ error: "invalid config",
124
+ });
125
+ continue;
126
+ }
127
+ try {
128
+ await mcpClient.connect(normalized.name, normalized);
129
+ mounted.push(normalized.name);
130
+ } catch (err) {
131
+ const message = err?.message || String(err);
132
+ skipped.push({ name: normalized.name, error: message });
133
+ if (typeof opts.onWarn === "function") {
134
+ opts.onWarn(
135
+ `[skill-mcp] Failed to mount "${normalized.name}" for skill "${skill?.id || skill?.name}": ${message}`,
136
+ err,
137
+ );
138
+ }
139
+ }
140
+ }
141
+
142
+ return { mounted, skipped };
143
+ }
144
+
145
+ /**
146
+ * Unmount previously-mounted skill MCP servers. Safe to call with an
147
+ * empty list or a handle from a failed mount.
148
+ *
149
+ * @param {object} mcpClient - An instance of MCPClient (must expose .disconnect)
150
+ * @param {string[]} mountedNames - Server names to unmount
151
+ * @returns {Promise<{ unmounted: string[], errors: Array<{ name: string, error: string }> }>}
152
+ */
153
+ export async function unmountSkillMcpServers(mcpClient, mountedNames) {
154
+ const names = Array.isArray(mountedNames) ? mountedNames : [];
155
+ const unmounted = [];
156
+ const errors = [];
157
+
158
+ if (names.length === 0) return { unmounted, errors };
159
+ if (!mcpClient || typeof mcpClient.disconnect !== "function") {
160
+ // Fall back to disconnectAll if present — still not a hard failure
161
+ if (typeof mcpClient?.disconnectAll === "function") {
162
+ try {
163
+ await mcpClient.disconnectAll();
164
+ return { unmounted: names.slice(), errors };
165
+ } catch (err) {
166
+ return {
167
+ unmounted,
168
+ errors: names.map((name) => ({
169
+ name,
170
+ error: err?.message || String(err),
171
+ })),
172
+ };
173
+ }
174
+ }
175
+ throw new Error(
176
+ "unmountSkillMcpServers requires an MCPClient with .disconnect or .disconnectAll",
177
+ );
178
+ }
179
+
180
+ for (const name of names) {
181
+ try {
182
+ await mcpClient.disconnect(name);
183
+ unmounted.push(name);
184
+ } catch (err) {
185
+ errors.push({ name, error: err?.message || String(err) });
186
+ }
187
+ }
188
+
189
+ return { unmounted, errors };
190
+ }
@@ -0,0 +1,94 @@
1
+ /**
2
+ * CLI-side read-only reader for Coding Agent workflow state.
3
+ *
4
+ * Mirrors the file layout written by
5
+ * `desktop-app-vue/src/main/ai-engine/code-agent/session-state-manager.js`
6
+ * but is intentionally read-only — mutation happens in the desktop main
7
+ * process or via the workflow skills. The CLI's job is to let users
8
+ * inspect what the agent has persisted.
9
+ *
10
+ * Layout:
11
+ * <projectRoot>/.chainlesschain/sessions/<sessionId>/
12
+ * ├── intent.md
13
+ * ├── plan.md (YAML frontmatter: approved, updated, session)
14
+ * ├── progress.log
15
+ * └── mode.json
16
+ */
17
+
18
+ import fs from "fs";
19
+ import path from "path";
20
+
21
+ function safeId(id) {
22
+ if (!id || typeof id !== "string" || !/^[A-Za-z0-9._-]+$/.test(id)) {
23
+ throw new Error(`Invalid sessionId: "${id}"`);
24
+ }
25
+ return id;
26
+ }
27
+
28
+ function sessionsRoot(projectRoot) {
29
+ return path.join(projectRoot, ".chainlesschain", "sessions");
30
+ }
31
+
32
+ export function listWorkflowSessions(projectRoot) {
33
+ const root = sessionsRoot(projectRoot);
34
+ if (!fs.existsSync(root)) {
35
+ return [];
36
+ }
37
+ return fs
38
+ .readdirSync(root, { withFileTypes: true })
39
+ .filter((e) => e.isDirectory())
40
+ .map((e) => {
41
+ const dir = path.join(root, e.name);
42
+ const mode = readMode(dir);
43
+ return {
44
+ sessionId: e.name,
45
+ stage: mode?.stage || null,
46
+ updatedAt: mode?.updatedAt || null,
47
+ hasIntent: fs.existsSync(path.join(dir, "intent.md")),
48
+ hasPlan: fs.existsSync(path.join(dir, "plan.md")),
49
+ approved: readPlanApproved(dir),
50
+ };
51
+ })
52
+ .sort((a, b) => (b.updatedAt || "").localeCompare(a.updatedAt || ""));
53
+ }
54
+
55
+ function readMode(dir) {
56
+ const file = path.join(dir, "mode.json");
57
+ if (!fs.existsSync(file)) return null;
58
+ try {
59
+ return JSON.parse(fs.readFileSync(file, "utf-8"));
60
+ } catch {
61
+ return null;
62
+ }
63
+ }
64
+
65
+ function readPlanApproved(dir) {
66
+ const file = path.join(dir, "plan.md");
67
+ if (!fs.existsSync(file)) return null;
68
+ const raw = fs.readFileSync(file, "utf-8");
69
+ const fm = raw.match(/^---\n([\s\S]*?)\n---\n/);
70
+ if (!fm) return false;
71
+ return /approved:\s*true/.test(fm[1]);
72
+ }
73
+
74
+ export function readWorkflowSession(projectRoot, sessionId) {
75
+ safeId(sessionId);
76
+ const dir = path.join(sessionsRoot(projectRoot), sessionId);
77
+ if (!fs.existsSync(dir)) {
78
+ return null;
79
+ }
80
+ const intentFile = path.join(dir, "intent.md");
81
+ const planFile = path.join(dir, "plan.md");
82
+ const logFile = path.join(dir, "progress.log");
83
+ return {
84
+ sessionId,
85
+ dir,
86
+ mode: readMode(dir),
87
+ intent: fs.existsSync(intentFile)
88
+ ? fs.readFileSync(intentFile, "utf-8")
89
+ : null,
90
+ plan: fs.existsSync(planFile) ? fs.readFileSync(planFile, "utf-8") : null,
91
+ planApproved: readPlanApproved(dir),
92
+ progress: fs.existsSync(logFile) ? fs.readFileSync(logFile, "utf-8") : "",
93
+ };
94
+ }