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.
@@ -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
- * - db_query: Query the ChainlessChain database
14
- * - note_add: Add a note
15
- * - note_search: Search notes
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: 30000,
368
+ timeout: 60000,
344
369
  maxBuffer: 1024 * 1024,
345
370
  });
346
- return { stdout: output.substring(0, 10000) };
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: 4096,
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-mini",
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 = 10;
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