swarm-code 0.1.19 → 0.1.21

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.
@@ -18,6 +18,7 @@
18
18
  */
19
19
  import "./env.js";
20
20
  import * as fs from "node:fs";
21
+ import * as os from "node:os";
21
22
  import * as path from "node:path";
22
23
  import { readTextInput } from "./ui/text-input.js";
23
24
  // Dynamic imports — ensures env.js has set process.env BEFORE pi-ai loads
@@ -691,6 +692,25 @@ async function cmdConfigure(config, resolved) {
691
692
  default:
692
693
  logWarn("Invalid option");
693
694
  }
695
+ // Persist config changes to ~/.swarm/config.yaml
696
+ if (choice >= "1" && choice <= "7") {
697
+ try {
698
+ const configDir = path.join(os.homedir(), ".swarm");
699
+ fs.mkdirSync(configDir, { recursive: true });
700
+ const configLines = [
701
+ "# Swarm user preferences (updated by /configure)",
702
+ `# Updated: ${new Date().toISOString()}`,
703
+ "",
704
+ `default_agent: ${config.default_agent}`,
705
+ `default_model: ${config.default_model}`,
706
+ "",
707
+ ];
708
+ fs.writeFileSync(path.join(configDir, "config.yaml"), configLines.join("\n"), "utf-8");
709
+ }
710
+ catch {
711
+ // Non-fatal
712
+ }
713
+ }
694
714
  out.write("\n");
695
715
  }
696
716
  // ── Interactive banner ──────────────────────────────────────────────────────
@@ -842,6 +862,7 @@ export async function runInteractiveSwarm(rawArgs) {
842
862
  // Start Python REPL
843
863
  await repl.start(sessionAc.signal);
844
864
  let currentTaskAc = null;
865
+ let currentRunLog = null;
845
866
  let cleanupCalled = false;
846
867
  async function cleanup() {
847
868
  if (cleanupCalled)
@@ -885,6 +906,17 @@ export async function runInteractiveSwarm(rawArgs) {
885
906
  },
886
907
  files,
887
908
  });
909
+ // Log thread to current run
910
+ currentRunLog?.addThread({
911
+ id: threadId,
912
+ task,
913
+ agent: resolvedAgent,
914
+ model: resolvedModel,
915
+ success: result.success,
916
+ durationMs: result.durationMs,
917
+ filesChanged: result.filesChanged,
918
+ error: result.success ? undefined : result.summary,
919
+ });
888
920
  // Record episode or failure
