@robzilla1738/agentswarm 0.2.0

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 (64) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +142 -0
  3. package/bin/swarm.js +10 -0
  4. package/dist/agent.js +211 -0
  5. package/dist/cli.js +667 -0
  6. package/dist/config.js +289 -0
  7. package/dist/control.js +96 -0
  8. package/dist/deepseek.js +321 -0
  9. package/dist/executor.js +988 -0
  10. package/dist/hub.js +553 -0
  11. package/dist/journal.js +152 -0
  12. package/dist/prompts.js +232 -0
  13. package/dist/providers.js +151 -0
  14. package/dist/run.js +309 -0
  15. package/dist/sandbox.js +505 -0
  16. package/dist/state.js +230 -0
  17. package/dist/terminal.js +298 -0
  18. package/dist/tools.js +491 -0
  19. package/dist/types.js +26 -0
  20. package/dist/util.js +209 -0
  21. package/dist/webtools.js +205 -0
  22. package/package.json +63 -0
  23. package/ui/out/404/index.html +1 -0
  24. package/ui/out/404.html +1 -0
  25. package/ui/out/_next/static/chunks/255-2aa030c9ba2867e3.js +1 -0
  26. package/ui/out/_next/static/chunks/383-289a866b246b41cc.js +1 -0
  27. package/ui/out/_next/static/chunks/4bd1b696-c023c6e3521b1417.js +1 -0
  28. package/ui/out/_next/static/chunks/619-ba102abea3e3d0e4.js +1 -0
  29. package/ui/out/_next/static/chunks/677-b37981ba0eca75b2.js +1 -0
  30. package/ui/out/_next/static/chunks/app/_not-found/page-2d0982e372f7be41.js +1 -0
  31. package/ui/out/_next/static/chunks/app/layout-37ad32c5fdb26f29.js +1 -0
  32. package/ui/out/_next/static/chunks/app/page-0c9f35bd4aa8e370.js +1 -0
  33. package/ui/out/_next/static/chunks/app/run/page-13dc41a57e34da71.js +1 -0
  34. package/ui/out/_next/static/chunks/app/settings/page-a1763be7f6de888c.js +1 -0
  35. package/ui/out/_next/static/chunks/framework-2c534e0e662575a2.js +1 -0
  36. package/ui/out/_next/static/chunks/main-app-889ed884f8bc78e3.js +1 -0
  37. package/ui/out/_next/static/chunks/main-eb90ae3b35d2fd16.js +1 -0
  38. package/ui/out/_next/static/chunks/pages/_app-7d307437aca18ad4.js +1 -0
  39. package/ui/out/_next/static/chunks/pages/_error-cb2a52f75f2162e2.js +1 -0
  40. package/ui/out/_next/static/chunks/polyfills-42372ed130431b0a.js +1 -0
  41. package/ui/out/_next/static/chunks/webpack-38639c05c96dbeca.js +1 -0
  42. package/ui/out/_next/static/css/82edaa7a5942f894.css +3 -0
  43. package/ui/out/_next/static/eiQeDU9uBHNsBj0CFkp8M/_buildManifest.js +1 -0
  44. package/ui/out/_next/static/eiQeDU9uBHNsBj0CFkp8M/_ssgManifest.js +1 -0
  45. package/ui/out/_next/static/media/0aa834ed78bf6d07-s.woff2 +0 -0
  46. package/ui/out/_next/static/media/438aa629764e75f3-s.woff2 +0 -0
  47. package/ui/out/_next/static/media/4c9affa5bc8f420e-s.p.woff2 +0 -0
  48. package/ui/out/_next/static/media/51251f8b9793cdb3-s.woff2 +0 -0
  49. package/ui/out/_next/static/media/67957d42bae0796d-s.woff2 +0 -0
  50. package/ui/out/_next/static/media/875ae681bfde4580-s.woff2 +0 -0
  51. package/ui/out/_next/static/media/886030b0b59bc5a7-s.woff2 +0 -0
  52. package/ui/out/_next/static/media/939c4f875ee75fbb-s.woff2 +0 -0
  53. package/ui/out/_next/static/media/bb3ef058b751a6ad-s.p.woff2 +0 -0
  54. package/ui/out/_next/static/media/cc978ac5ee68c2b6-s.woff2 +0 -0
  55. package/ui/out/_next/static/media/e857b654a2caa584-s.woff2 +0 -0
  56. package/ui/out/_next/static/media/f911b923c6adde36-s.woff2 +0 -0
  57. package/ui/out/icon.png +0 -0
  58. package/ui/out/index.html +1 -0
  59. package/ui/out/index.txt +22 -0
  60. package/ui/out/run/index.html +1 -0
  61. package/ui/out/run/index.txt +22 -0
  62. package/ui/out/settings/index.html +1 -0
  63. package/ui/out/settings/index.txt +22 -0
  64. package/ui/out/swarm-mark.png +0 -0
