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
@@ -1,193 +1,18 @@
1
1
  import { ToolRegistry, createDefaultToolRegistry } from "../tools/registry.js";
2
- import sharedCodingAgentPolicy from "./coding-agent-policy.cjs";
2
+ import sharedContract from "./coding-agent-contract-shared.cjs";
3
+
4
+ const {
5
+ CODING_AGENT_EXTENSION_TOOL_NAMES,
6
+ CODING_AGENT_MVP_TOOL_NAMES,
7
+ getCodingAgentFunctionToolDefinition,
8
+ getCodingAgentFunctionToolDefinitions,
9
+ getCodingAgentToolContract,
10
+ getCodingAgentToolContracts,
11
+ getCodingAgentToolPolicy,
12
+ isCodingAgentMvpTool,
13
+ listCodingAgentToolNames,
14
+ } = sharedContract;
3
15
 
4
- const { TOOL_POLICY_METADATA } = sharedCodingAgentPolicy;
5
-
6
- const CODING_AGENT_TOOL_CONTRACTS = Object.freeze([
7
- {
8
- name: "read_file",
9
- title: "Read File",
10
- kind: "filesystem",
11
- tier: "mvp",
12
- ...TOOL_POLICY_METADATA.read_file,
13
- permissions: {
14
- level: "readonly",
15
- scopes: ["filesystem:read"],
16
- },
17
- telemetry: {
18
- category: "filesystem",
19
- tags: ["tool:read_file", "contract:coding-agent", "tier:mvp"],
20
- },
21
- },
22
- {
23
- name: "write_file",
24
- title: "Write File",
25
- kind: "filesystem",
26
- tier: "mvp",
27
- ...TOOL_POLICY_METADATA.write_file,
28
- permissions: {
29
- level: "elevated",
30
- scopes: ["filesystem:write"],
31
- },
32
- telemetry: {
33
- category: "filesystem",
34
- tags: ["tool:write_file", "contract:coding-agent", "tier:mvp"],
35
- },
36
- },
37
- {
38
- name: "edit_file",
39
- title: "Edit File",
40
- kind: "filesystem",
41
- tier: "mvp",
42
- ...TOOL_POLICY_METADATA.edit_file,
43
- permissions: {
44
- level: "elevated",
45
- scopes: ["filesystem:write"],
46
- },
47
- telemetry: {
48
- category: "filesystem",
49
- tags: ["tool:edit_file", "contract:coding-agent", "tier:mvp"],
50
- },
51
- },
52
- {
53
- name: "run_shell",
54
- title: "Run Shell",
55
- kind: "shell",
56
- tier: "mvp",
57
- ...TOOL_POLICY_METADATA.run_shell,
58
- runtimeDescriptor: "shell",
59
- permissions: {
60
- level: "elevated",
61
- scopes: ["process:spawn", "filesystem:workspace"],
62
- },
63
- telemetry: {
64
- category: "shell",
65
- tags: ["tool:run_shell", "contract:coding-agent", "tier:mvp"],
66
- },
67
- },
68
- {
69
- name: "git",
70
- title: "Git",
71
- kind: "git",
72
- tier: "mvp",
73
- ...TOOL_POLICY_METADATA.git,
74
- runtimeDescriptor: "git",
75
- permissions: {
76
- level: "elevated",
77
- scopes: ["process:spawn", "filesystem:workspace", "vcs:git"],
78
- },
79
- telemetry: {
80
- category: "git",
81
- tags: ["tool:git", "contract:coding-agent", "tier:mvp"],
82
- },
83
- },
84
- {
85
- name: "search_files",
86
- title: "Search Files",
87
- kind: "filesystem",
88
- tier: "mvp",
89
- ...TOOL_POLICY_METADATA.search_files,
90
- permissions: {
91
- level: "readonly",
92
- scopes: ["filesystem:read", "search:content"],
93
- },
94
- telemetry: {
95
- category: "filesystem",
96
- tags: ["tool:search_files", "contract:coding-agent", "tier:mvp"],
97
- },
98
- },
99
- {
100
- name: "list_dir",
101
- title: "List Directory",
102
- kind: "filesystem",
103
- tier: "mvp",
104
- ...TOOL_POLICY_METADATA.list_dir,
105
- permissions: {
106
- level: "readonly",
107
- scopes: ["filesystem:read"],
108
- },
109
- telemetry: {
110
- category: "filesystem",
111
- tags: ["tool:list_dir", "contract:coding-agent", "tier:mvp"],
112
- },
113
- },
114
- {
115
- name: "run_skill",
116
- title: "Run Skill",
117
- kind: "skill",
118
- tier: "extension",
119
- ...TOOL_POLICY_METADATA.run_skill,
120
- permissions: {
121
- level: "standard",
122
- scopes: ["skill:invoke"],
123
- },
124
- telemetry: {
125
- category: "skill",
126
- tags: ["tool:run_skill", "contract:coding-agent", "tier:extension"],
127
- },
128
- },
129
- {
130
- name: "list_skills",
131
- title: "List Skills",
132
- kind: "skill",
133
- tier: "extension",
134
- ...TOOL_POLICY_METADATA.list_skills,
135
- permissions: {
136
- level: "readonly",
137
- scopes: ["skill:read"],
138
- },
139
- telemetry: {
140
- category: "skill",
141
- tags: ["tool:list_skills", "contract:coding-agent", "tier:extension"],
142
- },
143
- },
144
- {
145
- name: "run_code",
146
- title: "Run Code",
147
- kind: "code",
148
- tier: "extension",
149
- ...TOOL_POLICY_METADATA.run_code,
150
- permissions: {
151
- level: "elevated",
152
- scopes: ["process:spawn", "filesystem:workspace", "runtime:script"],
153
- },
154
- telemetry: {
155
- category: "code",
156
- tags: ["tool:run_code", "contract:coding-agent", "tier:extension"],
157
- },
158
- },
159
- {
160
- name: "spawn_sub_agent",
161
- title: "Spawn Sub Agent",
162
- kind: "agent",
163
- tier: "extension",
164
- ...TOOL_POLICY_METADATA.spawn_sub_agent,
165
- permissions: {
166
- level: "elevated",
167
- scopes: ["agent:spawn"],
168
- },
169
- telemetry: {
170
- category: "agent",
171
- tags: ["tool:spawn_sub_agent", "contract:coding-agent", "tier:extension"],
172
- },
173
- },
174
- ]);
175
-
176
- export const CODING_AGENT_MVP_TOOL_NAMES = Object.freeze(
177
- CODING_AGENT_TOOL_CONTRACTS.filter((tool) => tool.tier === "mvp").map(
178
- (tool) => tool.name,
179
- ),
180
- );
181
-
182
- export const CODING_AGENT_EXTENSION_TOOL_NAMES = Object.freeze(
183
- CODING_AGENT_TOOL_CONTRACTS.filter((tool) => tool.tier === "extension").map(
184
- (tool) => tool.name,
185
- ),
186
- );
187
-
188
- const TOOL_CONTRACT_MAP = new Map(
189
- CODING_AGENT_TOOL_CONTRACTS.map((tool) => [tool.name, tool]),
190
- );
191
16
  const runtimeRegistry = createDefaultToolRegistry();
