chainlesschain 0.40.2 → 0.40.3
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/README.md +22 -15
- package/package.json +1 -1
- package/src/lib/agent-core.js +861 -0
- package/src/lib/chat-core.js +177 -0
- package/src/lib/interaction-adapter.js +177 -0
- package/src/lib/interactive-planner.js +524 -0
- package/src/lib/llm-providers.js +9 -1
- package/src/lib/slot-filler.js +465 -0
- package/src/lib/task-model-selector.js +5 -5
- package/src/lib/ws-agent-handler.js +403 -0
- package/src/lib/ws-chat-handler.js +145 -0
- package/src/lib/ws-server.js +280 -1
- package/src/lib/ws-session-manager.js +363 -0
- package/src/repl/agent-repl.js +159 -11
package/src/repl/agent-repl.js
CHANGED
|
@@ -10,9 +10,9 @@
|
|
|
10
10
|
* - run_shell: Execute a shell command
|
|
11
11
|
* - search_files: Search for files by name/content
|
|
12
12
|
* - list_dir: List directory contents
|
|
13
|
-
* -
|
|
14
|
-
* -
|
|
15
|
-
* -
|
|
13
|
+
* - run_skill: Run a built-in skill
|
|
14
|
+
* - list_skills: List available skills
|
|
15
|
+
* - run_code: Write and execute code (Python/Node.js/Bash)
|
|
16
16
|
*
|
|
17
17
|
* The AI decides which tools to call based on user intent.
|
|
18
18
|
*/
|
|
@@ -22,6 +22,7 @@ import chalk from "chalk";
|
|
|
22
22
|
import fs from "fs";
|
|
23
23
|
import path from "path";
|
|
24
24
|
import { execSync } from "child_process";
|
|
25
|
+
import os from "os";
|
|
25
26
|
import { fileURLToPath } from "url";
|
|
26
27
|
import { logger } from "../lib/logger.js";
|
|
27
28
|
import { getPlanModeManager, PlanState } from "../lib/plan-mode.js";
|
|
@@ -203,6 +204,30 @@ const TOOLS = [
|
|
|
203
204
|
},
|
|
204
205
|
},
|
|
205
206
|
},
|
|
207
|
+
{
|
|
208
|
+
type: "function",
|
|
209
|
+
function: {
|
|
210
|
+
name: "run_code",
|
|
211
|
+
description:
|
|
212
|
+
"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. The code is saved to a temp file and executed.",
|
|
213
|
+
parameters: {
|
|
214
|
+
type: "object",
|
|
215
|
+
properties: {
|
|
216
|
+
language: {
|
|
217
|
+
type: "string",
|
|
218
|
+
enum: ["python", "node", "bash"],
|
|
219
|
+
description: "Programming language",
|
|
220
|
+
},
|
|
221
|
+
code: { type: "string", description: "Code to execute" },
|
|
222
|
+
timeout: {
|
|
223
|
+
type: "number",
|
|
224
|
+
description: "Execution timeout in seconds (default: 60, max: 300)",
|
|
225
|
+
},
|
|
226
|
+
},
|
|
227
|
+
required: ["language", "code"],
|
|
228
|
+
},
|
|
229
|
+
},
|
|
230
|
+
},
|
|
206
231
|
];
|
|
207
232
|
|
|
208
233
|
/**
|
|
@@ -228,7 +253,7 @@ async function executeTool(name, args) {
|
|
|
228
253
|
tool: name,
|
|
229
254
|
params: args,
|
|
230
255
|
estimatedImpact:
|
|
231
|
-
name === "run_shell"
|
|
256
|
+
name === "run_shell" || name === "run_code"
|
|
232
257
|
? "high"
|
|
233
258
|
: name === "write_file"
|
|
234
259
|
? "medium"
|
|
@@ -340,10 +365,10 @@ async function _executeToolInner(name, args) {
|
|
|
340
365
|
const output = execSync(args.command, {
|
|
341
366
|
cwd: args.cwd || process.cwd(),
|
|
342
367
|
encoding: "utf8",
|
|
343
|
-
timeout:
|
|
368
|
+
timeout: 60000,
|
|
344
369
|
maxBuffer: 1024 * 1024,
|
|
345
370
|
});
|
|
346
|
-
return { stdout: output.substring(0,
|
|
371
|
+
return { stdout: output.substring(0, 30000) };
|
|
347
372
|
} catch (err) {
|
|
348
373
|
return {
|
|
349
374
|
error: err.message.substring(0, 2000),
|
|
@@ -353,6 +378,69 @@ async function _executeToolInner(name, args) {
|
|
|
353
378
|
}
|
|
354
379
|
}
|
|
355
380
|
|
|
381
|
+
case "run_code": {
|
|
382
|
+
const lang = args.language;
|
|
383
|
+
const code = args.code;
|
|
384
|
+
const timeoutSec = Math.min(Math.max(args.timeout || 60, 1), 300);
|
|
385
|
+
|
|
386
|
+
const extMap = { python: ".py", node: ".js", bash: ".sh" };
|
|
387
|
+
const ext = extMap[lang];
|
|
388
|
+
if (!ext) {
|
|
389
|
+
return {
|
|
390
|
+
error: `Unsupported language: ${lang}. Use python, node, or bash.`,
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
const tmpFile = path.join(os.tmpdir(), `cc-agent-${Date.now()}${ext}`);
|
|
395
|
+
|
|
396
|
+
try {
|
|
397
|
+
fs.writeFileSync(tmpFile, code, "utf8");
|
|
398
|
+
|
|
399
|
+
let interpreter;
|
|
400
|
+
if (lang === "python") {
|
|
401
|
+
try {
|
|
402
|
+
execSync("python3 --version", { encoding: "utf8", timeout: 5000 });
|
|
403
|
+
interpreter = "python3";
|
|
404
|
+
} catch {
|
|
405
|
+
interpreter = "python";
|
|
406
|
+
}
|
|
407
|
+
} else if (lang === "node") {
|
|
408
|
+
interpreter = "node";
|
|
409
|
+
} else {
|
|
410
|
+
interpreter = "bash";
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
const start = Date.now();
|
|
414
|
+
const output = execSync(`${interpreter} "${tmpFile}"`, {
|
|
415
|
+
cwd: process.cwd(),
|
|
416
|
+
encoding: "utf8",
|
|
417
|
+
timeout: timeoutSec * 1000,
|
|
418
|
+
maxBuffer: 5 * 1024 * 1024,
|
|
419
|
+
});
|
|
420
|
+
const duration = Date.now() - start;
|
|
421
|
+
|
|
422
|
+
return {
|
|
423
|
+
success: true,
|
|
424
|
+
output: output.substring(0, 50000),
|
|
425
|
+
language: lang,
|
|
426
|
+
duration: `${duration}ms`,
|
|
427
|
+
};
|
|
428
|
+
} catch (err) {
|
|
429
|
+
return {
|
|
430
|
+
error: (err.stderr || err.message || "").substring(0, 5000),
|
|
431
|
+
stderr: (err.stderr || "").substring(0, 5000),
|
|
432
|
+
exitCode: err.status,
|
|
433
|
+
language: lang,
|
|
434
|
+
};
|
|
435
|
+
} finally {
|
|
436
|
+
try {
|
|
437
|
+
fs.unlinkSync(tmpFile);
|
|
438
|
+
} catch {
|
|
439
|
+
// Cleanup best-effort
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
356
444
|
case "search_files": {
|
|
357
445
|
const dir = args.directory ? path.resolve(args.directory) : process.cwd();
|
|
358
446
|
try {
|
|
@@ -530,7 +618,7 @@ async function chatWithTools(rawMessages, options) {
|
|
|
530
618
|
|
|
531
619
|
const body = {
|
|
532
620
|
model: model || "claude-sonnet-4-20250514",
|
|
533
|
-
max_tokens:
|
|
621
|
+
max_tokens: 8192,
|
|
534
622
|
messages: otherMsgs,
|
|
535
623
|
tools: anthropicTools,
|
|
536
624
|
};
|
|
@@ -597,7 +685,7 @@ async function chatWithTools(rawMessages, options) {
|
|
|
597
685
|
if (!key) throw new Error(`${envKey} required for provider ${provider}`);
|
|
598
686
|
|
|
599
687
|
const defaultModels = {
|
|
600
|
-
openai: "gpt-4o
|
|
688
|
+
openai: "gpt-4o",
|
|
601
689
|
deepseek: "deepseek-chat",
|
|
602
690
|
dashscope: "qwen-turbo",
|
|
603
691
|
mistral: "mistral-large-latest",
|
|
@@ -664,7 +752,7 @@ function _normalizeAnthropicResponse(data) {
|
|
|
664
752
|
* Agentic loop - keeps calling tools until the AI gives a final text response
|
|
665
753
|
*/
|
|
666
754
|
async function agentLoop(messages, options) {
|
|
667
|
-
const MAX_ITERATIONS =
|
|
755
|
+
const MAX_ITERATIONS = 15;
|
|
668
756
|
|
|
669
757
|
for (let i = 0; i < MAX_ITERATIONS; i++) {
|
|
670
758
|
const result = await chatWithTools(messages, options);
|
|
@@ -747,6 +835,8 @@ function formatToolArgs(name, args) {
|
|
|
747
835
|
return `${args.skill_name}: ${(args.input || "").substring(0, 50)}`;
|
|
748
836
|
case "list_skills":
|
|
749
837
|
return args.category || args.query || "all";
|
|
838
|
+
case "run_code":
|
|
839
|
+
return `${args.language} (${(args.code || "").length} chars)`;
|
|
750
840
|
default:
|
|
751
841
|
return JSON.stringify(args).substring(0, 60);
|
|
752
842
|
}
|
|
@@ -766,6 +856,14 @@ Key behaviors:
|
|
|
766
856
|
- Always explain what you're doing and show results
|
|
767
857
|
- Be concise but thorough
|
|
768
858
|
|
|
859
|
+
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:
|
|
860
|
+
- Proactively write and execute code using run_code tool
|
|
861
|
+
- Choose the best language: Python for data/math/scraping, Node.js for JSON/API, Bash for system tasks
|
|
862
|
+
- Show the results and explain them clearly
|
|
863
|
+
- If the first attempt fails, debug and retry with a different approach
|
|
864
|
+
|
|
865
|
+
You are not just a chatbot — you are a capable coding agent. Think step by step, write code when needed, and deliver real results.
|
|
866
|
+
|
|
769
867
|
Current working directory: ${process.cwd()}`;
|
|
770
868
|
}
|
|
771
869
|
|
|
@@ -773,7 +871,7 @@ Current working directory: ${process.cwd()}`;
|
|
|
773
871
|
* Start the agentic REPL
|
|
774
872
|
*/
|
|
775
873
|
export async function startAgentRepl(options = {}) {
|
|
776
|
-
let model = options.model || "qwen2:7b";
|
|
874
|
+
let model = options.model || "qwen2.5:7b";
|
|
777
875
|
let provider = options.provider || "ollama";
|
|
778
876
|
const baseUrl = options.baseUrl || "http://localhost:11434";
|
|
779
877
|
const apiKey = options.apiKey || null;
|
|
@@ -1551,9 +1649,59 @@ export async function startAgentRepl(options = {}) {
|
|
|
1551
1649
|
} else {
|
|
1552
1650
|
logger.info("Not in plan mode.");
|
|
1553
1651
|
}
|
|
1652
|
+
} else if (subCmd.startsWith("interactive")) {
|
|
1653
|
+
// Interactive planning with LLM-generated plan + skill recommendations
|
|
1654
|
+
const planRequest =
|
|
1655
|
+
subCmd.slice(11).trim() || "Help me with the current task";
|
|
1656
|
+
try {
|
|
1657
|
+
const { CLIInteractivePlanner } =
|
|
1658
|
+
await import("../lib/interactive-planner.js");
|
|
1659
|
+
const { TerminalInteractionAdapter } =
|
|
1660
|
+
await import("../lib/interaction-adapter.js");
|
|
1661
|
+
const chatFn = createChatFn({ provider, model, baseUrl, apiKey });
|
|
1662
|
+
const planner = new CLIInteractivePlanner({
|
|
1663
|
+
llmChat: chatFn,
|
|
1664
|
+
db,
|
|
1665
|
+
interaction: new TerminalInteractionAdapter(),
|
|
1666
|
+
});
|
|
1667
|
+
|
|
1668
|
+
logger.info("Generating interactive plan...");
|
|
1669
|
+
const result = await planner.startPlanSession(planRequest, {
|
|
1670
|
+
cwd: process.cwd(),
|
|
1671
|
+
});
|
|
1672
|
+
|
|
1673
|
+
if (result.plan) {
|
|
1674
|
+
logger.log(
|
|
1675
|
+
chalk.bold(
|
|
1676
|
+
`\n Plan: ${result.plan.overview?.title || "Untitled"}`,
|
|
1677
|
+
),
|
|
1678
|
+
);
|
|
1679
|
+
logger.log(
|
|
1680
|
+
chalk.gray(` ${result.plan.overview?.description || ""}\n`),
|
|
1681
|
+
);
|
|
1682
|
+
for (const step of result.plan.steps || []) {
|
|
1683
|
+
const toolStr = step.tool ? chalk.cyan(` [${step.tool}]`) : "";
|
|
1684
|
+
logger.log(` ${step.step}. ${step.title}${toolStr}`);
|
|
1685
|
+
}
|
|
1686
|
+
if (result.plan.recommendations?.skills?.length > 0) {
|
|
1687
|
+
logger.log(chalk.bold("\n Recommended skills:"));
|
|
1688
|
+
for (const s of result.plan.recommendations.skills) {
|
|
1689
|
+
logger.log(` - ${chalk.cyan(s.id)}: ${s.description}`);
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
logger.log("");
|
|
1693
|
+
logger.info(
|
|
1694
|
+
"Use /plan interactive:confirm, /plan interactive:cancel, or /plan interactive:regenerate",
|
|
1695
|
+
);
|
|
1696
|
+
} else {
|
|
1697
|
+
logger.info(result.message || "Failed to generate plan");
|
|
1698
|
+
}
|
|
1699
|
+
} catch (err) {
|
|
1700
|
+
logger.error(`Interactive plan failed: ${err.message}`);
|
|
1701
|
+
}
|
|
1554
1702
|
} else {
|
|
1555
1703
|
logger.info(
|
|
1556
|
-
"Unknown /plan subcommand. Try: /plan, /plan show, /plan approve, /plan reject, /plan exit",
|
|
1704
|
+
"Unknown /plan subcommand. Try: /plan, /plan show, /plan approve, /plan reject, /plan exit, /plan interactive <request>",
|
|
1557
1705
|
);
|
|
1558
1706
|
}
|
|
1559
1707
|
|