iosm-cli 0.2.0 → 0.2.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/CHANGELOG.md +53 -0
- package/README.md +64 -52
- package/dist/core/agent-teams.d.ts.map +1 -1
- package/dist/core/agent-teams.js +38 -11
- package/dist/core/agent-teams.js.map +1 -1
- package/dist/core/failure-retrospective.d.ts +12 -0
- package/dist/core/failure-retrospective.d.ts.map +1 -0
- package/dist/core/failure-retrospective.js +115 -0
- package/dist/core/failure-retrospective.js.map +1 -0
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +2 -3
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/models-dev-provider-catalog.d.ts +30 -0
- package/dist/core/models-dev-provider-catalog.d.ts.map +1 -0
- package/dist/core/models-dev-provider-catalog.js +118 -0
- package/dist/core/models-dev-provider-catalog.js.map +1 -0
- package/dist/core/models-dev-providers.d.ts +12 -0
- package/dist/core/models-dev-providers.d.ts.map +1 -0
- package/dist/core/models-dev-providers.js +736 -0
- package/dist/core/models-dev-providers.js.map +1 -0
- package/dist/core/project-index/index.d.ts +17 -0
- package/dist/core/project-index/index.d.ts.map +1 -0
- package/dist/core/project-index/index.js +323 -0
- package/dist/core/project-index/index.js.map +1 -0
- package/dist/core/project-index/types.d.ts +34 -0
- package/dist/core/project-index/types.d.ts.map +1 -0
- package/dist/core/project-index/types.js +2 -0
- package/dist/core/project-index/types.js.map +1 -0
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +8 -0
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/shared-memory.d.ts +46 -0
- package/dist/core/shared-memory.d.ts.map +1 -0
- package/dist/core/shared-memory.js +253 -0
- package/dist/core/shared-memory.js.map +1 -0
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +5 -1
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/core/subagents.js +1 -1
- package/dist/core/subagents.js.map +1 -1
- package/dist/core/swarm/gates.d.ts +9 -0
- package/dist/core/swarm/gates.d.ts.map +1 -0
- package/dist/core/swarm/gates.js +65 -0
- package/dist/core/swarm/gates.js.map +1 -0
- package/dist/core/swarm/index.d.ts +9 -0
- package/dist/core/swarm/index.d.ts.map +1 -0
- package/dist/core/swarm/index.js +9 -0
- package/dist/core/swarm/index.js.map +1 -0
- package/dist/core/swarm/locks.d.ts +21 -0
- package/dist/core/swarm/locks.d.ts.map +1 -0
- package/dist/core/swarm/locks.js +93 -0
- package/dist/core/swarm/locks.js.map +1 -0
- package/dist/core/swarm/planner.d.ts +16 -0
- package/dist/core/swarm/planner.d.ts.map +1 -0
- package/dist/core/swarm/planner.js +137 -0
- package/dist/core/swarm/planner.js.map +1 -0
- package/dist/core/swarm/retry.d.ts +16 -0
- package/dist/core/swarm/retry.d.ts.map +1 -0
- package/dist/core/swarm/retry.js +32 -0
- package/dist/core/swarm/retry.js.map +1 -0
- package/dist/core/swarm/scheduler.d.ts +48 -0
- package/dist/core/swarm/scheduler.d.ts.map +1 -0
- package/dist/core/swarm/scheduler.js +554 -0
- package/dist/core/swarm/scheduler.js.map +1 -0
- package/dist/core/swarm/spawn.d.ts +16 -0
- package/dist/core/swarm/spawn.d.ts.map +1 -0
- package/dist/core/swarm/spawn.js +42 -0
- package/dist/core/swarm/spawn.js.map +1 -0
- package/dist/core/swarm/state-store.d.ts +35 -0
- package/dist/core/swarm/state-store.d.ts.map +1 -0
- package/dist/core/swarm/state-store.js +106 -0
- package/dist/core/swarm/state-store.js.map +1 -0
- package/dist/core/swarm/types.d.ts +116 -0
- package/dist/core/swarm/types.d.ts.map +1 -0
- package/dist/core/swarm/types.js +2 -0
- package/dist/core/swarm/types.js.map +1 -0
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +3 -2
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/tools/shared-memory.d.ts +23 -0
- package/dist/core/tools/shared-memory.d.ts.map +1 -0
- package/dist/core/tools/shared-memory.js +134 -0
- package/dist/core/tools/shared-memory.js.map +1 -0
- package/dist/core/tools/task.d.ts +8 -1
- package/dist/core/tools/task.d.ts.map +1 -1
- package/dist/core/tools/task.js +664 -123
- package/dist/core/tools/task.js.map +1 -1
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +3 -11
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/components/login-dialog.d.ts +1 -0
- package/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
- package/dist/modes/interactive/components/login-dialog.js +27 -4
- package/dist/modes/interactive/components/login-dialog.js.map +1 -1
- package/dist/modes/interactive/components/oauth-selector.d.ts +13 -1
- package/dist/modes/interactive/components/oauth-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/oauth-selector.js +89 -27
- package/dist/modes/interactive/components/oauth-selector.js.map +1 -1
- package/dist/modes/interactive/components/subagent-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/subagent-message.js +14 -0
- package/dist/modes/interactive/components/subagent-message.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +50 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +1594 -51
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/docs/cli-reference.md +11 -1
- package/docs/configuration.md +4 -1
- package/docs/getting-started.md +2 -2
- package/docs/interactive-mode.md +43 -4
- package/docs/orchestration-and-subagents.md +96 -169
- package/package.json +5 -4
package/dist/core/tools/task.js
CHANGED
|
@@ -3,6 +3,7 @@ import * as path from "node:path";
|
|
|
3
3
|
import { spawnSync } from "node:child_process";
|
|
4
4
|
import { Type } from "@sinclair/typebox";
|
|
5
5
|
import { getTeamRun, updateTeamTaskStatus } from "../agent-teams.js";
|
|
6
|
+
import { buildRetrospectiveDirective, classifyFailureCause, formatFailureCauseCounts, isRetrospectiveRetryable, } from "../failure-retrospective.js";
|
|
6
7
|
const taskSchema = Type.Object({
|
|
7
8
|
description: Type.String({
|
|
8
9
|
description: "Short 3-5 word description of what the subagent will do",
|
|
@@ -13,16 +14,8 @@ const taskSchema = Type.Object({
|
|
|
13
14
|
agent: Type.Optional(Type.String({
|
|
14
15
|
description: "Optional custom subagent name loaded from .iosm/agents or global agents directory.",
|
|
15
16
|
})),
|
|
16
|
-
profile: Type.
|
|
17
|
-
|
|
18
|
-
Type.Literal("plan"),
|
|
19
|
-
Type.Literal("iosm"),
|
|
20
|
-
Type.Literal("iosm_analyst"),
|
|
21
|
-
Type.Literal("iosm_verifier"),
|
|
22
|
-
Type.Literal("cycle_planner"),
|
|
23
|
-
Type.Literal("full"),
|
|
24
|
-
], {
|
|
25
|
-
description: "Subagent capability profile: explore (read-only), plan (read + bash, no edits), iosm (full tools + IOSM methodology), iosm_analyst (read + bash for IOSM artifacts), iosm_verifier (artifact-focused checks), cycle_planner (cycle planning), full (all tools)",
|
|
17
|
+
profile: Type.String({
|
|
18
|
+
description: "Subagent capability profile. Recommended values: explore, plan, iosm, iosm_analyst, iosm_verifier, cycle_planner, full. For custom agents, pass the agent name via `agent`, not `profile`.",
|
|
26
19
|
}),
|
|
27
20
|
cwd: Type.Optional(Type.String({
|
|
28
21
|
description: "Optional working directory for this subagent. Relative paths are resolved from the current workspace.",
|
|
@@ -31,10 +24,10 @@ const taskSchema = Type.Object({
|
|
|
31
24
|
description: "Optional logical lock key for write serialization (e.g. src/api/**). Agents with the same lock key run write phases sequentially.",
|
|
32
25
|
})),
|
|
33
26
|
run_id: Type.Optional(Type.String({
|
|
34
|
-
description: "Optional orchestration run id (from /orchestrate). Use with task_id so the team board can track status.",
|
|
27
|
+
description: "Optional orchestration run id (from /orchestrate or /swarm). Use with task_id so the team board can track status. When omitted, task mode uses an internal run id for shared-memory collaboration within this task execution.",
|
|
35
28
|
})),
|
|
36
29
|
task_id: Type.Optional(Type.String({
|
|
37
|
-
description: "Optional orchestration task id (for example task_1). Use with run_id to update the team board.",
|
|
30
|
+
description: "Optional orchestration task id (for example task_1). Use with run_id to update the team board. When omitted, task mode uses an internal task id so task-scoped shared memory still works.",
|
|
38
31
|
})),
|
|
39
32
|
model: Type.Optional(Type.String({
|
|
40
33
|
description: "Optional model override for this subagent (for example anthropic/claude-sonnet-4 or model id).",
|
|
@@ -45,6 +38,11 @@ const taskSchema = Type.Object({
|
|
|
45
38
|
isolation: Type.Optional(Type.Union([Type.Literal("none"), Type.Literal("worktree")], {
|
|
46
39
|
description: "Optional isolation mode. Set to worktree to run this subagent in a temporary git worktree.",
|
|
47
40
|
})),
|
|
41
|
+
delegate_parallel_hint: Type.Optional(Type.Integer({
|
|
42
|
+
minimum: 1,
|
|
43
|
+
maximum: 10,
|
|
44
|
+
description: "Optional hint for intra-task delegation fan-out. Higher value allows more delegated subtasks to run in parallel inside a single task execution.",
|
|
45
|
+
})),
|
|
48
46
|
});
|
|
49
47
|
/** Tool names available per profile */
|
|
50
48
|
const toolsByProfile = {
|
|
@@ -94,6 +92,9 @@ class Semaphore {
|
|
|
94
92
|
next();
|
|
95
93
|
}
|
|
96
94
|
}
|
|
95
|
+
isIdle() {
|
|
96
|
+
return this.active === 0 && this.queue.length === 0;
|
|
97
|
+
}
|
|
97
98
|
}
|
|
98
99
|
class Mutex {
|
|
99
100
|
constructor() {
|
|
@@ -116,12 +117,19 @@ class Mutex {
|
|
|
116
117
|
next();
|
|
117
118
|
}
|
|
118
119
|
}
|
|
120
|
+
isIdle() {
|
|
121
|
+
return !this.locked && this.waiters.length === 0;
|
|
122
|
+
}
|
|
119
123
|
}
|
|
120
|
-
const maxParallelFromEnv =
|
|
121
|
-
const subagentSemaphore = new Semaphore(
|
|
124
|
+
const maxParallelFromEnv = parseBoundedInt(process.env.IOSM_SUBAGENT_MAX_PARALLEL, 20, 1, 20);
|
|
125
|
+
const subagentSemaphore = new Semaphore(maxParallelFromEnv);
|
|
122
126
|
const maxDelegationDepthFromEnv = parseBoundedInt(process.env.IOSM_SUBAGENT_MAX_DELEGATION_DEPTH, 1, 0, 3);
|
|
123
|
-
const maxDelegationsPerTaskFromEnv = parseBoundedInt(process.env.IOSM_SUBAGENT_MAX_DELEGATIONS_PER_TASK,
|
|
124
|
-
const maxDelegatedParallelFromEnv = parseBoundedInt(process.env.IOSM_SUBAGENT_MAX_DELEGATE_PARALLEL,
|
|
127
|
+
const maxDelegationsPerTaskFromEnv = parseBoundedInt(process.env.IOSM_SUBAGENT_MAX_DELEGATIONS_PER_TASK, 10, 0, 10);
|
|
128
|
+
const maxDelegatedParallelFromEnv = parseBoundedInt(process.env.IOSM_SUBAGENT_MAX_DELEGATE_PARALLEL, 10, 1, 10);
|
|
129
|
+
const emptyOutputRetriesFromEnv = parseBoundedInt(process.env.IOSM_SUBAGENT_EMPTY_OUTPUT_RETRIES, 1, 0, 2);
|
|
130
|
+
const retrospectiveRetriesFromEnv = parseBoundedInt(process.env.IOSM_SUBAGENT_RETRO_RETRIES, 1, 0, 1);
|
|
131
|
+
const orchestrationDependencyWaitTimeoutMsFromEnv = parseBoundedInt(process.env.IOSM_ORCHESTRATION_DEPENDENCY_WAIT_TIMEOUT_MS, 120_000, 5_000, 900_000);
|
|
132
|
+
const orchestrationDependencyPollMsFromEnv = parseBoundedInt(process.env.IOSM_ORCHESTRATION_DEPENDENCY_POLL_MS, 150, 50, 2_000);
|
|
125
133
|
const maxDelegatedOutputCharsFromEnv = parseBoundedInt(process.env.IOSM_SUBAGENT_DELEGATED_OUTPUT_MAX_CHARS, 6000, 500, 20_000);
|
|
126
134
|
const maxMetaUpdatesPerCheckpoint = parseBoundedInt(process.env.IOSM_SUBAGENT_META_MAX_ITEMS, 5, 1, 20);
|
|
127
135
|
const maxMetaUpdateChars = parseBoundedInt(process.env.IOSM_SUBAGENT_META_MAX_CHARS, 600, 100, 4000);
|
|
@@ -133,6 +141,65 @@ function parseBoundedInt(raw, fallback, min, max) {
|
|
|
133
141
|
return fallback;
|
|
134
142
|
return Math.max(min, Math.min(max, parsed));
|
|
135
143
|
}
|
|
144
|
+
function shouldAutoDelegateByAgent(agentName) {
|
|
145
|
+
if (!agentName)
|
|
146
|
+
return false;
|
|
147
|
+
const normalized = agentName.trim().toLowerCase();
|
|
148
|
+
return normalized.includes("orchestrator");
|
|
149
|
+
}
|
|
150
|
+
function deriveAutoDelegateParallelHint(agentName, description, prompt) {
|
|
151
|
+
if (!shouldAutoDelegateByAgent(agentName))
|
|
152
|
+
return undefined;
|
|
153
|
+
const text = `${description}\n${prompt}`.trim();
|
|
154
|
+
if (!text)
|
|
155
|
+
return 1;
|
|
156
|
+
const normalized = text.replace(/\s+/g, " ").trim();
|
|
157
|
+
const words = normalized.length > 0 ? normalized.split(/\s+/).length : 0;
|
|
158
|
+
const clauses = normalized
|
|
159
|
+
.split(/[.;:,\n]+/g)
|
|
160
|
+
.map((item) => item.trim())
|
|
161
|
+
.filter((item) => item.length > 0).length;
|
|
162
|
+
const pathLikeMatches = normalized.match(/\b(?:[A-Za-z0-9_.-]+\/)+[A-Za-z0-9_.-]+\b/g) ?? [];
|
|
163
|
+
const fileLikeMatches = normalized.match(/\b[A-Za-z0-9_.-]+\.[A-Za-z0-9]{1,8}\b/g) ?? [];
|
|
164
|
+
const listMarkers = text.match(/(?:^|\n)\s*(?:[-*]|\d+[.)])\s+/g)?.length ?? 0;
|
|
165
|
+
const hasCodeBlock = text.includes("```");
|
|
166
|
+
let score = 0;
|
|
167
|
+
if (words >= 40) {
|
|
168
|
+
score += 2;
|
|
169
|
+
}
|
|
170
|
+
else if (words >= 20) {
|
|
171
|
+
score += 1;
|
|
172
|
+
}
|
|
173
|
+
if (clauses >= 5) {
|
|
174
|
+
score += 2;
|
|
175
|
+
}
|
|
176
|
+
else if (clauses >= 3) {
|
|
177
|
+
score += 1;
|
|
178
|
+
}
|
|
179
|
+
if (listMarkers >= 2) {
|
|
180
|
+
score += 1;
|
|
181
|
+
}
|
|
182
|
+
const referenceCount = pathLikeMatches.length + fileLikeMatches.length;
|
|
183
|
+
if (referenceCount >= 3 || (referenceCount >= 1 && words >= 20)) {
|
|
184
|
+
score += 1;
|
|
185
|
+
}
|
|
186
|
+
if (hasCodeBlock) {
|
|
187
|
+
score += 1;
|
|
188
|
+
}
|
|
189
|
+
if (score >= 6)
|
|
190
|
+
return 10;
|
|
191
|
+
if (score >= 5)
|
|
192
|
+
return 8;
|
|
193
|
+
if (score >= 4)
|
|
194
|
+
return 6;
|
|
195
|
+
if (score >= 3)
|
|
196
|
+
return 4;
|
|
197
|
+
if (score >= 2)
|
|
198
|
+
return 3;
|
|
199
|
+
if (score >= 1)
|
|
200
|
+
return 2;
|
|
201
|
+
return 1;
|
|
202
|
+
}
|
|
136
203
|
function normalizeSpacing(text) {
|
|
137
204
|
return text.replace(/[ \t]+\n/g, "\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
138
205
|
}
|
|
@@ -172,7 +239,16 @@ function isAbortError(error) {
|
|
|
172
239
|
}
|
|
173
240
|
return false;
|
|
174
241
|
}
|
|
175
|
-
function
|
|
242
|
+
function mergeRunStats(base, next) {
|
|
243
|
+
if (!base && !next)
|
|
244
|
+
return undefined;
|
|
245
|
+
return {
|
|
246
|
+
toolCallsStarted: (base?.toolCallsStarted ?? 0) + (next?.toolCallsStarted ?? 0),
|
|
247
|
+
toolCallsCompleted: (base?.toolCallsCompleted ?? 0) + (next?.toolCallsCompleted ?? 0),
|
|
248
|
+
assistantMessages: (base?.assistantMessages ?? 0) + (next?.assistantMessages ?? 0),
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
function buildDelegationProtocolPrompt(depthRemaining, maxDelegations) {
|
|
176
252
|
if (depthRemaining <= 0) {
|
|
177
253
|
return [
|
|
178
254
|
`Delegation protocol: depth limit reached.`,
|
|
@@ -180,17 +256,31 @@ function buildDelegationProtocolPrompt(depthRemaining) {
|
|
|
180
256
|
].join("\n");
|
|
181
257
|
}
|
|
182
258
|
return [
|
|
183
|
-
`Delegation protocol (optional): if you discover
|
|
184
|
-
`<${delegationTagName} profile="explore|plan|iosm|iosm_analyst|iosm_verifier|cycle_planner|full" description="short title" cwd="optional relative path" lock_key="optional lock key" model="optional model override" isolation="none|worktree" depends_on="optional indices like 1|3">`,
|
|
259
|
+
`Delegation protocol (optional): if you discover concrete independent follow-ups, emit up to ${maxDelegations} XML block(s):`,
|
|
260
|
+
`<${delegationTagName} profile="explore|plan|iosm|iosm_analyst|iosm_verifier|cycle_planner|full" agent="optional custom subagent name" description="short title" cwd="optional relative path" lock_key="optional lock key" model="optional model override" isolation="none|worktree" depends_on="optional indices like 1|3">`,
|
|
185
261
|
"Detailed delegated task prompt",
|
|
186
262
|
`</${delegationTagName}>`,
|
|
187
263
|
`Only emit blocks when necessary. Keep normal analysis/answer text outside those blocks.`,
|
|
264
|
+
`When shared_memory tools are available, exchange intermediate state through shared_memory_write/shared_memory_read instead of repeating large context.`,
|
|
188
265
|
].join("\n");
|
|
189
266
|
}
|
|
190
|
-
function withDelegationPrompt(basePrompt, depthRemaining) {
|
|
191
|
-
const protocol = buildDelegationProtocolPrompt(depthRemaining);
|
|
267
|
+
function withDelegationPrompt(basePrompt, depthRemaining, maxDelegations) {
|
|
268
|
+
const protocol = buildDelegationProtocolPrompt(depthRemaining, maxDelegations);
|
|
192
269
|
return `${basePrompt}\n\n${protocol}`;
|
|
193
270
|
}
|
|
271
|
+
function buildSharedMemoryGuidance(runId, taskId) {
|
|
272
|
+
return [
|
|
273
|
+
"[SHARED_MEMORY]",
|
|
274
|
+
`run_id: ${runId}`,
|
|
275
|
+
`task_id: ${taskId ?? "(none)"}`,
|
|
276
|
+
"Use shared_memory_write/shared_memory_read to exchange intermediate state across parallel agents and delegates.",
|
|
277
|
+
"Guidelines:",
|
|
278
|
+
"- Use scope=run for cross-agent data and scope=task for task-local notes.",
|
|
279
|
+
"- Keep entries compact and key-based (for example: findings/auth, plan/step-1, risks/session).",
|
|
280
|
+
"- Read before overwrite when collaborating on the same key.",
|
|
281
|
+
"[/SHARED_MEMORY]",
|
|
282
|
+
].join("\n");
|
|
283
|
+
}
|
|
194
284
|
function parseDelegationRequests(output, maxRequests) {
|
|
195
285
|
const requests = [];
|
|
196
286
|
const warnings = [];
|
|
@@ -227,6 +317,7 @@ function parseDelegationRequests(output, maxRequests) {
|
|
|
227
317
|
requests.push({
|
|
228
318
|
description: (attrs.description ?? `delegated task ${requests.length + 1}`).trim(),
|
|
229
319
|
profile,
|
|
320
|
+
agent: attrs.agent?.trim() || undefined,
|
|
230
321
|
prompt,
|
|
231
322
|
cwd: attrs.cwd?.trim() || undefined,
|
|
232
323
|
lockKey: attrs.lock_key?.trim() || undefined,
|
|
@@ -260,6 +351,15 @@ function getOrCreateWriteLock(cwd) {
|
|
|
260
351
|
cwdWriteLocks.set(key, created);
|
|
261
352
|
return created;
|
|
262
353
|
}
|
|
354
|
+
function cleanupWriteLock(lockKey) {
|
|
355
|
+
if (!lockKey)
|
|
356
|
+
return;
|
|
357
|
+
const key = getCwdLockKey(lockKey);
|
|
358
|
+
const existing = cwdWriteLocks.get(key);
|
|
359
|
+
if (!existing || !existing.isIdle())
|
|
360
|
+
return;
|
|
361
|
+
cwdWriteLocks.delete(key);
|
|
362
|
+
}
|
|
263
363
|
function getRunParallelLimit(cwd, runId) {
|
|
264
364
|
const teamRun = getTeamRun(cwd, runId);
|
|
265
365
|
if (!teamRun)
|
|
@@ -284,6 +384,91 @@ function getOrCreateOrchestrationSemaphore(cwd, runId) {
|
|
|
284
384
|
orchestrationSemaphores.set(key, created);
|
|
285
385
|
return created;
|
|
286
386
|
}
|
|
387
|
+
function isTeamTaskTerminal(status) {
|
|
388
|
+
return status === "done" || status === "error" || status === "cancelled";
|
|
389
|
+
}
|
|
390
|
+
function cleanupOrchestrationSemaphore(cwd, runId) {
|
|
391
|
+
const prefix = `${path.resolve(cwd).toLowerCase()}::${runId}::`;
|
|
392
|
+
const run = getTeamRun(cwd, runId);
|
|
393
|
+
const canDeleteForRun = !run || run.tasks.every((task) => isTeamTaskTerminal(task.status));
|
|
394
|
+
if (!canDeleteForRun)
|
|
395
|
+
return;
|
|
396
|
+
for (const [key, semaphore] of orchestrationSemaphores.entries()) {
|
|
397
|
+
if (!key.startsWith(prefix))
|
|
398
|
+
continue;
|
|
399
|
+
if (!semaphore.isIdle())
|
|
400
|
+
continue;
|
|
401
|
+
orchestrationSemaphores.delete(key);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
function waitForWithAbort(ms, signal) {
|
|
405
|
+
if (!signal) {
|
|
406
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
407
|
+
}
|
|
408
|
+
if (signal.aborted) {
|
|
409
|
+
return Promise.reject(new Error("Operation aborted"));
|
|
410
|
+
}
|
|
411
|
+
return new Promise((resolve, reject) => {
|
|
412
|
+
const timer = setTimeout(() => {
|
|
413
|
+
signal.removeEventListener("abort", onAbort);
|
|
414
|
+
resolve();
|
|
415
|
+
}, ms);
|
|
416
|
+
const onAbort = () => {
|
|
417
|
+
clearTimeout(timer);
|
|
418
|
+
signal.removeEventListener("abort", onAbort);
|
|
419
|
+
reject(new Error("Operation aborted"));
|
|
420
|
+
};
|
|
421
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
async function waitForOrchestrationDependencies(input) {
|
|
425
|
+
const started = Date.now();
|
|
426
|
+
let lastWaiting = "";
|
|
427
|
+
while (true) {
|
|
428
|
+
if (input.signal?.aborted) {
|
|
429
|
+
throw new Error("Operation aborted");
|
|
430
|
+
}
|
|
431
|
+
const run = getTeamRun(input.cwd, input.runId);
|
|
432
|
+
if (!run) {
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
const current = run.tasks.find((task) => task.id === input.taskId);
|
|
436
|
+
if (!current) {
|
|
437
|
+
throw new Error(`Orchestration metadata missing task ${input.taskId} in run ${input.runId}.`);
|
|
438
|
+
}
|
|
439
|
+
const dependencies = current.dependsOn ?? [];
|
|
440
|
+
if (dependencies.length === 0) {
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
const dependencyTasks = dependencies.map((id) => run.tasks.find((task) => task.id === id));
|
|
444
|
+
const missing = dependencyTasks
|
|
445
|
+
.map((task, index) => (task ? undefined : dependencies[index]))
|
|
446
|
+
.filter((value) => typeof value === "string");
|
|
447
|
+
if (missing.length > 0) {
|
|
448
|
+
throw new Error(`Orchestration metadata invalid for ${input.taskId}: missing dependency task(s) ${missing.join(", ")}.`);
|
|
449
|
+
}
|
|
450
|
+
const failed = dependencyTasks.filter((task) => !!task && (task.status === "error" || task.status === "cancelled"));
|
|
451
|
+
if (failed.length > 0) {
|
|
452
|
+
throw new Error(`Blocked by failed dependency: ${failed.map((task) => `${task.id}=${task.status}`).join(", ")}.`);
|
|
453
|
+
}
|
|
454
|
+
const pending = dependencyTasks.filter((task) => !!task && task.status !== "done");
|
|
455
|
+
if (pending.length === 0) {
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
const waitedMs = Date.now() - started;
|
|
459
|
+
if (waitedMs >= orchestrationDependencyWaitTimeoutMsFromEnv) {
|
|
460
|
+
throw new Error(`Timed out waiting for dependencies of ${input.taskId}: ${pending
|
|
461
|
+
.map((task) => `${task.id}=${task.status}`)
|
|
462
|
+
.join(", ")}.`);
|
|
463
|
+
}
|
|
464
|
+
const waiting = pending.map((task) => `${task.id}=${task.status}`).join(", ");
|
|
465
|
+
if (waiting !== lastWaiting) {
|
|
466
|
+
lastWaiting = waiting;
|
|
467
|
+
input.onWaiting?.(waiting);
|
|
468
|
+
}
|
|
469
|
+
await waitForWithAbort(orchestrationDependencyPollMsFromEnv, input.signal);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
287
472
|
function persistSubagentTranscript(input) {
|
|
288
473
|
try {
|
|
289
474
|
const dir = path.join(input.rootCwd, ".iosm", "subagents", "runs");
|
|
@@ -408,21 +593,54 @@ export function createTaskTool(cwd, runner, options) {
|
|
|
408
593
|
"It may request bounded follow-up delegation via <delegate_task> blocks that are executed by the parent task tool." +
|
|
409
594
|
customAgentsSnippet,
|
|
410
595
|
parameters: taskSchema,
|
|
411
|
-
execute: async (_toolCallId, { description, prompt, agent: agentName, profile, cwd: targetCwd, lock_key: lockKey, run_id: orchestrationRunId, task_id: orchestrationTaskId, model: requestedModel, background, isolation, }, _signal, onUpdate) => {
|
|
596
|
+
execute: async (_toolCallId, { description, prompt, agent: agentName, profile, cwd: targetCwd, lock_key: lockKey, run_id: orchestrationRunId, task_id: orchestrationTaskId, model: requestedModel, background, isolation, delegate_parallel_hint: delegateParallelHint, }, _signal, onUpdate) => {
|
|
597
|
+
const updateTrackedTaskStatus = (status) => {
|
|
598
|
+
if (!orchestrationRunId || !orchestrationTaskId)
|
|
599
|
+
return;
|
|
600
|
+
updateTeamTaskStatus({
|
|
601
|
+
cwd,
|
|
602
|
+
runId: orchestrationRunId,
|
|
603
|
+
taskId: orchestrationTaskId,
|
|
604
|
+
status,
|
|
605
|
+
});
|
|
606
|
+
};
|
|
412
607
|
const throwIfAborted = () => {
|
|
413
608
|
if (_signal?.aborted) {
|
|
609
|
+
updateTrackedTaskStatus("cancelled");
|
|
414
610
|
throw new Error("Operation aborted");
|
|
415
611
|
}
|
|
416
612
|
};
|
|
417
613
|
const runId = `subagent_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
|
|
418
|
-
const
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
614
|
+
const sharedMemoryRunId = orchestrationRunId?.trim() || runId;
|
|
615
|
+
const sharedMemoryTaskId = orchestrationTaskId?.trim() || runId;
|
|
616
|
+
const availableCustomNames = options?.availableCustomSubagents ?? [];
|
|
617
|
+
const resolveCustom = (name) => {
|
|
618
|
+
if (!name || !options?.resolveCustomSubagent)
|
|
619
|
+
return undefined;
|
|
620
|
+
return options.resolveCustomSubagent(name);
|
|
621
|
+
};
|
|
622
|
+
let normalizedAgentName = agentName?.trim() || undefined;
|
|
623
|
+
let normalizedProfile = profile.trim().toLowerCase();
|
|
624
|
+
if (!normalizedProfile)
|
|
625
|
+
normalizedProfile = "full";
|
|
626
|
+
let customSubagent = resolveCustom(normalizedAgentName);
|
|
627
|
+
if (normalizedAgentName && !customSubagent) {
|
|
628
|
+
const available = availableCustomNames.length > 0 ? ` Available custom agents: ${availableCustomNames.join(", ")}.` : "";
|
|
629
|
+
throw new Error(`Unknown subagent: ${normalizedAgentName}.${available}`);
|
|
424
630
|
}
|
|
425
|
-
|
|
631
|
+
// Recovery path: if model placed a custom agent name into `profile`, remap automatically.
|
|
632
|
+
if (!customSubagent) {
|
|
633
|
+
const profileAsAgent = resolveCustom(normalizedProfile);
|
|
634
|
+
if (profileAsAgent) {
|
|
635
|
+
customSubagent = profileAsAgent;
|
|
636
|
+
normalizedAgentName = profileAsAgent.name;
|
|
637
|
+
normalizedProfile = (profileAsAgent.profile ?? "full").trim().toLowerCase();
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
if (!toolsByProfile[normalizedProfile]) {
|
|
641
|
+
normalizedProfile = "full";
|
|
642
|
+
}
|
|
643
|
+
const effectiveProfile = customSubagent?.profile ?? normalizedProfile;
|
|
426
644
|
let tools = customSubagent?.tools
|
|
427
645
|
? [...customSubagent.tools]
|
|
428
646
|
: [...(toolsByProfile[effectiveProfile] ?? toolsByProfile.explore)];
|
|
@@ -431,10 +649,22 @@ export function createTaskTool(cwd, runner, options) {
|
|
|
431
649
|
tools = tools.filter((tool) => !blocked.has(tool));
|
|
432
650
|
}
|
|
433
651
|
const delegationDepth = maxDelegationDepthFromEnv;
|
|
652
|
+
const requestedDelegateParallelHint = typeof delegateParallelHint === "number" && Number.isInteger(delegateParallelHint)
|
|
653
|
+
? Math.max(1, Math.min(10, delegateParallelHint))
|
|
654
|
+
: undefined;
|
|
655
|
+
const autoDelegateParallelHint = requestedDelegateParallelHint === undefined
|
|
656
|
+
? deriveAutoDelegateParallelHint(normalizedAgentName, description, prompt)
|
|
657
|
+
: undefined;
|
|
658
|
+
const effectiveDelegateParallelHint = requestedDelegateParallelHint ?? autoDelegateParallelHint;
|
|
659
|
+
const effectiveMaxDelegations = Math.max(0, Math.min(maxDelegationsPerTaskFromEnv, effectiveDelegateParallelHint ?? maxDelegationsPerTaskFromEnv));
|
|
660
|
+
const effectiveMaxDelegateParallel = Math.max(1, Math.min(maxDelegatedParallelFromEnv, effectiveDelegateParallelHint ?? maxDelegatedParallelFromEnv));
|
|
661
|
+
const minDelegationsPreferred = (effectiveDelegateParallelHint ?? 0) >= 2 && effectiveMaxDelegations >= 2
|
|
662
|
+
? Math.min(2, effectiveMaxDelegations)
|
|
663
|
+
: 0;
|
|
434
664
|
const baseSystemPrompt = customSubagent?.systemPrompt ??
|
|
435
665
|
systemPromptByProfile[effectiveProfile] ??
|
|
436
666
|
systemPromptByProfile.full;
|
|
437
|
-
const systemPrompt = withDelegationPrompt(baseSystemPrompt, delegationDepth);
|
|
667
|
+
const systemPrompt = withDelegationPrompt(baseSystemPrompt, delegationDepth, effectiveMaxDelegations);
|
|
438
668
|
const promptWithInstructions = customSubagent?.instructions && customSubagent.instructions.trim().length > 0
|
|
439
669
|
? `${customSubagent.instructions.trim()}\n\nUser task:\n${prompt}`
|
|
440
670
|
: prompt;
|
|
@@ -503,11 +733,57 @@ export function createTaskTool(cwd, runner, options) {
|
|
|
503
733
|
let releaseSlot;
|
|
504
734
|
let releaseWriteLock;
|
|
505
735
|
let releaseIsolation;
|
|
736
|
+
const explicitRootLockKey = lockKey?.trim();
|
|
506
737
|
let subagentCwd = requestedSubagentCwd;
|
|
507
738
|
let worktreePath;
|
|
508
739
|
let runStats;
|
|
509
740
|
try {
|
|
510
741
|
throwIfAborted();
|
|
742
|
+
if (orchestrationRunId && orchestrationTaskId) {
|
|
743
|
+
try {
|
|
744
|
+
await waitForOrchestrationDependencies({
|
|
745
|
+
cwd,
|
|
746
|
+
runId: orchestrationRunId,
|
|
747
|
+
taskId: orchestrationTaskId,
|
|
748
|
+
signal: _signal,
|
|
749
|
+
onWaiting: (waiting) => {
|
|
750
|
+
emitProgress({
|
|
751
|
+
kind: "subagent_progress",
|
|
752
|
+
phase: "queued",
|
|
753
|
+
message: `waiting for dependencies: ${waiting}`,
|
|
754
|
+
cwd: requestedSubagentCwd,
|
|
755
|
+
activeTool: undefined,
|
|
756
|
+
});
|
|
757
|
+
},
|
|
758
|
+
});
|
|
759
|
+
}
|
|
760
|
+
catch (error) {
|
|
761
|
+
if (_signal?.aborted || isAbortError(error)) {
|
|
762
|
+
updateTrackedTaskStatus("cancelled");
|
|
763
|
+
throw new Error("Operation aborted");
|
|
764
|
+
}
|
|
765
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
766
|
+
const cause = classifyFailureCause(message);
|
|
767
|
+
updateTrackedTaskStatus("error");
|
|
768
|
+
const details = {
|
|
769
|
+
profile: effectiveProfile,
|
|
770
|
+
description,
|
|
771
|
+
outputLength: 0,
|
|
772
|
+
cwd: requestedSubagentCwd,
|
|
773
|
+
agent: customSubagent?.name,
|
|
774
|
+
lockKey: lockKey?.trim() || undefined,
|
|
775
|
+
runId,
|
|
776
|
+
taskId: orchestrationTaskId,
|
|
777
|
+
model: effectiveModelOverride,
|
|
778
|
+
isolation: useWorktree ? "worktree" : "none",
|
|
779
|
+
worktreePath,
|
|
780
|
+
waitMs: Date.now() - queuedAt,
|
|
781
|
+
background: runInBackground,
|
|
782
|
+
failureCauses: { [cause]: 1 },
|
|
783
|
+
};
|
|
784
|
+
throw Object.assign(new Error(`Subagent failed: ${message}`), { details });
|
|
785
|
+
}
|
|
786
|
+
}
|
|
511
787
|
const orchestrationSemaphore = orchestrationRunId && orchestrationTaskId
|
|
512
788
|
? getOrCreateOrchestrationSemaphore(cwd, orchestrationRunId)
|
|
513
789
|
: undefined;
|
|
@@ -517,20 +793,12 @@ export function createTaskTool(cwd, runner, options) {
|
|
|
517
793
|
}
|
|
518
794
|
releaseSlot = await subagentSemaphore.acquire();
|
|
519
795
|
throwIfAborted();
|
|
520
|
-
|
|
521
|
-
updateTeamTaskStatus({
|
|
522
|
-
cwd,
|
|
523
|
-
runId: orchestrationRunId,
|
|
524
|
-
taskId: orchestrationTaskId,
|
|
525
|
-
status: "running",
|
|
526
|
-
});
|
|
527
|
-
}
|
|
796
|
+
updateTrackedTaskStatus("running");
|
|
528
797
|
if (writeCapableProfiles.has(effectiveProfile)) {
|
|
529
|
-
const explicitLockKey = lockKey?.trim();
|
|
530
798
|
// Parallel orchestration should remain truly parallel by default.
|
|
531
799
|
// Serialize write-capable agents only when an explicit lock_key is provided.
|
|
532
|
-
if (
|
|
533
|
-
const lock = getOrCreateWriteLock(
|
|
800
|
+
if (explicitRootLockKey) {
|
|
801
|
+
const lock = getOrCreateWriteLock(explicitRootLockKey);
|
|
534
802
|
releaseWriteLock = await lock.acquire();
|
|
535
803
|
}
|
|
536
804
|
}
|
|
@@ -552,18 +820,115 @@ export function createTaskTool(cwd, runner, options) {
|
|
|
552
820
|
let delegatedTasks = 0;
|
|
553
821
|
let delegatedSucceeded = 0;
|
|
554
822
|
let delegatedFailed = 0;
|
|
823
|
+
let retrospectiveAttempts = 0;
|
|
824
|
+
let retrospectiveRecovered = 0;
|
|
555
825
|
const delegationWarnings = [];
|
|
556
826
|
const delegatedSections = [];
|
|
827
|
+
const failureCauses = {};
|
|
557
828
|
const delegatedStats = {
|
|
558
829
|
toolCallsStarted: 0,
|
|
559
830
|
toolCallsCompleted: 0,
|
|
560
831
|
assistantMessages: 0,
|
|
561
832
|
};
|
|
833
|
+
const recordFailureCause = (cause) => {
|
|
834
|
+
failureCauses[cause] = (failureCauses[cause] ?? 0) + 1;
|
|
835
|
+
};
|
|
836
|
+
const rootSharedMemoryContext = {
|
|
837
|
+
rootCwd: cwd,
|
|
838
|
+
runId: sharedMemoryRunId,
|
|
839
|
+
taskId: sharedMemoryTaskId,
|
|
840
|
+
profile: effectiveProfile,
|
|
841
|
+
};
|
|
562
842
|
try {
|
|
843
|
+
const runRootPass = async (runPrompt) => {
|
|
844
|
+
let emptyAttempt = 0;
|
|
845
|
+
let retrospectiveAttempt = 0;
|
|
846
|
+
let mergedStats;
|
|
847
|
+
let sessionId;
|
|
848
|
+
let promptForAttempt = runPrompt;
|
|
849
|
+
while (true) {
|
|
850
|
+
try {
|
|
851
|
+
const result = await runner({
|
|
852
|
+
systemPrompt,
|
|
853
|
+
tools,
|
|
854
|
+
prompt: promptForAttempt,
|
|
855
|
+
cwd: subagentCwd,
|
|
856
|
+
modelOverride: effectiveModelOverride,
|
|
857
|
+
sharedMemoryContext: rootSharedMemoryContext,
|
|
858
|
+
signal: _signal,
|
|
859
|
+
onProgress: (progress) => emitProgress(progress),
|
|
860
|
+
});
|
|
861
|
+
throwIfAborted();
|
|
862
|
+
let attemptOutput;
|
|
863
|
+
let attemptStats;
|
|
864
|
+
if (typeof result === "string") {
|
|
865
|
+
attemptOutput = result;
|
|
866
|
+
}
|
|
867
|
+
else {
|
|
868
|
+
attemptOutput = result.output;
|
|
869
|
+
attemptStats = result.stats;
|
|
870
|
+
sessionId = result.sessionId ?? sessionId;
|
|
871
|
+
}
|
|
872
|
+
mergedStats = mergeRunStats(mergedStats, attemptStats);
|
|
873
|
+
if (attemptOutput.trim().length > 0) {
|
|
874
|
+
if (retrospectiveAttempt > 0) {
|
|
875
|
+
retrospectiveRecovered += 1;
|
|
876
|
+
}
|
|
877
|
+
return {
|
|
878
|
+
output: attemptOutput,
|
|
879
|
+
sessionId,
|
|
880
|
+
stats: mergedStats,
|
|
881
|
+
};
|
|
882
|
+
}
|
|
883
|
+
if (emptyAttempt >= emptyOutputRetriesFromEnv) {
|
|
884
|
+
const totalAttempts = emptyAttempt + 1;
|
|
885
|
+
throw new Error(`Subagent returned empty output after ${totalAttempts} attempt${totalAttempts === 1 ? "" : "s"}.`);
|
|
886
|
+
}
|
|
887
|
+
emptyAttempt += 1;
|
|
888
|
+
emitProgress({
|
|
889
|
+
kind: "subagent_progress",
|
|
890
|
+
phase: "running",
|
|
891
|
+
message: `root subagent returned empty output; retry ${emptyAttempt}/${emptyOutputRetriesFromEnv}`,
|
|
892
|
+
cwd: subagentCwd,
|
|
893
|
+
activeTool: undefined,
|
|
894
|
+
});
|
|
895
|
+
}
|
|
896
|
+
catch (error) {
|
|
897
|
+
if (_signal?.aborted || isAbortError(error)) {
|
|
898
|
+
throw new Error("Operation aborted");
|
|
899
|
+
}
|
|
900
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
901
|
+
const cause = classifyFailureCause(message);
|
|
902
|
+
recordFailureCause(cause);
|
|
903
|
+
const canRetryRetrospective = retrospectiveAttempt < retrospectiveRetriesFromEnv && isRetrospectiveRetryable(cause);
|
|
904
|
+
if (!canRetryRetrospective) {
|
|
905
|
+
throw Object.assign(new Error(message), { failureCause: cause });
|
|
906
|
+
}
|
|
907
|
+
retrospectiveAttempt += 1;
|
|
908
|
+
retrospectiveAttempts += 1;
|
|
909
|
+
const directive = buildRetrospectiveDirective({
|
|
910
|
+
cause,
|
|
911
|
+
errorMessage: message,
|
|
912
|
+
attempt: retrospectiveAttempt,
|
|
913
|
+
target: "root",
|
|
914
|
+
});
|
|
915
|
+
promptForAttempt = `${runPrompt}\n\n${directive}`;
|
|
916
|
+
emitProgress({
|
|
917
|
+
kind: "subagent_progress",
|
|
918
|
+
phase: "running",
|
|
919
|
+
message: `root retrospective retry ${retrospectiveAttempt}/${retrospectiveRetriesFromEnv} (${cause})`,
|
|
920
|
+
cwd: subagentCwd,
|
|
921
|
+
activeTool: undefined,
|
|
922
|
+
});
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
};
|
|
563
926
|
const rootMeta = formatMetaCheckpoint(options?.getMetaMessages?.());
|
|
927
|
+
const rootSharedMemoryGuidance = buildSharedMemoryGuidance(sharedMemoryRunId, sharedMemoryTaskId);
|
|
928
|
+
const rootPromptBase = `${promptWithInstructions}\n\n${rootSharedMemoryGuidance}`;
|
|
564
929
|
const rootPrompt = rootMeta.section && rootMeta.appliedCount > 0
|
|
565
|
-
? `${
|
|
566
|
-
:
|
|
930
|
+
? `${rootPromptBase}\n\n${rootMeta.section}`
|
|
931
|
+
: rootPromptBase;
|
|
567
932
|
if (rootMeta.appliedCount > 0) {
|
|
568
933
|
emitProgress({
|
|
569
934
|
kind: "subagent_progress",
|
|
@@ -573,25 +938,39 @@ export function createTaskTool(cwd, runner, options) {
|
|
|
573
938
|
activeTool: undefined,
|
|
574
939
|
});
|
|
575
940
|
}
|
|
576
|
-
const
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
941
|
+
const firstPass = await runRootPass(rootPrompt);
|
|
942
|
+
output = firstPass.output;
|
|
943
|
+
subagentSessionId = firstPass.sessionId;
|
|
944
|
+
runStats = firstPass.stats;
|
|
945
|
+
let parsedDelegation = parseDelegationRequests(output, delegationDepth > 0 ? effectiveMaxDelegations : 0);
|
|
946
|
+
if (minDelegationsPreferred > 0 && parsedDelegation.requests.length < minDelegationsPreferred) {
|
|
947
|
+
emitProgress({
|
|
948
|
+
kind: "subagent_progress",
|
|
949
|
+
phase: "running",
|
|
950
|
+
message: `delegation preference unmet (${parsedDelegation.requests.length}/${minDelegationsPreferred}), retrying with stronger split guidance`,
|
|
951
|
+
cwd: subagentCwd,
|
|
952
|
+
activeTool: undefined,
|
|
953
|
+
});
|
|
954
|
+
const enforcedPrompt = [
|
|
955
|
+
rootPrompt,
|
|
956
|
+
"",
|
|
957
|
+
"[DELEGATION_ENFORCEMENT]",
|
|
958
|
+
`Prefer emitting at least ${minDelegationsPreferred} <delegate_task> blocks for independent sub-work when beneficial.`,
|
|
959
|
+
`Target parallel fan-out: up to ${effectiveMaxDelegateParallel}.`,
|
|
960
|
+
"If decomposition is not beneficial, you may keep single-agent execution and optionally output one line:",
|
|
961
|
+
"DELEGATION_IMPOSSIBLE: <reason>",
|
|
962
|
+
"[/DELEGATION_ENFORCEMENT]",
|
|
963
|
+
].join("\n");
|
|
964
|
+
const secondPass = await runRootPass(enforcedPrompt);
|
|
965
|
+
output = secondPass.output;
|
|
966
|
+
subagentSessionId = secondPass.sessionId ?? subagentSessionId;
|
|
967
|
+
runStats = secondPass.stats ?? runStats;
|
|
968
|
+
parsedDelegation = parseDelegationRequests(output, delegationDepth > 0 ? effectiveMaxDelegations : 0);
|
|
588
969
|
}
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
runStats = result.stats;
|
|
970
|
+
if (minDelegationsPreferred > 0 && parsedDelegation.requests.length < minDelegationsPreferred) {
|
|
971
|
+
const impossibleReason = output.match(/^\s*DELEGATION_IMPOSSIBLE\s*:\s*(.+)$/im)?.[1]?.trim() ?? "not provided";
|
|
972
|
+
delegationWarnings.push(`Delegation fallback: kept single-agent execution (preferred >=${minDelegationsPreferred} delegates, got ${parsedDelegation.requests.length}). Reason: ${impossibleReason}.`);
|
|
593
973
|
}
|
|
594
|
-
const parsedDelegation = parseDelegationRequests(output, delegationDepth > 0 ? maxDelegationsPerTaskFromEnv : 0);
|
|
595
974
|
output = parsedDelegation.cleanedOutput;
|
|
596
975
|
delegationWarnings.push(...parsedDelegation.warnings);
|
|
597
976
|
const delegateTotal = parsedDelegation.requests.length;
|
|
@@ -619,7 +998,7 @@ export function createTaskTool(cwd, runner, options) {
|
|
|
619
998
|
emitProgress({
|
|
620
999
|
kind: "subagent_progress",
|
|
621
1000
|
phase: "running",
|
|
622
|
-
message: `delegation scheduler: ${delegateTotal} task(s), max parallel ${Math.min(delegateTotal,
|
|
1001
|
+
message: `delegation scheduler: ${delegateTotal} task(s), max parallel ${Math.min(delegateTotal, effectiveMaxDelegateParallel)}`,
|
|
623
1002
|
cwd: subagentCwd,
|
|
624
1003
|
activeTool: undefined,
|
|
625
1004
|
delegateTotal,
|
|
@@ -628,19 +1007,23 @@ export function createTaskTool(cwd, runner, options) {
|
|
|
628
1007
|
}
|
|
629
1008
|
const pendingIndices = new Set(Array.from({ length: delegateTotal }, (_v, i) => i));
|
|
630
1009
|
const runningDelegates = new Map();
|
|
631
|
-
const maxDelegateParallel = Math.max(1, Math.min(delegateTotal || 1,
|
|
1010
|
+
const maxDelegateParallel = Math.max(1, Math.min(delegateTotal || 1, effectiveMaxDelegateParallel));
|
|
632
1011
|
const statusOf = (idx) => delegateItems[idx]?.status ?? "pending";
|
|
633
|
-
const
|
|
1012
|
+
const formatDelegateTarget = (request) => {
|
|
1013
|
+
const agent = request.agent?.trim();
|
|
1014
|
+
return agent ? `${agent}/${request.profile}` : request.profile;
|
|
1015
|
+
};
|
|
1016
|
+
const markDelegateFailed = (index, message, details, cause) => {
|
|
634
1017
|
const request = parsedDelegation.requests[index];
|
|
635
1018
|
if (delegateItems[index]) {
|
|
636
1019
|
delegateItems[index].status = "failed";
|
|
637
1020
|
}
|
|
638
1021
|
delegatedFailed += 1;
|
|
639
1022
|
if (details) {
|
|
640
|
-
delegationWarnings.push(details);
|
|
1023
|
+
delegationWarnings.push(cause ? `${details} [cause=${cause}]` : details);
|
|
641
1024
|
}
|
|
642
|
-
|
|
643
|
-
|
|
1025
|
+
const causeLabel = cause ? ` [cause=${cause}]` : "";
|
|
1026
|
+
delegatedSections[index] = `#### ${index + 1}. ${request.description} (${formatDelegateTarget(request)})\nERROR${causeLabel}: ${message}`;
|
|
644
1027
|
emitProgress({
|
|
645
1028
|
kind: "subagent_progress",
|
|
646
1029
|
phase: "running",
|
|
@@ -657,7 +1040,19 @@ export function createTaskTool(cwd, runner, options) {
|
|
|
657
1040
|
const runDelegate = async (index) => {
|
|
658
1041
|
throwIfAborted();
|
|
659
1042
|
const request = parsedDelegation.requests[index];
|
|
660
|
-
const
|
|
1043
|
+
const requestedChildAgent = request.agent?.trim() || undefined;
|
|
1044
|
+
const childCustomSubagent = resolveCustom(requestedChildAgent);
|
|
1045
|
+
if (requestedChildAgent && !childCustomSubagent) {
|
|
1046
|
+
delegationWarnings.push(`Delegated task "${request.description}" requested unknown agent "${requestedChildAgent}". Falling back to profile "${request.profile}".`);
|
|
1047
|
+
}
|
|
1048
|
+
const childProfileRaw = childCustomSubagent?.profile ?? request.profile;
|
|
1049
|
+
const normalizedChildProfile = childProfileRaw.trim().toLowerCase();
|
|
1050
|
+
const childProfile = normalizedChildProfile && toolsByProfile[normalizedChildProfile]
|
|
1051
|
+
? normalizedChildProfile
|
|
1052
|
+
: "full";
|
|
1053
|
+
const childProfileLabel = childCustomSubagent?.name
|
|
1054
|
+
? `${childCustomSubagent.name}/${childProfile}`
|
|
1055
|
+
: childProfile;
|
|
661
1056
|
if (delegateItems[index]) {
|
|
662
1057
|
delegateItems[index].status = "running";
|
|
663
1058
|
}
|
|
@@ -670,23 +1065,34 @@ export function createTaskTool(cwd, runner, options) {
|
|
|
670
1065
|
delegateIndex: index + 1,
|
|
671
1066
|
delegateTotal,
|
|
672
1067
|
delegateDescription: request.description,
|
|
673
|
-
delegateProfile:
|
|
1068
|
+
delegateProfile: childProfileLabel,
|
|
674
1069
|
delegateItems,
|
|
675
1070
|
});
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
1071
|
+
let childTools = childCustomSubagent?.tools
|
|
1072
|
+
? [...childCustomSubagent.tools]
|
|
1073
|
+
: [...(toolsByProfile[childProfile] ?? toolsByProfile.explore)];
|
|
1074
|
+
if (childCustomSubagent?.disallowedTools?.length) {
|
|
1075
|
+
const blocked = new Set(childCustomSubagent.disallowedTools);
|
|
1076
|
+
childTools = childTools.filter((tool) => !blocked.has(tool));
|
|
1077
|
+
}
|
|
1078
|
+
const childBaseSystemPrompt = childCustomSubagent?.systemPrompt ??
|
|
1079
|
+
systemPromptByProfile[childProfile] ??
|
|
1080
|
+
systemPromptByProfile.full;
|
|
1081
|
+
const childSystemPrompt = withDelegationPrompt(childBaseSystemPrompt, Math.max(0, delegationDepth - 1), effectiveMaxDelegations);
|
|
1082
|
+
const requestedChildCwd = request.cwd
|
|
1083
|
+
? path.resolve(subagentCwd, request.cwd)
|
|
1084
|
+
: childCustomSubagent?.cwd ?? subagentCwd;
|
|
680
1085
|
if (!existsSync(requestedChildCwd) || !statSync(requestedChildCwd).isDirectory()) {
|
|
681
|
-
|
|
1086
|
+
recordFailureCause("dependency_env");
|
|
1087
|
+
markDelegateFailed(index, `delegate ${index + 1}/${delegateTotal} skipped: missing cwd`, `Delegated task "${request.description}" skipped: cwd does not exist (${requestedChildCwd}).`, "dependency_env");
|
|
682
1088
|
return;
|
|
683
1089
|
}
|
|
684
1090
|
let childReleaseLock;
|
|
685
1091
|
let childReleaseIsolation;
|
|
1092
|
+
const explicitChildLock = request.lockKey?.trim();
|
|
686
1093
|
let childCwd = requestedChildCwd;
|
|
687
1094
|
try {
|
|
688
1095
|
throwIfAborted();
|
|
689
|
-
const explicitChildLock = request.lockKey?.trim();
|
|
690
1096
|
if (writeCapableProfiles.has(childProfile) && explicitChildLock) {
|
|
691
1097
|
const lock = getOrCreateWriteLock(explicitChildLock);
|
|
692
1098
|
childReleaseLock = await lock.acquire();
|
|
@@ -698,9 +1104,14 @@ export function createTaskTool(cwd, runner, options) {
|
|
|
698
1104
|
childReleaseIsolation = isolated.cleanup;
|
|
699
1105
|
}
|
|
700
1106
|
const delegateMeta = formatMetaCheckpoint(options?.getMetaMessages?.());
|
|
701
|
-
const
|
|
702
|
-
? `${
|
|
1107
|
+
const childPromptWithInstructions = childCustomSubagent?.instructions && childCustomSubagent.instructions.trim().length > 0
|
|
1108
|
+
? `${childCustomSubagent.instructions.trim()}\n\nUser task:\n${request.prompt}`
|
|
703
1109
|
: request.prompt;
|
|
1110
|
+
const delegateSharedMemoryGuidance = buildSharedMemoryGuidance(sharedMemoryRunId, sharedMemoryTaskId);
|
|
1111
|
+
const delegatePromptBase = `${childPromptWithInstructions}\n\n${delegateSharedMemoryGuidance}`;
|
|
1112
|
+
const delegatePrompt = delegateMeta.section && delegateMeta.appliedCount > 0
|
|
1113
|
+
? `${delegatePromptBase}\n\n${delegateMeta.section}`
|
|
1114
|
+
: delegatePromptBase;
|
|
704
1115
|
if (delegateMeta.appliedCount > 0) {
|
|
705
1116
|
emitProgress({
|
|
706
1117
|
kind: "subagent_progress",
|
|
@@ -711,41 +1122,118 @@ export function createTaskTool(cwd, runner, options) {
|
|
|
711
1122
|
delegateIndex: index + 1,
|
|
712
1123
|
delegateTotal,
|
|
713
1124
|
delegateDescription: request.description,
|
|
714
|
-
delegateProfile:
|
|
1125
|
+
delegateProfile: childProfileLabel,
|
|
715
1126
|
delegateItems,
|
|
716
1127
|
});
|
|
717
1128
|
}
|
|
718
|
-
const
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
1129
|
+
const childModelOverride = request.model?.trim() || childCustomSubagent?.model?.trim() || undefined;
|
|
1130
|
+
const childSharedMemoryContext = {
|
|
1131
|
+
rootCwd: cwd,
|
|
1132
|
+
runId: sharedMemoryRunId,
|
|
1133
|
+
taskId: sharedMemoryTaskId,
|
|
1134
|
+
delegateId: String(index + 1),
|
|
1135
|
+
profile: childProfile,
|
|
1136
|
+
};
|
|
1137
|
+
let childOutput = "";
|
|
1138
|
+
let childStats;
|
|
1139
|
+
let childEmptyAttempt = 0;
|
|
1140
|
+
let childRetrospectiveAttempt = 0;
|
|
1141
|
+
let childPromptForAttempt = delegatePrompt;
|
|
1142
|
+
while (true) {
|
|
1143
|
+
try {
|
|
1144
|
+
const childResult = await runner({
|
|
1145
|
+
systemPrompt: childSystemPrompt,
|
|
1146
|
+
tools: childTools,
|
|
1147
|
+
prompt: childPromptForAttempt,
|
|
1148
|
+
cwd: childCwd,
|
|
1149
|
+
modelOverride: childModelOverride,
|
|
1150
|
+
sharedMemoryContext: childSharedMemoryContext,
|
|
1151
|
+
signal: _signal,
|
|
1152
|
+
onProgress: (progress) => {
|
|
1153
|
+
emitProgress({
|
|
1154
|
+
kind: "subagent_progress",
|
|
1155
|
+
phase: "running",
|
|
1156
|
+
message: `delegate ${index + 1}/${delegateTotal}: ${progress.message}`,
|
|
1157
|
+
cwd: progress.cwd ?? childCwd,
|
|
1158
|
+
activeTool: progress.activeTool,
|
|
1159
|
+
delegateIndex: index + 1,
|
|
1160
|
+
delegateTotal,
|
|
1161
|
+
delegateDescription: request.description,
|
|
1162
|
+
delegateProfile: childProfileLabel,
|
|
1163
|
+
delegateItems,
|
|
1164
|
+
});
|
|
1165
|
+
},
|
|
1166
|
+
});
|
|
1167
|
+
throwIfAborted();
|
|
1168
|
+
let attemptOutput;
|
|
1169
|
+
let attemptStats;
|
|
1170
|
+
if (typeof childResult === "string") {
|
|
1171
|
+
attemptOutput = childResult;
|
|
1172
|
+
}
|
|
1173
|
+
else {
|
|
1174
|
+
attemptOutput = childResult.output;
|
|
1175
|
+
attemptStats = childResult.stats;
|
|
1176
|
+
}
|
|
1177
|
+
childStats = mergeRunStats(childStats, attemptStats);
|
|
1178
|
+
if (attemptOutput.trim().length > 0) {
|
|
1179
|
+
childOutput = attemptOutput;
|
|
1180
|
+
if (childRetrospectiveAttempt > 0) {
|
|
1181
|
+
retrospectiveRecovered += 1;
|
|
1182
|
+
}
|
|
1183
|
+
break;
|
|
1184
|
+
}
|
|
1185
|
+
if (childEmptyAttempt >= emptyOutputRetriesFromEnv) {
|
|
1186
|
+
const totalAttempts = childEmptyAttempt + 1;
|
|
1187
|
+
throw new Error(`delegate ${index + 1}/${delegateTotal} returned empty output after ${totalAttempts} attempt${totalAttempts === 1 ? "" : "s"}.`);
|
|
1188
|
+
}
|
|
1189
|
+
childEmptyAttempt += 1;
|
|
726
1190
|
emitProgress({
|
|
727
1191
|
kind: "subagent_progress",
|
|
728
1192
|
phase: "running",
|
|
729
|
-
message: `delegate ${index + 1}/${delegateTotal}: ${
|
|
730
|
-
cwd:
|
|
731
|
-
activeTool:
|
|
1193
|
+
message: `delegate ${index + 1}/${delegateTotal}: empty output, retry ${childEmptyAttempt}/${emptyOutputRetriesFromEnv}`,
|
|
1194
|
+
cwd: childCwd,
|
|
1195
|
+
activeTool: undefined,
|
|
732
1196
|
delegateIndex: index + 1,
|
|
733
1197
|
delegateTotal,
|
|
734
1198
|
delegateDescription: request.description,
|
|
735
|
-
delegateProfile:
|
|
1199
|
+
delegateProfile: childProfileLabel,
|
|
736
1200
|
delegateItems,
|
|
737
1201
|
});
|
|
738
|
-
}
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
1202
|
+
}
|
|
1203
|
+
catch (error) {
|
|
1204
|
+
if (_signal?.aborted || isAbortError(error)) {
|
|
1205
|
+
throw new Error("Operation aborted");
|
|
1206
|
+
}
|
|
1207
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1208
|
+
const cause = classifyFailureCause(message);
|
|
1209
|
+
recordFailureCause(cause);
|
|
1210
|
+
const canRetryRetrospective = childRetrospectiveAttempt < retrospectiveRetriesFromEnv &&
|
|
1211
|
+
isRetrospectiveRetryable(cause);
|
|
1212
|
+
if (!canRetryRetrospective) {
|
|
1213
|
+
throw Object.assign(new Error(message), { failureCause: cause });
|
|
1214
|
+
}
|
|
1215
|
+
childRetrospectiveAttempt += 1;
|
|
1216
|
+
retrospectiveAttempts += 1;
|
|
1217
|
+
const directive = buildRetrospectiveDirective({
|
|
1218
|
+
cause,
|
|
1219
|
+
errorMessage: message,
|
|
1220
|
+
attempt: childRetrospectiveAttempt,
|
|
1221
|
+
target: "delegate",
|
|
1222
|
+
});
|
|
1223
|
+
childPromptForAttempt = `${delegatePrompt}\n\n${directive}`;
|
|
1224
|
+
emitProgress({
|
|
1225
|
+
kind: "subagent_progress",
|
|
1226
|
+
phase: "running",
|
|
1227
|
+
message: `delegate ${index + 1}/${delegateTotal}: retrospective retry ${childRetrospectiveAttempt}/${retrospectiveRetriesFromEnv} (${cause})`,
|
|
1228
|
+
cwd: childCwd,
|
|
1229
|
+
activeTool: undefined,
|
|
1230
|
+
delegateIndex: index + 1,
|
|
1231
|
+
delegateTotal,
|
|
1232
|
+
delegateDescription: request.description,
|
|
1233
|
+
delegateProfile: childProfileLabel,
|
|
1234
|
+
delegateItems,
|
|
1235
|
+
});
|
|
1236
|
+
}
|
|
749
1237
|
}
|
|
750
1238
|
const parsedChildDelegation = parseDelegationRequests(childOutput, 0);
|
|
751
1239
|
childOutput = parsedChildDelegation.cleanedOutput;
|
|
@@ -762,7 +1250,7 @@ export function createTaskTool(cwd, runner, options) {
|
|
|
762
1250
|
? `${normalizedChildOutput.slice(0, Math.max(1, maxDelegatedOutputCharsFromEnv - 3))}...`
|
|
763
1251
|
: normalizedChildOutput;
|
|
764
1252
|
delegatedSections[index] =
|
|
765
|
-
`#### ${index + 1}. ${request.description} (${
|
|
1253
|
+
`#### ${index + 1}. ${request.description} (${childProfileLabel})\n${childOutputExcerpt}`;
|
|
766
1254
|
emitProgress({
|
|
767
1255
|
kind: "subagent_progress",
|
|
768
1256
|
phase: "running",
|
|
@@ -772,7 +1260,7 @@ export function createTaskTool(cwd, runner, options) {
|
|
|
772
1260
|
delegateIndex: index + 1,
|
|
773
1261
|
delegateTotal,
|
|
774
1262
|
delegateDescription: request.description,
|
|
775
|
-
delegateProfile:
|
|
1263
|
+
delegateProfile: childProfileLabel,
|
|
776
1264
|
delegateItems,
|
|
777
1265
|
});
|
|
778
1266
|
}
|
|
@@ -781,11 +1269,18 @@ export function createTaskTool(cwd, runner, options) {
|
|
|
781
1269
|
if (_signal?.aborted || isAbortError(error)) {
|
|
782
1270
|
throw new Error("Operation aborted");
|
|
783
1271
|
}
|
|
784
|
-
|
|
1272
|
+
const classified = error && typeof error === "object" && "failureCause" in error
|
|
1273
|
+
? error.failureCause
|
|
1274
|
+
: classifyFailureCause(message);
|
|
1275
|
+
if (!(error && typeof error === "object" && "failureCause" in error)) {
|
|
1276
|
+
recordFailureCause(classified);
|
|
1277
|
+
}
|
|
1278
|
+
markDelegateFailed(index, `delegate ${index + 1}/${delegateTotal} failed`, message, classified);
|
|
785
1279
|
}
|
|
786
1280
|
finally {
|
|
787
1281
|
childReleaseIsolation?.();
|
|
788
1282
|
childReleaseLock?.();
|
|
1283
|
+
cleanupWriteLock(explicitChildLock);
|
|
789
1284
|
}
|
|
790
1285
|
};
|
|
791
1286
|
const resolveBlockedByFailedDependencies = () => {
|
|
@@ -798,7 +1293,8 @@ export function createTaskTool(cwd, runner, options) {
|
|
|
798
1293
|
if (!failedDep)
|
|
799
1294
|
continue;
|
|
800
1295
|
pendingIndices.delete(index);
|
|
801
|
-
|
|
1296
|
+
recordFailureCause("logic_error");
|
|
1297
|
+
markDelegateFailed(index, `delegate ${index + 1}/${delegateTotal} skipped: dependency ${failedDep} failed`, `Delegated task ${index + 1} skipped because dependency ${failedDep} failed.`, "logic_error");
|
|
802
1298
|
changed = true;
|
|
803
1299
|
}
|
|
804
1300
|
return changed;
|
|
@@ -836,7 +1332,8 @@ export function createTaskTool(cwd, runner, options) {
|
|
|
836
1332
|
for (const index of Array.from(pendingIndices)) {
|
|
837
1333
|
pendingIndices.delete(index);
|
|
838
1334
|
const deps = normalizedDependsOn[index] ?? [];
|
|
839
|
-
|
|
1335
|
+
recordFailureCause("logic_error");
|
|
1336
|
+
markDelegateFailed(index, `delegate ${index + 1}/${delegateTotal} blocked: unresolved depends_on`, `Delegated task ${index + 1} blocked by unresolved dependencies: ${deps.join(", ") || "unknown"}.`, "logic_error");
|
|
840
1337
|
}
|
|
841
1338
|
}
|
|
842
1339
|
break;
|
|
@@ -847,8 +1344,45 @@ export function createTaskTool(cwd, runner, options) {
|
|
|
847
1344
|
catch (error) {
|
|
848
1345
|
const message = error instanceof Error ? error.message : String(error);
|
|
849
1346
|
if (_signal?.aborted || isAbortError(error)) {
|
|
850
|
-
|
|
1347
|
+
recordFailureCause("aborted");
|
|
1348
|
+
const hasFailureCauses = Object.keys(failureCauses).length > 0;
|
|
1349
|
+
const details = {
|
|
1350
|
+
profile: effectiveProfile,
|
|
1351
|
+
description,
|
|
1352
|
+
outputLength: 0,
|
|
1353
|
+
cwd: subagentCwd,
|
|
1354
|
+
agent: customSubagent?.name,
|
|
1355
|
+
lockKey: lockKey?.trim() || undefined,
|
|
1356
|
+
runId,
|
|
1357
|
+
taskId: orchestrationTaskId,
|
|
1358
|
+
model: effectiveModelOverride,
|
|
1359
|
+
isolation: useWorktree ? "worktree" : "none",
|
|
1360
|
+
worktreePath,
|
|
1361
|
+
waitMs: Date.now() - queuedAt,
|
|
1362
|
+
background: runInBackground,
|
|
1363
|
+
toolCallsStarted: runStats?.toolCallsStarted ?? latestProgress?.toolCallsStarted,
|
|
1364
|
+
toolCallsCompleted: runStats?.toolCallsCompleted ?? latestProgress?.toolCallsCompleted,
|
|
1365
|
+
assistantMessages: runStats?.assistantMessages ?? latestProgress?.assistantMessages,
|
|
1366
|
+
delegatedTasks: delegatedTasks > 0 ? delegatedTasks : undefined,
|
|
1367
|
+
delegatedSucceeded: delegatedTasks > 0 ? delegatedSucceeded : undefined,
|
|
1368
|
+
delegatedFailed: delegatedTasks > 0 ? delegatedFailed : undefined,
|
|
1369
|
+
retrospectiveAttempts: retrospectiveAttempts > 0 ? retrospectiveAttempts : undefined,
|
|
1370
|
+
retrospectiveRecovered: retrospectiveRecovered > 0 ? retrospectiveRecovered : undefined,
|
|
1371
|
+
failureCauses: hasFailureCauses ? { ...failureCauses } : undefined,
|
|
1372
|
+
};
|
|
1373
|
+
updateTrackedTaskStatus("cancelled");
|
|
1374
|
+
throw Object.assign(new Error("Operation aborted"), {
|
|
1375
|
+
details,
|
|
1376
|
+
failureCause: "aborted",
|
|
1377
|
+
});
|
|
1378
|
+
}
|
|
1379
|
+
const classified = error && typeof error === "object" && "failureCause" in error
|
|
1380
|
+
? error.failureCause
|
|
1381
|
+
: classifyFailureCause(message);
|
|
1382
|
+
if (!(error && typeof error === "object" && "failureCause" in error)) {
|
|
1383
|
+
recordFailureCause(classified);
|
|
851
1384
|
}
|
|
1385
|
+
const hasFailureCauses = Object.keys(failureCauses).length > 0;
|
|
852
1386
|
const details = {
|
|
853
1387
|
profile: effectiveProfile,
|
|
854
1388
|
description,
|
|
@@ -869,15 +1403,11 @@ export function createTaskTool(cwd, runner, options) {
|
|
|
869
1403
|
delegatedTasks: delegatedTasks > 0 ? delegatedTasks : undefined,
|
|
870
1404
|
delegatedSucceeded: delegatedTasks > 0 ? delegatedSucceeded : undefined,
|
|
871
1405
|
delegatedFailed: delegatedTasks > 0 ? delegatedFailed : undefined,
|
|
1406
|
+
retrospectiveAttempts: retrospectiveAttempts > 0 ? retrospectiveAttempts : undefined,
|
|
1407
|
+
retrospectiveRecovered: retrospectiveRecovered > 0 ? retrospectiveRecovered : undefined,
|
|
1408
|
+
failureCauses: hasFailureCauses ? { ...failureCauses } : undefined,
|
|
872
1409
|
};
|
|
873
|
-
|
|
874
|
-
updateTeamTaskStatus({
|
|
875
|
-
cwd,
|
|
876
|
-
runId: orchestrationRunId,
|
|
877
|
-
taskId: orchestrationTaskId,
|
|
878
|
-
status: "error",
|
|
879
|
-
});
|
|
880
|
-
}
|
|
1410
|
+
updateTrackedTaskStatus("error");
|
|
881
1411
|
throw Object.assign(new Error(`Subagent failed: ${message}`), { details });
|
|
882
1412
|
}
|
|
883
1413
|
const normalizedOutput = output.trim().length > 0 ? output.trim() : "(Subagent completed with no output)";
|
|
@@ -890,6 +1420,15 @@ export function createTaskTool(cwd, runner, options) {
|
|
|
890
1420
|
if (delegationWarnings.length > 0) {
|
|
891
1421
|
finalSections.push(`### Delegation Notes\n${delegationWarnings.map((w) => `- ${w}`).join("\n")}`);
|
|
892
1422
|
}
|
|
1423
|
+
const failureCauseSummary = formatFailureCauseCounts(failureCauses);
|
|
1424
|
+
if (retrospectiveAttempts > 0 || failureCauseSummary) {
|
|
1425
|
+
finalSections.push([
|
|
1426
|
+
"### Retrospective",
|
|
1427
|
+
`- attempts: ${retrospectiveAttempts}`,
|
|
1428
|
+
`- recovered: ${retrospectiveRecovered}`,
|
|
1429
|
+
`- failure_causes: ${failureCauseSummary || "none"}`,
|
|
1430
|
+
].join("\n"));
|
|
1431
|
+
}
|
|
893
1432
|
const text = finalSections.join("\n\n");
|
|
894
1433
|
emitProgress({
|
|
895
1434
|
kind: "subagent_progress",
|
|
@@ -918,6 +1457,7 @@ export function createTaskTool(cwd, runner, options) {
|
|
|
918
1457
|
isolation: useWorktree ? "worktree" : "none",
|
|
919
1458
|
worktreePath,
|
|
920
1459
|
});
|
|
1460
|
+
const hasFailureCauses = Object.keys(failureCauses).length > 0;
|
|
921
1461
|
const details = {
|
|
922
1462
|
profile: effectiveProfile,
|
|
923
1463
|
description,
|
|
@@ -955,22 +1495,22 @@ export function createTaskTool(cwd, runner, options) {
|
|
|
955
1495
|
delegatedTasks: delegatedTasks > 0 ? delegatedTasks : undefined,
|
|
956
1496
|
delegatedSucceeded: delegatedTasks > 0 ? delegatedSucceeded : undefined,
|
|
957
1497
|
delegatedFailed: delegatedTasks > 0 ? delegatedFailed : undefined,
|
|
1498
|
+
retrospectiveAttempts: retrospectiveAttempts > 0 ? retrospectiveAttempts : undefined,
|
|
1499
|
+
retrospectiveRecovered: retrospectiveRecovered > 0 ? retrospectiveRecovered : undefined,
|
|
1500
|
+
failureCauses: hasFailureCauses ? { ...failureCauses } : undefined,
|
|
958
1501
|
};
|
|
959
|
-
|
|
960
|
-
updateTeamTaskStatus({
|
|
961
|
-
cwd,
|
|
962
|
-
runId: orchestrationRunId,
|
|
963
|
-
taskId: orchestrationTaskId,
|
|
964
|
-
status: "done",
|
|
965
|
-
});
|
|
966
|
-
}
|
|
1502
|
+
updateTrackedTaskStatus("done");
|
|
967
1503
|
return { text, details };
|
|
968
1504
|
}
|
|
969
1505
|
finally {
|
|
970
1506
|
releaseIsolation?.();
|
|
971
1507
|
releaseWriteLock?.();
|
|
1508
|
+
cleanupWriteLock(explicitRootLockKey);
|
|
972
1509
|
releaseSlot?.();
|
|
973
1510
|
releaseRunSlot?.();
|
|
1511
|
+
if (orchestrationRunId) {
|
|
1512
|
+
cleanupOrchestrationSemaphore(cwd, orchestrationRunId);
|
|
1513
|
+
}
|
|
974
1514
|
}
|
|
975
1515
|
};
|
|
976
1516
|
if (runInBackground) {
|
|
@@ -1013,9 +1553,10 @@ export function createTaskTool(cwd, runner, options) {
|
|
|
1013
1553
|
});
|
|
1014
1554
|
}
|
|
1015
1555
|
catch (error) {
|
|
1556
|
+
const aborted = isAbortError(error);
|
|
1016
1557
|
writeBackgroundRunStatus(cwd, {
|
|
1017
1558
|
runId,
|
|
1018
|
-
status: "error",
|
|
1559
|
+
status: aborted ? "cancelled" : "error",
|
|
1019
1560
|
createdAt: now,
|
|
1020
1561
|
finishedAt: new Date().toISOString(),
|
|
1021
1562
|
description,
|