chainlesschain 0.37.9 → 0.37.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/README.md +309 -19
  2. package/bin/chainlesschain.js +4 -0
  3. package/package.json +1 -1
  4. package/src/commands/a2a.js +374 -0
  5. package/src/commands/audit.js +286 -0
  6. package/src/commands/auth.js +387 -0
  7. package/src/commands/bi.js +240 -0
  8. package/src/commands/browse.js +184 -0
  9. package/src/commands/cowork.js +317 -0
  10. package/src/commands/did.js +376 -0
  11. package/src/commands/economy.js +375 -0
  12. package/src/commands/encrypt.js +233 -0
  13. package/src/commands/evolution.js +398 -0
  14. package/src/commands/export.js +125 -0
  15. package/src/commands/git.js +215 -0
  16. package/src/commands/hmemory.js +273 -0
  17. package/src/commands/hook.js +260 -0
  18. package/src/commands/import.js +259 -0
  19. package/src/commands/init.js +184 -0
  20. package/src/commands/instinct.js +202 -0
  21. package/src/commands/llm.js +155 -4
  22. package/src/commands/lowcode.js +320 -0
  23. package/src/commands/mcp.js +302 -0
  24. package/src/commands/memory.js +282 -0
  25. package/src/commands/note.js +187 -0
  26. package/src/commands/org.js +505 -0
  27. package/src/commands/p2p.js +274 -0
  28. package/src/commands/plugin.js +451 -0
  29. package/src/commands/sandbox.js +366 -0
  30. package/src/commands/search.js +237 -0
  31. package/src/commands/session.js +238 -0
  32. package/src/commands/skill.js +254 -201
  33. package/src/commands/sync.js +249 -0
  34. package/src/commands/tokens.js +214 -0
  35. package/src/commands/wallet.js +416 -0
  36. package/src/commands/workflow.js +359 -0
  37. package/src/commands/zkp.js +277 -0
  38. package/src/index.js +93 -1
  39. package/src/lib/a2a-protocol.js +371 -0
  40. package/src/lib/agent-coordinator.js +273 -0
  41. package/src/lib/agent-economy.js +369 -0
  42. package/src/lib/app-builder.js +377 -0
  43. package/src/lib/audit-logger.js +364 -0
  44. package/src/lib/bi-engine.js +299 -0
  45. package/src/lib/bm25-search.js +322 -0
  46. package/src/lib/browser-automation.js +216 -0
  47. package/src/lib/cowork/ab-comparator-cli.js +180 -0
  48. package/src/lib/cowork/code-knowledge-graph-cli.js +232 -0
  49. package/src/lib/cowork/debate-review-cli.js +144 -0
  50. package/src/lib/cowork/decision-kb-cli.js +153 -0
  51. package/src/lib/cowork/project-style-analyzer-cli.js +168 -0
  52. package/src/lib/cowork-adapter.js +106 -0
  53. package/src/lib/crypto-manager.js +246 -0
  54. package/src/lib/did-manager.js +270 -0
  55. package/src/lib/ensure-utf8.js +59 -0
  56. package/src/lib/evolution-system.js +508 -0
  57. package/src/lib/git-integration.js +220 -0
  58. package/src/lib/hierarchical-memory.js +471 -0
  59. package/src/lib/hook-manager.js +387 -0
  60. package/src/lib/instinct-manager.js +190 -0
  61. package/src/lib/knowledge-exporter.js +302 -0
  62. package/src/lib/knowledge-importer.js +293 -0
  63. package/src/lib/llm-providers.js +325 -0
  64. package/src/lib/mcp-client.js +413 -0
  65. package/src/lib/memory-manager.js +211 -0
  66. package/src/lib/note-versioning.js +244 -0
  67. package/src/lib/org-manager.js +424 -0
  68. package/src/lib/p2p-manager.js +317 -0
  69. package/src/lib/pdf-parser.js +96 -0
  70. package/src/lib/permission-engine.js +374 -0
  71. package/src/lib/plan-mode.js +333 -0
  72. package/src/lib/plugin-manager.js +430 -0
  73. package/src/lib/project-detector.js +53 -0
  74. package/src/lib/response-cache.js +156 -0
  75. package/src/lib/sandbox-v2.js +503 -0
  76. package/src/lib/service-container.js +183 -0
  77. package/src/lib/session-manager.js +189 -0
  78. package/src/lib/skill-loader.js +274 -0
  79. package/src/lib/sync-manager.js +347 -0
  80. package/src/lib/token-tracker.js +200 -0
  81. package/src/lib/wallet-manager.js +348 -0
  82. package/src/lib/workflow-engine.js +503 -0
  83. package/src/lib/zkp-engine.js +241 -0
  84. package/src/repl/agent-repl.js +259 -124