192
17
 
193
18
  function cloneValue(value) {
@@ -216,44 +41,17 @@ function getFallbackToolContract(name) {
216
41
  };
217
42
  }
218
43
 
219
- export function getCodingAgentToolContract(name) {
220
- const tool = TOOL_CONTRACT_MAP.get(name);
221
- return tool ? cloneValue(tool) : null;
222
- }
223
-
224
- export function getCodingAgentToolContracts({ tier = null } = {}) {
225
- return CODING_AGENT_TOOL_CONTRACTS.filter((tool) => {
226
- if (tier && tool.tier !== tier) {
227
- return false;
228
- }
229
- return true;
230
- }).map(cloneValue);
231
- }
232
-
233
- export function listCodingAgentToolNames({ tier = null } = {}) {
234
- return getCodingAgentToolContracts({ tier }).map((tool) => tool.name);
235
- }
236
-
237
- export function isCodingAgentMvpTool(name) {
238
- return CODING_AGENT_MVP_TOOL_NAMES.includes(name);
239
- }
240
-
241
- export function getCodingAgentToolPolicy(name) {
242
- const tool = TOOL_CONTRACT_MAP.get(name);
243
- if (!tool) {
244
- return null;
245
- }
246
-
247
- return {
248
- tier: tool.tier,
249
- riskLevel: tool.riskLevel,
250
- availableInPlanMode: tool.availableInPlanMode,
251
- planModeBehavior: tool.planModeBehavior || "standard",
252
- requiresPlanApproval: tool.requiresPlanApproval,
253
- approvalFlow: tool.approvalFlow,
254
- permissions: cloneValue(tool.permissions),
255
- };
256
- }
44
+ export {
45
+ CODING_AGENT_EXTENSION_TOOL_NAMES,
46
+ CODING_AGENT_MVP_TOOL_NAMES,
47
+ getCodingAgentFunctionToolDefinition,
48
+ getCodingAgentFunctionToolDefinitions,
49
+ getCodingAgentToolContract,
50
+ getCodingAgentToolContracts,
51
+ getCodingAgentToolPolicy,
52
+ isCodingAgentMvpTool,
53
+ listCodingAgentToolNames,
54
+ };
257
55
 