889
921
  if (result.success) {
890
922
  if (episodicMemory && routeSlot) {
@@ -946,6 +978,7 @@ export async function runInteractiveSwarm(rawArgs) {
946
978
  spinner.start();
947
979
  const startTime = Date.now();
948
980
  const runLog = new RunLogger(query, resolved.model.id, config.default_agent, args.dir, config.max_iterations || 20);
981
+ currentRunLog = runLog;
949
982
  try {
950
983
  // Update episodic memory hints per-task
951
984
  let taskSystemPrompt = systemPrompt;
@@ -1017,6 +1050,7 @@ export async function runInteractiveSwarm(rawArgs) {
1017
1050
  finally {
1018
1051
  sessionAc.signal.removeEventListener("abort", onSessionAbort);
1019
1052
  currentTaskAc = null;
1053
+ currentRunLog = null;
1020
1054
  }
1021
1055
  };
1022
1056
  // Process a line of input
@@ -44,28 +44,31 @@ export function readTextInput(_prompt) {
44
44
  process.stdin.setRawMode(true);
45
45
  process.stdin.resume();
46
46
  process.stdin.setEncoding("utf-8");
47
- // Track how many terminal rows we've drawn so we can clear them
48
- let drawnRows = 0;
47
+ // How many rows below the top border the cursor sits after drawBox()
48
+ let cursorRowFromTop = 0;
49
+ let hasDrawn = false;
49
50
  function drawBox() {
50
51
  const out = process.stderr;
51
- // Clear previously drawn rows
52
- if (drawnRows > 0) {
53
- for (let i = 0; i < drawnRows; i++) {
54
- out.write("\x1b[1A\x1b[2K");
52
+ const promptVisibleLen = 2; // "❯ "
53
+ if (hasDrawn) {
54
+ // Move cursor from its current position back to the top border row
55
+ if (cursorRowFromTop > 0) {
56
+ out.write(`\x1b[${cursorRowFromTop}A`);
55
57
  }
58
+ out.write("\x1b[0G"); // go to column 0
59
+ out.write("\x1b[J"); // erase from cursor to end of screen
56
60
  }
61
+ hasDrawn = true;
57
62
  // Top border — thin dim line
58
63
  const topLine = `${BORDER_COLOR}${"─".repeat(w)}${RESET}`;
59
64
  out.write(`${topLine}\n`);
60
65
  // Content rows — dark background, full width
61
66
  const promptChar = `${ACCENT_COLOR}❯${RESET} `;
62
- const promptVisibleLen = 2; // "❯ "
63
67
  for (let i = 0; i < linesBuf.length; i++) {
64
68
  const lineText = linesBuf[i];
65
69
  const prefix = i === 0 ? promptChar : `${dim("·")} `;
66
- const prefixVisibleLen = promptVisibleLen;
67
70
  // How much space for text content
68
- const contentWidth = w - prefixVisibleLen;
71
+ const contentWidth = w - promptVisibleLen;
69
72
  // Truncate display if line is too long
70
73
  const displayText = lineText.length > contentWidth ? lineText.slice(0, contentWidth - 1) + "…" : lineText;
71
74
  const padding = Math.max(0, contentWidth - displayText.length);
@@ -76,7 +79,6 @@ export function readTextInput(_prompt) {
76
79
  out.write(`${bottomLine}\n`);
77
80
  // Hints
78
81
  out.write(`${dim(" enter submit esc exit")}\n`);
79
- drawnRows = linesBuf.length + 3; // top border + content lines + bottom border + hints
80
82
  // Position cursor inside the text area
81
83
  // We're at the bottom (after hints). Move up to the correct content row.
82
84
  const currentLineIdx = linesBuf.length - 1; // cursor is always on last line
@@ -85,6 +87,8 @@ export function readTextInput(_prompt) {
85
87
  // Move to correct column: prefix width + cursor position
86
88
  const col = promptVisibleLen + cursorPos + 1;
87
89
  out.write(`\x1b[${col}G`);
90
+ // Track where cursor ended up (row 0 = top border)
91
+ cursorRowFromTop = 1 + currentLineIdx;
88
92
  }
89
93
  // Initial draw
90
94
  process.stderr.write(HIDE_CURSOR);
@@ -106,8 +110,6 @@ export function readTextInput(_prompt) {
106
110
  linesBuf.push(line);
107
111
  cursorPos = line.length;
108
112
  }
109
- // Move cursor back to top of box before redraw
110
- moveCursorToBoxTop();
111
113
  drawBox();
112
114
  return;
113
115
  }
@@ -120,28 +122,24 @@ export function readTextInput(_prompt) {
120
122
  if (code === "C" && cursorPos < linesBuf[linesBuf.length - 1].length) {
121
123
  cursorPos++;
122
124
  i += 2;
123
- moveCursorToBoxTop();
124
125
  drawBox();
125
126
  continue;
126
127
  }
127
128
  if (code === "D" && cursorPos > 0) {
128
129
  cursorPos--;
129
130
  i += 2;
130
- moveCursorToBoxTop();
131
131
  drawBox();
132
132
  continue;
133
133
  }
134
134
  if (code === "H") {
135
135
  cursorPos = 0;
136
136
  i += 2;
137
- moveCursorToBoxTop();
138
137
  drawBox();
139
138
  continue;
140
139
  }
141
140
  if (code === "F") {
142
141
  cursorPos = linesBuf[linesBuf.length - 1].length;
143
142
  i += 2;
144
- moveCursorToBoxTop();
145
143
  drawBox();
146
144
  continue;
147
145
  }
@@ -179,7 +177,6 @@ export function readTextInput(_prompt) {
179
177
  const line = linesBuf[linesBuf.length - 1];
180
178
  linesBuf[linesBuf.length - 1] = line.slice(0, cursorPos - 1) + line.slice(cursorPos);
181
179
  cursorPos--;
182
- moveCursorToBoxTop();
183
180
  drawBox();
184
181
  }
185
182
  continue;
@@ -189,7 +186,6 @@ export function readTextInput(_prompt) {
189
186
  const line = linesBuf[linesBuf.length - 1];
190
187
  linesBuf[linesBuf.length - 1] = line.slice(0, cursorPos) + " " + line.slice(cursorPos);
191
188
  cursorPos += 2;
192
- moveCursorToBoxTop();
193
189
  drawBox();
194
190
  continue;
195
191
  }
@@ -198,31 +194,20 @@ export function readTextInput(_prompt) {
198
194
  const line = linesBuf[linesBuf.length - 1];
199
195
  linesBuf[linesBuf.length - 1] = line.slice(0, cursorPos) + ch + line.slice(cursorPos);
200
196
  cursorPos++;
201
- moveCursorToBoxTop();
202
197
  drawBox();
203
198
  }
204
199
  }
205
200
  };
206
- function moveCursorToBoxTop() {
207
- // From current cursor position (inside the text area), move to the line
208
- // before the top border so drawBox() can clear and redraw from there.
209
- // Current cursor is at content row (linesBuf.length - 1 from top border)
210
- // We need to go up past: content rows above cursor + top border
211
- // But drawBox handles clearing with drawnRows, so just go up to start
212
- const currentLineIdx = linesBuf.length - 1;
213
- const rowsUp = currentLineIdx + 1; // content lines above + top border
214
- if (rowsUp > 0) {
215
- process.stderr.write(`\x1b[${rowsUp}A`);
216
- }
217
- process.stderr.write("\x1b[0G");
218
- }
219
201
  function finishAndClear() {
220
202
  process.stdin.removeListener("data", onData);
221
203
  if (origRawMode !== undefined) {
222
204
  process.stdin.setRawMode(origRawMode);
223
205
  }
224
206
  // Move cursor to top of box and clear everything
225
- moveCursorToBoxTop();
207
+ if (cursorRowFromTop > 0) {
208
+ process.stderr.write(`\x1b[${cursorRowFromTop}A`);
209
+ }
210
+ process.stderr.write("\x1b[0G");
226
211
  process.stderr.write("\x1b[J"); // erase to end of screen
227
212
  // Write the submitted text as a clean line (so it's visible in scrollback)
228
213
  const fullText = linesBuf.join("\n").trim();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "swarm-code",
3
- "version": "0.1.19",
3
+ "version": "0.1.21",
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": {