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.
- 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-2RCrdXxl.js +1 -0
- package/src/assets/web-panel/assets/AppLayout-D9pBLPC3.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-B2nB8o_F.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-DanoHPSI.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-CLlblJcG.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-DWBA4-cl.js +1 -0
- package/src/assets/web-panel/assets/index-CyGtHm63.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/session-protocol.js +1 -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 -756
- package/src/lib/ws-session-manager.js +8 -1417
- 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-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
package/src/lib/agent-core.js
CHANGED
|
@@ -1,1903 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
import { findProjectRoot, loadProjectConfig } from "./project-detector.js";
|
|
28
|
-
import { SubAgentContext } from "./sub-agent-context.js";
|
|
29
|
-
import {
|
|
30
|
-
createLegacyAgentToolRegistry,
|
|
31
|
-
getRuntimeToolDescriptor,
|
|
32
|
-
} from "../tools/legacy-agent-tools.js";
|
|
33
|
-
import { createToolContext } from "../tools/tool-context.js";
|
|
34
|
-
import { createToolTelemetryRecord } from "../tools/tool-telemetry.js";
|
|
35
|
-
import { DEFAULT_TOOL_DESCRIPTORS } from "../tools/registry.js";
|
|
36
|
-
import { isAbortError, throwIfAborted } from "./abort-utils.js";
|
|
37
|
-
|
|
38
|
-
const { isReadOnlyGitCommand, normalizeGitCommand } = sharedCodingAgentPolicy;
|
|
39
|
-
const { evaluateShellCommandPolicy } = sharedShellPolicy;
|
|
40
|
-
|
|
41
|
-
// ─── Tool definitions ────────────────────────────────────────────────────
|
|
42
|
-
|
|
43
|
-
export const AGENT_TOOLS = [
|
|
44
|
-
{
|
|
45
|
-
type: "function",
|
|
46
|
-
function: {
|
|
47
|
-
name: "read_file",
|
|
48
|
-
description: "Read a file's content",
|
|
49
|
-
parameters: {
|
|
50
|
-
type: "object",
|
|
51
|
-
properties: {
|
|
52
|
-
path: { type: "string", description: "File path to read" },
|
|
53
|
-
},
|
|
54
|
-
required: ["path"],
|
|
55
|
-
},
|
|
56
|
-
},
|
|
57
|
-
},
|
|
58
|
-
{
|
|
59
|
-
type: "function",
|
|
60
|
-
function: {
|
|
61
|
-
name: "write_file",
|
|
62
|
-
description: "Write content to a file (create or overwrite)",
|
|
63
|
-
parameters: {
|
|
64
|
-
type: "object",
|
|
65
|
-
properties: {
|
|
66
|
-
path: { type: "string", description: "File path" },
|
|
67
|
-
content: { type: "string", description: "File content" },
|
|
68
|
-
},
|
|
69
|
-
required: ["path", "content"],
|
|
70
|
-
},
|
|
71
|
-
},
|
|
72
|
-
},
|
|
73
|
-
{
|
|
74
|
-
type: "function",
|
|
75
|
-
function: {
|
|
76
|
-
name: "edit_file",
|
|
77
|
-
description: "Replace a specific string in a file with new content",
|
|
78
|
-
parameters: {
|
|
79
|
-
type: "object",
|
|
80
|
-
properties: {
|
|
81
|
-
path: { type: "string", description: "File path" },
|
|
82
|
-
old_string: {
|
|
83
|
-
type: "string",
|
|
84
|
-
description: "Exact string to find and replace",
|
|
85
|
-
},
|
|
86
|
-
new_string: {
|
|
87
|
-
type: "string",
|
|
88
|
-
description: "Replacement string",
|
|
89
|
-
},
|
|
90
|
-
},
|
|
91
|
-
required: ["path", "old_string", "new_string"],
|
|
92
|
-
},
|
|
93
|
-
},
|
|
94
|
-
},
|
|
95
|
-
{
|
|
96
|
-
type: "function",
|
|
97
|
-
function: {
|
|
98
|
-
name: "run_shell",
|
|
99
|
-
description:
|
|
100
|
-
"Execute a shell command and return the output. Use for running tests, linting, builds, and other non-git workspace commands.",
|
|
101
|
-
parameters: {
|
|
102
|
-
type: "object",
|
|
103
|
-
properties: {
|
|
104
|
-
command: { type: "string", description: "Shell command to execute" },
|
|
105
|
-
cwd: {
|
|
106
|
-
type: "string",
|
|
107
|
-
description: "Working directory (optional)",
|
|
108
|
-
},
|
|
109
|
-
},
|
|
110
|
-
required: ["command"],
|
|
111
|
-
},
|
|
112
|
-
},
|
|
113
|
-
},
|
|
114
|
-
{
|
|
115
|
-
type: "function",
|
|
116
|
-
function: {
|
|
117
|
-
name: "git",
|
|
118
|
-
description:
|
|
119
|
-
"Run a git command inside the workspace. Use this instead of run_shell for git status, diff, log, commit, branch, and related repository operations.",
|
|
120
|
-
parameters: {
|
|
121
|
-
type: "object",
|
|
122
|
-
properties: {
|
|
123
|
-
command: {
|
|
124
|
-
type: "string",
|
|
125
|
-
description:
|
|
126
|
-
'Git subcommand to execute, for example "status", "diff --stat", or "log --oneline -5"',
|
|
127
|
-
},
|
|
128
|
-
cwd: {
|
|
129
|
-
type: "string",
|
|
130
|
-
description: "Working directory (optional)",
|
|
131
|
-
},
|
|
132
|
-
},
|
|
133
|
-
required: ["command"],
|
|
134
|
-
},
|
|
135
|
-
},
|
|
136
|
-
},
|
|
137
|
-
{
|
|
138
|
-
type: "function",
|
|
139
|
-
function: {
|
|
140
|
-
name: "search_files",
|
|
141
|
-
description: "Search for files by name pattern or content",
|
|
142
|
-
parameters: {
|
|
143
|
-
type: "object",
|
|
144
|
-
properties: {
|
|
145
|
-
pattern: {
|
|
146
|
-
type: "string",
|
|
147
|
-
description: "Glob pattern or search string",
|
|
148
|
-
},
|
|
149
|
-
directory: {
|
|
150
|
-
type: "string",
|
|
151
|
-
description: "Directory to search in (default: cwd)",
|
|
152
|
-
},
|
|
153
|
-
content_search: {
|
|
154
|
-
type: "boolean",
|
|
155
|
-
description: "If true, search file contents instead of names",
|
|
156
|
-
},
|
|
157
|
-
},
|
|
158
|
-
required: ["pattern"],
|
|
159
|
-
},
|
|
160
|
-
},
|
|
161
|
-
},
|
|
162
|
-
{
|
|
163
|
-
type: "function",
|
|
164
|
-
function: {
|
|
165
|
-
name: "list_dir",
|
|
166
|
-
description: "List contents of a directory",
|
|
167
|
-
parameters: {
|
|
168
|
-
type: "object",
|
|
169
|
-
properties: {
|
|
170
|
-
path: {
|
|
171
|
-
type: "string",
|
|
172
|
-
description: "Directory path (default: cwd)",
|
|
173
|
-
},
|
|
174
|
-
},
|
|
175
|
-
},
|
|
176
|
-
},
|
|
177
|
-
},
|
|
178
|
-
{
|
|
179
|
-
type: "function",
|
|
180
|
-
function: {
|
|
181
|
-
name: "run_skill",
|
|
182
|
-
description:
|
|
183
|
-
"Run a built-in ChainlessChain skill. Available skills include: code-review, summarize, translate, refactor, unit-test, debug, explain-code, browser-automation, data-analysis, git-history-analyzer, and 130+ more. Use list_skills first to discover available skills.",
|
|
184
|
-
parameters: {
|
|
185
|
-
type: "object",
|
|
186
|
-
properties: {
|
|
187
|
-
skill_name: {
|
|
188
|
-
type: "string",
|
|
189
|
-
description:
|
|
190
|
-
"Name of the skill to run (e.g. code-review, summarize, translate)",
|
|
191
|
-
},
|
|
192
|
-
input: {
|
|
193
|
-
type: "string",
|
|
194
|
-
description: "Input text or parameters for the skill",
|
|
195
|
-
},
|
|
196
|
-
},
|
|
197
|
-
required: ["skill_name", "input"],
|
|
198
|
-
},
|
|
199
|
-
},
|
|
200
|
-
},
|
|
201
|
-
{
|
|
202
|
-
type: "function",
|
|
203
|
-
function: {
|
|
204
|
-
name: "list_skills",
|
|
205
|
-
description:
|
|
206
|
-
"List available built-in skills, optionally filtered by category or keyword",
|
|
207
|
-
parameters: {
|
|
208
|
-
type: "object",
|
|
209
|
-
properties: {
|
|
210
|
-
category: {
|
|
211
|
-
type: "string",
|
|
212
|
-
description:
|
|
213
|
-
"Filter by category (e.g. development, automation, data)",
|
|
214
|
-
},
|
|
215
|
-
query: {
|
|
216
|
-
type: "string",
|
|
217
|
-
description: "Search keyword to filter skills",
|
|
218
|
-
},
|
|
219
|
-
},
|
|
220
|
-
},
|
|
221
|
-
},
|
|
222
|
-
},
|
|
223
|
-
{
|
|
224
|
-
type: "function",
|
|
225
|
-
function: {
|
|
226
|
-
name: "run_code",
|
|
227
|
-
description:
|
|
228
|
-
"Write and execute code in Python, Node.js, or Bash. Use this when the user needs data processing, calculations, file batch operations, API calls, or any task best solved with a script. Scripts are saved for reference. Missing Python packages are auto-installed.",
|
|
229
|
-
parameters: {
|
|
230
|
-
type: "object",
|
|
231
|
-
properties: {
|
|
232
|
-
language: {
|
|
233
|
-
type: "string",
|
|
234
|
-
enum: ["python", "node", "bash"],
|
|
235
|
-
description: "Programming language",
|
|
236
|
-
},
|
|
237
|
-
code: { type: "string", description: "Code to execute" },
|
|
238
|
-
timeout: {
|
|
239
|
-
type: "number",
|
|
240
|
-
description: "Execution timeout in seconds (default: 60, max: 300)",
|
|
241
|
-
},
|
|
242
|
-
persist: {
|
|
243
|
-
type: "boolean",
|
|
244
|
-
description:
|
|
245
|
-
"If true (default), save script in .chainlesschain/agent-scripts/. If false, use temp file and clean up.",
|
|
246
|
-
},
|
|
247
|
-
},
|
|
248
|
-
required: ["language", "code"],
|
|
249
|
-
},
|
|
250
|
-
},
|
|
251
|
-
},
|
|
252
|
-
{
|
|
253
|
-
type: "function",
|
|
254
|
-
function: {
|
|
255
|
-
name: "spawn_sub_agent",
|
|
256
|
-
description:
|
|
257
|
-
"Spawn an isolated sub-agent to handle a subtask. The sub-agent has its own context and message history, and only returns a summary result. Use this for tasks that benefit from focused, independent execution (e.g. code review, summarization, translation).",
|
|
258
|
-
parameters: {
|
|
259
|
-
type: "object",
|
|
260
|
-
properties: {
|
|
261
|
-
role: {
|
|
262
|
-
type: "string",
|
|
263
|
-
description:
|
|
264
|
-
"Sub-agent role (e.g. code-review, summarizer, translator, debugger)",
|
|
265
|
-
},
|
|
266
|
-
task: {
|
|
267
|
-
type: "string",
|
|
268
|
-
description: "Task description for the sub-agent",
|
|
269
|
-
},
|
|
270
|
-
context: {
|
|
271
|
-
type: "string",
|
|
272
|
-
description:
|
|
273
|
-
"Optional condensed context from the parent agent to pass to the sub-agent",
|
|
274
|
-
},
|
|
275
|
-
tools: {
|
|
276
|
-
type: "array",
|
|
277
|
-
items: { type: "string" },
|
|
278
|
-
description:
|
|
279
|
-
'Optional tool whitelist for the sub-agent (e.g. ["read_file", "search_files"]). If omitted, all tools are available.',
|
|
280
|
-
},
|
|
281
|
-
},
|
|
282
|
-
required: ["role", "task"],
|
|
283
|
-
},
|
|
284
|
-
},
|
|
285
|
-
},
|
|
286
|
-
];
|
|
287
|
-
|
|
288
|
-
const STATIC_AGENT_TOOL_NAMES = new Set(
|
|
289
|
-
AGENT_TOOLS.map((tool) => tool.function.name),
|
|
290
|
-
);
|
|
291
|
-
|
|
292
|
-
export const AGENT_TOOL_REGISTRY = createLegacyAgentToolRegistry(AGENT_TOOLS);
|
|
293
|
-
const DEFAULT_TOOL_DESCRIPTOR_MAP = new Map(
|
|
294
|
-
DEFAULT_TOOL_DESCRIPTORS.map((descriptor) => [descriptor.name, descriptor]),
|
|
295
|
-
);
|
|
296
|
-
|
|
297
|
-
function mergeToolDefinitions(baseTools = [], extraTools = []) {
|
|
298
|
-
const merged = new Map();
|
|
299
|
-
|
|
300
|
-
for (const tool of [...baseTools, ...extraTools]) {
|
|
301
|
-
const name = tool?.function?.name;
|
|
302
|
-
if (!name) continue;
|
|
303
|
-
merged.set(name, tool);
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
return Array.from(merged.values());
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
export function getAgentToolDefinitions({
|
|
310
|
-
names = null,
|
|
311
|
-
disabledTools = [],
|
|
312
|
-
extraTools = [],
|
|
313
|
-
} = {}) {
|
|
314
|
-
const allowedNames =
|
|
315
|
-
Array.isArray(names) && names.length > 0 ? new Set(names) : null;
|
|
316
|
-
const disabledNames = new Set(
|
|
317
|
-
Array.isArray(disabledTools) ? disabledTools : [],
|
|
318
|
-
);
|
|
319
|
-
const extraToolNames = new Set(
|
|
320
|
-
(Array.isArray(extraTools) ? extraTools : [])
|
|
321
|
-
.map((tool) => tool?.function?.name)
|
|
322
|
-
.filter(Boolean),
|
|
323
|
-
);
|
|
324
|
-
const allTools = mergeToolDefinitions(
|
|
325
|
-
AGENT_TOOLS,
|
|
326
|
-
Array.isArray(extraTools) ? extraTools : [],
|
|
327
|
-
);
|
|
328
|
-
|
|
329
|
-
return allTools.filter((tool) => {
|
|
330
|
-
const name = tool?.function?.name;
|
|
331
|
-
if (!name) return false;
|
|
332
|
-
if (allowedNames && !allowedNames.has(name) && !extraToolNames.has(name)) {
|
|
333
|
-
return false;
|
|
334
|
-
}
|
|
335
|
-
if (disabledNames.has(name)) return false;
|
|
336
|
-
return true;
|
|
337
|
-
});
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
export function getAgentToolDescriptors(options = {}) {
|
|
341
|
-
const allowedNames = new Set(
|
|
342
|
-
getAgentToolDefinitions(options).map((tool) => tool.function.name),
|
|
343
|
-
);
|
|
344
|
-
return AGENT_TOOL_REGISTRY.list({ enabledOnly: options.enabledOnly }).filter(
|
|
345
|
-
(descriptor) => allowedNames.has(descriptor.name),
|
|
346
|
-
);
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
// ─── Shared skill loader ──────────────────────────────────────────────────
|
|
350
|
-
|
|
351
|
-
const _defaultSkillLoader = new CLISkillLoader();
|
|
352
|
-
|
|
353
|
-
// ─── Cached environment detection ────────────────────────────────────────
|
|
354
|
-
|
|
355
|
-
let _cachedPython = null;
|
|
356
|
-
let _cachedEnvInfo = null;
|
|
357
|
-
|
|
358
|
-
/**
|
|
359
|
-
* Get cached Python interpreter info (reuses cli-anything-bridge detection).
|
|
360
|
-
* @returns {{ found: boolean, command?: string, version?: string }}
|
|
361
|
-
*/
|
|
362
|
-
export function getCachedPython() {
|
|
363
|
-
if (!_cachedPython) {
|
|
364
|
-
_cachedPython = detectPython();
|
|
365
|
-
}
|
|
366
|
-
return _cachedPython;
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
/**
|
|
370
|
-
* Gather environment info (cached once per process).
|
|
371
|
-
* @returns {{ os: string, arch: string, python: string|null, pip: boolean, node: string|null, git: boolean }}
|
|
372
|
-
*/
|
|
373
|
-
export function getEnvironmentInfo() {
|
|
374
|
-
if (_cachedEnvInfo) return _cachedEnvInfo;
|
|
375
|
-
|
|
376
|
-
const py = getCachedPython();
|
|
377
|
-
|
|
378
|
-
let pipAvailable = false;
|
|
379
|
-
if (py.found) {
|
|
380
|
-
try {
|
|
381
|
-
execSync(`${py.command} -m pip --version`, {
|
|
382
|
-
encoding: "utf-8",
|
|
383
|
-
timeout: 10000,
|
|
384
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
385
|
-
});
|
|
386
|
-
pipAvailable = true;
|
|
387
|
-
} catch {
|
|
388
|
-
// pip not available
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
let nodeVersion = null;
|
|
393
|
-
try {
|
|
394
|
-
nodeVersion = execSync("node --version", {
|
|
395
|
-
encoding: "utf-8",
|
|
396
|
-
timeout: 5000,
|
|
397
|
-
}).trim();
|
|
398
|
-
} catch {
|
|
399
|
-
// Node not available (unlikely since we're running in Node)
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
let gitAvailable = false;
|
|
403
|
-
try {
|
|
404
|
-
execSync("git --version", {
|
|
405
|
-
encoding: "utf-8",
|
|
406
|
-
timeout: 5000,
|
|
407
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
408
|
-
});
|
|
409
|
-
gitAvailable = true;
|
|
410
|
-
} catch {
|
|
411
|
-
// git not available
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
_cachedEnvInfo = {
|
|
415
|
-
os: process.platform,
|
|
416
|
-
arch: process.arch,
|
|
417
|
-
python: py.found ? `${py.command} (${py.version})` : null,
|
|
418
|
-
pip: pipAvailable,
|
|
419
|
-
node: nodeVersion,
|
|
420
|
-
git: gitAvailable,
|
|
421
|
-
};
|
|
422
|
-
return _cachedEnvInfo;
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
// ─── System prompt ────────────────────────────────────────────────────────
|
|
426
|
-
|
|
427
|
-
export function getBaseSystemPrompt(cwd) {
|
|
428
|
-
const env = getEnvironmentInfo();
|
|
429
|
-
const envLines = [
|
|
430
|
-
`OS: ${env.os} (${env.arch})`,
|
|
431
|
-
env.python
|
|
432
|
-
? `Python: ${env.python}${env.pip ? " + pip" : ""}`
|
|
433
|
-
: "Python: not found",
|
|
434
|
-
env.node ? `Node.js: ${env.node}` : "Node.js: not found",
|
|
435
|
-
`Git: ${env.git ? "available" : "not found"}`,
|
|
436
|
-
];
|
|
437
|
-
|
|
438
|
-
return `You are ChainlessChain AI Assistant, a powerful agentic coding assistant running in the terminal.
|
|
439
|
-
|
|
440
|
-
You have access to tools that let you read files, write files, edit files, run shell commands, and search the codebase. When the user asks you to do something, USE THE TOOLS to actually do it — don't just describe what should be done.
|
|
441
|
-
|
|
442
|
-
Key behaviors:
|
|
443
|
-
- When asked to modify code, read the file first, then edit it
|
|
444
|
-
- When asked to create something, use write_file to create it
|
|
445
|
-
- When asked to run/test something, use run_shell to execute it
|
|
446
|
-
- When asked about git status, diff, log, or other repository operations, use the git tool instead of run_shell
|
|
447
|
-
- When asked about files or code, use read_file and search_files to find information
|
|
448
|
-
- You have multi-layer skills (built-in, marketplace, global, project-level) — use list_skills to discover them and run_skill to execute them
|
|
449
|
-
- Always explain what you're doing and show results
|
|
450
|
-
- Be concise but thorough
|
|
451
|
-
|
|
452
|
-
When the user's problem involves data processing, calculations, file operations, text parsing, API calls, web scraping, or any task that can be solved programmatically:
|
|
453
|
-
- Proactively write and execute code using run_code tool
|
|
454
|
-
- Choose the best language: Python for data/math/scraping, Node.js for JSON/API, Bash for system tasks
|
|
455
|
-
- Missing Python packages are auto-installed via pip when import errors are detected
|
|
456
|
-
- Scripts are persisted in .chainlesschain/agent-scripts/ for reference
|
|
457
|
-
- Show the results and explain them clearly
|
|
458
|
-
- If the first attempt fails, debug and retry with a different approach
|
|
459
|
-
|
|
460
|
-
You are not just a chatbot — you are a capable coding agent. Think step by step, write code when needed, and deliver real results.
|
|
461
|
-
|
|
462
|
-
## Sub-Agent Isolation
|
|
463
|
-
When a task involves multiple distinct roles (e.g. code review + code generation), or when you need
|
|
464
|
-
focused analysis without polluting your current context, use the spawn_sub_agent tool. Examples:
|
|
465
|
-
- Code review as a separate perspective while you're implementing
|
|
466
|
-
- Summarizing a large file before incorporating it into your response
|
|
467
|
-
- Running a focused analysis (security, performance) on specific code
|
|
468
|
-
- Translating or reformatting content independently
|
|
469
|
-
The sub-agent has its own message history and only returns a summary — your context stays clean.
|
|
470
|
-
Do NOT spawn sub-agents for trivial tasks that you can handle directly.
|
|
471
|
-
|
|
472
|
-
## Environment
|
|
473
|
-
${envLines.join("\n")}
|
|
474
|
-
|
|
475
|
-
Current working directory: ${cwd || process.cwd()}`;
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
// ─── Persona support ─────────────────────────────────────────────────────
|
|
479
|
-
|
|
480
|
-
/**
|
|
481
|
-
* Load persona configuration from project config.json
|
|
482
|
-
* @param {string} cwd - working directory
|
|
483
|
-
* @returns {object|null} persona object or null
|
|
484
|
-
*/
|
|
485
|
-
function _loadProjectPersona(cwd) {
|
|
486
|
-
try {
|
|
487
|
-
const projectRoot = findProjectRoot(cwd || process.cwd());
|
|
488
|
-
if (!projectRoot) return null;
|
|
489
|
-
const config = loadProjectConfig(projectRoot);
|
|
490
|
-
return config?.persona || null;
|
|
491
|
-
} catch {
|
|
492
|
-
return null;
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
/**
|
|
497
|
-
* Build a persona-specific system prompt
|
|
498
|
-
* @param {object} persona - persona configuration
|
|
499
|
-
* @param {string[]} envLines - environment info lines
|
|
500
|
-
* @param {string} cwd - working directory
|
|
501
|
-
* @returns {string}
|
|
502
|
-
*/
|
|
503
|
-
function _buildPersonaPrompt(persona, envLines, cwd) {
|
|
504
|
-
const lines = [];
|
|
505
|
-
lines.push(`You are ${persona.name || "AI Assistant"}.`);
|
|
506
|
-
if (persona.role) {
|
|
507
|
-
lines.push("");
|
|
508
|
-
lines.push(persona.role);
|
|
509
|
-
}
|
|
510
|
-
if (persona.behaviors?.length > 0) {
|
|
511
|
-
lines.push("");
|
|
512
|
-
lines.push("Key behaviors:");
|
|
513
|
-
for (const b of persona.behaviors) {
|
|
514
|
-
lines.push(`- ${b}`);
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
lines.push("");
|
|
518
|
-
lines.push(
|
|
519
|
-
"You have access to tools that let you read files, write files, edit files, run shell commands, and search the codebase. When the user asks you to do something, USE THE TOOLS to actually do it.",
|
|
520
|
-
);
|
|
521
|
-
if (persona.toolsPriority?.length > 0) {
|
|
522
|
-
lines.push(`\nPreferred tools: ${persona.toolsPriority.join(", ")}`);
|
|
523
|
-
}
|
|
524
|
-
lines.push(`\n## Environment\n${envLines.join("\n")}`);
|
|
525
|
-
lines.push(`\nCurrent working directory: ${cwd || process.cwd()}`);
|
|
526
|
-
return lines.join("\n");
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
/**
|
|
530
|
-
* Build the full system prompt with persona, rules.md, and auto-activated persona skills.
|
|
531
|
-
* Single entry point used by both agent-repl and ws-session-manager.
|
|
532
|
-
*
|
|
533
|
-
* Priority order:
|
|
534
|
-
* 1. config.json persona → replaces base system prompt
|
|
535
|
-
* 2. Auto-activated persona skills → appended
|
|
536
|
-
* 3. rules.md → appended
|
|
537
|
-
* 4. Default hardcoded prompt → fallback when no persona
|
|
538
|
-
*
|
|
539
|
-
* @param {string} [cwd] - working directory
|
|
540
|
-
* @returns {string} complete system prompt
|
|
541
|
-
*/
|
|
542
|
-
export function buildSystemPrompt(cwd) {
|
|
543
|
-
const dir = cwd || process.cwd();
|
|
544
|
-
|
|
545
|
-
// Check for project persona
|
|
546
|
-
const persona = _loadProjectPersona(dir);
|
|
547
|
-
let prompt;
|
|
548
|
-
if (persona) {
|
|
549
|
-
const env = getEnvironmentInfo();
|
|
550
|
-
const envLines = [
|
|
551
|
-
`OS: ${env.os} (${env.arch})`,
|
|
552
|
-
env.python
|
|
553
|
-
? `Python: ${env.python}${env.pip ? " + pip" : ""}`
|
|
554
|
-
: "Python: not found",
|
|
555
|
-
env.node ? `Node.js: ${env.node}` : "Node.js: not found",
|
|
556
|
-
`Git: ${env.git ? "available" : "not found"}`,
|
|
557
|
-
];
|
|
558
|
-
prompt = _buildPersonaPrompt(persona, envLines, dir);
|
|
559
|
-
} else {
|
|
560
|
-
prompt = getBaseSystemPrompt(dir);
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
// Append auto-activated persona skills
|
|
564
|
-
try {
|
|
565
|
-
const loader = new CLISkillLoader();
|
|
566
|
-
const allSkills = loader.getResolvedSkills();
|
|
567
|
-
const personaSkills = allSkills.filter(
|
|
568
|
-
(s) => s.category === "persona" && s.activation === "auto",
|
|
569
|
-
);
|
|
570
|
-
for (const p of personaSkills) {
|
|
571
|
-
if (p.body?.trim()) {
|
|
572
|
-
prompt += `\n\n## Persona: ${p.displayName}\n${p.body}`;
|
|
573
|
-
}
|
|
574
|
-
}
|
|
575
|
-
} catch {
|
|
576
|
-
// Non-critical — skill loader may not be available
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
// Append rules.md
|
|
580
|
-
try {
|
|
581
|
-
const projectRoot = findProjectRoot(dir);
|
|
582
|
-
if (projectRoot) {
|
|
583
|
-
const rulesPath = path.join(projectRoot, ".chainlesschain", "rules.md");
|
|
584
|
-
if (fs.existsSync(rulesPath)) {
|
|
585
|
-
const content = fs.readFileSync(rulesPath, "utf-8");
|
|
586
|
-
if (content.trim()) {
|
|
587
|
-
prompt += `\n\n## Project Rules\n${content}`;
|
|
588
|
-
}
|
|
589
|
-
}
|
|
590
|
-
}
|
|
591
|
-
} catch {
|
|
592
|
-
// Non-critical
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
return prompt;
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
// ─── Tool execution ──────────────────────────────────────────────────────
|
|
599
|
-
|
|
600
|
-
/**
|
|
601
|
-
* Execute a single tool call with plan-mode filtering and hook pipeline.
|
|
602
|
-
*
|
|
603
|
-
* @param {string} name - tool name
|
|
604
|
-
* @param {object} args - tool arguments
|
|
605
|
-
* @param {object} [context] - optional context
|
|
606
|
-
* @param {object} [context.hookDb] - DB for hooks
|
|
607
|
-
* @param {CLISkillLoader} [context.skillLoader] - skill loader instance
|
|
608
|
-
* @param {string} [context.cwd] - working directory override
|
|
609
|
-
* @returns {Promise<object>} tool result
|
|
610
|
-
*/
|
|
611
|
-
export async function executeTool(name, args, context = {}) {
|
|
612
|
-
const hookDb = context.hookDb || null;
|
|
613
|
-
const skillLoader = context.skillLoader || _defaultSkillLoader;
|
|
614
|
-
const cwd = context.cwd || process.cwd();
|
|
615
|
-
const planManager = context.planManager || getPlanModeManager();
|
|
616
|
-
const localToolDescriptor =
|
|
617
|
-
context.externalToolDescriptors &&
|
|
618
|
-
typeof context.externalToolDescriptors === "object"
|
|
619
|
-
? context.externalToolDescriptors[name] || null
|
|
620
|
-
: null;
|
|
621
|
-
const runtimeDescriptor =
|
|
622
|
-
getRuntimeToolDescriptor(name) || localToolDescriptor;
|
|
623
|
-
const toolContext = createToolContext({
|
|
624
|
-
toolName: runtimeDescriptor?.name || name,
|
|
625
|
-
cwd,
|
|
626
|
-
metadata: { descriptor: runtimeDescriptor },
|
|
627
|
-
});
|
|
628
|
-
|
|
629
|
-
// Persona toolsDisabled guard
|
|
630
|
-
const persona = _loadProjectPersona(cwd);
|
|
631
|
-
if (persona?.toolsDisabled?.includes(name)) {
|
|
632
|
-
return {
|
|
633
|
-
error: `Tool "${name}" is disabled by project persona configuration.`,
|
|
634
|
-
};
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
const toolPolicies =
|
|
638
|
-
context.hostManagedToolPolicy?.tools ||
|
|
639
|
-
context.hostManagedToolPolicy?.toolPolicies ||
|
|
640
|
-
null;
|
|
641
|
-
const hostToolPolicy =
|
|
642
|
-
toolPolicies && typeof toolPolicies === "object"
|
|
643
|
-
? toolPolicies[name]
|
|
644
|
-
: null;
|
|
645
|
-
const isExternalHostTool =
|
|
646
|
-
hostToolPolicy && !STATIC_AGENT_TOOL_NAMES.has(name);
|
|
647
|
-
const isExternalLocalTool =
|
|
648
|
-
localToolDescriptor && !STATIC_AGENT_TOOL_NAMES.has(name);
|
|
649
|
-
const hostPolicyAllowsReadOnlyGit =
|
|
650
|
-
name === "git" &&
|
|
651
|
-
hostToolPolicy?.planModeBehavior === "readonly-conditional" &&
|
|
652
|
-
isReadOnlyGitCommand(args.command);
|
|
653
|
-
const localReadOnlyAllowedInPlanMode =
|
|
654
|
-
isExternalLocalTool &&
|
|
655
|
-
planManager.isActive() &&
|
|
656
|
-
localToolDescriptor?.isReadOnly === true;
|
|
657
|
-
if (
|
|
658
|
-
hostToolPolicy &&
|
|
659
|
-
hostToolPolicy.allowed === false &&
|
|
660
|
-
!hostPolicyAllowsReadOnlyGit
|
|
661
|
-
) {
|
|
662
|
-
return {
|
|
663
|
-
error: `[Host Policy] Tool "${name}" is blocked by desktop host policy. ${hostToolPolicy.reason || "Desktop approval has not been synchronized yet."}`,
|
|
664
|
-
policy: {
|
|
665
|
-
decision: hostToolPolicy.decision || "blocked",
|
|
666
|
-
requiresPlanApproval: hostToolPolicy.requiresPlanApproval === true,
|
|
667
|
-
requiresConfirmation: hostToolPolicy.requiresConfirmation === true,
|
|
668
|
-
riskLevel: hostToolPolicy.riskLevel || null,
|
|
669
|
-
},
|
|
670
|
-
};
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
// Plan mode: check if tool is allowed
|
|
674
|
-
if (
|
|
675
|
-
planManager.isActive() &&
|
|
676
|
-
!(name === "git" && isReadOnlyGitCommand(args.command)) &&
|
|
677
|
-
!planManager.isToolAllowed(name) &&
|
|
678
|
-
!(isExternalHostTool && hostToolPolicy?.allowed === true) &&
|
|
679
|
-
!localReadOnlyAllowedInPlanMode
|
|
680
|
-
) {
|
|
681
|
-
planManager.addPlanItem({
|
|
682
|
-
title: `${name}: ${formatToolArgs(name, args)}`,
|
|
683
|
-
tool: name,
|
|
684
|
-
params: args,
|
|
685
|
-
estimatedImpact:
|
|
686
|
-
name === "run_shell" ||
|
|
687
|
-
name === "run_code" ||
|
|
688
|
-
name === "git" ||
|
|
689
|
-
localToolDescriptor?.riskLevel === "high"
|
|
690
|
-
? "high"
|
|
691
|
-
: name === "write_file" || localToolDescriptor?.riskLevel === "medium"
|
|
692
|
-
? "medium"
|
|
693
|
-
: "low",
|
|
694
|
-
});
|
|
695
|
-
return {
|
|
696
|
-
error: `[Plan Mode] Tool "${name}" is blocked during planning. It has been added to the plan. Use /plan approve to execute.`,
|
|
697
|
-
};
|
|
698
|
-
}
|
|
699
|
-
|
|
700
|
-
// PreToolUse hook
|
|
701
|
-
if (hookDb) {
|
|
702
|
-
try {
|
|
703
|
-
await executeHooks(hookDb, HookEvents.PreToolUse, {
|
|
704
|
-
tool: name,
|
|
705
|
-
args,
|
|
706
|
-
timestamp: new Date().toISOString(),
|
|
707
|
-
descriptor: runtimeDescriptor,
|
|
708
|
-
context: toolContext,
|
|
709
|
-
});
|
|
710
|
-
} catch (_err) {
|
|
711
|
-
// Hook failure should not block tool execution
|
|
712
|
-
}
|
|
713
|
-
}
|
|
714
|
-
|
|
715
|
-
const startTime = Date.now();
|
|
716
|
-
let toolResult;
|
|
717
|
-
try {
|
|
718
|
-
toolResult = await executeToolInner(name, args, {
|
|
719
|
-
skillLoader,
|
|
720
|
-
cwd,
|
|
721
|
-
parentMessages: context.parentMessages,
|
|
722
|
-
interaction: context.interaction,
|
|
723
|
-
sessionId: context.sessionId || null,
|
|
724
|
-
hostManagedToolPolicy: context.hostManagedToolPolicy || null,
|
|
725
|
-
externalToolDescriptors: context.externalToolDescriptors || null,
|
|
726
|
-
externalToolExecutors: context.externalToolExecutors || null,
|
|
727
|
-
mcpClient: context.mcpClient || null,
|
|
728
|
-
});
|
|
729
|
-
} catch (err) {
|
|
730
|
-
if (hookDb) {
|
|
731
|
-
try {
|
|
732
|
-
await executeHooks(hookDb, HookEvents.ToolError, {
|
|
733
|
-
tool: name,
|
|
734
|
-
args,
|
|
735
|
-
error: err.message,
|
|
736
|
-
});
|
|
737
|
-
} catch (_err) {
|
|
738
|
-
// Non-critical
|
|
739
|
-
}
|
|
740
|
-
}
|
|
741
|
-
throw err;
|
|
742
|
-
}
|
|
743
|
-
|
|
744
|
-
const durationMs = Date.now() - startTime;
|
|
745
|
-
const status = toolResult?.error ? "error" : "completed";
|
|
746
|
-
const telemetryRecord = createToolTelemetryRecord({
|
|
747
|
-
descriptor: runtimeDescriptor,
|
|
748
|
-
status,
|
|
749
|
-
durationMs,
|
|
750
|
-
sessionId: context.sessionId || null,
|
|
751
|
-
metadata: { args },
|
|
752
|
-
});
|
|
753
|
-
if (toolResult && typeof toolResult === "object") {
|
|
754
|
-
toolResult.toolTelemetryRecord = telemetryRecord;
|
|
755
|
-
}
|
|
756
|
-
|
|
757
|
-
// PostToolUse hook
|
|
758
|
-
if (hookDb) {
|
|
759
|
-
try {
|
|
760
|
-
await executeHooks(hookDb, HookEvents.PostToolUse, {
|
|
761
|
-
tool: name,
|
|
762
|
-
args,
|
|
763
|
-
result:
|
|
764
|
-
typeof toolResult === "object"
|
|
765
|
-
? JSON.stringify(toolResult).substring(0, 500)
|
|
766
|
-
: String(toolResult).substring(0, 500),
|
|
767
|
-
descriptor: runtimeDescriptor,
|
|
768
|
-
context: toolContext,
|
|
769
|
-
});
|
|
770
|
-
} catch (_err) {
|
|
771
|
-
// Non-critical
|
|
772
|
-
}
|
|
773
|
-
}
|
|
774
|
-
|
|
775
|
-
return toolResult;
|
|
776
|
-
}
|
|
777
|
-
|
|
778
|
-
/**
|
|
779
|
-
* Inner tool execution — no hooks, no plan-mode checks.
|
|
780
|
-
*/
|
|
781
|
-
async function executeToolInner(
|
|
782
|
-
name,
|
|
783
|
-
args,
|
|
784
|
-
{
|
|
785
|
-
skillLoader,
|
|
786
|
-
cwd,
|
|
787
|
-
parentMessages,
|
|
788
|
-
interaction,
|
|
789
|
-
sessionId,
|
|
790
|
-
hostManagedToolPolicy,
|
|
791
|
-
externalToolDescriptors,
|
|
792
|
-
externalToolExecutors,
|
|
793
|
-
mcpClient,
|
|
794
|
-
},
|
|
795
|
-
) {
|
|
796
|
-
const localToolDescriptor =
|
|
797
|
-
externalToolDescriptors && typeof externalToolDescriptors === "object"
|
|
798
|
-
? externalToolDescriptors[name] || null
|
|
799
|
-
: null;
|
|
800
|
-
const runtimeDescriptor =
|
|
801
|
-
getRuntimeToolDescriptor(name) || localToolDescriptor;
|
|
802
|
-
const hostToolPolicies =
|
|
803
|
-
hostManagedToolPolicy?.tools || hostManagedToolPolicy?.toolPolicies || null;
|
|
804
|
-
const hostToolPolicy =
|
|
805
|
-
hostToolPolicies && typeof hostToolPolicies === "object"
|
|
806
|
-
? hostToolPolicies[name]
|
|
807
|
-
: null;
|
|
808
|
-
const hostToolDefinition = Array.isArray(
|
|
809
|
-
hostManagedToolPolicy?.toolDefinitions,
|
|
810
|
-
)
|
|
811
|
-
? hostManagedToolPolicy.toolDefinitions.find(
|
|
812
|
-
(tool) => tool?.function?.name === name,
|
|
813
|
-
) || null
|
|
814
|
-
: null;
|
|
815
|
-
const buildPayload = (descriptor) =>
|
|
816
|
-
descriptor
|
|
817
|
-
? {
|
|
818
|
-
name: descriptor.name,
|
|
819
|
-
kind: descriptor.kind || descriptor.category || descriptor.source,
|
|
820
|
-
category: descriptor.category,
|
|
821
|
-
}
|
|
822
|
-
: null;
|
|
823
|
-
const descriptorPayload = buildPayload(runtimeDescriptor);
|
|
824
|
-
const attachDescriptor = (payload, overrideDescriptor = null) => {
|
|
825
|
-
const descriptor = buildPayload(overrideDescriptor || runtimeDescriptor);
|
|
826
|
-
return descriptor ? { ...payload, toolDescriptor: descriptor } : payload;
|
|
827
|
-
};
|
|
828
|
-
const resolveShellDescriptor = (command) => {
|
|
829
|
-
const trimmed = (command || "").trim();
|
|
830
|
-
if (!trimmed) return null;
|
|
831
|
-
const parts = trimmed.split(/\s+/);
|
|
832
|
-
if (parts[0] === "git") return DEFAULT_TOOL_DESCRIPTOR_MAP.get("git");
|
|
833
|
-
if (parts[0] === "mcp" || parts.includes("mcp")) {
|
|
834
|
-
return DEFAULT_TOOL_DESCRIPTOR_MAP.get("mcp");
|
|
835
|
-
}
|
|
836
|
-
return DEFAULT_TOOL_DESCRIPTOR_MAP.get("shell");
|
|
837
|
-
};
|
|
838
|
-
const localToolExecutor =
|
|
839
|
-
externalToolExecutors && typeof externalToolExecutors === "object"
|
|
840
|
-
? externalToolExecutors[name] || null
|
|
841
|
-
: null;
|
|
842
|
-
switch (name) {
|
|
843
|
-
case "read_file": {
|
|
844
|
-
const filePath = path.resolve(cwd, args.path);
|
|
845
|
-
if (!fs.existsSync(filePath)) {
|
|
846
|
-
return attachDescriptor({ error: `File not found: ${filePath}` });
|
|
847
|
-
}
|
|
848
|
-
const content = fs.readFileSync(filePath, "utf8");
|
|
849
|
-
if (content.length > 50000) {
|
|
850
|
-
return attachDescriptor({
|
|
851
|
-
content: content.substring(0, 50000) + "\n...(truncated)",
|
|
852
|
-
size: content.length,
|
|
853
|
-
});
|
|
854
|
-
}
|
|
855
|
-
return attachDescriptor({ content });
|
|
856
|
-
}
|
|
857
|
-
|
|
858
|
-
case "write_file": {
|
|
859
|
-
const filePath = path.resolve(cwd, args.path);
|
|
860
|
-
const dir = path.dirname(filePath);
|
|
861
|
-
if (!fs.existsSync(dir)) {
|
|
862
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
863
|
-
}
|
|
864
|
-
fs.writeFileSync(filePath, args.content, "utf8");
|
|
865
|
-
return attachDescriptor({
|
|
866
|
-
success: true,
|
|
867
|
-
path: filePath,
|
|
868
|
-
size: args.content.length,
|
|
869
|
-
});
|
|
870
|
-
}
|
|
871
|
-
|
|
872
|
-
case "edit_file": {
|
|
873
|
-
const filePath = path.resolve(cwd, args.path);
|
|
874
|
-
if (!fs.existsSync(filePath)) {
|
|
875
|
-
return attachDescriptor({ error: `File not found: ${filePath}` });
|
|
876
|
-
}
|
|
877
|
-
const content = fs.readFileSync(filePath, "utf8");
|
|
878
|
-
if (!content.includes(args.old_string)) {
|
|
879
|
-
return attachDescriptor({ error: "old_string not found in file" });
|
|
880
|
-
}
|
|
881
|
-
const newContent = content.replace(args.old_string, args.new_string);
|
|
882
|
-
fs.writeFileSync(filePath, newContent, "utf8");
|
|
883
|
-
return attachDescriptor({ success: true, path: filePath });
|
|
884
|
-
}
|
|
885
|
-
|
|
886
|
-
case "run_shell": {
|
|
887
|
-
const shellPolicy = evaluateShellCommandPolicy(args.command);
|
|
888
|
-
const override = resolveShellDescriptor(args.command);
|
|
889
|
-
if (!shellPolicy.allowed) {
|
|
890
|
-
return attachDescriptor(
|
|
891
|
-
{
|
|
892
|
-
error: `[Shell Policy] ${shellPolicy.reason}`,
|
|
893
|
-
shellCommandPolicy: shellPolicy,
|
|
894
|
-
},
|
|
895
|
-
override || runtimeDescriptor,
|
|
896
|
-
);
|
|
897
|
-
}
|
|
898
|
-
|
|
899
|
-
try {
|
|
900
|
-
const output = execSync(args.command, {
|
|
901
|
-
cwd: args.cwd || cwd,
|
|
902
|
-
encoding: "utf8",
|
|
903
|
-
timeout: 60000,
|
|
904
|
-
maxBuffer: 1024 * 1024,
|
|
905
|
-
});
|
|
906
|
-
return attachDescriptor(
|
|
907
|
-
{
|
|
908
|
-
stdout: output.substring(0, 30000),
|
|
909
|
-
shellCommandPolicy: shellPolicy,
|
|
910
|
-
},
|
|
911
|
-
override || runtimeDescriptor,
|
|
912
|
-
);
|
|
913
|
-
} catch (err) {
|
|
914
|
-
return attachDescriptor(
|
|
915
|
-
{
|
|
916
|
-
error: err.message.substring(0, 2000),
|
|
917
|
-
stderr: (err.stderr || "").substring(0, 2000),
|
|
918
|
-
exitCode: err.status,
|
|
919
|
-
shellCommandPolicy: shellPolicy,
|
|
920
|
-
},
|
|
921
|
-
override || runtimeDescriptor,
|
|
922
|
-
);
|
|
923
|
-
}
|
|
924
|
-
}
|
|
925
|
-
|
|
926
|
-
case "git": {
|
|
927
|
-
const normalizedCommand = normalizeGitCommand(args.command);
|
|
928
|
-
if (!normalizedCommand) {
|
|
929
|
-
return attachDescriptor({
|
|
930
|
-
error: "Git command is required.",
|
|
931
|
-
});
|
|
932
|
-
}
|
|
933
|
-
|
|
934
|
-
try {
|
|
935
|
-
const output = execSync(`git ${normalizedCommand}`, {
|
|
936
|
-
cwd: args.cwd || cwd,
|
|
937
|
-
encoding: "utf8",
|
|
938
|
-
timeout: 60000,
|
|
939
|
-
maxBuffer: 1024 * 1024,
|
|
940
|
-
});
|
|
941
|
-
return attachDescriptor({
|
|
942
|
-
stdout: output.substring(0, 30000),
|
|
943
|
-
command: normalizedCommand,
|
|
944
|
-
readOnly: isReadOnlyGitCommand(normalizedCommand),
|
|
945
|
-
});
|
|
946
|
-
} catch (err) {
|
|
947
|
-
return attachDescriptor({
|
|
948
|
-
error: err.message.substring(0, 2000),
|
|
949
|
-
stderr: (err.stderr || "").substring(0, 2000),
|
|
950
|
-
exitCode: err.status,
|
|
951
|
-
command: normalizedCommand,
|
|
952
|
-
readOnly: isReadOnlyGitCommand(normalizedCommand),
|
|
953
|
-
});
|
|
954
|
-
}
|
|
955
|
-
}
|
|
956
|
-
|
|
957
|
-
case "run_code": {
|
|
958
|
-
return attachDescriptor(await _executeRunCode(args, cwd));
|
|
959
|
-
}
|
|
960
|
-
|
|
961
|
-
case "spawn_sub_agent": {
|
|
962
|
-
return attachDescriptor(
|
|
963
|
-
await _executeSpawnSubAgent(args, {
|
|
964
|
-
skillLoader,
|
|
965
|
-
cwd,
|
|
966
|
-
parentMessages,
|
|
967
|
-
interaction,
|
|
968
|
-
sessionId,
|
|
969
|
-
}),
|
|
970
|
-
);
|
|
971
|
-
}
|
|
972
|
-
|
|
973
|
-
case "search_files": {
|
|
974
|
-
const dir = args.directory ? path.resolve(cwd, args.directory) : cwd;
|
|
975
|
-
try {
|
|
976
|
-
if (args.content_search) {
|
|
977
|
-
const cmd =
|
|
978
|
-
process.platform === "win32"
|
|
979
|
-
? `findstr /s /i /n "${args.pattern}" *`
|
|
980
|
-
: `grep -r -l -i "${args.pattern}" . --include="*" 2>/dev/null | head -20`;
|
|
981
|
-
const output = execSync(cmd, {
|
|
982
|
-
cwd: dir,
|
|
983
|
-
encoding: "utf8",
|
|
984
|
-
timeout: 10000,
|
|
985
|
-
});
|
|
986
|
-
return attachDescriptor({
|
|
987
|
-
matches: output.trim().split("\n").slice(0, 20),
|
|
988
|
-
});
|
|
989
|
-
} else {
|
|
990
|
-
const cmd =
|
|
991
|
-
process.platform === "win32"
|
|
992
|
-
? `dir /s /b *${args.pattern}* 2>NUL`
|
|
993
|
-
: `find . -name "*${args.pattern}*" -type f 2>/dev/null | head -20`;
|
|
994
|
-
const output = execSync(cmd, {
|
|
995
|
-
cwd: dir,
|
|
996
|
-
encoding: "utf8",
|
|
997
|
-
timeout: 10000,
|
|
998
|
-
});
|
|
999
|
-
return attachDescriptor({
|
|
1000
|
-
files: output.trim().split("\n").filter(Boolean).slice(0, 20),
|
|
1001
|
-
});
|
|
1002
|
-
}
|
|
1003
|
-
} catch {
|
|
1004
|
-
return attachDescriptor({
|
|
1005
|
-
files: [],
|
|
1006
|
-
message: "No matches found",
|
|
1007
|
-
});
|
|
1008
|
-
}
|
|
1009
|
-
}
|
|
1010
|
-
|
|
1011
|
-
case "list_dir": {
|
|
1012
|
-
const dirPath = args.path ? path.resolve(cwd, args.path) : cwd;
|
|
1013
|
-
if (!fs.existsSync(dirPath)) {
|
|
1014
|
-
return attachDescriptor({ error: `Directory not found: ${dirPath}` });
|
|
1015
|
-
}
|
|
1016
|
-
const entries = fs.readdirSync(dirPath, { withFileTypes: true });
|
|
1017
|
-
return attachDescriptor({
|
|
1018
|
-
entries: entries.map((e) => ({
|
|
1019
|
-
name: e.name,
|
|
1020
|
-
type: e.isDirectory() ? "dir" : "file",
|
|
1021
|
-
})),
|
|
1022
|
-
});
|
|
1023
|
-
}
|
|
1024
|
-
|
|
1025
|
-
case "run_skill": {
|
|
1026
|
-
const allSkills = skillLoader.getResolvedSkills();
|
|
1027
|
-
if (allSkills.length === 0) {
|
|
1028
|
-
return attachDescriptor({
|
|
1029
|
-
error:
|
|
1030
|
-
"No skills found. Make sure you're in the ChainlessChain project root or have skills installed.",
|
|
1031
|
-
});
|
|
1032
|
-
}
|
|
1033
|
-
const match = allSkills.find(
|
|
1034
|
-
(s) => s.id === args.skill_name || s.dirName === args.skill_name,
|
|
1035
|
-
);
|
|
1036
|
-
if (!match || !match.hasHandler) {
|
|
1037
|
-
return attachDescriptor({
|
|
1038
|
-
error: `Skill "${args.skill_name}" not found or has no handler. Use list_skills to see available skills.`,
|
|
1039
|
-
});
|
|
1040
|
-
}
|
|
1041
|
-
|
|
1042
|
-
// Check if skill requests isolation (via SKILL.md frontmatter)
|
|
1043
|
-
const skillIsolation = match.isolation === true;
|
|
1044
|
-
if (skillIsolation) {
|
|
1045
|
-
// Run skill through isolated sub-agent context
|
|
1046
|
-
const subCtx = SubAgentContext.create({
|
|
1047
|
-
role: `skill-${args.skill_name}`,
|
|
1048
|
-
task: `Execute the "${args.skill_name}" skill with input: ${(args.input || "").substring(0, 200)}`,
|
|
1049
|
-
allowedTools: ["read_file", "search_files", "list_dir"],
|
|
1050
|
-
cwd,
|
|
1051
|
-
});
|
|
1052
|
-
try {
|
|
1053
|
-
const result = await subCtx.run(args.input);
|
|
1054
|
-
return attachDescriptor({
|
|
1055
|
-
success: true,
|
|
1056
|
-
isolated: true,
|
|
1057
|
-
skill: args.skill_name,
|
|
1058
|
-
summary: result.summary,
|
|
1059
|
-
toolsUsed: result.toolsUsed,
|
|
1060
|
-
});
|
|
1061
|
-
} catch (err) {
|
|
1062
|
-
return attachDescriptor({
|
|
1063
|
-
error: `Isolated skill execution failed: ${err.message}`,
|
|
1064
|
-
});
|
|
1065
|
-
}
|
|
1066
|
-
}
|
|
1067
|
-
|
|
1068
|
-
try {
|
|
1069
|
-
const handlerPath = path.join(match.skillDir, "handler.js");
|
|
1070
|
-
const imported = await import(
|
|
1071
|
-
`file://${handlerPath.replace(/\\/g, "/")}`
|
|
1072
|
-
);
|
|
1073
|
-
const handler = imported.default || imported;
|
|
1074
|
-
if (handler.init) await handler.init(match);
|
|
1075
|
-
const task = {
|
|
1076
|
-
params: { input: args.input },
|
|
1077
|
-
input: args.input,
|
|
1078
|
-
action: args.input,
|
|
1079
|
-
};
|
|
1080
|
-
const taskContext = {
|
|
1081
|
-
projectRoot: cwd,
|
|
1082
|
-
workspacePath: cwd,
|
|
1083
|
-
};
|
|
1084
|
-
const result = await handler.execute(task, taskContext, match);
|
|
1085
|
-
return attachDescriptor(result);
|
|
1086
|
-
} catch (err) {
|
|
1087
|
-
return attachDescriptor({
|
|
1088
|
-
error: `Skill execution failed: ${err.message}`,
|
|
1089
|
-
});
|
|
1090
|
-
}
|
|
1091
|
-
}
|
|
1092
|
-
|
|
1093
|
-
case "list_skills": {
|
|
1094
|
-
let skills = skillLoader.getResolvedSkills();
|
|
1095
|
-
if (skills.length === 0) {
|
|
1096
|
-
return attachDescriptor({ error: "No skills found." });
|
|
1097
|
-
}
|
|
1098
|
-
if (args.category) {
|
|
1099
|
-
skills = skills.filter(
|
|
1100
|
-
(s) => s.category.toLowerCase() === args.category.toLowerCase(),
|
|
1101
|
-
);
|
|
1102
|
-
}
|
|
1103
|
-
if (args.query) {
|
|
1104
|
-
const q = args.query.toLowerCase();
|
|
1105
|
-
skills = skills.filter(
|
|
1106
|
-
(s) =>
|
|
1107
|
-
s.id.includes(q) ||
|
|
1108
|
-
s.description.toLowerCase().includes(q) ||
|
|
1109
|
-
s.category.toLowerCase().includes(q),
|
|
1110
|
-
);
|
|
1111
|
-
}
|
|
1112
|
-
return attachDescriptor({
|
|
1113
|
-
count: skills.length,
|
|
1114
|
-
skills: skills.map((s) => ({
|
|
1115
|
-
id: s.id,
|
|
1116
|
-
category: s.category,
|
|
1117
|
-
source: s.source,
|
|
1118
|
-
hasHandler: s.hasHandler,
|
|
1119
|
-
description: (s.description || "").substring(0, 80),
|
|
1120
|
-
})),
|
|
1121
|
-
});
|
|
1122
|
-
}
|
|
1123
|
-
|
|
1124
|
-
default:
|
|
1125
|
-
if (localToolExecutor?.kind === "mcp") {
|
|
1126
|
-
if (!mcpClient || typeof mcpClient.callTool !== "function") {
|
|
1127
|
-
return attachDescriptor({
|
|
1128
|
-
error: `MCP client is unavailable for tool: ${name}`,
|
|
1129
|
-
});
|
|
1130
|
-
}
|
|
1131
|
-
|
|
1132
|
-
try {
|
|
1133
|
-
const result = await mcpClient.callTool(
|
|
1134
|
-
localToolExecutor.serverName,
|
|
1135
|
-
localToolExecutor.toolName,
|
|
1136
|
-
args || {},
|
|
1137
|
-
);
|
|
1138
|
-
if (result && typeof result === "object") {
|
|
1139
|
-
return attachDescriptor(result);
|
|
1140
|
-
}
|
|
1141
|
-
return attachDescriptor({ result });
|
|
1142
|
-
} catch (err) {
|
|
1143
|
-
return attachDescriptor({
|
|
1144
|
-
error: `MCP tool execution failed: ${err.message}`,
|
|
1145
|
-
});
|
|
1146
|
-
}
|
|
1147
|
-
}
|
|
1148
|
-
|
|
1149
|
-
if (
|
|
1150
|
-
hostToolDefinition &&
|
|
1151
|
-
interaction &&
|
|
1152
|
-
typeof interaction.requestHostTool === "function"
|
|
1153
|
-
) {
|
|
1154
|
-
const hostedResult = await interaction.requestHostTool(name, args);
|
|
1155
|
-
if (hostedResult?.success === false) {
|
|
1156
|
-
return attachDescriptor({
|
|
1157
|
-
error:
|
|
1158
|
-
hostedResult.error || `Hosted tool execution failed: ${name}`,
|
|
1159
|
-
policy: hostToolPolicy || null,
|
|
1160
|
-
});
|
|
1161
|
-
}
|
|
1162
|
-
|
|
1163
|
-
if (hostedResult?.result && typeof hostedResult.result === "object") {
|
|
1164
|
-
return hostedResult.result;
|
|
1165
|
-
}
|
|
1166
|
-
|
|
1167
|
-
return attachDescriptor({
|
|
1168
|
-
result:
|
|
1169
|
-
hostedResult &&
|
|
1170
|
-
Object.prototype.hasOwnProperty.call(hostedResult, "result")
|
|
1171
|
-
? hostedResult.result
|
|
1172
|
-
: hostedResult,
|
|
1173
|
-
});
|
|
1174
|
-
}
|
|
1175
|
-
|
|
1176
|
-
return attachDescriptor({ error: `Unknown tool: ${name}` });
|
|
1177
|
-
}
|
|
1178
|
-
}
|
|
1179
|
-
|
|
1180
|
-
// ─── run_code implementation ──────────────────────────────────────────────
|
|
1181
|
-
|
|
1182
|
-
/**
|
|
1183
|
-
* Classify an error from code execution into a structured type with hints.
|
|
1184
|
-
* @param {string} stderr - stderr output
|
|
1185
|
-
* @param {string} message - error message
|
|
1186
|
-
* @param {number|null} exitCode - process exit code
|
|
1187
|
-
* @param {string} lang - language (python, node, bash)
|
|
1188
|
-
* @returns {{ errorType: string, hint: string }}
|
|
1189
|
-
*/
|
|
1190
|
-
export function classifyError(stderr, message, exitCode, lang) {
|
|
1191
|
-
const text = stderr || message || "";
|
|
1192
|
-
|
|
1193
|
-
// Import / module errors
|
|
1194
|
-
if (/ModuleNotFoundError|ImportError|No module named/i.test(text)) {
|
|
1195
|
-
const modMatch = text.match(/No module named ['"]([^'"]+)['"]/);
|
|
1196
|
-
return {
|
|
1197
|
-
errorType: "import_error",
|
|
1198
|
-
hint: modMatch
|
|
1199
|
-
? `Missing Python module "${modMatch[1]}". Will attempt auto-install.`
|
|
1200
|
-
: "Missing module. Check your imports.",
|
|
1201
|
-
};
|
|
1202
|
-
}
|
|
1203
|
-
|
|
1204
|
-
// Syntax errors
|
|
1205
|
-
if (/SyntaxError|IndentationError|TabError/i.test(text)) {
|
|
1206
|
-
const lineMatch = text.match(/line (\d+)/i);
|
|
1207
|
-
return {
|
|
1208
|
-
errorType: "syntax_error",
|
|
1209
|
-
hint: lineMatch
|
|
1210
|
-
? `Syntax error on line ${lineMatch[1]}. Check for typos, missing colons, or indentation.`
|
|
1211
|
-
: "Syntax error in code. Check for typos or missing brackets.",
|
|
1212
|
-
};
|
|
1213
|
-
}
|
|
1214
|
-
|
|
1215
|
-
// Timeout
|
|
1216
|
-
if (/ETIMEDOUT|timed?\s*out/i.test(text) || exitCode === null) {
|
|
1217
|
-
return {
|
|
1218
|
-
errorType: "timeout",
|
|
1219
|
-
hint: "Script timed out. Consider increasing timeout or optimizing the code.",
|
|
1220
|
-
};
|
|
1221
|
-
}
|
|
1222
|
-
|
|
1223
|
-
// Permission errors
|
|
1224
|
-
if (/EACCES|Permission denied|PermissionError/i.test(text)) {
|
|
1225
|
-
return {
|
|
1226
|
-
errorType: "permission_error",
|
|
1227
|
-
hint: "Permission denied. Try a different directory or run with appropriate permissions.",
|
|
1228
|
-
};
|
|
1229
|
-
}
|
|
1230
|
-
|
|
1231
|
-
// Generic runtime error
|
|
1232
|
-
const lineMatch = text.match(/(?:line |:)(\d+)/);
|
|
1233
|
-
return {
|
|
1234
|
-
errorType: "runtime_error",
|
|
1235
|
-
hint: lineMatch
|
|
1236
|
-
? `Runtime error near line ${lineMatch[1]}. Check the traceback above.`
|
|
1237
|
-
: "Runtime error. Check stderr for details.",
|
|
1238
|
-
};
|
|
1239
|
-
}
|
|
1240
|
-
|
|
1241
|
-
/**
|
|
1242
|
-
* Validate a package name for pip install (reject shell metacharacters).
|
|
1243
|
-
* @param {string} name
|
|
1244
|
-
* @returns {boolean}
|
|
1245
|
-
*/
|
|
1246
|
-
export function isValidPackageName(name) {
|
|
1247
|
-
return /^[a-zA-Z0-9_][a-zA-Z0-9._-]*$/.test(name) && name.length <= 100;
|
|
1248
|
-
}
|
|
1249
|
-
|
|
1250
|
-
/**
|
|
1251
|
-
* Execute code with auto pip-install, script persistence, and error classification.
|
|
1252
|
-
*/
|
|
1253
|
-
async function _executeRunCode(args, cwd) {
|
|
1254
|
-
const lang = args.language;
|
|
1255
|
-
const code = args.code;
|
|
1256
|
-
const timeoutSec = Math.min(Math.max(args.timeout || 60, 1), 300);
|
|
1257
|
-
const persist = args.persist !== false; // default true
|
|
1258
|
-
|
|
1259
|
-
const extMap = { python: ".py", node: ".js", bash: ".sh" };
|
|
1260
|
-
const ext = extMap[lang];
|
|
1261
|
-
if (!ext) {
|
|
1262
|
-
return {
|
|
1263
|
-
error: `Unsupported language: ${lang}. Use python, node, or bash.`,
|
|
1264
|
-
};
|
|
1265
|
-
}
|
|
1266
|
-
|
|
1267
|
-
// Determine script path
|
|
1268
|
-
let scriptPath;
|
|
1269
|
-
if (persist) {
|
|
1270
|
-
const scriptsDir = path.join(cwd, ".chainlesschain", "agent-scripts");
|
|
1271
|
-
if (!fs.existsSync(scriptsDir)) {
|
|
1272
|
-
fs.mkdirSync(scriptsDir, { recursive: true });
|
|
1273
|
-
}
|
|
1274
|
-
const timestamp = new Date()
|
|
1275
|
-
.toISOString()
|
|
1276
|
-
.replace(/[T:]/g, "-")
|
|
1277
|
-
.replace(/\.\d+Z$/, "");
|
|
1278
|
-
scriptPath = path.join(scriptsDir, `${timestamp}-${lang}${ext}`);
|
|
1279
|
-
} else {
|
|
1280
|
-
scriptPath = path.join(os.tmpdir(), `cc-agent-${Date.now()}${ext}`);
|
|
1281
|
-
}
|
|
1282
|
-
|
|
1283
|
-
try {
|
|
1284
|
-
fs.writeFileSync(scriptPath, code, "utf8");
|
|
1285
|
-
|
|
1286
|
-
// Determine interpreter
|
|
1287
|
-
let interpreter;
|
|
1288
|
-
if (lang === "python") {
|
|
1289
|
-
const py = getCachedPython();
|
|
1290
|
-
interpreter = py.found ? py.command : "python";
|
|
1291
|
-
} else if (lang === "node") {
|
|
1292
|
-
interpreter = "node";
|
|
1293
|
-
} else {
|
|
1294
|
-
interpreter = "bash";
|
|
1295
|
-
}
|
|
1296
|
-
|
|
1297
|
-
const start = Date.now();
|
|
1298
|
-
let output;
|
|
1299
|
-
try {
|
|
1300
|
-
output = execSync(`${interpreter} "${scriptPath}"`, {
|
|
1301
|
-
cwd,
|
|
1302
|
-
encoding: "utf8",
|
|
1303
|
-
timeout: timeoutSec * 1000,
|
|
1304
|
-
maxBuffer: 5 * 1024 * 1024,
|
|
1305
|
-
});
|
|
1306
|
-
} catch (err) {
|
|
1307
|
-
const stderr = (err.stderr || "").toString();
|
|
1308
|
-
const message = err.message || "";
|
|
1309
|
-
const classified = classifyError(stderr, message, err.status, lang);
|
|
1310
|
-
|
|
1311
|
-
// Auto-install missing Python packages
|
|
1312
|
-
if (lang === "python" && classified.errorType === "import_error") {
|
|
1313
|
-
const modMatch = stderr.match(/No module named ['"]([^'"]+)['"]/);
|
|
1314
|
-
if (modMatch) {
|
|
1315
|
-
// Use top-level package name (e.g. "foo.bar" → "foo")
|
|
1316
|
-
const packageName = modMatch[1].split(".")[0];
|
|
1317
|
-
|
|
1318
|
-
if (!isValidPackageName(packageName)) {
|
|
1319
|
-
return {
|
|
1320
|
-
error: `Invalid package name: "${packageName}"`,
|
|
1321
|
-
...classified,
|
|
1322
|
-
language: lang,
|
|
1323
|
-
scriptPath: persist ? scriptPath : undefined,
|
|
1324
|
-
};
|
|
1325
|
-
}
|
|
1326
|
-
|
|
1327
|
-
// Attempt pip install
|
|
1328
|
-
try {
|
|
1329
|
-
execSync(`${interpreter} -m pip install ${packageName}`, {
|
|
1330
|
-
encoding: "utf-8",
|
|
1331
|
-
timeout: 120000,
|
|
1332
|
-
maxBuffer: 2 * 1024 * 1024,
|
|
1333
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
1334
|
-
});
|
|
1335
|
-
|
|
1336
|
-
// Retry execution
|
|
1337
|
-
const retryStart = Date.now();
|
|
1338
|
-
const retryOutput = execSync(`${interpreter} "${scriptPath}"`, {
|
|
1339
|
-
cwd,
|
|
1340
|
-
encoding: "utf8",
|
|
1341
|
-
timeout: timeoutSec * 1000,
|
|
1342
|
-
maxBuffer: 5 * 1024 * 1024,
|
|
1343
|
-
});
|
|
1344
|
-
const retryDuration = Date.now() - retryStart;
|
|
1345
|
-
|
|
1346
|
-
return {
|
|
1347
|
-
success: true,
|
|
1348
|
-
output: retryOutput.substring(0, 50000),
|
|
1349
|
-
language: lang,
|
|
1350
|
-
duration: `${retryDuration}ms`,
|
|
1351
|
-
autoInstalled: [packageName],
|
|
1352
|
-
scriptPath: persist ? scriptPath : undefined,
|
|
1353
|
-
};
|
|
1354
|
-
} catch (pipErr) {
|
|
1355
|
-
return {
|
|
1356
|
-
error: (stderr || message).substring(0, 5000),
|
|
1357
|
-
stderr: stderr.substring(0, 5000),
|
|
1358
|
-
exitCode: err.status,
|
|
1359
|
-
language: lang,
|
|
1360
|
-
...classified,
|
|
1361
|
-
hint: `Failed to auto-install "${packageName}". ${(pipErr.stderr || pipErr.message || "").substring(0, 500)}`,
|
|
1362
|
-
scriptPath: persist ? scriptPath : undefined,
|
|
1363
|
-
};
|
|
1364
|
-
}
|
|
1365
|
-
}
|
|
1366
|
-
}
|
|
1367
|
-
|
|
1368
|
-
return {
|
|
1369
|
-
error: (stderr || message).substring(0, 5000),
|
|
1370
|
-
stderr: stderr.substring(0, 5000),
|
|
1371
|
-
exitCode: err.status,
|
|
1372
|
-
language: lang,
|
|
1373
|
-
...classified,
|
|
1374
|
-
scriptPath: persist ? scriptPath : undefined,
|
|
1375
|
-
};
|
|
1376
|
-
}
|
|
1377
|
-
|
|
1378
|
-
const duration = Date.now() - start;
|
|
1379
|
-
return {
|
|
1380
|
-
success: true,
|
|
1381
|
-
output: output.substring(0, 50000),
|
|
1382
|
-
language: lang,
|
|
1383
|
-
duration: `${duration}ms`,
|
|
1384
|
-
scriptPath: persist ? scriptPath : undefined,
|
|
1385
|
-
};
|
|
1386
|
-
} finally {
|
|
1387
|
-
// Only clean up if not persisting
|
|
1388
|
-
if (!persist) {
|
|
1389
|
-
try {
|
|
1390
|
-
fs.unlinkSync(scriptPath);
|
|
1391
|
-
} catch {
|
|
1392
|
-
// Cleanup best-effort
|
|
1393
|
-
}
|
|
1394
|
-
}
|
|
1395
|
-
}
|
|
1396
|
-
}
|
|
1397
|
-
|
|
1398
|
-
// ─── spawn_sub_agent implementation ──────────────────────────────────────
|
|
1399
|
-
|
|
1400
|
-
/**
|
|
1401
|
-
* Execute a spawn_sub_agent tool call.
|
|
1402
|
-
* Creates an isolated SubAgentContext, runs it, and returns only the summary.
|
|
1403
|
-
*
|
|
1404
|
-
* @param {object} args - { role, task, context?, tools? }
|
|
1405
|
-
* @param {object} ctx - { skillLoader, cwd, parentMessages, interaction, sessionId }
|
|
1406
|
-
* @returns {Promise<object>}
|
|
1407
|
-
*/
|
|
1408
|
-
async function _executeSpawnSubAgent(args, ctx) {
|
|
1409
|
-
const { role, task, context: inheritedContext, tools: allowedTools } = args;
|
|
1410
|
-
|
|
1411
|
-
if (!role || !task) {
|
|
1412
|
-
return { error: "Both 'role' and 'task' are required for spawn_sub_agent" };
|
|
1413
|
-
}
|
|
1414
|
-
|
|
1415
|
-
// Auto-condense parent context if caller didn't provide explicit context
|
|
1416
|
-
let resolvedContext = inheritedContext || null;
|
|
1417
|
-
if (!resolvedContext && Array.isArray(ctx.parentMessages)) {
|
|
1418
|
-
const recentMsgs = ctx.parentMessages
|
|
1419
|
-
.filter((m) => m.role === "assistant" && typeof m.content === "string")
|
|
1420
|
-
.slice(-3)
|
|
1421
|
-
.map((m) => m.content.substring(0, 200));
|
|
1422
|
-
if (recentMsgs.length > 0) {
|
|
1423
|
-
resolvedContext = recentMsgs.join("\n---\n");
|
|
1424
|
-
}
|
|
1425
|
-
}
|
|
1426
|
-
|
|
1427
|
-
// Link child to parent session so registry-scoped queries and
|
|
1428
|
-
// session-close cascade cleanup can find it.
|
|
1429
|
-
const parentSessionId = ctx.sessionId || null;
|
|
1430
|
-
const interaction = ctx.interaction || null;
|
|
1431
|
-
|
|
1432
|
-
const subCtx = SubAgentContext.create({
|
|
1433
|
-
role,
|
|
1434
|
-
task,
|
|
1435
|
-
parentId: parentSessionId,
|
|
1436
|
-
inheritedContext: resolvedContext,
|
|
1437
|
-
allowedTools: allowedTools || null,
|
|
1438
|
-
cwd: ctx.cwd,
|
|
1439
|
-
});
|
|
1440
|
-
|
|
1441
|
-
const emit = (type, payload) => {
|
|
1442
|
-
if (!interaction || typeof interaction.emit !== "function") return;
|
|
1443
|
-
try {
|
|
1444
|
-
interaction.emit(type, {
|
|
1445
|
-
sessionId: parentSessionId,
|
|
1446
|
-
subAgentId: subCtx.id,
|
|
1447
|
-
parentSessionId,
|
|
1448
|
-
role: subCtx.role,
|
|
1449
|
-
...payload,
|
|
1450
|
-
});
|
|
1451
|
-
} catch (_err) {
|
|
1452
|
-
// Event emission is best-effort — never break the tool call
|
|
1453
|
-
}
|
|
1454
|
-
};
|
|
1455
|
-
|
|
1456
|
-
try {
|
|
1457
|
-
// Notify registry if available
|
|
1458
|
-
const { SubAgentRegistry } = await import("./sub-agent-registry.js").catch(
|
|
1459
|
-
() => ({ SubAgentRegistry: null }),
|
|
1460
|
-
);
|
|
1461
|
-
if (SubAgentRegistry) {
|
|
1462
|
-
try {
|
|
1463
|
-
SubAgentRegistry.getInstance().register(subCtx);
|
|
1464
|
-
} catch (_err) {
|
|
1465
|
-
// Registry not available — non-critical
|
|
1466
|
-
}
|
|
1467
|
-
}
|
|
1468
|
-
|
|
1469
|
-
emit("sub-agent.started", {
|
|
1470
|
-
task: subCtx.task,
|
|
1471
|
-
allowedTools: allowedTools || null,
|
|
1472
|
-
maxIterations: subCtx.maxIterations,
|
|
1473
|
-
createdAt: subCtx.createdAt,
|
|
1474
|
-
});
|
|
1475
|
-
|
|
1476
|
-
const result = await subCtx.run(task);
|
|
1477
|
-
|
|
1478
|
-
// Complete in registry
|
|
1479
|
-
if (SubAgentRegistry) {
|
|
1480
|
-
try {
|
|
1481
|
-
SubAgentRegistry.getInstance().complete(subCtx.id, result);
|
|
1482
|
-
} catch (_err) {
|
|
1483
|
-
// Non-critical
|
|
1484
|
-
}
|
|
1485
|
-
}
|
|
1486
|
-
|
|
1487
|
-
emit("sub-agent.completed", {
|
|
1488
|
-
status: subCtx.status,
|
|
1489
|
-
summary: result.summary,
|
|
1490
|
-
toolsUsed: result.toolsUsed,
|
|
1491
|
-
iterationCount: result.iterationCount,
|
|
1492
|
-
tokenCount: result.tokenCount,
|
|
1493
|
-
artifactCount: result.artifacts.length,
|
|
1494
|
-
completedAt: subCtx.completedAt,
|
|
1495
|
-
});
|
|
1496
|
-
|
|
1497
|
-
return {
|
|
1498
|
-
success: true,
|
|
1499
|
-
subAgentId: subCtx.id,
|
|
1500
|
-
role: subCtx.role,
|
|
1501
|
-
parentSessionId,
|
|
1502
|
-
summary: result.summary,
|
|
1503
|
-
toolsUsed: result.toolsUsed,
|
|
1504
|
-
iterationCount: result.iterationCount,
|
|
1505
|
-
artifactCount: result.artifacts.length,
|
|
1506
|
-
};
|
|
1507
|
-
} catch (err) {
|
|
1508
|
-
subCtx.forceComplete(err.message);
|
|
1509
|
-
|
|
1510
|
-
emit("sub-agent.failed", {
|
|
1511
|
-
status: subCtx.status,
|
|
1512
|
-
error: err.message,
|
|
1513
|
-
completedAt: subCtx.completedAt,
|
|
1514
|
-
});
|
|
1515
|
-
|
|
1516
|
-
return {
|
|
1517
|
-
error: `Sub-agent failed: ${err.message}`,
|
|
1518
|
-
subAgentId: subCtx.id,
|
|
1519
|
-
role: subCtx.role,
|
|
1520
|
-
parentSessionId,
|
|
1521
|
-
};
|
|
1522
|
-
}
|
|
1523
|
-
}
|
|
1524
|
-
|
|
1525
|
-
// ─── LLM chat with tools ─────────────────────────────────────────────────
|
|
1526
|
-
|
|
1527
|
-
/**
|
|
1528
|
-
* Send a chat completion request with tool definitions.
|
|
1529
|
-
* Supports 8 providers: ollama, anthropic, openai, deepseek, dashscope, gemini, mistral, volcengine
|
|
1530
|
-
*
|
|
1531
|
-
* @param {Array} rawMessages
|
|
1532
|
-
* @param {object} options
|
|
1533
|
-
* @returns {Promise<object>} response with .message
|
|
1534
|
-
*/
|
|
1535
|
-
export async function chatWithTools(rawMessages, options) {
|
|
1536
|
-
const {
|
|
1537
|
-
provider,
|
|
1538
|
-
model,
|
|
1539
|
-
baseUrl,
|
|
1540
|
-
apiKey,
|
|
1541
|
-
contextEngine: ce,
|
|
1542
|
-
signal,
|
|
1543
|
-
} = options;
|
|
1544
|
-
|
|
1545
|
-
const persona = _loadProjectPersona(options.cwd);
|
|
1546
|
-
const tools = getAgentToolDefinitions({
|
|
1547
|
-
names: options.enabledToolNames,
|
|
1548
|
-
disabledTools: persona?.toolsDisabled,
|
|
1549
|
-
extraTools: [
|
|
1550
|
-
...(options.hostManagedToolPolicy?.toolDefinitions || []),
|
|
1551
|
-
...(options.extraToolDefinitions || []),
|
|
1552
|
-
],
|
|
1553
|
-
});
|
|
1554
|
-
|
|
1555
|
-
const lastUserMsg = [...rawMessages].reverse().find((m) => m.role === "user");
|
|
1556
|
-
const messages = ce
|
|
1557
|
-
? ce.buildOptimizedMessages(rawMessages, {
|
|
1558
|
-
userQuery: lastUserMsg?.content,
|
|
1559
|
-
})
|
|
1560
|
-
: rawMessages;
|
|
1561
|
-
|
|
1562
|
-
throwIfAborted(signal);
|
|
1563
|
-
|
|
1564
|
-
if (provider === "ollama") {
|
|
1565
|
-
const response = await fetch(`${baseUrl}/api/chat`, {
|
|
1566
|
-
method: "POST",
|
|
1567
|
-
headers: { "Content-Type": "application/json" },
|
|
1568
|
-
signal,
|
|
1569
|
-
body: JSON.stringify({
|
|
1570
|
-
model,
|
|
1571
|
-
messages,
|
|
1572
|
-
tools,
|
|
1573
|
-
stream: false,
|
|
1574
|
-
}),
|
|
1575
|
-
});
|
|
1576
|
-
if (!response.ok) {
|
|
1577
|
-
throw new Error(`Ollama error: ${response.status}`);
|
|
1578
|
-
}
|
|
1579
|
-
return await response.json();
|
|
1580
|
-
}
|
|
1581
|
-
|
|
1582
|
-
if (provider === "anthropic") {
|
|
1583
|
-
const key = apiKey || process.env.ANTHROPIC_API_KEY;
|
|
1584
|
-
if (!key) throw new Error("ANTHROPIC_API_KEY required");
|
|
1585
|
-
|
|
1586
|
-
const systemMsgs = messages.filter((m) => m.role === "system");
|
|
1587
|
-
const otherMsgs = messages.filter((m) => m.role !== "system");
|
|
1588
|
-
|
|
1589
|
-
const anthropicTools = tools.map((t) => ({
|
|
1590
|
-
name: t.function.name,
|
|
1591
|
-
description: t.function.description,
|
|
1592
|
-
input_schema: t.function.parameters,
|
|
1593
|
-
}));
|
|
1594
|
-
|
|
1595
|
-
const body = {
|
|
1596
|
-
model: model || "claude-sonnet-4-20250514",
|
|
1597
|
-
max_tokens: 8192,
|
|
1598
|
-
messages: otherMsgs,
|
|
1599
|
-
tools: anthropicTools,
|
|
1600
|
-
};
|
|
1601
|
-
if (systemMsgs.length > 0) {
|
|
1602
|
-
body.system = systemMsgs.map((m) => m.content).join("\n");
|
|
1603
|
-
}
|
|
1604
|
-
|
|
1605
|
-
const url =
|
|
1606
|
-
baseUrl && baseUrl !== "http://localhost:11434"
|
|
1607
|
-
? baseUrl
|
|
1608
|
-
: "https://api.anthropic.com/v1";
|
|
1609
|
-
|
|
1610
|
-
const response = await fetch(`${url}/messages`, {
|
|
1611
|
-
method: "POST",
|
|
1612
|
-
headers: {
|
|
1613
|
-
"Content-Type": "application/json",
|
|
1614
|
-
"x-api-key": key,
|
|
1615
|
-
"anthropic-version": "2023-06-01",
|
|
1616
|
-
},
|
|
1617
|
-
signal,
|
|
1618
|
-
body: JSON.stringify(body),
|
|
1619
|
-
});
|
|
1620
|
-
|
|
1621
|
-
if (!response.ok) {
|
|
1622
|
-
throw new Error(`Anthropic error: ${response.status}`);
|
|
1623
|
-
}
|
|
1624
|
-
|
|
1625
|
-
const data = await response.json();
|
|
1626
|
-
return _normalizeAnthropicResponse(data);
|
|
1627
|
-
}
|
|
1628
|
-
|
|
1629
|
-
// OpenAI-compatible providers
|
|
1630
|
-
const providerUrls = {
|
|
1631
|
-
openai: "https://api.openai.com/v1",
|
|
1632
|
-
deepseek: "https://api.deepseek.com/v1",
|
|
1633
|
-
dashscope: "https://dashscope.aliyuncs.com/compatible-mode/v1",
|
|
1634
|
-
mistral: "https://api.mistral.ai/v1",
|
|
1635
|
-
gemini: "https://generativelanguage.googleapis.com/v1beta/openai",
|
|
1636
|
-
volcengine: "https://ark.cn-beijing.volces.com/api/v3",
|
|
1637
|
-
};
|
|
1638
|
-
|
|
1639
|
-
const providerApiKeyEnvs = {
|
|
1640
|
-
openai: "OPENAI_API_KEY",
|
|
1641
|
-
deepseek: "DEEPSEEK_API_KEY",
|
|
1642
|
-
dashscope: "DASHSCOPE_API_KEY",
|
|
1643
|
-
mistral: "MISTRAL_API_KEY",
|
|
1644
|
-
gemini: "GEMINI_API_KEY",
|
|
1645
|
-
volcengine: "VOLCENGINE_API_KEY",
|
|
1646
|
-
};
|
|
1647
|
-
|
|
1648
|
-
const url =
|
|
1649
|
-
baseUrl && baseUrl !== "http://localhost:11434"
|
|
1650
|
-
? baseUrl
|
|
1651
|
-
: providerUrls[provider];
|
|
1652
|
-
|
|
1653
|
-
if (!url) {
|
|
1654
|
-
throw new Error(
|
|
1655
|
-
`Unsupported provider: ${provider}. Supported: ollama, anthropic, openai, deepseek, dashscope, mistral, gemini, volcengine`,
|
|
1656
|
-
);
|
|
1657
|
-
}
|
|
1658
|
-
|
|
1659
|
-
const envKey = providerApiKeyEnvs[provider] || "OPENAI_API_KEY";
|
|
1660
|
-
const key = apiKey || process.env[envKey];
|
|
1661
|
-
if (!key) throw new Error(`${envKey} required for provider ${provider}`);
|
|
1662
|
-
|
|
1663
|
-
const defaultModels = {
|
|
1664
|
-
openai: "gpt-4o",
|
|
1665
|
-
deepseek: "deepseek-chat",
|
|
1666
|
-
dashscope: "qwen-turbo",
|
|
1667
|
-
mistral: "mistral-large-latest",
|
|
1668
|
-
gemini: "gemini-2.0-flash",
|
|
1669
|
-
volcengine: "doubao-seed-1-6-251015",
|
|
1670
|
-
};
|
|
1671
|
-
|
|
1672
|
-
const response = await fetch(`${url}/chat/completions`, {
|
|
1673
|
-
method: "POST",
|
|
1674
|
-
headers: {
|
|
1675
|
-
"Content-Type": "application/json",
|
|
1676
|
-
Authorization: `Bearer ${key}`,
|
|
1677
|
-
},
|
|
1678
|
-
signal,
|
|
1679
|
-
body: JSON.stringify({
|
|
1680
|
-
model: model || defaultModels[provider] || "gpt-4o-mini",
|
|
1681
|
-
messages,
|
|
1682
|
-
tools,
|
|
1683
|
-
}),
|
|
1684
|
-
});
|
|
1685
|
-
|
|
1686
|
-
if (!response.ok) {
|
|
1687
|
-
throw new Error(`${provider} API error: ${response.status}`);
|
|
1688
|
-
}
|
|
1689
|
-
|
|
1690
|
-
const data = await response.json();
|
|
1691
|
-
if (!data.choices || !data.choices[0]) {
|
|
1692
|
-
throw new Error("Invalid API response: no choices returned");
|
|
1693
|
-
}
|
|
1694
|
-
const choice = data.choices[0];
|
|
1695
|
-
return { message: choice.message };
|
|
1696
|
-
}
|
|
1697
|
-
|
|
1698
|
-
function _normalizeAnthropicResponse(data) {
|
|
1699
|
-
const content = data.content || [];
|
|
1700
|
-
const textBlocks = content.filter((b) => b.type === "text");
|
|
1701
|
-
const toolBlocks = content.filter((b) => b.type === "tool_use");
|
|
1702
|
-
|
|
1703
|
-
const message = {
|
|
1704
|
-
role: "assistant",
|
|
1705
|
-
content: textBlocks.map((b) => b.text).join("\n") || "",
|
|
1706
|
-
};
|
|
1707
|
-
|
|
1708
|
-
if (toolBlocks.length > 0) {
|
|
1709
|
-
message.tool_calls = toolBlocks.map((b) => ({
|
|
1710
|
-
id: b.id,
|
|
1711
|
-
type: "function",
|
|
1712
|
-
function: {
|
|
1713
|
-
name: b.name,
|
|
1714
|
-
arguments: JSON.stringify(b.input),
|
|
1715
|
-
},
|
|
1716
|
-
}));
|
|
1717
|
-
}
|
|
1718
|
-
|
|
1719
|
-
return { message };
|
|
1720
|
-
}
|
|
1721
|
-
|
|
1722
|
-
// ─── Agent loop (async generator) ─────────────────────────────────────────
|
|
1723
|
-
|
|
1724
|
-
/**
|
|
1725
|
-
* Async generator that drives the agentic tool-use loop.
|
|
1726
|
-
*
|
|
1727
|
-
* Yields events:
|
|
1728
|
-
* { type: "slot-filling", slot, question } — when asking user for missing info
|
|
1729
|
-
* { type: "tool-executing", tool, args }
|
|
1730
|
-
* { type: "tool-result", tool, result, error }
|
|
1731
|
-
* { type: "response-complete", content }
|
|
1732
|
-
*
|
|
1733
|
-
* @param {Array} messages - mutable messages array (will be appended to)
|
|
1734
|
-
* @param {object} options - provider, model, baseUrl, apiKey, contextEngine, hookDb, skillLoader, cwd, slotFiller, interaction
|
|
1735
|
-
*/
|
|
1736
|
-
export async function* agentLoop(messages, options) {
|
|
1737
|
-
const MAX_ITERATIONS = 15;
|
|
1738
|
-
const signal = options.signal || null;
|
|
1739
|
-
const toolContext = {
|
|
1740
|
-
hookDb: options.hookDb || null,
|
|
1741
|
-
skillLoader: options.skillLoader || _defaultSkillLoader,
|
|
1742
|
-
cwd: options.cwd || process.cwd(),
|
|
1743
|
-
planManager: options.planManager || null,
|
|
1744
|
-
sessionId: options.sessionId || null,
|
|
1745
|
-
hostManagedToolPolicy: options.hostManagedToolPolicy || null,
|
|
1746
|
-
externalToolDescriptors: options.externalToolDescriptors || null,
|
|
1747
|
-
externalToolExecutors: options.externalToolExecutors || null,
|
|
1748
|
-
mcpClient: options.mcpClient || null,
|
|
1749
|
-
parentMessages: messages, // pass parent messages for sub-agent auto-condensation
|
|
1750
|
-
interaction: options.interaction || null,
|
|
1751
|
-
};
|
|
1752
|
-
|
|
1753
|
-
throwIfAborted(signal);
|
|
1754
|
-
|
|
1755
|
-
// ── Slot-filling phase ──────────────────────────────────────────────
|
|
1756
|
-
// Before calling the LLM, check if the user's message matches a known
|
|
1757
|
-
// intent with missing required parameters. If so, interactively fill them
|
|
1758
|
-
// and append the gathered context to the user message.
|
|
1759
|
-
if (options.slotFiller && options.interaction) {
|
|
1760
|
-
const lastUserMsg = [...messages].reverse().find((m) => m.role === "user");
|
|
1761
|
-
if (lastUserMsg) {
|
|
1762
|
-
try {
|
|
1763
|
-
const { CLISlotFiller } = await import("./slot-filler.js");
|
|
1764
|
-
const intent = CLISlotFiller.detectIntent(lastUserMsg.content);
|
|
1765
|
-
|
|
1766
|
-
if (intent) {
|
|
1767
|
-
const requiredSlots = CLISlotFiller.getSlotDefinitions(
|
|
1768
|
-
intent.type,
|
|
1769
|
-
).required;
|
|
1770
|
-
const missingSlots = requiredSlots.filter((s) => !intent.entities[s]);
|
|
1771
|
-
|
|
1772
|
-
if (missingSlots.length > 0) {
|
|
1773
|
-
const result = await options.slotFiller.fillSlots(intent, {
|
|
1774
|
-
cwd: options.cwd || process.cwd(),
|
|
1775
|
-
});
|
|
1776
|
-
|
|
1777
|
-
// Yield slot-filling events for each filled slot
|
|
1778
|
-
for (const slot of result.filledSlots) {
|
|
1779
|
-
yield {
|
|
1780
|
-
type: "slot-filling",
|
|
1781
|
-
slot,
|
|
1782
|
-
question: `Filled "${slot}" = "${result.entities[slot]}"`,
|
|
1783
|
-
};
|
|
1784
|
-
}
|
|
1785
|
-
|
|
1786
|
-
// Append gathered context to the user message so the LLM has full info
|
|
1787
|
-
if (result.filledSlots.length > 0) {
|
|
1788
|
-
const contextParts = Object.entries(result.entities)
|
|
1789
|
-
.filter(([, v]) => v)
|
|
1790
|
-
.map(([k, v]) => `${k}: ${v}`);
|
|
1791
|
-
lastUserMsg.content += `\n\n[Context — user provided: ${contextParts.join(", ")}]`;
|
|
1792
|
-
}
|
|
1793
|
-
}
|
|
1794
|
-
}
|
|
1795
|
-
} catch (error) {
|
|
1796
|
-
if (isAbortError(error) || signal?.aborted) {
|
|
1797
|
-
throw error;
|
|
1798
|
-
}
|
|
1799
|
-
// Slot-filling failure is non-critical — proceed to LLM
|
|
1800
|
-
}
|
|
1801
|
-
}
|
|
1802
|
-
}
|
|
1803
|
-
|
|
1804
|
-
for (let i = 0; i < MAX_ITERATIONS; i++) {
|
|
1805
|
-
throwIfAborted(signal);
|
|
1806
|
-
const result = await chatWithTools(messages, options);
|
|
1807
|
-
throwIfAborted(signal);
|
|
1808
|
-
const msg = result?.message;
|
|
1809
|
-
|
|
1810
|
-
if (!msg) {
|
|
1811
|
-
yield { type: "response-complete", content: "(No response from LLM)" };
|
|
1812
|
-
return;
|
|
1813
|
-
}
|
|
1814
|
-
|
|
1815
|
-
const toolCalls = msg.tool_calls;
|
|
1816
|
-
|
|
1817
|
-
if (!toolCalls || toolCalls.length === 0) {
|
|
1818
|
-
yield { type: "response-complete", content: msg.content || "" };
|
|
1819
|
-
return;
|
|
1820
|
-
}
|
|
1821
|
-
|
|
1822
|
-
// Add assistant message with tool calls
|
|
1823
|
-
messages.push(msg);
|
|
1824
|
-
|
|
1825
|
-
for (const call of toolCalls) {
|
|
1826
|
-
throwIfAborted(signal);
|
|
1827
|
-
const fn = call.function;
|
|
1828
|
-
const toolName = fn.name;
|
|
1829
|
-
let toolArgs;
|
|
1830
|
-
|
|
1831
|
-
try {
|
|
1832
|
-
toolArgs =
|
|
1833
|
-
typeof fn.arguments === "string"
|
|
1834
|
-
? JSON.parse(fn.arguments)
|
|
1835
|
-
: fn.arguments;
|
|
1836
|
-
} catch {
|
|
1837
|
-
toolArgs = {};
|
|
1838
|
-
}
|
|
1839
|
-
|
|
1840
|
-
yield { type: "tool-executing", tool: toolName, args: toolArgs };
|
|
1841
|
-
|
|
1842
|
-
let toolResult;
|
|
1843
|
-
let toolError = null;
|
|
1844
|
-
try {
|
|
1845
|
-
toolResult = await executeTool(toolName, toolArgs, toolContext);
|
|
1846
|
-
} catch (err) {
|
|
1847
|
-
toolResult = { error: err.message };
|
|
1848
|
-
toolError = err.message;
|
|
1849
|
-
}
|
|
1850
|
-
|
|
1851
|
-
throwIfAborted(signal);
|
|
1852
|
-
|
|
1853
|
-
yield {
|
|
1854
|
-
type: "tool-result",
|
|
1855
|
-
tool: toolName,
|
|
1856
|
-
result: toolResult,
|
|
1857
|
-
error: toolError,
|
|
1858
|
-
};
|
|
1859
|
-
|
|
1860
|
-
messages.push({
|
|
1861
|
-
role: "tool",
|
|
1862
|
-
content: JSON.stringify(toolResult).substring(0, 5000),
|
|
1863
|
-
tool_call_id: call.id,
|
|
1864
|
-
});
|
|
1865
|
-
}
|
|
1866
|
-
}
|
|
1867
|
-
|
|
1868
|
-
yield {
|
|
1869
|
-
type: "response-complete",
|
|
1870
|
-
content: "(Reached max tool call iterations)",
|
|
1871
|
-
};
|
|
1872
|
-
}
|
|
1873
|
-
|
|
1874
|
-
// ─── Format helpers ───────────────────────────────────────────────────────
|
|
1875
|
-
|
|
1876
|
-
export function formatToolArgs(name, args) {
|
|
1877
|
-
switch (name) {
|
|
1878
|
-
case "read_file":
|
|
1879
|
-
return args.path;
|
|
1880
|
-
case "write_file":
|
|
1881
|
-
return `${args.path} (${args.content?.length || 0} chars)`;
|
|
1882
|
-
case "edit_file":
|
|
1883
|
-
return args.path;
|
|
1884
|
-
case "run_shell":
|
|
1885
|
-
return args.command;
|
|
1886
|
-
case "git":
|
|
1887
|
-
return args.command;
|
|
1888
|
-
case "search_files":
|
|
1889
|
-
return args.pattern;
|
|
1890
|
-
case "list_dir":
|
|
1891
|
-
return args.path || ".";
|
|
1892
|
-
case "run_skill":
|
|
1893
|
-
return `${args.skill_name}: ${(args.input || "").substring(0, 50)}`;
|
|
1894
|
-
case "list_skills":
|
|
1895
|
-
return args.category || args.query || "all";
|
|
1896
|
-
case "run_code":
|
|
1897
|
-
return `${args.language} (${(args.code || "").length} chars)`;
|
|
1898
|
-
case "spawn_sub_agent":
|
|
1899
|
-
return `[${args.role}] ${(args.task || "").substring(0, 60)}`;
|
|
1900
|
-
default:
|
|
1901
|
-
return JSON.stringify(args).substring(0, 60);
|
|
1902
|
-
}
|
|
1903
|
-
}
|
|
2
|
+
* @deprecated — canonical implementation lives in
|
|
3
|
+
* `../runtime/agent-core.js` as of the CLI Runtime Convergence roadmap
|
|
4
|
+
* (Phase 6b, 2026-04-09). This file is retained as a re-export shim for
|
|
5
|
+
* backwards compatibility and will be removed once all external consumers
|
|
6
|
+
* have migrated.
|
|
7
|
+
*
|
|
8
|
+
* Please import from `packages/cli/src/runtime/agent-core.js` in new code.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export {
|
|
12
|
+
AGENT_TOOLS,
|
|
13
|
+
AGENT_TOOL_REGISTRY,
|
|
14
|
+
getAgentToolDefinitions,
|
|
15
|
+
getAgentToolDescriptors,
|
|
16
|
+
getCachedPython,
|
|
17
|
+
getEnvironmentInfo,
|
|
18
|
+
getBaseSystemPrompt,
|
|
19
|
+
buildSystemPrompt,
|
|
20
|
+
executeTool,
|
|
21
|
+
classifyError,
|
|
22
|
+
isValidPackageName,
|
|
23
|
+
chatWithTools,
|
|
24
|
+
agentLoop,
|
|
25
|
+
formatToolArgs,
|
|
26
|
+
} from "../runtime/agent-core.js";
|