258
56
  export function mapCodingAgentToolDefinition(definition = {}, options = {}) {
259
57
  const fn = definition.function || {};
@@ -269,8 +67,14 @@ export function mapCodingAgentToolDefinition(definition = {}, options = {}) {
269
67
  name,
270
68
  title: contract.title || name,
271
69
  kind: contract.kind,
272
- description: fn.description || "",
273
- schema: fn.parameters || { type: "object", properties: {} },
70
+ description: fn.description || contract.description || "",
71
+ schema: fn.parameters ||
72
+ (contract.inputSchema
73
+ ? cloneValue(contract.inputSchema)
74
+ : {
75
+ type: "object",
76
+ properties: {},
77
+ }),
274
78
  permissions: cloneValue(contract.permissions),
275
79
  telemetry: cloneValue(contract.telemetry),
276
80
  source: options.source || "agent-core",
@@ -286,9 +90,25 @@ export function createCodingAgentToolRegistry(definitions = [], options = {}) {
286
90
  }
287
91
 
288
92
  export function getCodingAgentRuntimeDescriptor(toolName) {
289
- const descriptorName = TOOL_CONTRACT_MAP.get(toolName)?.runtimeDescriptor;
93
+ const descriptorName = getCodingAgentToolContract(toolName)?.runtimeDescriptor;
290
94
  if (!descriptorName) {
291
95
  return null;
292
96
  }
293
97
  return runtimeRegistry.get(descriptorName) || null;
294
98
  }
99
+
100
+ export function getCodingAgentRuntimeDescriptorByCommand(command) {
101
+ const trimmed = String(command || "").trim();
102
+ if (!trimmed) {
103
+ return null;
104
+ }
105
+
106
+ const parts = trimmed.split(/\s+/);
107
+ if (parts[0] === "git") {
108
+ return runtimeRegistry.get("git") || null;
109
+ }
110
+ if (parts[0] === "mcp" || parts.includes("mcp")) {
111
+ return runtimeRegistry.get("mcp") || null;
112
+ }
113
+ return runtimeRegistry.get("shell") || null;
114
+ }
@@ -74,6 +74,16 @@ const TOOL_POLICY_METADATA = Object.freeze({
74
74
  approvalFlow: "plan",
75
75
  isReadOnly: false,
76
76
  },
77
+ edit_file_hashed: {
78
+ riskLevel: RISK_LEVELS.MEDIUM,
79
+ category: TOOL_CATEGORIES.WRITE,
80
+ availableInPlanMode: false,
81
+ planModeBehavior: "blocked",
82
+ requiresPlanApproval: true,
83
+ requiresConfirmation: false,
84
+ approvalFlow: "plan",
85
+ isReadOnly: false,
86
+ },
77
87
  write_file: {
78
88
  riskLevel: RISK_LEVELS.MEDIUM,
79
89
  category: TOOL_CATEGORIES.WRITE,
@@ -193,14 +203,53 @@ function resolveToolPolicy(toolName, descriptor = null) {
193
203
  base.riskLevel = normalizeRiskLevel(descriptor.riskLevel, base.riskLevel);
194
204
  }
195
205
 
206
+ if (descriptor?.category) {
207
+ base.category = descriptor.category;
208
+ }
209
+
210
+ if (typeof descriptor?.availableInPlanMode === "boolean") {
211
+ base.availableInPlanMode = descriptor.availableInPlanMode;
212
+ }
213
+
214
+ if (descriptor?.planModeBehavior) {
215
+ base.planModeBehavior = descriptor.planModeBehavior;
216
+ }
217
+
218
+ if (typeof descriptor?.requiresPlanApproval === "boolean") {
219
+ base.requiresPlanApproval = descriptor.requiresPlanApproval;
220
+ }
221
+
222
+ if (typeof descriptor?.requiresConfirmation === "boolean") {
223
+ base.requiresConfirmation = descriptor.requiresConfirmation;
224
+ }
225
+
226
+ if (descriptor?.approvalFlow) {
227
+ base.approvalFlow = descriptor.approvalFlow;
228
+ }
229
+
230
+ if (Array.isArray(descriptor?.readOnlySubcommands)) {
231
+ base.readOnlySubcommands = [...descriptor.readOnlySubcommands];
232
+ }
233
+
196
234
  if (descriptor?.isReadOnly === true) {
197
235
  base.isReadOnly = true;
198
236
  base.riskLevel = RISK_LEVELS.LOW;
199
- base.availableInPlanMode = true;
200
- base.planModeBehavior = "allow";
201
- base.requiresPlanApproval = false;
202
- base.requiresConfirmation = false;
203
- base.approvalFlow = "auto";
237
+ base.category = descriptor?.category || TOOL_CATEGORIES.READ;
238
+ if (typeof descriptor?.availableInPlanMode !== "boolean") {
239
+ base.availableInPlanMode = true;
240
+ }
241
+ if (!descriptor?.planModeBehavior) {
242
+ base.planModeBehavior = "allow";
243
+ }
244
+ if (typeof descriptor?.requiresPlanApproval !== "boolean") {
245
+ base.requiresPlanApproval = false;
246
+ }
247
+ if (typeof descriptor?.requiresConfirmation !== "boolean") {
248
+ base.requiresConfirmation = false;
249
+ }
250
+ if (!descriptor?.approvalFlow) {
251
+ base.approvalFlow = "auto";
252
+ }
204
253
  }
205
254
 
206
255
  return base;
@@ -0,0 +1,317 @@
1
+ /**
2
+ * Runtime diagnostics — machine-readable doctor/status reports.
3
+ *
4
+ * Canonical data collectors for `chainlesschain doctor` and
5
+ * `chainlesschain status`. These return plain JSON-serializable
6
+ * objects that both the human-readable CLI renderers and external
7
+ * consumers (IDE integrations, monitoring) can rely on.
8
+ *
9
+ * Introduced as part of the CLI Runtime Convergence roadmap (Phase 5,
10
+ * 2026-04-09) per ADR decision D6 (machine-readable doctor/status JSON).
11
+ *
12
+ * Refs: docs/implementation-plans/CLI_RUNTIME_CONVERGENCE_ADR.md
13
+ */
14
+
15
+ import { execSync } from "node:child_process";
16
+ import { existsSync, readdirSync } from "node:fs";
17
+ import { createConnection } from "node:net";
18
+ import semver from "semver";
19
+
20
+ import { MIN_NODE_VERSION, DEFAULT_PORTS, VERSION } from "../constants.js";
21
+ import { getHomeDir, getConfigPath, getBinDir } from "../lib/paths.js";
22
+ import {
23
+ isDockerAvailable,
24
+ isDockerComposeAvailable,
25
+ getServiceStatus,
26
+ findComposeFile,
27
+ } from "../lib/service-manager.js";
28
+ import { loadConfig } from "../lib/config-manager.js";
29
+ import { isAppRunning, getAppPid } from "../lib/process-manager.js";
30
+
31
+ /**
32
+ * Probe a TCP port. Resolves true if something is listening.
33
+ */
34
+ export function checkPort(port, host = "127.0.0.1", timeoutMs = 1000) {
35
+ return new Promise((resolve) => {
36
+ const socket = createConnection({ port, host, timeout: timeoutMs });
37
+ socket.on("connect", () => {
38
+ socket.destroy();
39
+ resolve(true);
40
+ });
41
+ socket.on("error", () => resolve(false));
42
+ socket.on("timeout", () => {
43
+ socket.destroy();
44
+ resolve(false);
45
+ });
46
+ });
47
+ }
48
+
49
+ function readdirSafe(dir) {
50
+ try {
51
+ return readdirSync(dir);
52
+ } catch {
53
+ return [];
54
+ }
55
+ }
56
+
57
+ function safeExec(cmd) {
58
+ try {
59
+ return execSync(cmd, { encoding: "utf-8" }).trim();
60
+ } catch {
61
+ return null;
62
+ }
63
+ }
64
+
65
+ /**
66
+ * Collect a machine-readable doctor report.
67
+ *
68
+ * Shape:
69
+ * {
70
+ * schema: "chainlesschain.doctor.v1",
71
+ * version: string, // CLI version
72
+ * generatedAt: string, // ISO timestamp
73
+ * checks: Array<{ id, name, ok, optional, detail }>,
74
+ * ports: Array<{ name, port, open }>,
75
+ * disk: { homeDir, freeGB } | null,
76
+ * summary: { total, passed, failed, criticalFailed }
77
+ * }
78
+ */
79
+ export async function collectDoctorReport() {
80
+ const checks = [];
81
+
82
+ // Node.js
83
+ const nodeVersion = process.versions.node;
84
+ const nodeOk = semver.gte(nodeVersion, MIN_NODE_VERSION);
85
+ checks.push({
86
+ id: "node",
87
+ name: `Node.js ${nodeVersion}`,
88
+ ok: nodeOk,
89
+ optional: false,
90
+ detail: nodeOk ? "" : `Requires >=${MIN_NODE_VERSION}`,
91
+ });
92
+
93
+ // npm
94
+ const npmVersion = safeExec("npm --version");
95
+ checks.push({
96
+ id: "npm",
97
+ name: npmVersion ? `npm ${npmVersion}` : "npm",
98
+ ok: Boolean(npmVersion),
99
+ optional: false,
100
+ detail: npmVersion ? "" : "Not found",
101
+ });
102
+
103
+ // Docker
104
+ const dockerOk = isDockerAvailable();
105
+ checks.push({
106
+ id: "docker",
107
+ name: "Docker",
108
+ ok: dockerOk,
109
+ optional: true,
110
+ detail: dockerOk ? "" : "Not installed (optional)",
111
+ });
112
+
113
+ const composeOk = isDockerComposeAvailable();
114
+ checks.push({
115
+ id: "docker-compose",
116
+ name: "Docker Compose",
117
+ ok: composeOk,
118
+ optional: true,
119
+ detail: composeOk ? "" : "Not installed (optional)",
120
+ });
121
+
122
+ // Git
123
+ const gitVersion = safeExec("git --version");
124
+ checks.push({
125
+ id: "git",
126
+ name: gitVersion || "Git",
127
+ ok: Boolean(gitVersion),
128
+ optional: false,
129
+ detail: gitVersion ? "" : "Not found",
130
+ });
131
+
132
+ // Config directory
133
+ const homeDir = getHomeDir();
134
+ const homeOk = existsSync(homeDir);
135
+ checks.push({
136
+ id: "config-dir",
137
+ name: `Config dir: ${homeDir}`,
138
+ ok: homeOk,
139
+ optional: false,
140
+ detail: homeOk ? "" : 'Run "chainlesschain setup"',
141
+ });
142
+
143
+ // Config file
144
+ const configPath = getConfigPath();
145
+ const configOk = existsSync(configPath);
146
+ checks.push({
147
+ id: "config-file",
148
+ name: "Config file",
149
+ ok: configOk,
150
+ optional: false,
151
+ detail: configOk ? "" : 'Run "chainlesschain setup"',
152
+ });
153
+
154
+ // Desktop binary
155
+ const binDir = getBinDir();
156
+ const hasBin = existsSync(binDir) && readdirSafe(binDir).length > 0;
157
+ checks.push({
158
+ id: "desktop-binary",
159
+ name: "Desktop binary",
160
+ ok: hasBin,
161
+ optional: true,
162
+ detail: hasBin
163
+ ? ""
164
+ : 'Run "chainlesschain setup" or "chainlesschain update"',
165
+ });
166
+
167
+ // Setup completed
168
+ let setupCompleted = false;
169
+ try {
170
+ setupCompleted = Boolean(loadConfig().setupCompleted);
171
+ } catch {
172
+ // loadConfig may throw if config missing
173
+ }
174
+ checks.push({
175
+ id: "setup-completed",
176
+ name: "Setup completed",
177
+ ok: setupCompleted,
178
+ optional: false,
179
+ detail: setupCompleted ? "" : 'Run "chainlesschain setup"',
180
+ });
181
+
182
+ // Port scan
183
+ const ports = [];
184
+ for (const [name, port] of Object.entries(DEFAULT_PORTS)) {
185
+ const open = await checkPort(port);
186
+ ports.push({ name, port, open });
187
+ }
188
+
189
+ // Disk (Node 22+ statfsSync)
190
+ let disk = null;
191
+ try {
192
+ const { statfsSync } = await import("node:fs");
193
+ if (typeof statfsSync === "function") {
194
+ const stats = statfsSync(homeDir);
195
+ const freeGB = (stats.bavail * stats.bsize) / (1024 * 1024 * 1024);
196
+ disk = { homeDir, freeGB: Number(freeGB.toFixed(2)) };
197
+ }
198
+ } catch {
199
+ // statfsSync unavailable on some platforms
200
+ }
201
+
202
+ const failed = checks.filter((c) => !c.ok);
203
+ const criticalFailed = failed.filter((c) => !c.optional);
204
+
205
+ return {
206
+ schema: "chainlesschain.doctor.v1",
207
+ version: VERSION,
208
+ generatedAt: new Date().toISOString(),
209
+ checks,
210
+ ports,
211
+ disk,
212
+ summary: {
213
+ total: checks.length,
214
+ passed: checks.length - failed.length,
215
+ failed: failed.length,
216
+ criticalFailed: criticalFailed.length,
217
+ },
218
+ };
219
+ }
220
+
221
+ /**
222
+ * Collect a machine-readable status report.
223
+ *
224
+ * Shape:
225
+ * {
226
+ * schema: "chainlesschain.status.v1",
227
+ * version: string,
228
+ * generatedAt: string,
229
+ * app: { running, pid },
230
+ * setup: { completed, completedAt, edition, llm: { provider, model } } | null,
231
+ * docker: {
232
+ * available,
233
+ * composePath: string | null,
234
+ * services: Array<{ name, state }> | null,
235
+ * note: string | null
236
+ * },
237
+ * ports: Array<{ name, port, open }>
238
+ * }
239
+ */
240
+ export async function collectStatusReport() {
241
+ let config = {};
242
+ try {
243
+ config = loadConfig();
244
+ } catch {
245
+ // loadConfig may throw if config missing
246
+ }
247
+
248
+ // App
249
+ const running = isAppRunning();
250
+ const app = {
251
+ running,
252
+ pid: running ? getAppPid() : null,
253
+ };
254
+
255
+ // Setup
256
+ const setup = config.setupCompleted
257
+ ? {
258
+ completed: true,
259
+ completedAt: config.completedAt || null,
260
+ edition: config.edition || null,
261
+ llm: config.llm
262
+ ? {
263
+ provider: config.llm.provider || null,
264
+ model: config.llm.model || null,
265
+ }
266
+ : null,
267
+ }
268
+ : { completed: false };
269
+
270
+ // Docker services
271
+ const dockerAvailable = isDockerAvailable();
272
+ const docker = {
273
+ available: dockerAvailable,
274
+ composePath: null,
275
+ services: null,
276
+ note: null,
277
+ };
278
+
279
+ if (!dockerAvailable) {
280
+ docker.note = "Docker not available";
281
+ } else {
282
+ const composePath = findComposeFile([process.cwd(), "backend/docker"]);
283
+ if (!composePath) {
284
+ docker.note = "docker-compose.yml not found";
285
+ } else {
286
+ docker.composePath = composePath;
287
+ const raw = getServiceStatus(composePath);
288
+ if (Array.isArray(raw)) {
289
+ docker.services = raw.map((svc) => ({
290
+ name: svc.Service || svc.Name || null,
291
+ state: svc.State || null,
292
+ }));
293
+ } else if (raw) {
294
+ docker.note = String(raw);
295
+ } else {
296
+ docker.note = "No services running";
297
+ }
298
+ }
299
+ }
300
+
301
+ // Ports
302
+ const ports = [];
303
+ for (const [name, port] of Object.entries(DEFAULT_PORTS)) {
304
+ const open = await checkPort(port);
305
+ ports.push({ name, port, open });
306
+ }
307
+
308
+ return {
309
+ schema: "chainlesschain.status.v1",
310
+ version: VERSION,
311
+ generatedAt: new Date().toISOString(),
312
+ app,
313
+ setup,
314
+ docker,
315
+ ports,
316
+ };
317
+ }
@@ -15,7 +15,10 @@ export {
15
15
  CODING_AGENT_MVP_TOOL_NAMES,
16
16
  CODING_AGENT_EXTENSION_TOOL_NAMES,
17
17
  createCodingAgentToolRegistry,
18
+ getCodingAgentFunctionToolDefinition,
19
+ getCodingAgentFunctionToolDefinitions,
18
20
  getCodingAgentRuntimeDescriptor,
21
+ getCodingAgentRuntimeDescriptorByCommand,
19
22
  getCodingAgentToolContract,
20
23
  getCodingAgentToolContracts,
21
24
  getCodingAgentToolPolicy,