@@ -24,6 +24,8 @@ import path from "path";
24
24
  import { execSync } from "child_process";
25
25
  import { fileURLToPath } from "url";
26
26
  import { logger } from "../lib/logger.js";
27
+ import { getPlanModeManager, PlanState } from "../lib/plan-mode.js";
28
+ import { CLISkillLoader } from "../lib/skill-loader.js";
27
29
 
28
30
  /**
29
31
  * Tool definitions for function calling
@@ -188,82 +190,34 @@ const TOOLS = [
188
190
  ];
189
191
 
190
192
  /**
191
- * Find and load bundled skills (shared with skill command)
193
+ * Shared multi-layer skill loader
192
194
  */
193
- const __agentDirname = path.dirname(fileURLToPath(import.meta.url));
194
-
195
- function findSkillsDir() {
196
- const candidates = [
197
- path.resolve(
198
- __agentDirname,
199
- "../../../../../desktop-app-vue/src/main/ai-engine/cowork/skills/builtin",
200
- ),
201
- path.resolve(
202
- process.cwd(),
203
- "desktop-app-vue/src/main/ai-engine/cowork/skills/builtin",
204
- ),
205
- ];
206
- for (const dir of candidates) {
207
- if (fs.existsSync(dir)) return dir;
208
- }
209
- return null;
210
- }
211
-
212
- function loadSkillList(skillsDir) {
213
- const skills = [];
214
- try {
215
- const dirs = fs.readdirSync(skillsDir, { withFileTypes: true });
216
- for (const dir of dirs) {
217
- if (!dir.isDirectory()) continue;
218
- const skillMd = path.join(skillsDir, dir.name, "SKILL.md");
219
- if (!fs.existsSync(skillMd)) continue;
220
- try {
221
- const content = fs.readFileSync(skillMd, "utf8");
222
- const lines = content.split("\n");
223
- if (lines[0].trim() !== "---") continue;
224
- let endIndex = -1;
225
- for (let i = 1; i < lines.length; i++) {
226
- if (lines[i].trim() === "---") {
227
- endIndex = i;
228
- break;
229
- }
230
- }
231
- if (endIndex === -1) continue;
232
- const data = {};
233
- for (const line of lines.slice(1, endIndex)) {
234
- const ci = line.indexOf(":");
235
- if (ci > 0) {
236
- const key = line.slice(0, ci).trim();
237
- const val = line
238
- .slice(ci + 1)
239
- .trim()
240
- .replace(/^['"]|['"]$/g, "");
241
- data[key] = val;
242
- }
243
- }
244
- skills.push({
245
- id: data.name || dir.name,
246
- description: data.description || "",
247
- category: data.category || "uncategorized",
248
- dirName: dir.name,
249
- hasHandler: fs.existsSync(
250
- path.join(skillsDir, dir.name, "handler.js"),
251
- ),
252
- });
253
- } catch {
254
- // Skip malformed skill files
255
- }
256
- }
257
- } catch {
258
- // Skills dir unreadable
259
- }
260
- return skills;
261
- }
195
+ const skillLoader = new CLISkillLoader();
262
196
 
263
197
  /**
264
- * Execute a tool call
198
+ * Execute a tool call (with plan mode filtering)
265
199
  */
266
200
  async function executeTool(name, args) {
201
+ // Plan mode: check if tool is allowed
202
+ const planManager = getPlanModeManager();
203
+ if (planManager.isActive() && !planManager.isToolAllowed(name)) {
204
+ // In plan mode, log the blocked tool as a plan item
205
+ planManager.addPlanItem({
206
+ title: `${name}: ${formatToolArgs(name, args)}`,
207
+ tool: name,
208
+ params: args,
209
+ estimatedImpact:
210
+ name === "run_shell"
211
+ ? "high"
212
+ : name === "write_file"
213
+ ? "medium"
214
+ : "low",
215
+ });
216
+ return {
217
+ error: `[Plan Mode] Tool "${name}" is blocked during planning. It has been added to the plan. Use /plan approve to execute.`,
218
+ };
219
+ }
220
+
267
221
  switch (name) {
268
222
  case "read_file": {
269
223
  const filePath = path.resolve(args.path);
@@ -373,50 +327,28 @@ async function executeTool(name, args) {
373
327
  }
374
328
 
375
329
  case "run_skill": {
376
- const skillsDir = findSkillsDir();
377
- if (!skillsDir) {
330
+ const allSkills = skillLoader.getResolvedSkills();
331
+ if (allSkills.length === 0) {
378
332
  return {
379
333
  error:
380
- "Skills directory not found. Make sure you're in the ChainlessChain project root.",
334
+ "No skills found. Make sure you're in the ChainlessChain project root or have skills installed.",
381
335
  };
382
336
  }
383
- const handlerPath = path.join(skillsDir, args.skill_name, "handler.js");
384
- if (!fs.existsSync(handlerPath)) {
385
- // Try fuzzy match
386
- const skills = loadSkillList(skillsDir);
387
- const match = skills.find(
388
- (s) => s.id === args.skill_name || s.dirName === args.skill_name,
389
- );
390
- if (match && match.hasHandler) {
391
- const matchedPath = path.join(skillsDir, match.dirName, "handler.js");
392
- try {
393
- const handler = (
394
- await import(`file://${matchedPath.replace(/\\/g, "/")}`)
395
- ).default;
396
- const task = {
397
- params: { input: args.input },
398
- input: args.input,
399
- action: args.input,
400
- };
401
- const context = {
402
- projectRoot: process.cwd(),
403
- workspacePath: process.cwd(),
404
- };
405
- const result = await handler.execute(task, context);
406
- return result;
407
- } catch (err) {
408
- return { error: `Skill execution failed: ${err.message}` };
409
- }
410
- }
337
+ const match = allSkills.find(
338
+ (s) => s.id === args.skill_name || s.dirName === args.skill_name,
339
+ );
340
+ if (!match || !match.hasHandler) {
411
341
  return {
412
342
  error: `Skill "${args.skill_name}" not found or has no handler. Use list_skills to see available skills.`,
413
343
  };
414
344
  }
415
345
  try {
416
- const handler = (
417
- await import(`file://${handlerPath.replace(/\\/g, "/")}`)
418
- ).default;
419
- if (handler.init) await handler.init({});
346
+ const handlerPath = path.join(match.skillDir, "handler.js");
347
+ const imported = await import(
348
+ `file://${handlerPath.replace(/\\/g, "/")}`
349
+ );
350
+ const handler = imported.default || imported;
351
+ if (handler.init) await handler.init(match);
420
352
  const task = {
421
353
  params: { input: args.input },
422
354
  input: args.input,
@@ -426,7 +358,7 @@ async function executeTool(name, args) {
426
358
  projectRoot: process.cwd(),
427
359
  workspacePath: process.cwd(),
428
360
  };
429
- const result = await handler.execute(task, context);
361
+ const result = await handler.execute(task, context, match);
430
362
  return result;
431
363
  } catch (err) {
432
364
  return { error: `Skill execution failed: ${err.message}` };
@@ -434,11 +366,10 @@ async function executeTool(name, args) {
434
366
  }
435
367
 
436
368
  case "list_skills": {
437
- const skillsDir = findSkillsDir();
438
- if (!skillsDir) {
439
- return { error: "Skills directory not found." };
369
+ let skills = skillLoader.getResolvedSkills();
370
+ if (skills.length === 0) {
371
+ return { error: "No skills found." };
440
372
  }
441
- let skills = loadSkillList(skillsDir);
442
373
  if (args.category) {
443
374
  skills = skills.filter(
444
375
  (s) => s.category.toLowerCase() === args.category.toLowerCase(),
@@ -458,8 +389,9 @@ async function executeTool(name, args) {
458
389
  skills: skills.map((s) => ({
459
390
  id: s.id,
460
391
  category: s.category,
392
+ source: s.source,
461
393
  hasHandler: s.hasHandler,
462
- description: s.description.substring(0, 80),
394
+ description: (s.description || "").substring(0, 80),
463
395
  })),
464
396
  };
465
397
  }
@@ -520,6 +452,9 @@ async function chatWithTools(messages, options) {
520
452
 
521
453
  const data = await response.json();
522
454
  // Normalize to Ollama-like format
455
+ if (!data.choices || !data.choices[0]) {
456
+ throw new Error("Invalid API response: no choices returned");
457
+ }
523
458
  const choice = data.choices[0];
524
459
  return {
525
460
  message: choice.message,
@@ -537,7 +472,11 @@ async function agentLoop(messages, options) {
537
472
 
538
473
  for (let i = 0; i < MAX_ITERATIONS; i++) {
539
474
  const result = await chatWithTools(messages, options);
540
- const msg = result.message;
475
+ const msg = result?.message;
476
+
477
+ if (!msg) {
478
+ return "(No response from LLM)";
479
+ }
541
480
 
542
481
  // Check for tool calls
543
482
  const toolCalls = msg.tool_calls;
@@ -626,7 +565,7 @@ Key behaviors:
626
565
  - When asked to create something, use write_file to create it
627
566
  - When asked to run/test something, use run_shell to execute it
628
567
  - When asked about files or code, use read_file and search_files to find information
629
- - You have 138 built-in skills (code-review, summarize, translate, refactor, etc.) — use list_skills to discover them and run_skill to execute them
568
+ - You have multi-layer skills (built-in, marketplace, global, project-level) — use list_skills to discover them and run_skill to execute them
630
569
  - Always explain what you're doing and show results
631
570
  - Be concise but thorough
632
571
 
@@ -643,10 +582,22 @@ export async function startAgentRepl(options = {}) {
643
582
 
644
583
  const messages = [{ role: "system", content: SYSTEM_PROMPT }];
645
584
 
585
+ const getPrompt = () => {
586
+ const planManager = getPlanModeManager();
587
+ if (planManager.isActive()) {
588
+ const state = planManager.state;
589
+ if (state === PlanState.APPROVED || state === PlanState.EXECUTING) {
590
+ return chalk.green("[plan:exec] > ");
591
+ }
592
+ return chalk.yellow("[plan] > ");
593
+ }
594
+ return chalk.green("> ");
595
+ };
596
+
646
597
  const rl = readline.createInterface({
647
598
  input: process.stdin,
648
599
  output: process.stdout,
649
- prompt: chalk.green("> "),
600
+ prompt: getPrompt(),
650
601
  terminal: true,
651
602
  });
652
603
 
@@ -661,12 +612,16 @@ export async function startAgentRepl(options = {}) {
661
612
  );
662
613
  logger.log(chalk.gray("Type /exit to quit, /help for commands\n"));
663
614
 
664
- rl.prompt();
615
+ const prompt = () => {
616
+ rl.setPrompt(getPrompt());
617
+ rl.prompt();
618
+ };
619
+ prompt();
665
620
 
666
621
  rl.on("line", async (input) => {
667
622
  const trimmed = input.trim();
668
623
  if (!trimmed) {
669
- rl.prompt();
624
+ prompt();
670
625
  return;
671
626
  }
672
627
 
@@ -686,13 +641,24 @@ export async function startAgentRepl(options = {}) {
686
641
  logger.log(` ${chalk.cyan("/provider")} Show/change provider`);
687
642
  logger.log(` ${chalk.cyan("/clear")} Clear conversation`);
688
643
  logger.log(` ${chalk.cyan("/compact")} Keep only last 4 messages`);
644
+ logger.log(
645
+ ` ${chalk.cyan("/cowork")} Multi-agent collaboration (debate, compare)`,
646
+ );
647
+ logger.log(
648
+ ` ${chalk.cyan("/plan")} Enter plan mode (read-only analysis first)`,
649
+ );
650
+ logger.log(` ${chalk.cyan("/plan show")} Show current plan`);
651
+ logger.log(
652
+ ` ${chalk.cyan("/plan approve")} Approve and execute the plan`,
653
+ );
654
+ logger.log(` ${chalk.cyan("/plan reject")} Reject the plan`);
689
655
  logger.log(chalk.bold("\nCapabilities:"));
690
656
  logger.log(" Read, write, and edit files");
691
657
  logger.log(" Run shell commands (git, npm, etc.)");
692
658
  logger.log(" Search codebase by filename or content");
693
659
  logger.log(" Run 138 built-in skills (code-review, summarize, etc.)");
694
- logger.log(" Answer questions about code\n");
695
- rl.prompt();
660
+ logger.log(" Plan mode: analyze first, execute after approval\n");
661
+ prompt();
696
662
  return;
697
663
  }
698
664
 
@@ -704,7 +670,7 @@ export async function startAgentRepl(options = {}) {
704
670
  } else {
705
671
  logger.info(`Current model: ${chalk.cyan(model)}`);
706
672
  }
707
- rl.prompt();
673
+ prompt();
708
674
  return;
709
675
  }
710
676
 
@@ -716,14 +682,14 @@ export async function startAgentRepl(options = {}) {
716
682
  } else {
717
683
  logger.info(`Current provider: ${chalk.cyan(provider)}`);
718
684
  }
719
- rl.prompt();
685
+ prompt();
720
686
  return;
721
687
  }
722
688
 
723
689
  if (trimmed === "/clear") {
724
690
  messages.length = 1; // Keep system prompt
725
691
  logger.info("Conversation cleared");
726
- rl.prompt();
692
+ prompt();
727
693
  return;
728
694
  }
729
695
 
@@ -736,7 +702,176 @@ export async function startAgentRepl(options = {}) {
736
702
  messages.push(systemMsg, ...recent);
737
703
  logger.info("Conversation compacted to last 4 messages");
738
704
  }
739
- rl.prompt();
705
+ prompt();
706
+ return;
707
+ }
708
+
709
+ // Cowork commands
710
+ if (trimmed.startsWith("/cowork")) {
711
+ const coworkArgs = trimmed.slice(7).trim();
712
+ const [subCmd, ...rest] = coworkArgs.split(/\s+/);
713
+ const coworkInput = rest.join(" ");
714
+
715
+ if (!subCmd || subCmd === "help") {
716
+ logger.log(chalk.bold("\nCowork Commands:"));
717
+ logger.log(
718
+ ` ${chalk.cyan("/cowork debate <file>")} Multi-perspective code review`,
719
+ );
720
+ logger.log(
721
+ ` ${chalk.cyan("/cowork compare <prompt>")} A/B solution comparison`,
722
+ );
723
+ logger.log("");
724
+ } else if (subCmd === "debate" && coworkInput) {
725
+ try {
726
+ const { startDebate } =
727
+ await import("../lib/cowork/debate-review-cli.js");
728
+ let code = coworkInput;
729
+ let targetLabel = coworkInput;
730
+ const resolved = path.resolve(coworkInput);
731
+ if (fs.existsSync(resolved)) {
732
+ code = fs.readFileSync(resolved, "utf-8");
733
+ targetLabel = resolved;
734
+ if (code.length > 15000) {
735
+ code = code.substring(0, 15000) + "\n... (truncated)";
736
+ }
737
+ }
738
+ process.stdout.write(chalk.gray("\n Running debate review...\n"));
739
+ const result = await startDebate({
740
+ target: targetLabel,
741
+ code,
742
+ llmOptions: { provider, model, baseUrl, apiKey },
743
+ });
744
+ for (const review of result.reviews) {
745
+ const vc =
746
+ review.verdict === "APPROVE"
747
+ ? chalk.green
748
+ : review.verdict === "REJECT"
749
+ ? chalk.red
750
+ : chalk.yellow;
751
+ process.stdout.write(
752
+ ` ${chalk.bold(review.role)}: ${vc(review.verdict)}\n`,
753
+ );
754
+ }
755
+ process.stdout.write(
756
+ `\n ${chalk.bold("Verdict:")} ${result.verdict} Consensus: ${result.consensusScore}%\n\n`,
757
+ );
758
+ // Add summary to conversation for context
759
+ messages.push({
760
+ role: "assistant",
761
+ content: `[Cowork Debate Result] ${result.verdict} (consensus: ${result.consensusScore}%)\n${result.summary.substring(0, 500)}`,
762
+ });
763
+ } catch (err) {
764
+ logger.error(`Debate failed: ${err.message}`);
765
+ }
766
+ } else if (subCmd === "compare" && coworkInput) {
767
+ try {
768
+ const { compare } =
769
+ await import("../lib/cowork/ab-comparator-cli.js");
770
+ process.stdout.write(chalk.gray("\n Generating variants...\n"));
771
+ const result = await compare({
772
+ prompt: coworkInput,
773
+ llmOptions: { provider, model, baseUrl, apiKey },
774
+ });
775
+ for (const v of result.variants) {
776
+ process.stdout.write(
777
+ ` ${chalk.cyan(v.name)}: score ${v.totalScore}\n`,
778
+ );
779
+ }
780
+ process.stdout.write(
781
+ `\n ${chalk.bold("Winner:")} ${chalk.green(result.winner)}\n\n`,
782
+ );
783
+ messages.push({
784
+ role: "assistant",
785
+ content: `[Cowork Compare Result] Winner: ${result.winner}. ${result.reason}`,
786
+ });
787
+ } catch (err) {
788
+ logger.error(`Compare failed: ${err.message}`);
789
+ }
790
+ } else {
791
+ logger.info(
792
+ "Usage: /cowork debate <file-or-topic> or /cowork compare <prompt>",
793
+ );
794
+ }
795
+
796
+ prompt();
797
+ return;
798
+ }
799
+
800
+ // Plan mode commands
801
+ if (trimmed.startsWith("/plan")) {
802
+ const planManager = getPlanModeManager();
803
+ const subCmd = trimmed.slice(5).trim();
804
+
805
+ if (!subCmd || subCmd === "enter") {
806
+ if (planManager.isActive()) {
807
+ logger.info(
808
+ "Already in plan mode. Use /plan show, /plan approve, or /plan reject.",
809
+ );
810
+ } else {
811
+ planManager.enterPlanMode({ title: "Agent Plan" });
812
+ logger.success(
813
+ "Entered plan mode. Write tools are blocked until you approve the plan.",
814
+ );
815
+ logger.info(
816
+ "The AI can still read files and search. Blocked tools become plan items.",
817
+ );
818
+ logger.info(
819
+ "Use /plan show to see the plan, /plan approve to execute.",
820
+ );
821
+ // Inject plan mode context into system prompt
822
+ messages.push({
823
+ role: "system",
824
+ content:
825
+ "[PLAN MODE ACTIVE] You are now in plan mode. You can read files, search, and analyze — but write/execute tools are blocked. Any blocked tool calls will be recorded as plan items. Analyze the task thoroughly, then the user will approve your plan.",
826
+ });
827
+ }
828
+ } else if (subCmd === "show") {
829
+ if (!planManager.isActive()) {
830
+ logger.info("Not in plan mode. Use /plan to enter.");
831
+ } else {
832
+ logger.log("\n" + planManager.generatePlanSummary() + "\n");
833
+ }
834
+ } else if (subCmd === "approve" || subCmd === "yes") {
835
+ if (!planManager.isActive()) {
836
+ logger.info("No plan to approve.");
837
+ } else if (planManager.currentPlan.items.length === 0) {
838
+ logger.info(
839
+ "Plan has no items yet. Let the AI analyze the task first.",
840
+ );
841
+ } else {
842
+ planManager.approvePlan();
843
+ logger.success(
844
+ `Plan approved! ${planManager.currentPlan.items.length} items ready for execution.`,
845
+ );
846
+ logger.info(
847
+ "Write/execute tools are now unlocked. The AI can proceed.",
848
+ );
849
+ messages.push({
850
+ role: "system",
851
+ content: `[PLAN APPROVED] The user has approved your plan with ${planManager.currentPlan.items.length} items. You can now use all tools including write_file, edit_file, run_shell, and run_skill. Execute the plan items in order.`,
852
+ });
853
+ }
854
+ } else if (subCmd === "reject" || subCmd === "no") {
855
+ if (!planManager.isActive()) {
856
+ logger.info("No plan to reject.");
857
+ } else {
858
+ planManager.rejectPlan("User rejected");
859
+ logger.info("Plan rejected. Exited plan mode.");
860
+ }
861
+ } else if (subCmd === "exit") {
862
+ if (planManager.isActive()) {
863
+ planManager.exitPlanMode({ savePlan: true });
864
+ logger.info("Exited plan mode.");
865
+ } else {
866
+ logger.info("Not in plan mode.");
867
+ }
868
+ } else {
869
+ logger.info(
870
+ "Unknown /plan subcommand. Try: /plan, /plan show, /plan approve, /plan reject, /plan exit",
871
+ );
872
+ }
873
+
874
+ prompt();
740
875
  return;
741
876
  }
742
877
 
@@ -773,7 +908,7 @@ export async function startAgentRepl(options = {}) {
773
908
  }
774
909
  }
775
910
 
776
- rl.prompt();
911
+ prompt();
777
912
  });
778
913
 
779
914
  rl.on("close", () => {