@hncode/hncode 0.5.0 → 0.5.2
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/bin/install.cjs +24 -44
- package/bin/preinstall.cjs +0 -14
- package/package.json +1 -1
- package/src/agent-task.js +12 -1
- package/src/agent.js +115 -35
- package/src/cell-buffer.js +5 -0
- package/src/clipboard.js +42 -6
- package/src/colors.js +478 -387
- package/src/config.js +38 -11
- package/src/context-bar.js +46 -5
- package/src/doctor.js +214 -11
- package/src/git.js +35 -4
- package/src/llm.js +6 -0
- package/src/memory-profile.js +247 -0
- package/src/permissions.js +29 -7
- package/src/registry-browser.js +21 -6
- package/src/row-cache.js +147 -142
- package/src/session-core.js +203 -0
- package/src/session-host.js +213 -0
- package/src/session-hub.js +1 -0
- package/src/session-view.js +110 -0
- package/src/settings-schema.js +304 -0
- package/src/shell-safety.js +95 -6
- package/src/swarm-progress.js +348 -293
- package/src/tasks-browser.js +4 -1
- package/src/team/constants.js +20 -0
- package/src/team/context.js +89 -0
- package/src/team/mailbox.js +139 -0
- package/src/team/runner.js +347 -0
- package/src/team/spawn.js +247 -0
- package/src/team/store.js +145 -0
- package/src/team/tasks.js +164 -0
- package/src/team/team.js +311 -0
- package/src/team-panel.js +226 -0
- package/src/term.js +27 -0
- package/src/tool-result.js +279 -236
- package/src/tools/agent-swarm.js +114 -11
- package/src/tools/agent.js +53 -3
- package/src/tools/edit.js +591 -436
- package/src/tools/git.js +15 -3
- package/src/tools/index.js +2 -0
- package/src/tools/team.js +398 -0
- package/src/tools/web-search.js +88 -75
- package/src/tui.js +6187 -4780
- package/src/update.js +285 -115
- package/src/web-daemon.js +27 -4
- package/src/web-live.js +250 -0
- package/src/web.js +3 -1
package/src/tools/agent-swarm.js
CHANGED
|
@@ -18,9 +18,25 @@ import { createTask, appendTaskOutput, settleTask } from '../agent-task.js';
|
|
|
18
18
|
|
|
19
19
|
export const PROMPT_TEMPLATE_PLACEHOLDER = '{{item}}';
|
|
20
20
|
export const MAX_AGENT_SWARM_SUBAGENTS = 128;
|
|
21
|
-
// How many subagents run at once.
|
|
22
|
-
//
|
|
23
|
-
|
|
21
|
+
// How many subagents run at once. Each worker awaits ONE subagent at a time, so this is the
|
|
22
|
+
// number of models generating concurrently — the reason a swarm exists at all, given that
|
|
23
|
+
// `Agent` calls in one message are run in sequence.
|
|
24
|
+
const SWARM_CONCURRENCY = 7;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Median of a list of numbers, or 0 when there is nothing to go on.
|
|
28
|
+
*
|
|
29
|
+
* Median rather than mean: one agent that loops for a hundred steps would drag a mean far
|
|
30
|
+
* above what a typical agent costs, and every other bar would then sit near empty while its
|
|
31
|
+
* agent was in fact nearly done.
|
|
32
|
+
*/
|
|
33
|
+
function medianSteps(counts) {
|
|
34
|
+
if (!Array.isArray(counts) || !counts.length) return 0;
|
|
35
|
+
const sorted = counts.slice().sort((a, b) => a - b);
|
|
36
|
+
const mid = Math.floor(sorted.length / 2);
|
|
37
|
+
return sorted.length % 2 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2;
|
|
38
|
+
}
|
|
39
|
+
|
|
24
40
|
|
|
25
41
|
// Validate and expand the swarm request. Returns { prompts } or { error }.
|
|
26
42
|
export function planSwarm(args) {
|
|
@@ -35,6 +51,22 @@ export function planSwarm(args) {
|
|
|
35
51
|
? Object.entries(args.resume_agent_ids).map(([id, p]) => [String(id).trim(), String(p).trim()])
|
|
36
52
|
: [];
|
|
37
53
|
|
|
54
|
+
// The SHAPE is checked before the counts, so the message names the field that is wrong. An
|
|
55
|
+
// array of ids used to be silently dropped, and the caller was then told to provide "a
|
|
56
|
+
// resume_agent_ids map" — which it had given, in the only shape it knew.
|
|
57
|
+
if (args.resume_agent_ids !== undefined
|
|
58
|
+
&& (typeof args.resume_agent_ids !== 'object' || Array.isArray(args.resume_agent_ids))) {
|
|
59
|
+
return { error: 'Error: `resume_agent_ids` must be an object mapping agent_id to prompt, such as {"<agent_id>": "continue"}. An array of ids is not accepted.' };
|
|
60
|
+
}
|
|
61
|
+
if (args.items !== undefined && !Array.isArray(args.items)) {
|
|
62
|
+
return { error: 'Error: `items` must be an array of strings.' };
|
|
63
|
+
}
|
|
64
|
+
// Blank entries were dropped above. Two blank items is a request the caller THINKS it made, and
|
|
65
|
+
// "provide at least 2 items" reads as a refusal of something that was provided.
|
|
66
|
+
const rawCount = Array.isArray(args.items) ? args.items.length : 0;
|
|
67
|
+
if (rawCount > 0 && items.length < rawCount && items.length + resumeMap.length < 2) {
|
|
68
|
+
return { error: `Error: ${rawCount} item(s) given but ${rawCount - items.length} were blank, leaving ${items.length}. Each item must be a non-empty string.` };
|
|
69
|
+
}
|
|
38
70
|
if (items.length === 0 && resumeMap.length === 0) {
|
|
39
71
|
return { error: 'Error: provide at least 2 items, or a resume_agent_ids map.' };
|
|
40
72
|
}
|
|
@@ -73,7 +105,7 @@ Use enough subagents to keep the work focused and parallel. AgentSwarm supports
|
|
|
73
105
|
When NOT to use AgentSwarm:
|
|
74
106
|
- It only exists while swarm mode is ON (\`/swarm\`). With the mode off the tool is not offered at all and calling it is refused. If a fan-out would genuinely help, say so and ask the user to turn the mode on.
|
|
75
107
|
- Never call this on your own initiative. It requires the same explicit permission as \`Agent\`: the user, or the applicable AGENTS.md / project instructions, must have asked for sub-agents, delegation, or parallel agent work. Requests for depth, thoroughness, research, investigation, or detailed codebase analysis do NOT count as permission.
|
|
76
|
-
- Do not use it for two or three differently-shaped tasks: use separate \`Agent\` calls instead
|
|
108
|
+
- Do not use it for two or three differently-shaped tasks: use separate \`Agent\` calls instead. Note that several \`Agent\` calls in one message run ONE AT A TIME — only read-only tools batch — so a swarm is the only way to overlap subagents, and a 3-item swarm really does run 3 at once.
|
|
77
109
|
- Do not use it to fan out work you could finish yourself in a few steps.
|
|
78
110
|
|
|
79
111
|
If \`AgentSwarm\` is called, that call must be the only tool call in the response.`,
|
|
@@ -122,45 +154,116 @@ If \`AgentSwarm\` is called, that call must be the only tool call in the respons
|
|
|
122
154
|
detached: false,
|
|
123
155
|
});
|
|
124
156
|
|
|
157
|
+
// ONE TASK PER SUBAGENT, parented to this one. A foreground subagent creates no task of
|
|
158
|
+
// its own, so a 7-item swarm put a single row in /tasks and there was no way to see what
|
|
159
|
+
// each subagent was doing — the panel is organised by task, so no task meant no window.
|
|
160
|
+
const subTasks = jobs.map((job, i) => {
|
|
161
|
+
const t = createTask(ctx, 'agent', {
|
|
162
|
+
description: `${description} #${i + 1}`,
|
|
163
|
+
subagentType: type,
|
|
164
|
+
model: args.model || '',
|
|
165
|
+
detached: true,
|
|
166
|
+
parentTaskId: task.taskId,
|
|
167
|
+
});
|
|
168
|
+
return t;
|
|
169
|
+
});
|
|
170
|
+
|
|
125
171
|
// Run with a bounded window. Each job appends its verdict to the task output
|
|
126
172
|
// as it finishes, so TaskOutput shows partial progress.
|
|
127
173
|
const results = new Array(jobs.length).fill(null);
|
|
174
|
+
// Tool-call counts of the agents that have FINISHED, so a running agent's bar can be
|
|
175
|
+
// scaled against what the swarm is actually costing. A running agent does not contribute
|
|
176
|
+
// here — its own count is still growing and would understate the reference.
|
|
177
|
+
const finishedSteps = [];
|
|
128
178
|
let next = 0;
|
|
129
179
|
const worker = async () => {
|
|
130
180
|
for (;;) {
|
|
131
181
|
const i = next++;
|
|
132
182
|
if (i >= jobs.length) return;
|
|
133
183
|
const job = jobs[i];
|
|
184
|
+
const subTask = subTasks[i];
|
|
185
|
+
// Announce the START, not only the finish. Without this the only signal on the wire
|
|
186
|
+
// was "[n/m] finished", which says how many are DONE and nothing about which are
|
|
187
|
+
// running — so the progress block marked everything unfinished as "queued" and a
|
|
188
|
+
// 7-wide swarm looked like one working cell and six stuck ones.
|
|
189
|
+
if (typeof ctx.onOutput === 'function') ctx.onOutput(`[#${i + 1}] started\n`);
|
|
190
|
+
appendTaskOutput(task, `[#${i + 1}] started\n`);
|
|
191
|
+
// Declared OUTSIDE the try: the catch below still has to report this agent's cost,
|
|
192
|
+
// and the block that does so runs whether the agent succeeded or threw.
|
|
193
|
+
const t0 = Date.now();
|
|
194
|
+
let steps = 0;
|
|
134
195
|
try {
|
|
135
196
|
const subArgs = {
|
|
136
197
|
prompt: job.prompt,
|
|
137
198
|
description: `${description} #${i + 1}`,
|
|
138
199
|
...(job.kind === 'resume' ? { resume: job.agentId } : { subagent_type: type }),
|
|
200
|
+
...(job.kind === 'resume' ? { resume: job.agentId } : { subagent_type: type }),
|
|
139
201
|
...(args.model ? { model: args.model } : {}),
|
|
140
202
|
...(args.fork === true && job.kind === 'new' ? { fork: true } : {}),
|
|
141
203
|
};
|
|
142
|
-
|
|
204
|
+
// `steps` counts this agent's tool calls so far. There is no "x of y" to show — a
|
|
205
|
+
// subagent runs with an unbounded step budget — so the bar is paced against the
|
|
206
|
+
// swarm's OWN observed speed: the median number of steps the agents that have
|
|
207
|
+
// already FINISHED needed. That is the only estimate available, and it sharpens as
|
|
208
|
+
// more of them land.
|
|
209
|
+
//
|
|
210
|
+
// The reference is never allowed to be 0. An agent that answers straight from the
|
|
211
|
+
// prompt runs no tools at all, so it finishes with 0 steps — and a median of 0 would
|
|
212
|
+
// send EVERY other agent down the elapsed-time branch below, where the bar needs a
|
|
213
|
+
// full minute to reach 40% and a fast agent reports 0% the whole way. One tool-less
|
|
214
|
+
// agent must not disable the scale for the rest of the swarm.
|
|
215
|
+
const progressLine = () => {
|
|
216
|
+
if (typeof ctx.onOutput !== 'function') return;
|
|
217
|
+
const reference = Math.max(1, medianSteps(finishedSteps));
|
|
218
|
+
const ratio = finishedSteps.length
|
|
219
|
+
? Math.min(0.95, steps / (reference + 1))
|
|
220
|
+
// Nothing has finished yet, so there is no pace to compare against. Creep
|
|
221
|
+
// rather than assert a figure: `steps` alone would read as a tiny percentage
|
|
222
|
+
// next to an unknown total.
|
|
223
|
+
: Math.min(0.4, steps / 40, 0.1 + ((Date.now() - t0) / 60000) * 0.3);
|
|
224
|
+
ctx.onOutput(`[#${i + 1}] progress ${Math.round(ratio * 100)}\n`);
|
|
225
|
+
};
|
|
226
|
+
const out = await agentSpec.execute(subArgs, {
|
|
227
|
+
...ctx,
|
|
228
|
+
_progressTag: `#${i + 1}`,
|
|
229
|
+
_subTaskId: subTask.taskId,
|
|
230
|
+
_onSubProgress: () => { steps++; progressLine(); },
|
|
231
|
+
});
|
|
143
232
|
// `agentSpec.execute` returns `agent_id: <id>\n\n<handoff>`. The swarm's
|
|
144
233
|
// caller wants the handoff, and the id is only useful for a later
|
|
145
234
|
// `resume`; keep it on one labelled line rather than letting it lead the
|
|
146
235
|
// block, so the reported text is what each subagent actually produced.
|
|
147
236
|
const raw = String(out);
|
|
148
237
|
const m = /^agent_id:\s*(\S+)\s*\n+([\s\S]*)$/.exec(raw);
|
|
238
|
+
// A subagent that died returns `Error: subagent … failed: …` rather than throwing, so
|
|
239
|
+
// the catch below never sees it. Without this test the cell was marked a success and
|
|
240
|
+
// the failure text was buried in a body nobody reads as an error.
|
|
241
|
+
const died = /^Error: subagent /.test(raw);
|
|
149
242
|
const body = (m ? m[2] : raw).trim();
|
|
150
243
|
const head = `--- #${i + 1}${job.kind === 'resume' ? ` (resumed ${job.agentId})` : ''}${m ? ` [${m[1]}]` : ''} ---`;
|
|
151
244
|
results[i] = `${head}\n${body}`;
|
|
245
|
+
settleTask(subTask, died ? 'failed' : 'completed', died ? { stopReason: body } : undefined);
|
|
152
246
|
} catch (e) {
|
|
153
247
|
results[i] = `--- #${i + 1} ---\n[error: ${e.message}]`;
|
|
248
|
+
settleTask(subTask, 'failed', { stopReason: e.message });
|
|
154
249
|
}
|
|
250
|
+
// Contribute this agent's cost to the reference the running ones scale against. A
|
|
251
|
+
// Contribute this agent's cost to the reference the running ones scale against. A
|
|
252
|
+
// failed agent is recorded too: it did that much work, so it says as much about the
|
|
253
|
+
// pace as a successful one, and excluding it would bias the bar optimistically.
|
|
254
|
+
finishedSteps.push(steps);
|
|
155
255
|
// Report progress down BOTH channels, and they are not interchangeable:
|
|
156
256
|
// * the task buffer feeds TaskOutput and survives a detach;
|
|
157
|
-
// * `ctx.onOutput` is the LIVE sink
|
|
158
|
-
//
|
|
159
|
-
//
|
|
160
|
-
//
|
|
161
|
-
//
|
|
257
|
+
// * `ctx.onOutput` is the LIVE sink the transcript's progress block reads.
|
|
258
|
+
//
|
|
259
|
+
// WHICH agent finished, not just how many. A bare `[n/m]` count left the reader no
|
|
260
|
+
// choice but to assume the first `n` agents were the finished ones — and with
|
|
261
|
+
// SWARM_CONCURRENCY workers pulling from a shared queue, they finish in whatever order
|
|
262
|
+
// they happen to run. The grid then lit up the wrong cells and only agreed with
|
|
263
|
+
// reality once every one of them was done. `[#i] finished` names the agent; the count
|
|
264
|
+
// is kept for readers that only track totals.
|
|
162
265
|
const done = results.filter((r) => r !== null).length;
|
|
163
|
-
const line = `[${done}/${jobs.length}]
|
|
266
|
+
const line = `[#${i + 1}] finished [${done}/${jobs.length}]\n`;
|
|
164
267
|
appendTaskOutput(task, line);
|
|
165
268
|
if (typeof ctx.onOutput === 'function') ctx.onOutput(line);
|
|
166
269
|
}
|
package/src/tools/agent.js
CHANGED
|
@@ -54,7 +54,7 @@ function systemPromptFor(type, cfg, systemPrompt) {
|
|
|
54
54
|
// stream, and each tool it runs) so the TUI can show it under the `Using Agent`
|
|
55
55
|
// row the way Bash's output is shown. `agentId` labels the lines when several
|
|
56
56
|
// subagents run at once (AgentSwarm), so the reader can tell them apart.
|
|
57
|
-
async function runSubagent({ cfg, type, prompt, history, signal, onEvent, onProgress, agentId, onAgent }) {
|
|
57
|
+
async function runSubagent({ cfg, type, prompt, history, signal, onEvent, onProgress, onStep, agentId, onAgent }) {
|
|
58
58
|
const { Agent, SYSTEM_PROMPT } = await import('../agent.js');
|
|
59
59
|
const subCfg = {
|
|
60
60
|
...cfg,
|
|
@@ -72,6 +72,9 @@ async function runSubagent({ cfg, type, prompt, history, signal, onEvent, onProg
|
|
|
72
72
|
: [{ role: 'user', content: prompt }];
|
|
73
73
|
|
|
74
74
|
let text = '';
|
|
75
|
+
// A failure used to leave `text` empty, so a caller could not tell "produced nothing" from
|
|
76
|
+
// "died". The Agent's only signal on error is an event, so capture it here.
|
|
77
|
+
let failure = null;
|
|
75
78
|
const agent = new Agent({
|
|
76
79
|
cfg: subCfg,
|
|
77
80
|
messages,
|
|
@@ -87,12 +90,19 @@ async function runSubagent({ cfg, type, prompt, history, signal, onEvent, onProg
|
|
|
87
90
|
if (e.text) onProgress(tag + e.text);
|
|
88
91
|
} else if (e.type === 'tool_start') {
|
|
89
92
|
onProgress(`\n${tag}\u25b8 ${e.name}\n`);
|
|
93
|
+
// One more round of work done. A SUBAGENT has no fixed step budget (agent.js sets
|
|
94
|
+
// `stepLimit = Infinity` unless the caller bounds it), so "steps remaining" is not
|
|
95
|
+
// a real quantity and a percentage of it would be invented. Tool calls completed is
|
|
96
|
+
// the honest measure: it only ever grows, it is what the run is actually doing, and
|
|
97
|
+
// the swarm turns it into a bar against the pace its other agents have set.
|
|
98
|
+
if (typeof onStep === 'function') onStep();
|
|
90
99
|
} else if (e.type === 'tool_result') {
|
|
91
100
|
const body = String(e.content == null ? '' : e.content);
|
|
92
101
|
const first = body.split('\n').slice(0, 3).join('\n');
|
|
93
102
|
onProgress(`${tag}${first}${body.split('\n').length > 3 ? '\n\u2026' : ''}\n`);
|
|
94
103
|
} else if (e.type === 'error') {
|
|
95
104
|
onProgress(`\n${tag}[error] ${(e.error && e.error.message) || String(e.error)}\n`);
|
|
105
|
+
failure = (e.error && e.error.message) || String(e.error);
|
|
96
106
|
}
|
|
97
107
|
}
|
|
98
108
|
if (onEvent) onEvent(e);
|
|
@@ -139,7 +149,10 @@ async function runSubagent({ cfg, type, prompt, history, signal, onEvent, onProg
|
|
|
139
149
|
const lastAssistant = [...agent.messages].reverse()
|
|
140
150
|
.find((m) => m.role === 'assistant' && typeof m.content === 'string' && stripThink(m.content));
|
|
141
151
|
const result = (lastAssistant && stripThink(lastAssistant.content)) || stripThink(text) || '(no output)';
|
|
142
|
-
|
|
152
|
+
// The failure travels WITH the text. A run that died and a run that simply produced nothing
|
|
153
|
+
// both used to arrive as an empty/`(no output)` string, so the caller could not tell them apart
|
|
154
|
+
// and reported a dead agent as a successful empty answer.
|
|
155
|
+
return { text: result, messages: agent.messages, failed: failure };
|
|
143
156
|
}
|
|
144
157
|
|
|
145
158
|
export const spec = {
|
|
@@ -206,7 +219,7 @@ ${availableTypes()}`,
|
|
|
206
219
|
|
|
207
220
|
// ---- model override ---------------------------------------------------
|
|
208
221
|
// Precedence: the call's own `model` > the user's configured subagent model
|
|
209
|
-
// (/
|
|
222
|
+
// (/subagent-model) > the session's model. 'primary' always means "this
|
|
210
223
|
// session's model", so an explicit primary beats a configured default.
|
|
211
224
|
let subCfg = ctx;
|
|
212
225
|
const wantPrimary = args.model === 'primary';
|
|
@@ -214,6 +227,26 @@ ${availableTypes()}`,
|
|
|
214
227
|
? args.model : '';
|
|
215
228
|
const configured = ctx.subagentModel && ctx.raw && ctx.raw.models && ctx.raw.models[ctx.subagentModel]
|
|
216
229
|
? ctx.subagentModel : '';
|
|
230
|
+
// A NAME THAT DOES NOT EXIST IS AN ERROR, not a fallback. Resolving it to "" ran the
|
|
231
|
+
// subagent on the session's model while the caller believed it had chosen — a typo, or a
|
|
232
|
+
// model configured in another session, produced a silently different agent.
|
|
233
|
+
if (args.model && !wantPrimary && !explicit) {
|
|
234
|
+
const known = Object.keys((ctx.raw && ctx.raw.models) || {});
|
|
235
|
+
const list = known.length ? known.join(', ') : 'none';
|
|
236
|
+
return `Error: unknown model "${args.model}". Configured: ${list}. Use "primary" for the session model.`;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// The user's `/subagent-model` setting can go stale when a model is renamed or removed, and
|
|
240
|
+
// it used to fail the same silent way. Say so, through onOutput so it reaches the transcript.
|
|
241
|
+
if (ctx.subagentModel && !configured) {
|
|
242
|
+
const list = Object.keys((ctx.raw && ctx.raw.models) || {}).join(', ') || 'none';
|
|
243
|
+
try {
|
|
244
|
+
if (typeof ctx.onOutput === 'function') {
|
|
245
|
+
ctx.onOutput(`[subagent model] "${ctx.subagentModel}" is no longer configured, so this subagent runs on the session model. Re-pick it with /swarm-sub-agent. Available: ${list}\n`);
|
|
246
|
+
}
|
|
247
|
+
} catch { /* a view that cannot be written to is not a reason to fail the call */ }
|
|
248
|
+
}
|
|
249
|
+
|
|
217
250
|
const chosen = wantPrimary ? '' : (explicit || configured);
|
|
218
251
|
if (chosen) {
|
|
219
252
|
const { resolveModelArg } = await import('../config.js');
|
|
@@ -316,11 +349,23 @@ ${availableTypes()}`,
|
|
|
316
349
|
// `_progressTag` is set by AgentSwarm to the item index, so a swarm's
|
|
317
350
|
// concurrently-running subagents are distinguishable in the live stream.
|
|
318
351
|
// A plain Agent call has no tag and prints its stream unlabelled.
|
|
352
|
+
//
|
|
353
|
+
// `_subTaskId` is also set by AgentSwarm: it names the task THIS subagent owns, so its
|
|
354
|
+
// stream lands on its own row in /tasks instead of being folded into the swarm's single
|
|
355
|
+
// buffer. Without it a 7-item swarm had one task and no way to see what any of them was
|
|
356
|
+
// doing.
|
|
319
357
|
onProgress: (text) => {
|
|
358
|
+
if (ctx._subTaskId) {
|
|
359
|
+
const own = ctx.tasks && ctx.tasks[ctx._subTaskId];
|
|
360
|
+
if (own) appendTaskOutput(own, text);
|
|
361
|
+
}
|
|
320
362
|
if (typeof ctx.onOutput !== 'function') return;
|
|
321
363
|
const tag = ctx._progressTag ? `[${ctx._progressTag}] ` : '';
|
|
322
364
|
ctx.onOutput(tag ? text.split('\n').map((l) => (l ? tag + l : l)).join('\n') : text);
|
|
323
365
|
},
|
|
366
|
+
// Each completed tool call is one unit of progress. Only AgentSwarm supplies the sink;
|
|
367
|
+
// a lone Agent call has no grid to fill and nothing to report to.
|
|
368
|
+
onStep: () => { if (typeof ctx._onSubProgress === 'function') ctx._onSubProgress(); },
|
|
324
369
|
});
|
|
325
370
|
// Registered here so a foreground subagent is reachable by AgentMessage / AgentInterrupt
|
|
326
371
|
// too — that is precisely the case where a user wants to redirect one: it is the run
|
|
@@ -382,6 +427,11 @@ ${availableTypes()}`,
|
|
|
382
427
|
entry.type = type;
|
|
383
428
|
subagents.set(agentId, entry);
|
|
384
429
|
|
|
430
|
+
// A FAILED subagent returns an error, not an empty string. `run.text` is '' when the Agent
|
|
431
|
+
// died, which the swarm printed as "(no output)" — so four agents that had all failed on one
|
|
432
|
+
// error looked like four agents that had found nothing to say.
|
|
433
|
+
if (run.failed) return `Error: subagent "${agentId}" failed: ${run.failed}`;
|
|
434
|
+
|
|
385
435
|
return `agent_id: ${agentId}\n\n${run.text}`;
|
|
386
436
|
},
|
|
387
437
|
};
|