@desplega.ai/agent-swarm 1.9.0 → 1.10.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.
- package/Dockerfile +43 -5
- package/README.md +16 -0
- package/UI.md +40 -0
- package/deploy/prod-db.ts +2 -2
- package/docker-compose.example.yml +49 -50
- package/docker-entrypoint.sh +4 -1
- package/package.json +1 -1
- package/plugin/commands/review-offered-task.md +45 -0
- package/plugin/commands/start-leader.md +7 -5
- package/plugin/commands/start-worker.md +5 -0
- package/src/cli.tsx +44 -5
- package/src/commands/lead.ts +2 -0
- package/src/commands/runner.ts +273 -45
- package/src/commands/worker.ts +2 -0
- package/src/http.ts +145 -1
- package/src/prompts/base-prompt.ts +17 -3
- package/src/tests/runner-polling-api.test.ts +456 -0
- package/src/utils/pretty-print.ts +137 -120
- package/thoughts/shared/plans/2025-12-23-runner-level-polling.md +934 -0
- package/thoughts/shared/research/2025-12-22-runner-loop-architecture.md +582 -0
|
@@ -71,149 +71,166 @@ function formatToolInput(input: Record<string, unknown>): string {
|
|
|
71
71
|
|
|
72
72
|
/** Pretty print a single JSON line from Claude output */
|
|
73
73
|
export function prettyPrintLine(line: string, role: string): void {
|
|
74
|
-
if (!line.trim()) return;
|
|
75
|
-
|
|
76
|
-
let json: Record<string, unknown>;
|
|
77
74
|
try {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
switch (type) {
|
|
89
|
-
case "system": {
|
|
90
|
-
const subtype = json.subtype as string;
|
|
91
|
-
if (subtype === "init") {
|
|
92
|
-
const model = json.model as string;
|
|
93
|
-
const tools = json.tools as string[];
|
|
94
|
-
console.log(
|
|
95
|
-
`${prefix} ${c.cyan}●${c.reset} ${c.bold}Session started${c.reset} ${c.dim}(${model}, ${tools?.length || 0} tools)${c.reset}`,
|
|
96
|
-
);
|
|
97
|
-
} else if (subtype === "hook_response") {
|
|
98
|
-
const hookName = json.hook_name as string;
|
|
99
|
-
const stdout = json.stdout as string;
|
|
100
|
-
console.log(`${prefix} ${c.yellow}⚡${c.reset} Hook: ${c.yellow}${hookName}${c.reset}`);
|
|
101
|
-
if (stdout) {
|
|
102
|
-
const lines = stdout.split("\n").filter((l) => l.trim());
|
|
103
|
-
for (const l of lines.slice(0, 3)) {
|
|
104
|
-
console.log(`${prefix} ${c.dim}${truncate(l, 80)}${c.reset}`);
|
|
105
|
-
}
|
|
106
|
-
if (lines.length > 3) {
|
|
107
|
-
console.log(`${prefix} ${c.dim}... (${lines.length - 3} more lines)${c.reset}`);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
} else {
|
|
111
|
-
const msg = (json.message as string) || (json.content as string) || "";
|
|
112
|
-
console.log(
|
|
113
|
-
`${prefix} ${c.cyan}ℹ${c.reset} System${subtype ? ` (${subtype})` : ""}: ${truncate(msg, 100)}`,
|
|
114
|
-
);
|
|
115
|
-
}
|
|
116
|
-
break;
|
|
75
|
+
if (!line.trim()) return;
|
|
76
|
+
|
|
77
|
+
let json: Record<string, unknown>;
|
|
78
|
+
try {
|
|
79
|
+
json = JSON.parse(line.trim());
|
|
80
|
+
} catch {
|
|
81
|
+
// Raw output - just print it
|
|
82
|
+
console.log(`${c.dim}[${role}]${c.reset} ${line.trim()}`);
|
|
83
|
+
return;
|
|
117
84
|
}
|
|
118
85
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
if (!message) break;
|
|
86
|
+
const type = json.type as string;
|
|
87
|
+
const prefix = `${c.dim}[${role}]${c.reset}`;
|
|
122
88
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
const
|
|
129
|
-
console.log(`${prefix} ${c.green}◆${c.reset} ${c.bold}Assistant:${c.reset}`);
|
|
130
|
-
// Print text with nice indentation, truncate long lines
|
|
131
|
-
const lines = text.split("\n");
|
|
132
|
-
for (const l of lines.slice(0, 5)) {
|
|
133
|
-
console.log(`${prefix} ${truncate(l, 100)}`);
|
|
134
|
-
}
|
|
135
|
-
if (lines.length > 5) {
|
|
136
|
-
console.log(`${prefix} ${c.dim}... (${lines.length - 5} more lines)${c.reset}`);
|
|
137
|
-
}
|
|
138
|
-
} else if (block.type === "tool_use") {
|
|
139
|
-
const toolName = formatToolName((block.name as string) || "unknown");
|
|
140
|
-
const input = (block.input as Record<string, unknown>) || {};
|
|
89
|
+
switch (type) {
|
|
90
|
+
case "system": {
|
|
91
|
+
const subtype = json.subtype as string;
|
|
92
|
+
if (subtype === "init") {
|
|
93
|
+
const model = json.model as string;
|
|
94
|
+
const tools = json.tools as string[];
|
|
141
95
|
console.log(
|
|
142
|
-
`${prefix} ${c.
|
|
96
|
+
`${prefix} ${c.cyan}●${c.reset} ${c.bold}Session started${c.reset} ${c.dim}(${model}, ${tools?.length || 0} tools)${c.reset}`,
|
|
143
97
|
);
|
|
144
|
-
} else if (
|
|
145
|
-
const
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
98
|
+
} else if (subtype === "hook_response") {
|
|
99
|
+
const hookName = json.hook_name as string;
|
|
100
|
+
const stdout = json.stdout as string;
|
|
101
|
+
console.log(`${prefix} ${c.yellow}⚡${c.reset} Hook: ${c.yellow}${hookName}${c.reset}`);
|
|
102
|
+
if (stdout) {
|
|
103
|
+
const lines = stdout.split("\n").filter((l) => l.trim());
|
|
104
|
+
for (const l of lines.slice(0, 3)) {
|
|
105
|
+
console.log(`${prefix} ${c.dim}${truncate(l, 80)}${c.reset}`);
|
|
106
|
+
}
|
|
107
|
+
if (lines.length > 3) {
|
|
108
|
+
console.log(`${prefix} ${c.dim}... (${lines.length - 3} more lines)${c.reset}`);
|
|
109
|
+
}
|
|
149
110
|
}
|
|
111
|
+
} else {
|
|
112
|
+
const msg = (json.message as string) || (json.content as string) || "";
|
|
113
|
+
console.log(
|
|
114
|
+
`${prefix} ${c.cyan}ℹ${c.reset} System${subtype ? ` (${subtype})` : ""}: ${truncate(msg, 100)}`,
|
|
115
|
+
);
|
|
150
116
|
}
|
|
117
|
+
break;
|
|
151
118
|
}
|
|
152
|
-
break;
|
|
153
|
-
}
|
|
154
119
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
120
|
+
case "assistant": {
|
|
121
|
+
const message = json.message as Record<string, unknown>;
|
|
122
|
+
if (!message) break;
|
|
158
123
|
|
|
159
|
-
if (toolResult) {
|
|
160
|
-
const isError = toolResult.includes("Error") || toolResult.includes("error");
|
|
161
|
-
const icon = isError ? `${c.red}✗${c.reset}` : `${c.green}✓${c.reset}`;
|
|
162
|
-
console.log(`${prefix} ${icon} Result: ${truncate(toolResult, 100)}`);
|
|
163
|
-
} else if (message) {
|
|
164
124
|
const content = message.content as Array<Record<string, unknown>>;
|
|
165
|
-
if (content)
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
125
|
+
if (!content) break;
|
|
126
|
+
|
|
127
|
+
for (const block of content) {
|
|
128
|
+
if (block.type === "text") {
|
|
129
|
+
const text = block.text as string;
|
|
130
|
+
console.log(`${prefix} ${c.green}◆${c.reset} ${c.bold}Assistant:${c.reset}`);
|
|
131
|
+
// Print text with nice indentation, truncate long lines
|
|
132
|
+
const lines = text.split("\n");
|
|
133
|
+
for (const l of lines.slice(0, 5)) {
|
|
134
|
+
console.log(`${prefix} ${truncate(l, 100)}`);
|
|
135
|
+
}
|
|
136
|
+
if (lines.length > 5) {
|
|
137
|
+
console.log(`${prefix} ${c.dim}... (${lines.length - 5} more lines)${c.reset}`);
|
|
138
|
+
}
|
|
139
|
+
} else if (block.type === "tool_use") {
|
|
140
|
+
const toolName = formatToolName((block.name as string) || "unknown");
|
|
141
|
+
const input = (block.input as Record<string, unknown>) || {};
|
|
142
|
+
console.log(
|
|
143
|
+
`${prefix} ${c.magenta}▶${c.reset} Tool: ${c.magenta}${toolName}${c.reset}${formatToolInput(input)}`,
|
|
144
|
+
);
|
|
145
|
+
} else if (block.type === "thinking") {
|
|
146
|
+
const thinking = block.thinking as string;
|
|
147
|
+
console.log(`${prefix} ${c.blue}💭${c.reset} ${c.dim}Thinking...${c.reset}`);
|
|
148
|
+
if (thinking) {
|
|
149
|
+
console.log(`${prefix} ${c.dim}${truncate(thinking, 80)}${c.reset}`);
|
|
172
150
|
}
|
|
173
151
|
}
|
|
174
152
|
}
|
|
153
|
+
break;
|
|
175
154
|
}
|
|
176
|
-
break;
|
|
177
|
-
}
|
|
178
155
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
156
|
+
case "user": {
|
|
157
|
+
const message = json.message as Record<string, unknown>;
|
|
158
|
+
const rawToolResult = json.tool_use_result;
|
|
159
|
+
const toolResult =
|
|
160
|
+
typeof rawToolResult === "string"
|
|
161
|
+
? rawToolResult
|
|
162
|
+
: rawToolResult
|
|
163
|
+
? JSON.stringify(rawToolResult)
|
|
164
|
+
: null;
|
|
165
|
+
|
|
166
|
+
if (toolResult) {
|
|
167
|
+
const isError = toolResult.includes("Error") || toolResult.includes("error");
|
|
168
|
+
const icon = isError ? `${c.red}✗${c.reset}` : `${c.green}✓${c.reset}`;
|
|
169
|
+
console.log(`${prefix} ${icon} Result: ${truncate(toolResult, 100)}`);
|
|
170
|
+
} else if (message) {
|
|
171
|
+
const content = message.content as Array<Record<string, unknown>>;
|
|
172
|
+
if (content) {
|
|
173
|
+
for (const block of content) {
|
|
174
|
+
if (block.type === "tool_result") {
|
|
175
|
+
const rawResult = block.content;
|
|
176
|
+
const result =
|
|
177
|
+
typeof rawResult === "string"
|
|
178
|
+
? rawResult
|
|
179
|
+
: rawResult
|
|
180
|
+
? JSON.stringify(rawResult)
|
|
181
|
+
: "";
|
|
182
|
+
const isError = block.is_error as boolean;
|
|
183
|
+
const icon = isError ? `${c.red}✗${c.reset}` : `${c.green}✓${c.reset}`;
|
|
184
|
+
console.log(`${prefix} ${icon} Result: ${truncate(result, 100)}`);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
199
188
|
}
|
|
200
|
-
|
|
201
|
-
|
|
189
|
+
break;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
case "result": {
|
|
193
|
+
const subtype = json.subtype as string;
|
|
194
|
+
const isError = json.is_error as boolean;
|
|
195
|
+
const duration = json.duration_ms as number;
|
|
196
|
+
const cost = json.total_cost_usd as number;
|
|
197
|
+
const numTurns = json.num_turns as number;
|
|
198
|
+
const result = json.result as string;
|
|
199
|
+
|
|
200
|
+
const icon = isError ? `${c.red}✗${c.reset}` : `${c.green}✓${c.reset}`;
|
|
201
|
+
const durationStr = duration ? `${(duration / 1000).toFixed(1)}s` : "";
|
|
202
|
+
const costStr = cost ? `$${cost.toFixed(4)}` : "";
|
|
203
|
+
|
|
204
|
+
console.log(
|
|
205
|
+
`${prefix} ${icon} ${c.bold}Done${c.reset} ${c.dim}(${subtype}, ${numTurns} turns, ${durationStr}, ${costStr})${c.reset}`,
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
if (result) {
|
|
209
|
+
const lines = result.split("\n").filter((l) => l.trim());
|
|
210
|
+
for (const l of lines.slice(0, 3)) {
|
|
211
|
+
console.log(`${prefix} ${truncate(l, 100)}`);
|
|
212
|
+
}
|
|
213
|
+
if (lines.length > 3) {
|
|
214
|
+
console.log(`${prefix} ${c.dim}... (${lines.length - 3} more lines)${c.reset}`);
|
|
215
|
+
}
|
|
202
216
|
}
|
|
217
|
+
break;
|
|
203
218
|
}
|
|
204
|
-
break;
|
|
205
|
-
}
|
|
206
219
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
220
|
+
case "error": {
|
|
221
|
+
const error = (json.error as string) || (json.message as string) || JSON.stringify(json);
|
|
222
|
+
console.log(`${prefix} ${c.red}✗ Error:${c.reset} ${truncate(error, 100)}`);
|
|
223
|
+
break;
|
|
224
|
+
}
|
|
212
225
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
226
|
+
default: {
|
|
227
|
+
// Unknown type - print a summary
|
|
228
|
+
console.log(`${prefix} ${c.dim}[${type}]${c.reset} ${truncate(JSON.stringify(json), 100)}`);
|
|
229
|
+
}
|
|
216
230
|
}
|
|
231
|
+
} catch (_err) {
|
|
232
|
+
// Never let pretty-print crash the runner - just log raw line
|
|
233
|
+
console.log(`${c.dim}[${role}]${c.reset} ${line}`);
|
|
217
234
|
}
|
|
218
235
|
}
|
|
219
236
|
|