swarm-code 0.1.21 → 0.1.23
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/dist/core/rlm.js +7 -3
- package/dist/interactive-swarm.js +20 -1
- package/dist/threads/manager.d.ts +2 -1
- package/dist/threads/manager.js +4 -3
- package/dist/ui/text-input.js +78 -37
- package/package.json +1 -1
package/dist/core/rlm.js
CHANGED
|
@@ -171,6 +171,7 @@ export async function runRlmLoop(options) {
|
|
|
171
171
|
const { context, query, model, repl, signal, onProgress, onSubQueryStart, onSubQuery, systemPrompt, threadHandler, mergeHandler, } = options;
|
|
172
172
|
let totalSubQueries = 0;
|
|
173
173
|
let iterationSubQueries = 0;
|
|
174
|
+
let mergedAlready = false;
|
|
174
175
|
const llmQueryHandler = async (subContext, instruction) => {
|
|
175
176
|
if (signal?.aborted)
|
|
176
177
|
throw new Error("Aborted");
|
|
@@ -217,7 +218,10 @@ export async function runRlmLoop(options) {
|
|
|
217
218
|
repl.setThreadHandler(threadHandler);
|
|
218
219
|
}
|
|
219
220
|
if (mergeHandler) {
|
|
220
|
-
repl.setMergeHandler(
|
|
221
|
+
repl.setMergeHandler(async () => {
|
|
222
|
+
mergedAlready = true;
|
|
223
|
+
return mergeHandler();
|
|
224
|
+
});
|
|
221
225
|
}
|
|
222
226
|
}
|
|
223
227
|
await initRepl();
|
|
@@ -374,7 +378,7 @@ export async function runRlmLoop(options) {
|
|
|
374
378
|
});
|
|
375
379
|
if (execResult.hasFinal && execResult.finalValue !== null) {
|
|
376
380
|
// Auto-merge any unmerged thread branches before returning
|
|
377
|
-
if (mergeHandler) {
|
|
381
|
+
if (mergeHandler && !mergedAlready) {
|
|
378
382
|
try {
|
|
379
383
|
await mergeHandler();
|
|
380
384
|
}
|
|
@@ -422,7 +426,7 @@ export async function runRlmLoop(options) {
|
|
|
422
426
|
});
|
|
423
427
|
}
|
|
424
428
|
// Auto-merge any remaining thread branches even though FINAL was never called
|
|
425
|
-
if (mergeHandler) {
|
|
429
|
+
if (mergeHandler && !mergedAlready) {
|
|
426
430
|
try {
|
|
427
431
|
await mergeHandler();
|
|
428
432
|
}
|
|
@@ -757,8 +757,11 @@ export async function runInteractiveSwarm(rawArgs) {
|
|
|
757
757
|
logError(`Directory "${args.dir}" does not exist`);
|
|
758
758
|
process.exit(1);
|
|
759
759
|
}
|
|
760
|
-
// First-run onboarding
|
|
760
|
+
// First-run onboarding (may create ~/.swarm/config.yaml with user's chosen model)
|
|
761
761
|
await runOnboarding();
|
|
762
|
+
// Reload config to pick up any changes from onboarding
|
|
763
|
+
// (onboarding writes ~/.swarm/config.yaml but loadConfig() already ran before it)
|
|
764
|
+
Object.assign(config, loadConfig());
|
|
762
765
|
// Override config with CLI args
|
|
763
766
|
if (args.agent)
|
|
764
767
|
config.default_agent = args.agent;
|
|
@@ -960,6 +963,22 @@ export async function runInteractiveSwarm(rawArgs) {
|
|
|
960
963
|
else if (merged > 0) {
|
|
961
964
|
logSuccess(`Merged ${merged} branches`);
|
|
962
965
|
}
|
|
966
|
+
// Clean up merged worktrees
|
|
967
|
+
if (config.auto_cleanup_worktrees) {
|
|
968
|
+
for (const r of results) {
|
|
969
|
+
if (r.success) {
|
|
970
|
+
const thread = threads.find((t) => t.branchName === r.branch);
|
|
971
|
+
if (thread) {
|
|
972
|
+
try {
|
|
973
|
+
await threadManager.destroyWorktree(thread.id);
|
|
974
|
+
}
|
|
975
|
+
catch {
|
|
976
|
+
// Non-fatal
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
}
|
|
963
982
|
const summary = results
|
|
964
983
|
.map((r) => (r.success ? `Merged ${r.branch}: ${r.message}` : `FAILED ${r.branch}: ${r.message}`))
|
|
965
984
|
.join("\n");
|
|
@@ -80,6 +80,7 @@ export declare class ThreadManager {
|
|
|
80
80
|
};
|
|
81
81
|
/** Cleanup all worktrees. */
|
|
82
82
|
cleanup(): Promise<void>;
|
|
83
|
-
|
|
83
|
+
/** Destroy a specific thread's worktree and branch. */
|
|
84
|
+
destroyWorktree(threadId: string): Promise<void>;
|
|
84
85
|
private failResult;
|
|
85
86
|
}
|
package/dist/threads/manager.js
CHANGED
|
@@ -486,7 +486,7 @@ export class ThreadManager {
|
|
|
486
486
|
state.status = "cancelled";
|
|
487
487
|
state.phase = "cancelled";
|
|
488
488
|
state.completedAt = Date.now();
|
|
489
|
-
await this.
|
|
489
|
+
await this.destroyWorktree(threadId);
|
|
490
490
|
return this.failResult(state, "Thread cancelled during execution");
|
|
491
491
|
}
|
|
492
492
|
// Capture diff
|
|
@@ -570,7 +570,7 @@ export class ThreadManager {
|
|
|
570
570
|
const { cost } = this.budget.recordCost(threadId, errModel);
|
|
571
571
|
state.estimatedCostUsd = cost;
|
|
572
572
|
// Cleanup worktree on failure
|
|
573
|
-
await this.
|
|
573
|
+
await this.destroyWorktree(threadId);
|
|
574
574
|
return this.failResult(state, errorMsg);
|
|
575
575
|
}
|
|
576
576
|
finally {
|
|
@@ -637,7 +637,8 @@ export class ThreadManager {
|
|
|
637
637
|
await this.worktreeManager.destroyAll();
|
|
638
638
|
}
|
|
639
639
|
}
|
|
640
|
-
|
|
640
|
+
/** Destroy a specific thread's worktree and branch. */
|
|
641
|
+
async destroyWorktree(threadId) {
|
|
641
642
|
try {
|
|
642
643
|
await this.worktreeManager.destroy(threadId, true);
|
|
643
644
|
}
|
package/dist/ui/text-input.js
CHANGED
|
@@ -44,51 +44,93 @@ export function readTextInput(_prompt) {
|
|
|
44
44
|
process.stdin.setRawMode(true);
|
|
45
45
|
process.stdin.resume();
|
|
46
46
|
process.stdin.setEncoding("utf-8");
|
|
47
|
-
//
|
|
47
|
+
// Tracks cursor position relative to top border after each drawBox()
|
|
48
48
|
let cursorRowFromTop = 0;
|
|
49
|
-
let
|
|
50
|
-
function
|
|
51
|
-
const
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
out.write(`\x1b[${cursorRowFromTop}A`);
|
|
57
|
-
}
|
|
58
|
-
out.write("\x1b[0G"); // go to column 0
|
|
59
|
-
out.write("\x1b[J"); // erase from cursor to end of screen
|
|
60
|
-
}
|
|
61
|
-
hasDrawn = true;
|
|
62
|
-
// Top border — thin dim line
|
|
63
|
-
const topLine = `${BORDER_COLOR}${"─".repeat(w)}${RESET}`;
|
|
64
|
-
out.write(`${topLine}\n`);
|
|
65
|
-
// Content rows — dark background, full width
|
|
49
|
+
let prevTotalRows = 0;
|
|
50
|
+
function buildRows() {
|
|
51
|
+
const promptVisibleLen = 2;
|
|
52
|
+
const rows = [];
|
|
53
|
+
// Top border
|
|
54
|
+
rows.push(`${BORDER_COLOR}${"─".repeat(w)}${RESET}`);
|
|
55
|
+
// Content rows
|
|
66
56
|
const promptChar = `${ACCENT_COLOR}❯${RESET} `;
|
|
67
57
|
for (let i = 0; i < linesBuf.length; i++) {
|
|
68
58
|
const lineText = linesBuf[i];
|
|
69
59
|
const prefix = i === 0 ? promptChar : `${dim("·")} `;
|
|
70
|
-
// How much space for text content
|
|
71
60
|
const contentWidth = w - promptVisibleLen;
|
|
72
|
-
// Truncate display if line is too long
|
|
73
61
|
const displayText = lineText.length > contentWidth ? lineText.slice(0, contentWidth - 1) + "…" : lineText;
|
|
74
62
|
const padding = Math.max(0, contentWidth - displayText.length);
|
|
75
|
-
|
|
63
|
+
rows.push(`${BG_DARK}${prefix}${displayText}${" ".repeat(padding)}${RESET}`);
|
|
64
|
+
}
|
|
65
|
+
// Bottom border
|
|
66
|
+
rows.push(`${ACCENT_COLOR}${"─".repeat(w)}${RESET}`);
|
|
67
|
+
// Hints — pad to full width so it fully overwrites old content
|
|
68
|
+
const hintsText = " enter submit esc exit";
|
|
69
|
+
const hintsPad = Math.max(0, w - hintsText.length);
|
|
70
|
+
rows.push(`${dim(hintsText)}${" ".repeat(hintsPad)}`);
|
|
71
|
+
return rows;
|
|
72
|
+
}
|
|
73
|
+
function drawBox() {
|
|
74
|
+
const out = process.stderr;
|
|
75
|
+
const promptVisibleLen = 2;
|
|
76
|
+
const rows = buildRows();
|
|
77
|
+
const totalRows = rows.length;
|
|
78
|
+
if (prevTotalRows > 0) {
|
|
79
|
+
// ── Redraw: overwrite rows in place (no \n, no scrolling) ──
|
|
80
|
+
// Move cursor to top border row
|
|
81
|
+
if (cursorRowFromTop > 0) {
|
|
82
|
+
out.write(`\x1b[${cursorRowFromTop}A`);
|
|
83
|
+
}
|
|
84
|
+
out.write("\r");
|
|
85
|
+
// Overwrite each row in place
|
|
86
|
+
const commonRows = Math.min(totalRows, prevTotalRows);
|
|
87
|
+
for (let i = 0; i < commonRows; i++) {
|
|
88
|
+
out.write(`\x1b[2K${rows[i]}`);
|
|
89
|
+
if (i < commonRows - 1) {
|
|
90
|
+
out.write("\x1b[1B\r"); // cursor down 1 (no scroll), start of line
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
if (totalRows > prevTotalRows) {
|
|
94
|
+
// More rows than before (multi-line paste) — append with \n
|
|
95
|
+
for (let i = commonRows; i < totalRows; i++) {
|
|
96
|
+
out.write(`\n\x1b[2K${rows[i]}`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
else if (prevTotalRows > totalRows) {
|
|
100
|
+
// Fewer rows than before — erase leftover old rows
|
|
101
|
+
for (let i = totalRows; i < prevTotalRows; i++) {
|
|
102
|
+
out.write("\x1b[1B\r\x1b[2K");
|
|
103
|
+
}
|
|
104
|
+
// Move back to last new row
|
|
105
|
+
const extra = prevTotalRows - totalRows;
|
|
106
|
+
if (extra > 0)
|
|
107
|
+
out.write(`\x1b[${extra}A`);
|
|
108
|
+
}
|
|
109
|
+
// Cursor is now on the last row (hints).
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
// ── Initial draw: use \n between rows, no trailing \n ──
|
|
113
|
+
for (let i = 0; i < totalRows; i++) {
|
|
114
|
+
if (i > 0)
|
|
115
|
+
out.write("\n");
|
|
116
|
+
out.write(rows[i]);
|
|
117
|
+
}
|
|
118
|
+
// Cursor is on the hints line (last row).
|
|
76
119
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
//
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
//
|
|
120
|
+
prevTotalRows = totalRows;
|
|
121
|
+
// Position cursor at the active content row
|
|
122
|
+
// Cursor is currently on the hints line (last row = index totalRows-1).
|
|
123
|
+
// Content cursor is on row (1 + currentLineIdx).
|
|
124
|
+
const currentLineIdx = linesBuf.length - 1;
|
|
125
|
+
const targetRow = 1 + currentLineIdx;
|
|
126
|
+
const hintsRow = totalRows - 1;
|
|
127
|
+
const rowsUp = hintsRow - targetRow;
|
|
128
|
+
if (rowsUp > 0)
|
|
129
|
+
out.write(`\x1b[${rowsUp}A`);
|
|
130
|
+
// Set column
|
|
88
131
|
const col = promptVisibleLen + cursorPos + 1;
|
|
89
132
|
out.write(`\x1b[${col}G`);
|
|
90
|
-
|
|
91
|
-
cursorRowFromTop = 1 + currentLineIdx;
|
|
133
|
+
cursorRowFromTop = targetRow;
|
|
92
134
|
}
|
|
93
135
|
// Initial draw
|
|
94
136
|
process.stderr.write(HIDE_CURSOR);
|
|
@@ -203,12 +245,11 @@ export function readTextInput(_prompt) {
|
|
|
203
245
|
if (origRawMode !== undefined) {
|
|
204
246
|
process.stdin.setRawMode(origRawMode);
|
|
205
247
|
}
|
|
206
|
-
// Move cursor to top of box
|
|
248
|
+
// Move cursor to top of box
|
|
207
249
|
if (cursorRowFromTop > 0) {
|
|
208
250
|
process.stderr.write(`\x1b[${cursorRowFromTop}A`);
|
|
209
251
|
}
|
|
210
|
-
process.stderr.write("\x1b[
|
|
211
|
-
process.stderr.write("\x1b[J"); // erase to end of screen
|
|
252
|
+
process.stderr.write("\r\x1b[J"); // erase from here to end of screen
|
|
212
253
|
// Write the submitted text as a clean line (so it's visible in scrollback)
|
|
213
254
|
const fullText = linesBuf.join("\n").trim();
|
|
214
255
|
if (fullText) {
|
package/package.json
CHANGED