chainlesschain 0.45.67 → 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.
- package/package.json +1 -1
- package/src/assets/web-panel/.build-hash +1 -1
- package/src/assets/web-panel/assets/Analytics-B4OM8S8X.css +1 -0
- package/src/assets/web-panel/assets/Analytics-sBrYoc3A.js +3 -0
- package/src/assets/web-panel/assets/AppLayout-BhJ3YFWt.js +1 -0
- package/src/assets/web-panel/assets/AppLayout-Cr2lWhF-.css +1 -0
- package/src/assets/web-panel/assets/Backup-D68fenbD.js +1 -0
- package/src/assets/web-panel/assets/Backup-fZqtfC1m.css +1 -0
- package/src/assets/web-panel/assets/{Chat-DXtvKoM0.js → Chat-DaxTP3x8.js} +1 -1
- package/src/assets/web-panel/assets/{Cron-BJ4ODHOy.js → Cron-CNs03iHJ.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-BZd4wDPQ.js → Dashboard-CjlX4CrX.js} +2 -2
- package/src/assets/web-panel/assets/Git-CCMVr3Y8.js +2 -0
- package/src/assets/web-panel/assets/Git-DGcuBXST.css +1 -0
- package/src/assets/web-panel/assets/{Logs-CSeKZEG_.js → Logs-BY6A0UNG.js} +2 -2
- package/src/assets/web-panel/assets/{McpTools-BYQAK11r.js → McpTools-CrBVYlg6.js} +2 -2
- package/src/assets/web-panel/assets/{Memory-gkUAPyuZ.js → Memory-CWx3SpUt.js} +2 -2
- package/src/assets/web-panel/assets/{Notes-bjNrQgAo.js → Notes-1LcGD49x.js} +2 -2
- package/src/assets/web-panel/assets/Organization-DdOOM4ic.css +1 -0
- package/src/assets/web-panel/assets/Organization-Dx2DhbkM.js +4 -0
- package/src/assets/web-panel/assets/P2P-B16fjqfJ.js +2 -0
- package/src/assets/web-panel/assets/P2P-OEzOeMZX.css +1 -0
- package/src/assets/web-panel/assets/Permissions-BQbC9FzG.js +4 -0
- package/src/assets/web-panel/assets/Permissions-C9WlkGl-.css +1 -0
- package/src/assets/web-panel/assets/Projects-CjhZbNYm.js +2 -0
- package/src/assets/web-panel/assets/Projects-DxKelI5h.css +1 -0
- package/src/assets/web-panel/assets/Providers-BEakqcO5.css +1 -0
- package/src/assets/web-panel/assets/Providers-ivOAQtHM.js +2 -0
- package/src/assets/web-panel/assets/RssFeed-BlFC20eg.css +1 -0
- package/src/assets/web-panel/assets/RssFeed-BrsErdrU.js +3 -0
- package/src/assets/web-panel/assets/Security-DnEvJU5h.js +4 -0
- package/src/assets/web-panel/assets/Security-Dwxw7rfP.css +1 -0
- package/src/assets/web-panel/assets/{Services-CS0oMdxh.js → Services-7jQywNbl.js} +2 -2
- package/src/assets/web-panel/assets/Skills-BCvgBkD3.js +1 -0
- package/src/assets/web-panel/assets/{Tasks-qULws8pc.js → Tasks-CmJBC1cf.js} +1 -1
- package/src/assets/web-panel/assets/Templates-DOY_oZnm.css +1 -0
- package/src/assets/web-panel/assets/Templates-RXT8-DNk.js +1 -0
- package/src/assets/web-panel/assets/Wallet-3iYASEx_.js +4 -0
- package/src/assets/web-panel/assets/Wallet-DnIumafl.css +1 -0
- package/src/assets/web-panel/assets/WebAuthn-CNPl2VQR.css +1 -0
- package/src/assets/web-panel/assets/WebAuthn-s3Hzd9db.js +5 -0
- package/src/assets/web-panel/assets/{antd-CJSBocer.js → antd-gZyc63Qr.js} +114 -114
- package/src/assets/web-panel/assets/chat-BmwHBi9M.js +1 -0
- package/src/assets/web-panel/assets/index-DrmEk9S3.js +2 -0
- package/src/assets/web-panel/assets/{markdown-Bo5cVN4u.js → markdown-Bv7nG63L.js} +1 -1
- package/src/assets/web-panel/assets/ws-CU7Gvoom.js +1 -0
- package/src/assets/web-panel/index.html +2 -2
- package/src/commands/doctor.js +33 -151
- package/src/commands/mcp.js +1 -1
- package/src/commands/plugin.js +1 -1
- package/src/commands/session.js +106 -7
- package/src/commands/status.js +39 -69
- package/src/gateways/ws/message-dispatcher.js +9 -0
- package/src/gateways/ws/session-protocol.js +368 -1
- package/src/gateways/ws/ws-agent-handler.js +484 -0
- package/src/gateways/ws/ws-server.js +758 -4
- package/src/gateways/ws/ws-session-gateway.js +1432 -1
- package/src/harness/mcp-client.js +417 -0
- package/src/harness/mock-llm-provider.js +167 -0
- package/src/harness/plugin-manager.js +434 -0
- package/src/lib/agent-core.js +25 -1902
- package/src/lib/hashline.js +208 -0
- package/src/lib/jsonl-session-store.js +11 -0
- package/src/lib/mcp-client.js +14 -412
- package/src/lib/plugin-manager.js +29 -428
- package/src/lib/prompt-compressor.js +11 -0
- package/src/lib/session-hooks.js +61 -0
- package/src/lib/skill-loader.js +4 -0
- package/src/lib/skill-mcp.js +190 -0
- package/src/lib/workflow-state-reader.js +94 -0
- package/src/lib/ws-agent-handler.js +8 -472
- package/src/lib/ws-server.js +12 -726
- package/src/lib/ws-session-manager.js +8 -1178
- package/src/repl/agent-repl.js +27 -3
- package/src/runtime/agent-core.js +1760 -0
- package/src/runtime/agent-runtime.js +3 -1
- package/src/runtime/coding-agent-contract-shared.cjs +496 -0
- package/src/runtime/coding-agent-contract.js +49 -229
- package/src/runtime/coding-agent-events.cjs +14 -0
- package/src/runtime/coding-agent-policy.cjs +54 -5
- package/src/runtime/diagnostics.js +317 -0
- package/src/runtime/index.js +3 -0
- package/src/tools/index.js +3 -0
- package/src/tools/legacy-agent-tools.js +5 -0
- package/src/assets/web-panel/assets/AppLayout-B_tkw3Pn.js +0 -1
- package/src/assets/web-panel/assets/AppLayout-CFP4dGIJ.css +0 -1
- package/src/assets/web-panel/assets/Providers-Brm-S_hS.css +0 -1
- package/src/assets/web-panel/assets/Providers-Dbf57Tbv.js +0 -1
- package/src/assets/web-panel/assets/Skills-B2fgruv8.js +0 -1
- package/src/assets/web-panel/assets/chat-DnH09sSR.js +0 -1
- package/src/assets/web-panel/assets/index-IK-oro0g.js +0 -2
- 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
|
+
}
|