package/dist/state.js ADDED
@@ -0,0 +1,230 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RunState = void 0;
4
+ const types_1 = require("./types");
5
+ /**
6
+ * Pure reducer over the journal. Both the live executor and the read-only hub
7
+ * build identical state from the same events — the journal is the truth.
8
+ */
9
+ class RunState {
10
+ meta = null;
11
+ status = "planning";
12
+ statusReason = "";
13
+ tasks = new Map();
14
+ taskOrder = [];
15
+ agents = new Map();
16
+ notes = [];
17
+ conductorLog = [];
18
+ operatorNotes = [];
19
+ usageByModel = new Map();
20
+ totalUsage = { ...types_1.ZERO_USAGE };
21
+ cost = 0;
22
+ finalSummary;
23
+ finalReportPath;
24
+ lastSeq = 0;
25
+ lastT = 0;
26
+ createdAt = 0;
27
+ updatedAt = 0;
28
+ pricing;
29
+ constructor(pricing = {}) {
30
+ this.pricing = pricing;
31
+ }
32
+ apply(ev) {
33
+ this.lastSeq = ev.seq;
34
+ this.lastT = ev.t;
35
+ this.updatedAt = ev.t;
36
+ switch (ev.type) {
37
+ case "run.created": {
38
+ this.meta = ev.meta;
39
+ this.createdAt = this.meta.createdAt;
40
+ if (this.meta.options) {
41
+ // pricing may be passed through meta for the hub
42
+ }
43
+ break;
44
+ }
45
+ case "run.status":
46
+ this.status = ev.status;
47
+ if (ev.reason)
48
+ this.statusReason = String(ev.reason);
49
+ break;
50
+ case "run.resumed": {
51
+ // Tasks that were in flight when the engine died re-run from scratch;
52
+ // agents the dead process owned can no longer be running.
53
+ const resets = Array.isArray(ev.resets) ? ev.resets : [];
54
+ for (const id of resets) {
55
+ const t = this.tasks.get(id);
56
+ if (t) {
57
+ t.status = "pending";
58
+ t.startedAt = undefined;
59
+ t.endedAt = undefined;
60
+ }
61
+ }
62
+ for (const a of this.agents.values()) {
63
+ if (a.status === "running") {
64
+ a.status = "done";
65
+ a.endedAt = ev.t;
66
+ }
67
+ }
68
+ this.statusReason = "";
69
+ break;
70
+ }
71
+ case "task.created": {
72
+ const t = ev.task;
73
+ if (!this.tasks.has(t.id))
74
+ this.taskOrder.push(t.id);
75
+ this.tasks.set(t.id, { ...t });
76
+ break;
77
+ }
78
+ case "task.status": {
79
+ const t = this.tasks.get(ev.taskId);
80
+ if (t) {
81
+ t.status = ev.status;
82
+ if (typeof ev.attempt === "number")
83
+ t.attempt = ev.attempt;
84
+ if (ev.status === "running" && !t.startedAt)
85
+ t.startedAt = ev.t;
86
+ if (["done", "failed", "blocked"].includes(String(ev.status)))
87
+ t.endedAt = ev.t;
88
+ if (ev.reason)
89
+ t.error = String(ev.reason);
90
+ }
91
+ break;
92
+ }
93
+ case "task.report": {
94
+ const t = this.tasks.get(ev.taskId);
95
+ if (t) {
96
+ t.report = ev.report;
97
+ t.reportStatus = ev.status;
98
+ t.artifacts = ev.artifacts ?? t.artifacts;
99
+ }
100
+ break;
101
+ }
102
+ case "verify.result": {
103
+ const t = this.tasks.get(ev.taskId);
104
+ if (t)
105
+ t.feedback = ev.feedback;
106
+ break;
107
+ }
108
+ case "agent.spawned":
109
+ this.agents.set(ev.agentId, {
110
+ id: ev.agentId,
111
+ taskId: ev.taskId,
112
+ role: ev.role ?? "agent",
113
+ model: ev.model ?? "",
114
+ purpose: ev.purpose ?? "",
115
+ status: "running",
116
+ steps: 0,
117
+ startedAt: ev.t,
118
+ lastText: "",
119
+ lastThink: "",
120
+ });
121
+ break;
122
+ case "agent.delta": {
123
+ const a = this.agents.get(ev.agentId);
124
+ if (a) {
125
+ if (ev.channel === "text")
126
+ a.lastText = clipTail(a.lastText + ev.text, 4000);
127
+ else
128
+ a.lastThink = clipTail(a.lastThink + ev.text, 4000);
129
+ }
130
+ break;
131
+ }
132
+ case "agent.done": {
133
+ const a = this.agents.get(ev.agentId);
134
+ if (a) {
135
+ a.status = "done";
136
+ a.endedAt = ev.t;
137
+ a.steps = ev.steps ?? a.steps;
138
+ }
139
+ break;
140
+ }
141
+ case "tool.call": {
142
+ const a = this.agents.get(ev.agentId);
143
+ if (a) {
144
+ a.lastTool = ev.name;
145
+ a.steps++;
146
+ }
147
+ break;
148
+ }
149
+ case "note.added":
150
+ this.notes.push({
151
+ t: ev.t,
152
+ taskId: ev.taskId,
153
+ agentId: ev.agentId,
154
+ key: ev.key,
155
+ text: ev.text,
156
+ });
157
+ // Reduced state is held live by the hub and the resume seed — keep
158
+ // only the tail that digests/views actually use.
159
+ if (this.notes.length > 1000)
160
+ this.notes.splice(0, this.notes.length - 1000);
161
+ break;
162
+ case "conductor.say":
163
+ this.conductorLog.push({ t: ev.t, text: ev.text });
164
+ if (this.conductorLog.length > 300)
165
+ this.conductorLog.splice(0, this.conductorLog.length - 300);
166
+ break;
167
+ case "operator.note":
168
+ this.operatorNotes.push({ t: ev.t, text: ev.text, consumed: false });
169
+ break;
170
+ case "operator.note.consumed": {
171
+ const idx = this.operatorNotes.findIndex((n) => !n.consumed);
172
+ if (idx >= 0)
173
+ this.operatorNotes[idx].consumed = true;
174
+ break;
175
+ }
176
+ case "usage": {
177
+ const u = ev.usage;
178
+ const model = ev.model ?? "unknown";
179
+ this.usageByModel.set(model, (0, types_1.addUsage)(this.usageByModel.get(model) ?? { ...types_1.ZERO_USAGE }, u));
180
+ this.totalUsage = (0, types_1.addUsage)(this.totalUsage, u);
181
+ this.cost += (0, types_1.usageCost)(u, this.pricing[model]);
182
+ break;
183
+ }
184
+ case "run.final":
185
+ this.finalSummary = ev.summary;
186
+ this.finalReportPath = ev.reportPath;
187
+ break;
188
+ }
189
+ }
190
+ taskList() {
191
+ return this.taskOrder.map((id) => this.tasks.get(id)).filter(Boolean);
192
+ }
193
+ activeAgents() {
194
+ return [...this.agents.values()].filter((a) => a.status === "running");
195
+ }
196
+ pendingOperatorNotes() {
197
+ return this.operatorNotes.filter((n) => !n.consumed).map((n) => n.text);
198
+ }
199
+ summary() {
200
+ const tasks = this.taskList();
201
+ const count = (s) => tasks.filter((t) => t.status === s).length;
202
+ return {
203
+ id: this.meta?.id ?? "",
204
+ mission: this.meta?.mission ?? "",
205
+ status: this.status,
206
+ statusReason: this.statusReason || undefined,
207
+ createdAt: this.createdAt,
208
+ updatedAt: this.updatedAt,
209
+ heartbeatAt: this.lastT,
210
+ pid: null,
211
+ model: this.meta?.options.model ?? "",
212
+ tasks: {
213
+ total: tasks.length,
214
+ done: count("done"),
215
+ failed: count("failed"),
216
+ running: count("running") + count("verifying"),
217
+ pending: count("pending"),
218
+ blocked: count("blocked"),
219
+ },
220
+ agentsActive: this.activeAgents().length,
221
+ usage: this.totalUsage,
222
+ cost: this.cost,
223
+ finalSummary: this.finalSummary,
224
+ };
225
+ }
226
+ }
227
+ exports.RunState = RunState;
228
+ function clipTail(s, max) {
229
+ return s.length <= max ? s : s.slice(s.length - max);
230
+ }
@@ -0,0 +1,298 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TerminalRenderer = void 0;
4
+ exports.watchRun = watchRun;
5
+ const journal_1 = require("./journal");
6
+ const config_1 = require("./config");
7
+ const state_1 = require("./state");
8
+ const util_1 = require("./util");
9
+ const STATUS_STYLE = {
10
+ pending: util_1.ansi.gray,
11
+ running: util_1.ansi.cyan,
12
+ verifying: util_1.ansi.magenta,
13
+ done: util_1.ansi.green,
14
+ failed: util_1.ansi.red,
15
+ blocked: util_1.ansi.yellow,
16
+ };
17
+ const SPINNER = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
18
+ class TerminalRenderer {
19
+ state;
20
+ frame = 0;
21
+ timer = null;
22
+ active = false;
23
+ tty;
24
+ height = 40;
25
+ width = 100;
26
+ constructor(pricing) {
27
+ this.state = new state_1.RunState(pricing);
28
+ this.tty = Boolean(process.stdout.isTTY);
29
+ }
30
+ ingest(ev) {
31
+ this.state.apply(ev);
32
+ if (!this.tty)
33
+ this.streamLine(ev);
34
+ }
35
+ start() {
36
+ if (!this.tty) {
37
+ process.stdout.write("agentswarm: streaming (no TTY)\n");
38
+ return;
39
+ }
40
+ this.active = true;
41
+ process.stdout.write("\x1b[?25l"); // hide cursor
42
+ this.timer = setInterval(() => this.render(), 250);
43
+ }
44
+ stop() {
45
+ if (this.timer)
46
+ clearInterval(this.timer);
47
+ this.timer = null;
48
+ if (this.tty && this.active) {
49
+ this.render();
50
+ process.stdout.write("\x1b[?25h"); // show cursor
51
+ }
52
+ this.active = false;
53
+ }
54
+ getState() {
55
+ return this.state;
56
+ }
57
+ // ---------- non-tty streaming ----------
58
+ streamLine(ev) {
59
+ let line = "";
60
+ switch (ev.type) {
61
+ case "run.status":
62
+ line = `[run] ${ev.status}${ev.reason ? ` — ${ev.reason}` : ""}`;
63
+ break;
64
+ case "task.created":
65
+ line = `[task+] ${ev.task.id} ${ev.task.title}`;
66
+ break;
67
+ case "task.status":
68
+ line = `[task] ${ev.taskId} → ${ev.status}${ev.reason ? ` (${(0, util_1.oneLine)(String(ev.reason), 80)})` : ""}`;
69
+ break;
70
+ case "agent.spawned":
71
+ line = `[agent+] ${ev.agentId} (${ev.role}) ${(0, util_1.oneLine)(String(ev.purpose ?? ""), 60)}`;
72
+ break;
73
+ case "tool.call":
74
+ line = ` ${ev.agentId} · ${ev.name}`;
75
+ break;
76
+ case "conductor.action":
77
+ line = `[conductor] ${ev.kind}`;
78
+ break;
79
+ case "note.added":
80
+ line = `[note] ${(0, util_1.oneLine)(String(ev.text), 100)}`;
81
+ break;
82
+ case "operator.note":
83
+ line = `[operator] ${(0, util_1.oneLine)(String(ev.text), 100)}`;
84
+ break;
85
+ case "run.final":
86
+ line = `[final] ${(0, util_1.oneLine)(String(ev.summary), 200)}`;
87
+ break;
88
+ case "log":
89
+ if (ev.level !== "info")
90
+ line = `[${ev.level}] ${ev.msg}`;
91
+ break;
92
+ }
93
+ if (line)
94
+ process.stdout.write(line + "\n");
95
+ }
96
+ // ---------- tty dashboard ----------
97
+ render() {
98
+ if (!this.tty)
99
+ return;
100
+ this.frame++;
101
+ this.height = process.stdout.rows || 40;
102
+ this.width = Math.min(process.stdout.columns || 100, 120);
103
+ const lines = this.compose();
104
+ const clipped = lines.slice(0, this.height - 1);
105
+ let out = "\x1b[H"; // cursor home
106
+ out += clipped.map((l) => l + "\x1b[K").join("\n"); // each line clears to EOL
107
+ out += "\x1b[J"; // clear below
108
+ process.stdout.write(out);
109
+ }
110
+ compose() {
111
+ const s = this.state;
112
+ const spin = SPINNER[this.frame % SPINNER.length];
113
+ const L = [];
114
+ const W = this.width;
115
+ const live = ["planning", "running", "synthesizing"].includes(s.status);
116
+ const statusIcon = live ? util_1.ansi.cyan(spin) : s.status === "done" ? util_1.ansi.green("●") : util_1.ansi.red("●");
117
+ L.push(`${statusIcon} ${util_1.ansi.bold("agentswarm")} ${util_1.ansi.gray("·")} ${statusColor(s.status)} ${util_1.ansi.gray("·")} ${util_1.ansi.gray(s.meta?.id ?? "")}`);
118
+ L.push(util_1.ansi.gray(" mission: ") + (0, util_1.clip)((0, util_1.oneLine)(s.meta?.mission ?? "", W - 12), W - 12));
119
+ // budget bar
120
+ const cap = s.meta?.options.maxTokens ?? 1;
121
+ const pct = Math.min(100, Math.round((s.totalUsage.promptTokens + s.totalUsage.completionTokens) / cap * 100));
122
+ const barW = Math.max(10, Math.min(40, W - 50));
123
+ const filled = Math.round((pct / 100) * barW);
124
+ const bar = util_1.ansi.cyan("█".repeat(filled)) + util_1.ansi.gray("░".repeat(barW - filled));
125
+ const spent = s.totalUsage.promptTokens + s.totalUsage.completionTokens;
126
+ L.push(` ${bar} ${pct}% ${util_1.ansi.gray((0, util_1.fmtTokens)(spent) + "/" + (0, util_1.fmtTokens)(cap) + " tok")} ${util_1.ansi.green((0, util_1.fmtMoney)(s.cost))}`);
127
+ const tasks = s.taskList();
128
+ const counts = {
129
+ done: tasks.filter((t) => t.status === "done").length,
130
+ running: tasks.filter((t) => t.status === "running" || t.status === "verifying").length,
131
+ pending: tasks.filter((t) => t.status === "pending").length,
132
+ failed: tasks.filter((t) => t.status === "failed").length,
133
+ blocked: tasks.filter((t) => t.status === "blocked").length,
134
+ };
135
+ L.push(util_1.ansi.gray(" ") +
136
+ `${util_1.ansi.green(counts.done + " done")} ${util_1.ansi.cyan(counts.running + " running")} ${util_1.ansi.gray(counts.pending + " pending")} ${counts.failed ? util_1.ansi.red(counts.failed + " failed") : util_1.ansi.gray("0 failed")} ${counts.blocked ? util_1.ansi.yellow(counts.blocked + " blocked") : util_1.ansi.gray("0 blocked")}`);
137
+ L.push("");
138
+ // active agents
139
+ const agents = s.activeAgents();
140
+ if (agents.length) {
141
+ L.push(util_1.ansi.bold(` Active agents (${agents.length})`));
142
+ for (const a of agents.slice(0, 8)) {
143
+ L.push(this.agentLine(a, spin, W));
144
+ }
145
+ if (agents.length > 8)
146
+ L.push(util_1.ansi.gray(` …and ${agents.length - 8} more`));
147
+ L.push("");
148
+ }
149
+ // task table
150
+ L.push(util_1.ansi.bold(" Tasks"));
151
+ const visibleTasks = this.pickTasks(tasks, Math.max(6, this.height - L.length - 10));
152
+ for (const t of visibleTasks) {
153
+ L.push(this.taskLine(t, spin, W));
154
+ }
155
+ if (tasks.length > visibleTasks.length) {
156
+ L.push(util_1.ansi.gray(` …${tasks.length - visibleTasks.length} more tasks`));
157
+ }
158
+ // conductor latest
159
+ const lastSay = s.conductorLog[s.conductorLog.length - 1];
160
+ if (lastSay) {
161
+ L.push("");
162
+ L.push(util_1.ansi.bold(" Conductor"));
163
+ for (const ln of wrap((0, util_1.oneLine)(lastSay.text, 600), W - 6).slice(0, 3)) {
164
+ L.push(util_1.ansi.gray(" ") + util_1.ansi.italic(ln));
165
+ }
166
+ }
167
+ // notes
168
+ if (s.notes.length) {
169
+ L.push("");
170
+ L.push(util_1.ansi.bold(` Blackboard (${s.notes.length})`));
171
+ for (const n of s.notes.slice(-3)) {
172
+ L.push(util_1.ansi.gray(" • ") + (0, util_1.clip)((0, util_1.oneLine)((n.key ? `[${n.key}] ` : "") + n.text, W - 8), W - 8));
173
+ }
174
+ }
175
+ // operator hint
176
+ if (live) {
177
+ L.push("");
178
+ L.push(util_1.ansi.gray(" Ctrl-C to detach (run keeps going) · ") + util_1.ansi.gray(`swarm note ${s.meta?.id} "…" to steer · swarm cancel ${s.meta?.id}`));
179
+ }
180
+ else if (s.finalSummary) {
181
+ L.push("");
182
+ L.push(util_1.ansi.bold(util_1.ansi.green(" ✓ Final summary")));
183
+ for (const ln of wrap(s.finalSummary, W - 6).slice(0, 6))
184
+ L.push(" " + ln);
185
+ if (s.finalReportPath)
186
+ L.push(util_1.ansi.gray(" report: ") + s.finalReportPath);
187
+ }
188
+ return L;
189
+ }
190
+ agentLine(a, spin, W) {
191
+ const role = util_1.ansi.magenta(`${a.role}`);
192
+ const head = ` ${util_1.ansi.cyan(spin)} ${util_1.ansi.gray(a.taskId)} ${role} ${util_1.ansi.gray("·")} `;
193
+ const tool = a.lastTool ? util_1.ansi.yellow(a.lastTool) + " " : "";
194
+ const txt = (0, util_1.oneLine)(a.lastText || a.lastThink || a.purpose, W - 40);
195
+ return (0, util_1.clip)(head + tool + util_1.ansi.gray(txt), W + 40); // +40 accounts for ansi codes roughly
196
+ }
197
+ taskLine(t, spin, W) {
198
+ const style = STATUS_STYLE[t.status] ?? util_1.ansi.white;
199
+ const icon = t.status === "running" || t.status === "verifying"
200
+ ? util_1.ansi.cyan(spin)
201
+ : t.status === "done"
202
+ ? util_1.ansi.green("✓")
203
+ : t.status === "failed"
204
+ ? util_1.ansi.red("✗")
205
+ : t.status === "blocked"
206
+ ? util_1.ansi.yellow("⊘")
207
+ : util_1.ansi.gray("○");
208
+ const id = style(t.id.padEnd(4));
209
+ const role = util_1.ansi.gray(`(${t.role})`);
210
+ const deps = t.deps.length ? util_1.ansi.gray(` ⇠${t.deps.join(",")}`) : "";
211
+ const v = t.verify ? util_1.ansi.magenta(" ⊛") : "";
212
+ const dur = t.startedAt ? util_1.ansi.gray(" " + (0, util_1.fmtDur)((t.endedAt ?? Date.now()) - t.startedAt)) : "";
213
+ const title = (0, util_1.clip)(t.title, Math.max(20, W - 38));
214
+ return ` ${icon} ${id} ${title} ${role}${deps}${v}${dur}`;
215
+ }
216
+ pickTasks(tasks, budget) {
217
+ if (tasks.length <= budget)
218
+ return tasks;
219
+ // Prioritize active + recently settled.
220
+ const active = tasks.filter((t) => ["running", "verifying", "pending"].includes(t.status));
221
+ const settled = tasks.filter((t) => ["done", "failed", "blocked"].includes(t.status));
222
+ const keepSettled = Math.max(0, budget - active.length);
223
+ return [...settled.slice(-keepSettled), ...active].slice(-budget);
224
+ }
225
+ }
226
+ exports.TerminalRenderer = TerminalRenderer;
227
+ function statusColor(s) {
228
+ switch (s) {
229
+ case "done":
230
+ return util_1.ansi.green(util_1.ansi.bold("done"));
231
+ case "failed":
232
+ return util_1.ansi.red(util_1.ansi.bold("failed"));
233
+ case "cancelled":
234
+ return util_1.ansi.yellow("cancelled");
235
+ case "synthesizing":
236
+ return util_1.ansi.magenta("synthesizing");
237
+ case "running":
238
+ return util_1.ansi.cyan("running");
239
+ case "planning":
240
+ return util_1.ansi.cyan("planning");
241
+ default:
242
+ return s;
243
+ }
244
+ }
245
+ function wrap(text, width) {
246
+ const words = text.split(/\s+/);
247
+ const lines = [];
248
+ let cur = "";
249
+ for (const w of words) {
250
+ if ((cur + " " + w).trim().length > width) {
251
+ if (cur)
252
+ lines.push(cur);
253
+ cur = w;
254
+ }
255
+ else {
256
+ cur = (cur + " " + w).trim();
257
+ }
258
+ }
259
+ if (cur)
260
+ lines.push(cur);
261
+ return lines;
262
+ }
263
+ /** Tail a run's journal into a renderer until it goes terminal (for `watch`). */
264
+ async function watchRun(id, pricing) {
265
+ const renderer = new TerminalRenderer(pricing);
266
+ const file = (0, journal_1.eventsFile)((0, config_1.runDir)(id));
267
+ const tail = { offset: 0, carry: "" };
268
+ renderer.start();
269
+ return new Promise((resolve) => {
270
+ const tick = () => {
271
+ let evs = [];
272
+ try {
273
+ evs = (0, journal_1.readNewEvents)(file, tail);
274
+ }
275
+ catch {
276
+ /* file not ready */
277
+ }
278
+ for (const ev of evs)
279
+ renderer.ingest(ev);
280
+ const st = renderer.getState().status;
281
+ if (["done", "failed", "cancelled"].includes(st)) {
282
+ setTimeout(() => {
283
+ // one last drain
284
+ try {
285
+ for (const ev of (0, journal_1.readNewEvents)(file, tail))
286
+ renderer.ingest(ev);
287
+ }
288
+ catch { /* ignore */ }
289
+ renderer.stop();
290
+ resolve();
291
+ }, 500);
292
+ return;
293
+ }
294
+ setTimeout(tick, 300);
295
+ };
296
+ tick();
297
+ });
298
+ }