swarm-code 0.1.8 → 0.1.10

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.
@@ -244,6 +244,7 @@ function cmdHelp() {
244
244
  out.write(` ${cyan("/dag")} ${dim("Show thread DAG with status indicators")}\n`);
245
245
  out.write(` ${cyan("/budget")} ${dim("Show budget state")}\n`);
246
246
  out.write(` ${cyan("/status")} ${dim("Overall session status")}\n`);
247
+ out.write(` ${cyan("/configure")} ${dim("(/c)")} ${dim("Change agent, model, or backend")}\n`);
247
248
  out.write(` ${cyan("/help")} ${dim("Show this help")}\n`);
248
249
  out.write(` ${cyan("/quit")} ${dim("(/exit)")} ${dim("Cleanup and exit")}\n`);
249
250
  out.write("\n");
@@ -536,6 +537,120 @@ function cmdStatus(threadManager, sessionStartTime, taskCount) {
536
537
  }
537
538
  out.write("\n");
538
539
  }
540
+ // ── Configure command ───────────────────────────────────────────────────────
541
+ async function cmdConfigure(config, resolved, rl) {
542
+ const out = process.stderr;
543
+ const ask = (q) => new Promise((res) => rl.question(q, (a) => res(a.trim())));
544
+ out.write("\n");
545
+ out.write(` ${bold(cyan("Configuration"))}\n`);
546
+ out.write(` ${dim(symbols.horizontal.repeat(40))}\n\n`);
547
+ out.write(` ${dim("Current settings:")}\n`);
548
+ out.write(` ${cyan("1")} Agent ${bold(config.default_agent)}\n`);
549
+ out.write(` ${cyan("2")} Model ${bold(config.default_model)}\n`);
550
+ out.write(` ${cyan("3")} Max threads ${bold(String(config.max_threads))}\n`);
551
+ out.write(` ${cyan("4")} Auto routing ${bold(config.auto_model_selection ? "on" : "off")}\n`);
552
+ out.write(` ${cyan("5")} Session budget ${bold(`$${config.max_session_budget_usd.toFixed(2)}`)}\n`);
553
+ out.write(` ${cyan("6")} Thread budget ${bold(`$${config.max_thread_budget_usd.toFixed(2)}`)}\n`);
554
+ out.write(` ${cyan("7")} Compression ${bold(config.compression_strategy)}\n`);
555
+ out.write("\n");
556
+ const choice = await ask(` ${coral(symbols.arrow)} Setting to change [1-7, or enter to cancel]: `);
557
+ if (!choice) {
558
+ out.write(` ${dim("No changes made.")}\n\n`);
559
+ return;
560
+ }
561
+ switch (choice) {
562
+ case "1": {
563
+ const agents = (await import("./agents/provider.js")).listAgents();
564
+ out.write(`\n ${dim("Available agents:")} ${agents.join(", ")}\n`);
565
+ const val = await ask(` ${coral(symbols.arrow)} New agent [${config.default_agent}]: `);
566
+ if (val && agents.includes(val)) {
567
+ config.default_agent = val;
568
+ logSuccess(`Agent set to ${bold(val)}`);
569
+ }
570
+ else if (val) {
571
+ logWarn(`Unknown agent "${val}"`);
572
+ }
573
+ break;
574
+ }
575
+ case "2": {
576
+ out.write(`\n ${dim("Enter model ID (e.g. ollama/deepseek-coder-v2, anthropic/claude-sonnet-4-6, openrouter/auto)")}\n`);
577
+ const val = await ask(` ${coral(symbols.arrow)} New model [${config.default_model}]: `);
578
+ if (val) {
579
+ const lookupId = val.startsWith("ollama/") || val.startsWith("openrouter/")
580
+ ? val
581
+ : val.replace(/^(anthropic|openai|google)\//, "");
582
+ const newResolved = resolveModel(lookupId, logWarn);
583
+ if (newResolved) {
584
+ config.default_model = val;
585
+ resolved.model = newResolved.model;
586
+ resolved.provider = newResolved.provider;
587
+ logSuccess(`Model set to ${bold(val)}`);
588
+ }
589
+ else {
590
+ logWarn(`Could not resolve model "${val}"`);
591
+ }
592
+ }
593
+ break;
594
+ }
595
+ case "3": {
596
+ const val = await ask(` ${coral(symbols.arrow)} Max concurrent threads [${config.max_threads}]: `);
597
+ const n = parseInt(val, 10);
598
+ if (n >= 1 && n <= 20) {
599
+ config.max_threads = n;
600
+ logSuccess(`Max threads set to ${bold(String(n))}`);
601
+ }
602
+ else if (val) {
603
+ logWarn("Must be between 1 and 20");
604
+ }
605
+ break;
606
+ }
607
+ case "4": {
608
+ config.auto_model_selection = !config.auto_model_selection;
609
+ logSuccess(`Auto routing ${bold(config.auto_model_selection ? "enabled" : "disabled")}`);
610
+ break;
611
+ }
612
+ case "5": {
613
+ const val = await ask(` ${coral(symbols.arrow)} Session budget USD [${config.max_session_budget_usd}]: `);
614
+ const n = parseFloat(val);
615
+ if (Number.isFinite(n) && n > 0) {
616
+ config.max_session_budget_usd = n;
617
+ logSuccess(`Session budget set to ${bold(`$${n.toFixed(2)}`)}`);
618
+ }
619
+ else if (val) {
620
+ logWarn("Must be a positive number");
621
+ }
622
+ break;
623
+ }
624
+ case "6": {
625
+ const val = await ask(` ${coral(symbols.arrow)} Per-thread budget USD [${config.max_thread_budget_usd}]: `);
626
+ const n = parseFloat(val);
627
+ if (Number.isFinite(n) && n > 0) {
628
+ config.max_thread_budget_usd = n;
629
+ logSuccess(`Thread budget set to ${bold(`$${n.toFixed(2)}`)}`);
630
+ }
631
+ else if (val) {
632
+ logWarn("Must be a positive number");
633
+ }
634
+ break;
635
+ }
636
+ case "7": {
637
+ const strategies = ["structured", "llm-summary", "diff-only", "truncate"];
638
+ out.write(`\n ${dim("Options:")} ${strategies.join(", ")}\n`);
639
+ const val = await ask(` ${coral(symbols.arrow)} Compression [${config.compression_strategy}]: `);
640
+ if (val && strategies.includes(val)) {
641
+ config.compression_strategy = val;
642
+ logSuccess(`Compression set to ${bold(val)}`);
643
+ }
644
+ else if (val) {
645
+ logWarn(`Unknown strategy "${val}"`);
646
+ }
647
+ break;
648
+ }
649
+ default:
650
+ logWarn("Invalid option");
651
+ }
652
+ out.write("\n");
653
+ }
539
654
  // ── Interactive banner ──────────────────────────────────────────────────────
540
655
  function renderInteractiveBanner(config) {
541
656
  const w = Math.max(Math.min(termWidth(), 60), 24);
@@ -932,6 +1047,11 @@ export async function runInteractiveSwarm(rawArgs) {
932
1047
  case "/s":
933
1048
  cmdStatus(threadManager, sessionStartTime, taskCount);
934
1049
  break;
1050
+ case "/configure":
1051
+ case "/config":
1052
+ case "/c":
1053
+ await cmdConfigure(config, resolved, rl);
1054
+ break;
935
1055
  case "/quit":
936
1056
  case "/exit":
937
1057
  case "/q":
@@ -21,14 +21,22 @@ const DEFAULT_MODELS = {
21
21
  };
22
22
  /**
23
23
  * Create a synthetic pi-ai Model for Ollama (OpenAI-compatible API at localhost:11434).
24
+ *
25
+ * Uses provider "openai" so pi-ai's API key lookup resolves to OPENAI_API_KEY.
26
+ * We set a dummy OPENAI_API_KEY if none exists — Ollama ignores auth headers.
24
27
  */
25
28
  function createOllamaModel(modelId) {
26
29
  const shortId = modelId.replace("ollama/", "");
30
+ // pi-ai requires an API key for the "openai" provider. Ollama doesn't need one,
31
+ // but we must satisfy pi-ai's check. Set a dummy key if no real one exists.
32
+ if (!process.env.OPENAI_API_KEY) {
33
+ process.env.OPENAI_API_KEY = "ollama-local";
34
+ }
27
35
  return {
28
36
  id: shortId,
29
37
  name: shortId,
30
38
  api: "openai-completions",
31
- provider: "ollama",
39
+ provider: "openai",
32
40
  baseUrl: "http://localhost:11434/v1",
33
41
  reasoning: false,
34
42
  input: ["text"],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "swarm-code",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
4
4
  "description": "Open-source swarm-native coding agent orchestrator — spawns parallel coding agents in isolated git worktrees, built on RLM (arXiv:2512.24601)",
5
5
  "type": "module",
6
6
  "bin": {