iosm-cli 0.2.3 → 0.2.5
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 +56 -0
- package/README.md +43 -10
- package/dist/cli/args.d.ts +1 -1
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +1 -1
- package/dist/cli/args.js.map +1 -1
- package/dist/core/agent-profiles.d.ts +2 -1
- package/dist/core/agent-profiles.d.ts.map +1 -1
- package/dist/core/agent-profiles.js +18 -5
- package/dist/core/agent-profiles.js.map +1 -1
- package/dist/core/agent-session.d.ts +7 -0
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +64 -15
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/agent-teams.d.ts.map +1 -1
- package/dist/core/agent-teams.js +90 -19
- package/dist/core/agent-teams.js.map +1 -1
- package/dist/core/extensions/types.d.ts +1 -1
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/footer-data-provider.d.ts +6 -1
- package/dist/core/footer-data-provider.d.ts.map +1 -1
- package/dist/core/footer-data-provider.js +9 -0
- package/dist/core/footer-data-provider.js.map +1 -1
- package/dist/core/orchestration-limits.d.ts +6 -0
- package/dist/core/orchestration-limits.d.ts.map +1 -0
- package/dist/core/orchestration-limits.js +6 -0
- package/dist/core/orchestration-limits.js.map +1 -0
- package/dist/core/parallel-task-agent.d.ts +23 -1
- package/dist/core/parallel-task-agent.d.ts.map +1 -1
- package/dist/core/parallel-task-agent.js +110 -20
- package/dist/core/parallel-task-agent.js.map +1 -1
- package/dist/core/sdk.d.ts +6 -1
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +34 -6
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/shared-memory.d.ts +2 -2
- package/dist/core/shared-memory.d.ts.map +1 -1
- package/dist/core/shared-memory.js +220 -91
- package/dist/core/shared-memory.js.map +1 -1
- package/dist/core/singular.d.ts.map +1 -1
- package/dist/core/singular.js +3 -1
- package/dist/core/singular.js.map +1 -1
- package/dist/core/subagents.d.ts +1 -1
- package/dist/core/subagents.d.ts.map +1 -1
- package/dist/core/subagents.js +32 -10
- package/dist/core/subagents.js.map +1 -1
- package/dist/core/swarm/planner.d.ts.map +1 -1
- package/dist/core/swarm/planner.js +200 -12
- package/dist/core/swarm/planner.js.map +1 -1
- package/dist/core/swarm/scheduler.d.ts +2 -0
- package/dist/core/swarm/scheduler.d.ts.map +1 -1
- package/dist/core/swarm/scheduler.js +87 -6
- package/dist/core/swarm/scheduler.js.map +1 -1
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +1 -0
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/tools/ast-grep.d.ts.map +1 -1
- package/dist/core/tools/ast-grep.js +2 -0
- package/dist/core/tools/ast-grep.js.map +1 -1
- package/dist/core/tools/edit.d.ts +8 -4
- package/dist/core/tools/edit.d.ts.map +1 -1
- package/dist/core/tools/edit.js +18 -3
- package/dist/core/tools/edit.js.map +1 -1
- package/dist/core/tools/index.d.ts +9 -7
- package/dist/core/tools/index.d.ts.map +1 -1
- package/dist/core/tools/shared-memory.d.ts.map +1 -1
- package/dist/core/tools/shared-memory.js +34 -6
- package/dist/core/tools/shared-memory.js.map +1 -1
- package/dist/core/tools/task.d.ts +10 -3
- package/dist/core/tools/task.d.ts.map +1 -1
- package/dist/core/tools/task.js +830 -184
- package/dist/core/tools/task.js.map +1 -1
- package/dist/core/tools/todo.d.ts +10 -10
- package/dist/core/tools/todo.d.ts.map +1 -1
- package/dist/core/tools/todo.js +135 -17
- package/dist/core/tools/todo.js.map +1 -1
- package/dist/core/tools/yq.d.ts.map +1 -1
- package/dist/core/tools/yq.js +2 -0
- package/dist/core/tools/yq.js.map +1 -1
- package/dist/modes/interactive/components/footer.d.ts +1 -1
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +10 -9
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +22 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +957 -75
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/docs/cli-reference.md +8 -0
- package/docs/configuration.md +5 -1
- package/docs/interactive-mode.md +7 -1
- package/docs/orchestration-and-subagents.md +5 -0
- package/package.json +1 -1
package/dist/core/tools/task.js
CHANGED
|
@@ -4,19 +4,27 @@ import { spawnSync } from "node:child_process";
|
|
|
4
4
|
import { Type } from "@sinclair/typebox";
|
|
5
5
|
import { getTeamRun, updateTeamTaskStatus } from "../agent-teams.js";
|
|
6
6
|
import { buildRetrospectiveDirective, classifyFailureCause, formatFailureCauseCounts, isRetrospectiveRetryable, } from "../failure-retrospective.js";
|
|
7
|
+
import { MAX_ORCHESTRATION_AGENTS, MAX_ORCHESTRATION_PARALLEL, MAX_SUBAGENT_DELEGATE_PARALLEL, MAX_SUBAGENT_DELEGATION_DEPTH, MAX_SUBAGENT_DELEGATIONS_PER_TASK, } from "../orchestration-limits.js";
|
|
8
|
+
import { AGENT_PROFILES, isReadOnlyProfileName, isValidProfileName, } from "../agent-profiles.js";
|
|
7
9
|
const taskSchema = Type.Object({
|
|
8
|
-
description: Type.String({
|
|
9
|
-
description: "
|
|
10
|
-
}),
|
|
11
|
-
|
|
12
|
-
description: "
|
|
13
|
-
}),
|
|
10
|
+
description: Type.Optional(Type.String({
|
|
11
|
+
description: "Optional short 3-5 word description of what the subagent will do. If omitted, it is derived from prompt.",
|
|
12
|
+
})),
|
|
13
|
+
task: Type.Optional(Type.String({
|
|
14
|
+
description: "Legacy alias for prompt. If provided, it is treated as the subagent prompt when prompt is omitted.",
|
|
15
|
+
})),
|
|
16
|
+
args: Type.Optional(Type.String({
|
|
17
|
+
description: "Legacy alias for prompt used by some models. If provided, it is treated as the subagent prompt when prompt/task are omitted.",
|
|
18
|
+
})),
|
|
19
|
+
prompt: Type.Optional(Type.String({
|
|
20
|
+
description: "Optional full task prompt for the subagent. If omitted, the description is used as the prompt.",
|
|
21
|
+
})),
|
|
14
22
|
agent: Type.Optional(Type.String({
|
|
15
23
|
description: "Optional custom subagent name loaded from .iosm/agents or global agents directory.",
|
|
16
24
|
})),
|
|
17
|
-
profile: Type.String({
|
|
18
|
-
description: "
|
|
19
|
-
}),
|
|
25
|
+
profile: Type.Optional(Type.String({
|
|
26
|
+
description: "Optional subagent capability profile. Defaults to the current host profile when omitted (or full if host profile is unavailable). Recommended values: explore, plan, iosm, meta, iosm_analyst, iosm_verifier, cycle_planner, full. For custom agents, pass the agent name via `agent`, not `profile`.",
|
|
27
|
+
})),
|
|
20
28
|
cwd: Type.Optional(Type.String({
|
|
21
29
|
description: "Optional working directory for this subagent. Relative paths are resolved from the current workspace.",
|
|
22
30
|
})),
|
|
@@ -40,31 +48,30 @@ const taskSchema = Type.Object({
|
|
|
40
48
|
})),
|
|
41
49
|
delegate_parallel_hint: Type.Optional(Type.Integer({
|
|
42
50
|
minimum: 1,
|
|
43
|
-
maximum:
|
|
51
|
+
maximum: MAX_SUBAGENT_DELEGATE_PARALLEL,
|
|
44
52
|
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
53
|
})),
|
|
46
54
|
});
|
|
47
|
-
/** Tool names available per profile */
|
|
48
|
-
const toolsByProfile = {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
iosm_analyst: ["read", "bash", "grep", "find", "ls"],
|
|
53
|
-
iosm_verifier: ["read", "bash", "write"],
|
|
54
|
-
cycle_planner: ["read", "bash", "write"],
|
|
55
|
-
full: ["read", "bash", "edit", "write", "grep", "find", "ls"],
|
|
56
|
-
};
|
|
55
|
+
/** Tool names available per profile (kept in sync with AGENT_PROFILES). */
|
|
56
|
+
const toolsByProfile = Object.values(AGENT_PROFILES).reduce((acc, profile) => {
|
|
57
|
+
acc[profile.name] = [...profile.tools];
|
|
58
|
+
return acc;
|
|
59
|
+
}, {});
|
|
57
60
|
/** System prompt injected per profile */
|
|
58
61
|
const systemPromptByProfile = {
|
|
59
62
|
explore: "You are a fast read-only codebase explorer. Answer concisely. Never write or edit files.",
|
|
60
63
|
plan: "You are a technical architect. Analyze the codebase and produce a clear implementation plan. Do not write or edit files.",
|
|
61
64
|
iosm: "You are an IOSM execution agent. Use IOSM methodology and keep IOSM artifacts synchronized with implementation.",
|
|
65
|
+
meta: "You are a meta orchestration agent. Your main job is to maximize safe parallel execution through delegates, not to personally do most of the implementation. Start with bounded read-only recon, then form a concrete execution graph: subtasks, delegate subtasks, dependencies, lock domains, and verification steps. The parent agent remains responsible for orchestration and synthesis, so decompose work aggressively instead of collapsing complex work into one worker. For any non-trivial task, orchestration is required: after recon, launch multiple focused delegates instead of continuing manual implementation in the parent agent, avoid direct write/edit work in the parent agent before delegation unless the task is clearly trivial, and do not hand the whole task to one specialist child when independent workstreams exist. If a delegated workstream still contains multiple independent slices, split it again with nested <delegate_task> blocks. Default to aggressive safe parallelism. If the user requested a specific degree of parallelism, honor it when feasible or explain the exact blocker. Use shared_memory as the default coordination channel between delegates: use stable namespaced keys, prefer read-before-write, and use CAS (if_version) for contested updates; reserve append mode for timeline/log keys. When delegation is not used for non-trivial work, explain why in one line and include DELEGATION_IMPOSSIBLE. Enforce test verification for code changes, complete only after all delegated branches are resolved, and explicitly justify any no-code path where tests are skipped.",
|
|
62
66
|
iosm_analyst: "You are an IOSM metrics analyst. Analyze .iosm/ artifacts and codebase metrics. Be precise and evidence-based.",
|
|
63
67
|
iosm_verifier: "You are an IOSM verifier. Validate checks and update only required IOSM artifacts with deterministic reasoning.",
|
|
64
68
|
cycle_planner: "You are an IOSM cycle planner. Propose and align cycle goals with measurable outcomes and concrete risks.",
|
|
65
69
|
full: "You are a software engineering agent. Execute the task end-to-end.",
|
|
66
70
|
};
|
|
67
|
-
const
|
|
71
|
+
const writeCapableTools = new Set(["bash", "edit", "write"]);
|
|
72
|
+
const backgroundUnsafeTools = new Set(writeCapableTools);
|
|
73
|
+
const writeCapableProfiles = new Set(Object.keys(toolsByProfile).filter((profileName) => toolsByProfile[profileName].some((tool) => writeCapableTools.has(tool))));
|
|
74
|
+
const backgroundSafeProfiles = Object.keys(toolsByProfile).filter((profileName) => toolsByProfile[profileName].every((tool) => !backgroundUnsafeTools.has(tool)));
|
|
68
75
|
const delegationTagName = "delegate_task";
|
|
69
76
|
class Semaphore {
|
|
70
77
|
constructor(limit) {
|
|
@@ -121,11 +128,11 @@ class Mutex {
|
|
|
121
128
|
return !this.locked && this.waiters.length === 0;
|
|
122
129
|
}
|
|
123
130
|
}
|
|
124
|
-
const maxParallelFromEnv = parseBoundedInt(process.env.IOSM_SUBAGENT_MAX_PARALLEL,
|
|
131
|
+
const maxParallelFromEnv = parseBoundedInt(process.env.IOSM_SUBAGENT_MAX_PARALLEL, MAX_ORCHESTRATION_PARALLEL, 1, MAX_ORCHESTRATION_PARALLEL);
|
|
125
132
|
const subagentSemaphore = new Semaphore(maxParallelFromEnv);
|
|
126
|
-
const maxDelegationDepthFromEnv = parseBoundedInt(process.env.IOSM_SUBAGENT_MAX_DELEGATION_DEPTH,
|
|
127
|
-
const maxDelegationsPerTaskFromEnv = parseBoundedInt(process.env.IOSM_SUBAGENT_MAX_DELEGATIONS_PER_TASK,
|
|
128
|
-
const maxDelegatedParallelFromEnv = parseBoundedInt(process.env.IOSM_SUBAGENT_MAX_DELEGATE_PARALLEL,
|
|
133
|
+
const maxDelegationDepthFromEnv = parseBoundedInt(process.env.IOSM_SUBAGENT_MAX_DELEGATION_DEPTH, 2, 0, MAX_SUBAGENT_DELEGATION_DEPTH);
|
|
134
|
+
const maxDelegationsPerTaskFromEnv = parseBoundedInt(process.env.IOSM_SUBAGENT_MAX_DELEGATIONS_PER_TASK, MAX_SUBAGENT_DELEGATIONS_PER_TASK, 0, MAX_SUBAGENT_DELEGATIONS_PER_TASK);
|
|
135
|
+
const maxDelegatedParallelFromEnv = parseBoundedInt(process.env.IOSM_SUBAGENT_MAX_DELEGATE_PARALLEL, MAX_SUBAGENT_DELEGATE_PARALLEL, 1, MAX_SUBAGENT_DELEGATE_PARALLEL);
|
|
129
136
|
const emptyOutputRetriesFromEnv = parseBoundedInt(process.env.IOSM_SUBAGENT_EMPTY_OUTPUT_RETRIES, 1, 0, 2);
|
|
130
137
|
const retrospectiveRetriesFromEnv = parseBoundedInt(process.env.IOSM_SUBAGENT_RETRO_RETRIES, 1, 0, 1);
|
|
131
138
|
const orchestrationDependencyWaitTimeoutMsFromEnv = parseBoundedInt(process.env.IOSM_ORCHESTRATION_DEPENDENCY_WAIT_TIMEOUT_MS, 120_000, 5_000, 900_000);
|
|
@@ -141,14 +148,21 @@ function parseBoundedInt(raw, fallback, min, max) {
|
|
|
141
148
|
return fallback;
|
|
142
149
|
return Math.max(min, Math.min(max, parsed));
|
|
143
150
|
}
|
|
144
|
-
function
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
151
|
+
function shouldAutoDelegate(input) {
|
|
152
|
+
const profile = input.profile?.trim().toLowerCase();
|
|
153
|
+
if (profile === "meta")
|
|
154
|
+
return true;
|
|
155
|
+
const hostProfile = input.hostProfile?.trim().toLowerCase();
|
|
156
|
+
if (hostProfile === "meta")
|
|
157
|
+
return true;
|
|
158
|
+
const agentName = input.agentName?.trim().toLowerCase();
|
|
159
|
+
return !!agentName && agentName.includes("orchestrator");
|
|
149
160
|
}
|
|
150
|
-
function deriveAutoDelegateParallelHint(agentName, description, prompt) {
|
|
151
|
-
|
|
161
|
+
function deriveAutoDelegateParallelHint(profile, agentName, hostProfile, description, prompt) {
|
|
162
|
+
const normalizedProfile = profile?.trim().toLowerCase();
|
|
163
|
+
const isMetaProfile = normalizedProfile === "meta";
|
|
164
|
+
const isMetaHost = hostProfile?.trim().toLowerCase() === "meta";
|
|
165
|
+
if (!shouldAutoDelegate({ profile: normalizedProfile, agentName, hostProfile }))
|
|
152
166
|
return undefined;
|
|
153
167
|
const text = `${description}\n${prompt}`.trim();
|
|
154
168
|
if (!text)
|
|
@@ -163,6 +177,9 @@ function deriveAutoDelegateParallelHint(agentName, description, prompt) {
|
|
|
163
177
|
const fileLikeMatches = normalized.match(/\b[A-Za-z0-9_.-]+\.[A-Za-z0-9]{1,8}\b/g) ?? [];
|
|
164
178
|
const listMarkers = text.match(/(?:^|\n)\s*(?:[-*]|\d+[.)])\s+/g)?.length ?? 0;
|
|
165
179
|
const hasCodeBlock = text.includes("```");
|
|
180
|
+
const actionTokenMatches = normalized.match(/\b(?:audit|security|auth|rbac|sqli|sql|injection|fix|implement|refactor|migrat|harden|verify|test|scan|orchestrate|parallel|delegate|bug|vulnerab)\w*/gi) ?? [];
|
|
181
|
+
const strongActionSignal = new Set(actionTokenMatches.map((token) => token.toLowerCase())).size >= 2;
|
|
182
|
+
const metaOrchestratorContext = isMetaProfile || isMetaHost;
|
|
166
183
|
let score = 0;
|
|
167
184
|
if (words >= 40) {
|
|
168
185
|
score += 2;
|
|
@@ -180,12 +197,33 @@ function deriveAutoDelegateParallelHint(agentName, description, prompt) {
|
|
|
180
197
|
score += 1;
|
|
181
198
|
}
|
|
182
199
|
const referenceCount = pathLikeMatches.length + fileLikeMatches.length;
|
|
200
|
+
const metaNonTrivialSignal = words >= 12 ||
|
|
201
|
+
clauses >= 3 ||
|
|
202
|
+
listMarkers >= 1 ||
|
|
203
|
+
referenceCount >= 1 ||
|
|
204
|
+
hasCodeBlock ||
|
|
205
|
+
(strongActionSignal && words >= 4);
|
|
183
206
|
if (referenceCount >= 3 || (referenceCount >= 1 && words >= 20)) {
|
|
184
207
|
score += 1;
|
|
185
208
|
}
|
|
186
209
|
if (hasCodeBlock) {
|
|
187
210
|
score += 1;
|
|
188
211
|
}
|
|
212
|
+
if (metaOrchestratorContext) {
|
|
213
|
+
// In meta orchestration, require delegation pressure for non-trivial prompts
|
|
214
|
+
// even when lexical scoring is still low.
|
|
215
|
+
if (score === 0) {
|
|
216
|
+
if (strongActionSignal && words >= 4) {
|
|
217
|
+
score = 1;
|
|
218
|
+
}
|
|
219
|
+
else if (metaNonTrivialSignal) {
|
|
220
|
+
score = 2;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
else if (score > 0) {
|
|
224
|
+
score += 1;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
189
227
|
if (score >= 6)
|
|
190
228
|
return 10;
|
|
191
229
|
if (score >= 5)
|
|
@@ -203,6 +241,40 @@ function deriveAutoDelegateParallelHint(agentName, description, prompt) {
|
|
|
203
241
|
function normalizeSpacing(text) {
|
|
204
242
|
return text.replace(/[ \t]+\n/g, "\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
205
243
|
}
|
|
244
|
+
function deriveTaskDescriptionFromPrompt(prompt) {
|
|
245
|
+
const firstMeaningfulLine = prompt
|
|
246
|
+
.split("\n")
|
|
247
|
+
.map((line) => line.trim())
|
|
248
|
+
.find((line) => line.length > 0) ?? "Run subtask";
|
|
249
|
+
const normalized = firstMeaningfulLine
|
|
250
|
+
.replace(/^[-*]\s+/, "")
|
|
251
|
+
.replace(/^\d+[.)]\s+/, "")
|
|
252
|
+
.replace(/\s+/g, " ")
|
|
253
|
+
.trim();
|
|
254
|
+
if (normalized.length <= 80) {
|
|
255
|
+
return normalized;
|
|
256
|
+
}
|
|
257
|
+
return `${normalized.slice(0, 77).trimEnd()}...`;
|
|
258
|
+
}
|
|
259
|
+
function normalizeTaskPayload(input) {
|
|
260
|
+
const rawDescription = input.description?.trim();
|
|
261
|
+
const rawTask = input.task?.trim();
|
|
262
|
+
const rawArgs = input.args?.trim();
|
|
263
|
+
const rawPrompt = input.prompt?.trim() || rawTask || rawArgs;
|
|
264
|
+
if (rawDescription && rawPrompt) {
|
|
265
|
+
return { description: rawDescription, prompt: rawPrompt };
|
|
266
|
+
}
|
|
267
|
+
if (rawDescription) {
|
|
268
|
+
return { description: rawDescription, prompt: rawDescription };
|
|
269
|
+
}
|
|
270
|
+
if (rawPrompt) {
|
|
271
|
+
return {
|
|
272
|
+
description: deriveTaskDescriptionFromPrompt(rawPrompt),
|
|
273
|
+
prompt: rawPrompt,
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
throw new Error('Task tool requires at least one of "description", "task", "args", or "prompt".');
|
|
277
|
+
}
|
|
206
278
|
function cloneDelegateItems(items) {
|
|
207
279
|
return items ? items.map((item) => ({ ...item })) : undefined;
|
|
208
280
|
}
|
|
@@ -248,26 +320,193 @@ function mergeRunStats(base, next) {
|
|
|
248
320
|
assistantMessages: (base?.assistantMessages ?? 0) + (next?.assistantMessages ?? 0),
|
|
249
321
|
};
|
|
250
322
|
}
|
|
251
|
-
function buildDelegationProtocolPrompt(depthRemaining, maxDelegations) {
|
|
323
|
+
function buildDelegationProtocolPrompt(depthRemaining, maxDelegations, minDelegationsPreferred = 0) {
|
|
252
324
|
if (depthRemaining <= 0) {
|
|
253
325
|
return [
|
|
254
326
|
`Delegation protocol: depth limit reached.`,
|
|
255
327
|
`Do not emit <${delegationTagName}> blocks.`,
|
|
256
328
|
].join("\n");
|
|
257
329
|
}
|
|
330
|
+
if (minDelegationsPreferred > 0) {
|
|
331
|
+
const required = Math.min(Math.max(1, minDelegationsPreferred), maxDelegations);
|
|
332
|
+
return [
|
|
333
|
+
`Delegation protocol (required for this run): emit at least ${required} XML block(s) when the assigned work still contains independent slices.`,
|
|
334
|
+
`For broad audit, implementation, or verification tasks, split by subsystem, file family, or verification stream instead of producing one monolithic answer.`,
|
|
335
|
+
`<${delegationTagName} profile="explore|plan|iosm|meta|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">`,
|
|
336
|
+
"Detailed delegated task prompt",
|
|
337
|
+
`</${delegationTagName}>`,
|
|
338
|
+
`Keep a brief coordinator note outside the blocks, but do not collapse the full workload into one monolithic answer.`,
|
|
339
|
+
`If safe decomposition is truly impossible, output exactly one line: DELEGATION_IMPOSSIBLE: <precise reason>.`,
|
|
340
|
+
`When shared_memory tools are available, exchange intermediate state through shared_memory_write/shared_memory_read instead of repeating large context.`,
|
|
341
|
+
`Shared-memory protocol: use stable namespaced keys (findings/<stream>, plan/<stream>, risks/<stream>).`,
|
|
342
|
+
`Use scope=run for cross-stream coordination, scope=task for local scratch state, read before overwrite, and use if_version for contested updates.`,
|
|
343
|
+
`Reserve mode=append for timeline/log keys only; avoid append on canonical state keys.`,
|
|
344
|
+
].join("\n");
|
|
345
|
+
}
|
|
258
346
|
return [
|
|
259
347
|
`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">`,
|
|
348
|
+
`<${delegationTagName} profile="explore|plan|iosm|meta|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">`,
|
|
261
349
|
"Detailed delegated task prompt",
|
|
262
350
|
`</${delegationTagName}>`,
|
|
263
351
|
`Only emit blocks when necessary. Keep normal analysis/answer text outside those blocks.`,
|
|
264
352
|
`When shared_memory tools are available, exchange intermediate state through shared_memory_write/shared_memory_read instead of repeating large context.`,
|
|
353
|
+
`Shared-memory protocol: prefer namespaced keys and read-before-write discipline; use CAS (if_version) on shared state updates.`,
|
|
354
|
+
`Reserve mode=append for timeline/log keys only.`,
|
|
265
355
|
].join("\n");
|
|
266
356
|
}
|
|
267
|
-
function
|
|
268
|
-
const
|
|
357
|
+
function truncateForDelegationContext(text, maxChars = 2200) {
|
|
358
|
+
const normalized = normalizeSpacing(text);
|
|
359
|
+
if (normalized.length <= maxChars)
|
|
360
|
+
return normalized;
|
|
361
|
+
return `${normalized.slice(0, Math.max(100, maxChars - 3)).trimEnd()}...`;
|
|
362
|
+
}
|
|
363
|
+
function extractDelegationWorkstreams(text, maxItems) {
|
|
364
|
+
if (maxItems <= 0)
|
|
365
|
+
return [];
|
|
366
|
+
const seen = new Set();
|
|
367
|
+
const pushUnique = (raw) => {
|
|
368
|
+
const cleaned = raw
|
|
369
|
+
.replace(/^[-*]\s+/, "")
|
|
370
|
+
.replace(/^\d+[.)]\s+/, "")
|
|
371
|
+
.replace(/\s+/g, " ")
|
|
372
|
+
.trim();
|
|
373
|
+
if (cleaned.length < 5)
|
|
374
|
+
return;
|
|
375
|
+
const key = cleaned.toLowerCase();
|
|
376
|
+
if (seen.has(key))
|
|
377
|
+
return;
|
|
378
|
+
seen.add(key);
|
|
379
|
+
};
|
|
380
|
+
for (const line of text.split("\n")) {
|
|
381
|
+
if (!/^\s*(?:[-*]|\d+[.)])\s+/.test(line))
|
|
382
|
+
continue;
|
|
383
|
+
pushUnique(line);
|
|
384
|
+
if (seen.size >= maxItems)
|
|
385
|
+
break;
|
|
386
|
+
}
|
|
387
|
+
if (seen.size < maxItems) {
|
|
388
|
+
const fragments = text
|
|
389
|
+
.split(/[\n.;:]+/g)
|
|
390
|
+
.map((fragment) => fragment.trim())
|
|
391
|
+
.filter((fragment) => fragment.length >= 10)
|
|
392
|
+
.slice(0, maxItems * 3);
|
|
393
|
+
for (const fragment of fragments) {
|
|
394
|
+
pushUnique(fragment);
|
|
395
|
+
if (seen.size >= maxItems)
|
|
396
|
+
break;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
return Array.from(seen).slice(0, maxItems);
|
|
400
|
+
}
|
|
401
|
+
function deriveAutoDelegateProfile(baseProfile, description, prompt) {
|
|
402
|
+
const signal = `${description}\n${prompt}`.toLowerCase();
|
|
403
|
+
const writeIntent = /\b(?:implement|fix|patch|refactor|rewrite|edit|update|migrate|change|write|apply)\b/.test(signal);
|
|
404
|
+
if (baseProfile === "full")
|
|
405
|
+
return writeIntent ? "full" : "explore";
|
|
406
|
+
if (baseProfile === "meta")
|
|
407
|
+
return writeIntent ? "full" : "explore";
|
|
408
|
+
if (baseProfile === "iosm")
|
|
409
|
+
return writeIntent ? "full" : "explore";
|
|
410
|
+
if (baseProfile === "iosm_verifier")
|
|
411
|
+
return "iosm_verifier";
|
|
412
|
+
if (baseProfile === "cycle_planner")
|
|
413
|
+
return "cycle_planner";
|
|
414
|
+
if (baseProfile === "plan" || baseProfile === "iosm_analyst")
|
|
415
|
+
return "explore";
|
|
416
|
+
return "explore";
|
|
417
|
+
}
|
|
418
|
+
function pickAutoDelegateAgent(workstream, availableCustomNames) {
|
|
419
|
+
if (availableCustomNames.length === 0)
|
|
420
|
+
return undefined;
|
|
421
|
+
const normalizedWorkstream = workstream.toLowerCase();
|
|
422
|
+
const names = availableCustomNames.map((name) => ({ raw: name, normalized: name.toLowerCase() }));
|
|
423
|
+
const findByHint = (hints) => {
|
|
424
|
+
for (const hint of hints) {
|
|
425
|
+
const exact = names.find((item) => item.normalized === hint);
|
|
426
|
+
if (exact)
|
|
427
|
+
return exact.raw;
|
|
428
|
+
const contains = names.find((item) => item.normalized.includes(hint));
|
|
429
|
+
if (contains)
|
|
430
|
+
return contains.raw;
|
|
431
|
+
}
|
|
432
|
+
return undefined;
|
|
433
|
+
};
|
|
434
|
+
if (/\b(?:test|qa|coverage|verification|regression)\b/.test(normalizedWorkstream)) {
|
|
435
|
+
return findByHint(["qa_test_engineer", "qa", "tester", "verification"]);
|
|
436
|
+
}
|
|
437
|
+
if (/\b(?:ui|ux|design|layout|accessibility)\b/.test(normalizedWorkstream)) {
|
|
438
|
+
return findByHint(["uiux_top_senior", "uiux", "ui", "ux", "design"]);
|
|
439
|
+
}
|
|
440
|
+
if (/\b(?:architecture|codebase|refactor|security|rbac|auth|database|api)\b/.test(normalizedWorkstream)) {
|
|
441
|
+
return findByHint(["codebase_auditor", "architect", "security", "backend"]);
|
|
442
|
+
}
|
|
443
|
+
return undefined;
|
|
444
|
+
}
|
|
445
|
+
function buildAutoDelegationPrompt(input) {
|
|
446
|
+
const objective = truncateForDelegationContext(`${input.rootDescription}\n\n${input.rootPrompt}`);
|
|
447
|
+
return normalizeSpacing([
|
|
448
|
+
`Workstream ${input.ordinal}/${input.total}: ${input.streamTitle}`,
|
|
449
|
+
"Scope:",
|
|
450
|
+
`- Own this stream end-to-end and avoid duplicating sibling streams.`,
|
|
451
|
+
`- Produce concrete findings/changes for this stream only.`,
|
|
452
|
+
"Coordinator objective:",
|
|
453
|
+
objective,
|
|
454
|
+
].join("\n"));
|
|
455
|
+
}
|
|
456
|
+
function synthesizeDelegationRequests(input) {
|
|
457
|
+
const desiredTotal = Math.max(0, Math.min(input.maxDelegations, input.minDelegationsPreferred));
|
|
458
|
+
const missing = Math.max(0, desiredTotal - input.currentDelegates);
|
|
459
|
+
if (missing <= 0)
|
|
460
|
+
return [];
|
|
461
|
+
const combined = `${input.description}\n${input.prompt}`.trim();
|
|
462
|
+
const extracted = extractDelegationWorkstreams(combined, Math.max(missing, desiredTotal));
|
|
463
|
+
const fallbackByIndex = [
|
|
464
|
+
"Architecture and structure analysis",
|
|
465
|
+
"Behavioral verification and tests",
|
|
466
|
+
"Risk, regressions, and remediation",
|
|
467
|
+
"Integration and dependency checks",
|
|
468
|
+
"Delivery summary and rollout constraints",
|
|
469
|
+
];
|
|
470
|
+
const titles = [];
|
|
471
|
+
for (const stream of extracted) {
|
|
472
|
+
titles.push(stream);
|
|
473
|
+
if (titles.length >= missing)
|
|
474
|
+
break;
|
|
475
|
+
}
|
|
476
|
+
for (let index = 0; titles.length < missing && index < fallbackByIndex.length; index += 1) {
|
|
477
|
+
titles.push(fallbackByIndex[index]);
|
|
478
|
+
}
|
|
479
|
+
while (titles.length < missing) {
|
|
480
|
+
titles.push(`Independent workstream ${titles.length + 1}`);
|
|
481
|
+
}
|
|
482
|
+
const defaultProfile = deriveAutoDelegateProfile(input.baseProfile, input.description, input.prompt);
|
|
483
|
+
const synthesizedLockKey = writeCapableProfiles.has(defaultProfile) ? "auto-synth-write-lock" : undefined;
|
|
484
|
+
return titles.map((streamTitle, index) => ({
|
|
485
|
+
description: `Auto: ${streamTitle}`,
|
|
486
|
+
profile: defaultProfile,
|
|
487
|
+
agent: pickAutoDelegateAgent(streamTitle, input.availableCustomNames),
|
|
488
|
+
prompt: buildAutoDelegationPrompt({
|
|
489
|
+
streamTitle,
|
|
490
|
+
rootDescription: input.description,
|
|
491
|
+
rootPrompt: input.prompt,
|
|
492
|
+
ordinal: input.currentDelegates + index + 1,
|
|
493
|
+
total: input.currentDelegates + titles.length,
|
|
494
|
+
}),
|
|
495
|
+
cwd: undefined,
|
|
496
|
+
lockKey: synthesizedLockKey,
|
|
497
|
+
model: undefined,
|
|
498
|
+
isolation: undefined,
|
|
499
|
+
dependsOn: undefined,
|
|
500
|
+
}));
|
|
501
|
+
}
|
|
502
|
+
function withDelegationPrompt(basePrompt, depthRemaining, maxDelegations, minDelegationsPreferred = 0) {
|
|
503
|
+
const protocol = buildDelegationProtocolPrompt(depthRemaining, maxDelegations, minDelegationsPreferred);
|
|
269
504
|
return `${basePrompt}\n\n${protocol}`;
|
|
270
505
|
}
|
|
506
|
+
function withSubagentInstructions(basePrompt, instructions) {
|
|
507
|
+
const trimmed = instructions?.trim();
|
|
508
|
+
return trimmed ? `${basePrompt}\n\n${trimmed}` : basePrompt;
|
|
509
|
+
}
|
|
271
510
|
function buildSharedMemoryGuidance(runId, taskId) {
|
|
272
511
|
return [
|
|
273
512
|
"[SHARED_MEMORY]",
|
|
@@ -277,7 +516,10 @@ function buildSharedMemoryGuidance(runId, taskId) {
|
|
|
277
516
|
"Guidelines:",
|
|
278
517
|
"- Use scope=run for cross-agent data and scope=task for task-local notes.",
|
|
279
518
|
"- Keep entries compact and key-based (for example: findings/auth, plan/step-1, risks/session).",
|
|
519
|
+
"- Prefer one canonical key per stream and deduplicate updates; avoid redundant writes in loops.",
|
|
280
520
|
"- Read before overwrite when collaborating on the same key.",
|
|
521
|
+
"- Use if_version CAS for contested updates on shared keys.",
|
|
522
|
+
"- Use mode=append only for log/timeline keys; use mode=set for canonical state.",
|
|
281
523
|
"[/SHARED_MEMORY]",
|
|
282
524
|
].join("\n");
|
|
283
525
|
}
|
|
@@ -295,19 +537,21 @@ function parseDelegationRequests(output, maxRequests) {
|
|
|
295
537
|
return "";
|
|
296
538
|
}
|
|
297
539
|
const attrs = {};
|
|
298
|
-
for (const match of attrsRaw.matchAll(/([A-Za-z_][A-Za-z0-9_-]*)
|
|
299
|
-
attrs[match[1].toLowerCase()] = match[2];
|
|
540
|
+
for (const match of attrsRaw.matchAll(/([A-Za-z_][A-Za-z0-9_-]*)\s*=\s*(?:"([^"]*)"|'([^']*)')/g)) {
|
|
541
|
+
attrs[match[1].toLowerCase()] = (match[2] ?? match[3] ?? "").trim();
|
|
300
542
|
}
|
|
301
543
|
const prompt = normalizeSpacing(bodyRaw ?? "");
|
|
302
544
|
if (!prompt) {
|
|
303
545
|
warnings.push(`Ignored delegation block with empty prompt.`);
|
|
304
546
|
return "";
|
|
305
547
|
}
|
|
306
|
-
const
|
|
307
|
-
if (!
|
|
308
|
-
warnings.push(`Ignored delegation block with
|
|
548
|
+
const profileRaw = (attrs.profile ?? "explore").trim();
|
|
549
|
+
if (!profileRaw) {
|
|
550
|
+
warnings.push(`Ignored delegation block with empty profile.`);
|
|
309
551
|
return "";
|
|
310
552
|
}
|
|
553
|
+
const normalizedProfile = profileRaw.toLowerCase();
|
|
554
|
+
const profile = isValidProfileName(normalizedProfile) ? normalizedProfile : profileRaw;
|
|
311
555
|
const isolationRaw = (attrs.isolation ?? "").trim().toLowerCase();
|
|
312
556
|
const isolation = isolationRaw === "worktree" ? "worktree" : isolationRaw === "none" ? "none" : undefined;
|
|
313
557
|
if (isolationRaw && !isolation) {
|
|
@@ -338,6 +582,9 @@ function parseDelegationRequests(output, maxRequests) {
|
|
|
338
582
|
cleanedOutput: normalizeSpacing(cleaned),
|
|
339
583
|
};
|
|
340
584
|
}
|
|
585
|
+
function isBackgroundSafeToolset(tools) {
|
|
586
|
+
return tools.every((toolName) => !backgroundUnsafeTools.has(toolName));
|
|
587
|
+
}
|
|
341
588
|
function getCwdLockKey(cwd) {
|
|
342
589
|
// Normalize lock key to keep behavior consistent across path aliases.
|
|
343
590
|
return path.resolve(cwd).toLowerCase();
|
|
@@ -368,9 +615,9 @@ function getRunParallelLimit(cwd, runId) {
|
|
|
368
615
|
return 1;
|
|
369
616
|
const maxParallel = teamRun.maxParallel;
|
|
370
617
|
if (!Number.isInteger(maxParallel) || !maxParallel || maxParallel < 1) {
|
|
371
|
-
return Math.max(1, Math.min(teamRun.agents,
|
|
618
|
+
return Math.max(1, Math.min(teamRun.agents, MAX_ORCHESTRATION_AGENTS));
|
|
372
619
|
}
|
|
373
|
-
return Math.max(1, Math.min(maxParallel,
|
|
620
|
+
return Math.max(1, Math.min(maxParallel, MAX_ORCHESTRATION_PARALLEL));
|
|
374
621
|
}
|
|
375
622
|
function getOrCreateOrchestrationSemaphore(cwd, runId) {
|
|
376
623
|
const limit = getRunParallelLimit(cwd, runId);
|
|
@@ -587,13 +834,13 @@ export function createTaskTool(cwd, runner, options) {
|
|
|
587
834
|
label: "task",
|
|
588
835
|
description: "Launch a specialized subagent to handle a subtask in isolation. " +
|
|
589
836
|
"Use for: codebase exploration (profile=explore), architectural planning (profile=plan), " +
|
|
590
|
-
"IOSM artifact analysis (profile=iosm_analyst/iosm_verifier/cycle_planner), or end-to-end implementation (profile=full). " +
|
|
837
|
+
"IOSM artifact analysis (profile=iosm_analyst/iosm_verifier/cycle_planner), orchestration-first execution (profile=meta), or end-to-end implementation (profile=full). " +
|
|
591
838
|
"Set cwd to isolate subagents into different project areas when orchestrating parallel work. " +
|
|
592
839
|
"The subagent runs to completion and returns its full text output. " +
|
|
593
840
|
"It may request bounded follow-up delegation via <delegate_task> blocks that are executed by the parent task tool." +
|
|
594
841
|
customAgentsSnippet,
|
|
595
842
|
parameters: taskSchema,
|
|
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) => {
|
|
843
|
+
execute: async (_toolCallId, { description: rawDescription, task: rawTask, args: rawArgs, prompt: rawPrompt, 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
844
|
const updateTrackedTaskStatus = (status) => {
|
|
598
845
|
if (!orchestrationRunId || !orchestrationTaskId)
|
|
599
846
|
return;
|
|
@@ -610,6 +857,12 @@ export function createTaskTool(cwd, runner, options) {
|
|
|
610
857
|
throw new Error("Operation aborted");
|
|
611
858
|
}
|
|
612
859
|
};
|
|
860
|
+
const { description, prompt } = normalizeTaskPayload({
|
|
861
|
+
description: rawDescription,
|
|
862
|
+
task: rawTask,
|
|
863
|
+
args: rawArgs,
|
|
864
|
+
prompt: rawPrompt,
|
|
865
|
+
});
|
|
613
866
|
const runId = `subagent_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
|
|
614
867
|
const sharedMemoryRunId = orchestrationRunId?.trim() || runId;
|
|
615
868
|
const sharedMemoryTaskId = orchestrationTaskId?.trim() || runId;
|
|
@@ -617,13 +870,26 @@ export function createTaskTool(cwd, runner, options) {
|
|
|
617
870
|
const resolveCustom = (name) => {
|
|
618
871
|
if (!name || !options?.resolveCustomSubagent)
|
|
619
872
|
return undefined;
|
|
620
|
-
|
|
873
|
+
const trimmed = name.trim();
|
|
874
|
+
if (!trimmed)
|
|
875
|
+
return undefined;
|
|
876
|
+
const resolved = options.resolveCustomSubagent(trimmed);
|
|
877
|
+
if (resolved)
|
|
878
|
+
return resolved;
|
|
879
|
+
const lowered = trimmed.toLowerCase();
|
|
880
|
+
if (lowered !== trimmed) {
|
|
881
|
+
return options.resolveCustomSubagent(lowered);
|
|
882
|
+
}
|
|
883
|
+
return undefined;
|
|
621
884
|
};
|
|
622
885
|
let normalizedAgentName = agentName?.trim() || undefined;
|
|
623
|
-
let normalizedProfile = profile.trim().toLowerCase();
|
|
624
|
-
if (!normalizedProfile)
|
|
625
|
-
normalizedProfile = "full";
|
|
626
886
|
let customSubagent = resolveCustom(normalizedAgentName);
|
|
887
|
+
const requestedProfileRaw = profile?.trim() || undefined;
|
|
888
|
+
const normalizedHostProfile = options?.getHostProfileName?.()?.trim().toLowerCase() ?? options?.hostProfileName?.trim().toLowerCase();
|
|
889
|
+
const hostProfileFallback = normalizedHostProfile && isValidProfileName(normalizedHostProfile) ? normalizedHostProfile : "full";
|
|
890
|
+
let normalizedProfile = requestedProfileRaw?.toLowerCase() ||
|
|
891
|
+
customSubagent?.profile?.trim().toLowerCase() ||
|
|
892
|
+
hostProfileFallback;
|
|
627
893
|
if (normalizedAgentName && !customSubagent) {
|
|
628
894
|
const available = availableCustomNames.length > 0 ? ` Available custom agents: ${availableCustomNames.join(", ")}.` : "";
|
|
629
895
|
throw new Error(`Unknown subagent: ${normalizedAgentName}.${available}`);
|
|
@@ -637,37 +903,61 @@ export function createTaskTool(cwd, runner, options) {
|
|
|
637
903
|
normalizedProfile = (profileAsAgent.profile ?? "full").trim().toLowerCase();
|
|
638
904
|
}
|
|
639
905
|
}
|
|
640
|
-
if (!
|
|
641
|
-
normalizedProfile
|
|
906
|
+
if (!customSubagent && !isValidProfileName(normalizedProfile)) {
|
|
907
|
+
throw new Error(`Unknown profile "${requestedProfileRaw ?? normalizedProfile}". Valid profiles: ${Object.keys(toolsByProfile).join(", ")}.`);
|
|
908
|
+
}
|
|
909
|
+
const effectiveProfileCandidate = (customSubagent?.profile ?? normalizedProfile).trim().toLowerCase();
|
|
910
|
+
if (!isValidProfileName(effectiveProfileCandidate)) {
|
|
911
|
+
throw new Error(`Invalid resolved profile "${effectiveProfileCandidate}". Valid profiles: ${Object.keys(toolsByProfile).join(", ")}.`);
|
|
642
912
|
}
|
|
643
|
-
const effectiveProfile =
|
|
913
|
+
const effectiveProfile = effectiveProfileCandidate;
|
|
644
914
|
let tools = customSubagent?.tools
|
|
645
915
|
? [...customSubagent.tools]
|
|
646
|
-
: [...
|
|
916
|
+
: [...toolsByProfile[effectiveProfile]];
|
|
647
917
|
if (customSubagent?.disallowedTools?.length) {
|
|
648
918
|
const blocked = new Set(customSubagent.disallowedTools);
|
|
649
919
|
tools = tools.filter((tool) => !blocked.has(tool));
|
|
650
920
|
}
|
|
921
|
+
if (isReadOnlyProfileName(normalizedHostProfile) && tools.some((tool) => writeCapableTools.has(tool))) {
|
|
922
|
+
throw new Error(`Host profile "${normalizedHostProfile}" is read-only. Switch to full/meta/iosm to launch write-capable subtasks.`);
|
|
923
|
+
}
|
|
651
924
|
const delegationDepth = maxDelegationDepthFromEnv;
|
|
652
925
|
const requestedDelegateParallelHint = typeof delegateParallelHint === "number" && Number.isInteger(delegateParallelHint)
|
|
653
|
-
? Math.max(1, Math.min(
|
|
926
|
+
? Math.max(1, Math.min(MAX_SUBAGENT_DELEGATE_PARALLEL, delegateParallelHint))
|
|
654
927
|
: undefined;
|
|
655
928
|
const autoDelegateParallelHint = requestedDelegateParallelHint === undefined
|
|
656
|
-
? deriveAutoDelegateParallelHint(normalizedAgentName, description, prompt)
|
|
929
|
+
? deriveAutoDelegateParallelHint(effectiveProfile, normalizedAgentName, normalizedHostProfile, description, prompt)
|
|
657
930
|
: undefined;
|
|
658
|
-
|
|
659
|
-
const
|
|
660
|
-
|
|
931
|
+
let effectiveDelegateParallelHint = requestedDelegateParallelHint ?? autoDelegateParallelHint;
|
|
932
|
+
const effectiveDelegationDepth = effectiveProfile === "meta" || normalizedHostProfile === "meta" || normalizedAgentName?.toLowerCase().includes("orchestrator")
|
|
933
|
+
? Math.max(delegationDepth, 2)
|
|
934
|
+
: delegationDepth;
|
|
935
|
+
const orchestratedRunContext = !!(orchestrationRunId && orchestrationTaskId);
|
|
936
|
+
const strictDelegationContract = effectiveProfile === "meta" ||
|
|
937
|
+
normalizedHostProfile === "meta" ||
|
|
938
|
+
normalizedAgentName?.toLowerCase().includes("orchestrator") ||
|
|
939
|
+
(orchestratedRunContext && (effectiveDelegateParallelHint ?? 0) >= 2);
|
|
940
|
+
let effectiveMaxDelegations = Math.max(0, Math.min(maxDelegationsPerTaskFromEnv, effectiveDelegateParallelHint ?? maxDelegationsPerTaskFromEnv));
|
|
941
|
+
let effectiveMaxDelegateParallel = Math.max(1, Math.min(maxDelegatedParallelFromEnv, effectiveDelegateParallelHint ?? maxDelegatedParallelFromEnv));
|
|
942
|
+
const isMetaDelegationContext = effectiveProfile === "meta" || normalizedHostProfile === "meta";
|
|
943
|
+
const preferredDelegationFloorBase = isMetaDelegationContext ? 3 : 2;
|
|
944
|
+
const preferredDelegationFloorMin = isMetaDelegationContext ? 2 : 1;
|
|
945
|
+
const metaDelegationCapacityFloor = 3;
|
|
946
|
+
const preferredDelegationFloor = Math.max(preferredDelegationFloorMin, Math.min(preferredDelegationFloorBase, effectiveDelegateParallelHint ?? preferredDelegationFloorBase));
|
|
947
|
+
const applyMetaDelegationFloor = requestedDelegateParallelHint === undefined &&
|
|
948
|
+
(effectiveProfile === "meta" || normalizedHostProfile === "meta");
|
|
949
|
+
if (applyMetaDelegationFloor) {
|
|
950
|
+
effectiveMaxDelegations = Math.max(effectiveMaxDelegations, Math.min(maxDelegationsPerTaskFromEnv, metaDelegationCapacityFloor));
|
|
951
|
+
effectiveMaxDelegateParallel = Math.max(effectiveMaxDelegateParallel, Math.min(maxDelegatedParallelFromEnv, preferredDelegationFloor));
|
|
952
|
+
}
|
|
661
953
|
const minDelegationsPreferred = (effectiveDelegateParallelHint ?? 0) >= 2 && effectiveMaxDelegations >= 2
|
|
662
|
-
? Math.min(
|
|
954
|
+
? Math.min(preferredDelegationFloor, effectiveMaxDelegations, effectiveDelegateParallelHint ?? preferredDelegationFloor)
|
|
663
955
|
: 0;
|
|
664
|
-
const baseSystemPrompt = customSubagent?.systemPrompt ??
|
|
956
|
+
const baseSystemPrompt = withSubagentInstructions(customSubagent?.systemPrompt ??
|
|
665
957
|
systemPromptByProfile[effectiveProfile] ??
|
|
666
|
-
systemPromptByProfile.full;
|
|
667
|
-
const systemPrompt = withDelegationPrompt(baseSystemPrompt,
|
|
668
|
-
const promptWithInstructions =
|
|
669
|
-
? `${customSubagent.instructions.trim()}\n\nUser task:\n${prompt}`
|
|
670
|
-
: prompt;
|
|
958
|
+
systemPromptByProfile.full, customSubagent?.instructions);
|
|
959
|
+
const systemPrompt = withDelegationPrompt(baseSystemPrompt, effectiveDelegationDepth, effectiveMaxDelegations, minDelegationsPreferred);
|
|
960
|
+
const promptWithInstructions = prompt;
|
|
671
961
|
const effectiveModelOverride = requestedModel?.trim() || customSubagent?.model?.trim() || undefined;
|
|
672
962
|
const requestedBackground = background === true || customSubagent?.background === true;
|
|
673
963
|
const trackedOrchestrationRun = orchestrationRunId && orchestrationTaskId ? getTeamRun(cwd, orchestrationRunId) : undefined;
|
|
@@ -680,8 +970,10 @@ export function createTaskTool(cwd, runner, options) {
|
|
|
680
970
|
if (!existsSync(requestedSubagentCwd) || !statSync(requestedSubagentCwd).isDirectory()) {
|
|
681
971
|
throw new Error(`Subagent cwd does not exist or is not a directory: ${requestedSubagentCwd}`);
|
|
682
972
|
}
|
|
683
|
-
if (runInBackground &&
|
|
684
|
-
throw new Error(`Background policy violation: profile "${effectiveProfile}"
|
|
973
|
+
if (runInBackground && !isBackgroundSafeToolset(tools)) {
|
|
974
|
+
throw new Error(`Background policy violation: profile "${effectiveProfile}" has mutable tools (${tools
|
|
975
|
+
.filter((toolName) => backgroundUnsafeTools.has(toolName))
|
|
976
|
+
.join(", ")}). Background mode requires read-only toolsets. Safe baseline profiles: ${backgroundSafeProfiles.join(", ")}.`);
|
|
685
977
|
}
|
|
686
978
|
const useWorktree = isolation === "worktree";
|
|
687
979
|
const queuedAt = Date.now();
|
|
@@ -737,6 +1029,42 @@ export function createTaskTool(cwd, runner, options) {
|
|
|
737
1029
|
let subagentCwd = requestedSubagentCwd;
|
|
738
1030
|
let worktreePath;
|
|
739
1031
|
let runStats;
|
|
1032
|
+
const heldWriteLocks = new Map();
|
|
1033
|
+
const acquireLocalWriteLock = async (rawLockKey) => {
|
|
1034
|
+
const trimmed = rawLockKey?.trim();
|
|
1035
|
+
if (!trimmed)
|
|
1036
|
+
return undefined;
|
|
1037
|
+
const normalizedKey = getCwdLockKey(trimmed);
|
|
1038
|
+
const existing = heldWriteLocks.get(normalizedKey);
|
|
1039
|
+
if (existing) {
|
|
1040
|
+
existing.count += 1;
|
|
1041
|
+
return () => {
|
|
1042
|
+
const current = heldWriteLocks.get(normalizedKey);
|
|
1043
|
+
if (!current)
|
|
1044
|
+
return;
|
|
1045
|
+
current.count -= 1;
|
|
1046
|
+
if (current.count <= 0) {
|
|
1047
|
+
heldWriteLocks.delete(normalizedKey);
|
|
1048
|
+
current.release();
|
|
1049
|
+
cleanupWriteLock(trimmed);
|
|
1050
|
+
}
|
|
1051
|
+
};
|
|
1052
|
+
}
|
|
1053
|
+
const lock = getOrCreateWriteLock(trimmed);
|
|
1054
|
+
const release = await lock.acquire();
|
|
1055
|
+
heldWriteLocks.set(normalizedKey, { count: 1, release });
|
|
1056
|
+
return () => {
|
|
1057
|
+
const current = heldWriteLocks.get(normalizedKey);
|
|
1058
|
+
if (!current)
|
|
1059
|
+
return;
|
|
1060
|
+
current.count -= 1;
|
|
1061
|
+
if (current.count <= 0) {
|
|
1062
|
+
heldWriteLocks.delete(normalizedKey);
|
|
1063
|
+
current.release();
|
|
1064
|
+
cleanupWriteLock(trimmed);
|
|
1065
|
+
}
|
|
1066
|
+
};
|
|
1067
|
+
};
|
|
740
1068
|
try {
|
|
741
1069
|
throwIfAborted();
|
|
742
1070
|
if (orchestrationRunId && orchestrationTaskId) {
|
|
@@ -798,8 +1126,7 @@ export function createTaskTool(cwd, runner, options) {
|
|
|
798
1126
|
// Parallel orchestration should remain truly parallel by default.
|
|
799
1127
|
// Serialize write-capable agents only when an explicit lock_key is provided.
|
|
800
1128
|
if (explicitRootLockKey) {
|
|
801
|
-
|
|
802
|
-
releaseWriteLock = await lock.acquire();
|
|
1129
|
+
releaseWriteLock = await acquireLocalWriteLock(explicitRootLockKey);
|
|
803
1130
|
}
|
|
804
1131
|
}
|
|
805
1132
|
if (useWorktree) {
|
|
@@ -850,6 +1177,7 @@ export function createTaskTool(cwd, runner, options) {
|
|
|
850
1177
|
try {
|
|
851
1178
|
const result = await runner({
|
|
852
1179
|
systemPrompt,
|
|
1180
|
+
profileName: effectiveProfile,
|
|
853
1181
|
tools,
|
|
854
1182
|
prompt: promptForAttempt,
|
|
855
1183
|
cwd: subagentCwd,
|
|
@@ -942,7 +1270,7 @@ export function createTaskTool(cwd, runner, options) {
|
|
|
942
1270
|
output = firstPass.output;
|
|
943
1271
|
subagentSessionId = firstPass.sessionId;
|
|
944
1272
|
runStats = firstPass.stats;
|
|
945
|
-
let parsedDelegation = parseDelegationRequests(output,
|
|
1273
|
+
let parsedDelegation = parseDelegationRequests(output, effectiveDelegationDepth > 0 ? effectiveMaxDelegations : 0);
|
|
946
1274
|
if (minDelegationsPreferred > 0 && parsedDelegation.requests.length < minDelegationsPreferred) {
|
|
947
1275
|
emitProgress({
|
|
948
1276
|
kind: "subagent_progress",
|
|
@@ -965,10 +1293,35 @@ export function createTaskTool(cwd, runner, options) {
|
|
|
965
1293
|
output = secondPass.output;
|
|
966
1294
|
subagentSessionId = secondPass.sessionId ?? subagentSessionId;
|
|
967
1295
|
runStats = secondPass.stats ?? runStats;
|
|
968
|
-
parsedDelegation = parseDelegationRequests(output,
|
|
1296
|
+
parsedDelegation = parseDelegationRequests(output, effectiveDelegationDepth > 0 ? effectiveMaxDelegations : 0);
|
|
1297
|
+
}
|
|
1298
|
+
if (minDelegationsPreferred > 0 &&
|
|
1299
|
+
parsedDelegation.requests.length === 0 &&
|
|
1300
|
+
strictDelegationContract) {
|
|
1301
|
+
const impossibleMatch = output.match(/^\s*DELEGATION_IMPOSSIBLE\s*:\s*(.+)$/im);
|
|
1302
|
+
if (!impossibleMatch) {
|
|
1303
|
+
const synthesizedRequests = synthesizeDelegationRequests({
|
|
1304
|
+
description,
|
|
1305
|
+
prompt,
|
|
1306
|
+
baseProfile: effectiveProfile,
|
|
1307
|
+
currentDelegates: parsedDelegation.requests.length,
|
|
1308
|
+
minDelegationsPreferred,
|
|
1309
|
+
maxDelegations: effectiveMaxDelegations,
|
|
1310
|
+
availableCustomNames,
|
|
1311
|
+
});
|
|
1312
|
+
if (synthesizedRequests.length > 0) {
|
|
1313
|
+
parsedDelegation.requests.push(...synthesizedRequests);
|
|
1314
|
+
delegationWarnings.push(`Delegation auto-fanout: synthesized ${synthesizedRequests.length} delegate(s) to satisfy parallelism contract.`);
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
969
1317
|
}
|
|
970
1318
|
if (minDelegationsPreferred > 0 && parsedDelegation.requests.length < minDelegationsPreferred) {
|
|
971
|
-
const
|
|
1319
|
+
const impossibleMatch = output.match(/^\s*DELEGATION_IMPOSSIBLE\s*:\s*(.+)$/im);
|
|
1320
|
+
const impossibleReason = impossibleMatch?.[1]?.trim() ?? "not provided";
|
|
1321
|
+
if (strictDelegationContract && parsedDelegation.requests.length === 0 && !impossibleMatch) {
|
|
1322
|
+
throw new Error(`Delegation contract violated: expected >=${minDelegationsPreferred} delegates, got ${parsedDelegation.requests.length}. ` +
|
|
1323
|
+
`Provide nested <delegate_task> fan-out or an explicit "DELEGATION_IMPOSSIBLE: <reason>" line.`);
|
|
1324
|
+
}
|
|
972
1325
|
delegationWarnings.push(`Delegation fallback: kept single-agent execution (preferred >=${minDelegationsPreferred} delegates, got ${parsedDelegation.requests.length}). Reason: ${impossibleReason}.`);
|
|
973
1326
|
}
|
|
974
1327
|
output = parsedDelegation.cleanedOutput;
|
|
@@ -1037,19 +1390,241 @@ export function createTaskTool(cwd, runner, options) {
|
|
|
1037
1390
|
delegateItems,
|
|
1038
1391
|
});
|
|
1039
1392
|
};
|
|
1393
|
+
const executeNestedDelegates = async (requests, parentCwd, depthRemaining, lineage) => {
|
|
1394
|
+
if (requests.length === 0 || depthRemaining <= 0) {
|
|
1395
|
+
return { sections: [], warnings: [] };
|
|
1396
|
+
}
|
|
1397
|
+
const nestedWarnings = [];
|
|
1398
|
+
const sectionsByIndex = new Array(requests.length);
|
|
1399
|
+
const statuses = Array.from({ length: requests.length }, () => "pending");
|
|
1400
|
+
const totalNested = requests.length;
|
|
1401
|
+
const normalizedDependsOn = requests.map((request, index) => {
|
|
1402
|
+
const current = index + 1;
|
|
1403
|
+
const raw = request.dependsOn ?? [];
|
|
1404
|
+
const unique = new Set();
|
|
1405
|
+
for (const dep of raw) {
|
|
1406
|
+
if (!Number.isInteger(dep) || dep <= 0 || dep > totalNested || dep === current) {
|
|
1407
|
+
nestedWarnings.push(`Nested delegated task ${lineage}${current} has invalid depends_on reference "${dep}" and it was ignored.`);
|
|
1408
|
+
continue;
|
|
1409
|
+
}
|
|
1410
|
+
unique.add(dep);
|
|
1411
|
+
}
|
|
1412
|
+
return Array.from(unique).sort((a, b) => a - b);
|
|
1413
|
+
});
|
|
1414
|
+
const statusOf = (index) => statuses[index] ?? "pending";
|
|
1415
|
+
const markNestedFailed = (nestedIndex, requestLabel, nestedRequest, nestedProfileLabel, message, cause) => {
|
|
1416
|
+
statuses[nestedIndex] = "failed";
|
|
1417
|
+
recordFailureCause(cause);
|
|
1418
|
+
delegatedTasks += 1;
|
|
1419
|
+
delegatedFailed += 1;
|
|
1420
|
+
sectionsByIndex[nestedIndex] =
|
|
1421
|
+
`###### ${requestLabel}. ${nestedRequest.description} (${nestedProfileLabel})\nERROR [cause=${cause}]: ${message}`;
|
|
1422
|
+
};
|
|
1423
|
+
const runNestedDelegate = async (nestedIndex) => {
|
|
1424
|
+
const nestedRequest = requests[nestedIndex];
|
|
1425
|
+
const requestLabel = `${lineage}${nestedIndex + 1}`;
|
|
1426
|
+
statuses[nestedIndex] = "running";
|
|
1427
|
+
let requestedNestedAgent = nestedRequest.agent?.trim() || undefined;
|
|
1428
|
+
let nestedCustomSubagent = resolveCustom(requestedNestedAgent);
|
|
1429
|
+
if (!nestedCustomSubagent && !requestedNestedAgent) {
|
|
1430
|
+
const profileAsAgent = resolveCustom(nestedRequest.profile);
|
|
1431
|
+
if (profileAsAgent) {
|
|
1432
|
+
nestedCustomSubagent = profileAsAgent;
|
|
1433
|
+
requestedNestedAgent = profileAsAgent.name;
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
if (requestedNestedAgent && !nestedCustomSubagent) {
|
|
1437
|
+
nestedWarnings.push(`Nested delegated task "${nestedRequest.description}" requested unknown agent "${requestedNestedAgent}". Falling back to profile "${nestedRequest.profile}".`);
|
|
1438
|
+
}
|
|
1439
|
+
const nestedProfileCandidate = (nestedCustomSubagent?.profile ?? nestedRequest.profile).trim();
|
|
1440
|
+
const normalizedNestedProfile = nestedProfileCandidate.toLowerCase();
|
|
1441
|
+
if (!isValidProfileName(normalizedNestedProfile)) {
|
|
1442
|
+
markNestedFailed(nestedIndex, requestLabel, nestedRequest, nestedProfileCandidate || "unknown", `nested delegate skipped: unknown profile "${nestedProfileCandidate || nestedRequest.profile}"`, "logic_error");
|
|
1443
|
+
return;
|
|
1444
|
+
}
|
|
1445
|
+
const nestedProfile = normalizedNestedProfile;
|
|
1446
|
+
const nestedProfileLabel = nestedCustomSubagent?.name
|
|
1447
|
+
? `${nestedCustomSubagent.name}/${nestedProfile}`
|
|
1448
|
+
: nestedProfile;
|
|
1449
|
+
let nestedTools = nestedCustomSubagent?.tools
|
|
1450
|
+
? [...nestedCustomSubagent.tools]
|
|
1451
|
+
: [...toolsByProfile[nestedProfile]];
|
|
1452
|
+
if (nestedCustomSubagent?.disallowedTools?.length) {
|
|
1453
|
+
const blocked = new Set(nestedCustomSubagent.disallowedTools);
|
|
1454
|
+
nestedTools = nestedTools.filter((tool) => !blocked.has(tool));
|
|
1455
|
+
}
|
|
1456
|
+
const nestedBaseSystemPrompt = withSubagentInstructions(nestedCustomSubagent?.systemPrompt ??
|
|
1457
|
+
systemPromptByProfile[nestedProfile] ??
|
|
1458
|
+
systemPromptByProfile.full, nestedCustomSubagent?.instructions);
|
|
1459
|
+
const nestedSystemPrompt = withDelegationPrompt(nestedBaseSystemPrompt, Math.max(0, depthRemaining - 1), effectiveMaxDelegations);
|
|
1460
|
+
const requestedNestedCwd = nestedRequest.cwd
|
|
1461
|
+
? path.resolve(parentCwd, nestedRequest.cwd)
|
|
1462
|
+
: nestedCustomSubagent?.cwd ?? parentCwd;
|
|
1463
|
+
if (!existsSync(requestedNestedCwd) || !statSync(requestedNestedCwd).isDirectory()) {
|
|
1464
|
+
markNestedFailed(nestedIndex, requestLabel, nestedRequest, nestedProfileLabel, "nested delegate skipped: missing cwd", "dependency_env");
|
|
1465
|
+
return;
|
|
1466
|
+
}
|
|
1467
|
+
let nestedReleaseLock;
|
|
1468
|
+
let nestedReleaseIsolation;
|
|
1469
|
+
let nestedCwd = requestedNestedCwd;
|
|
1470
|
+
try {
|
|
1471
|
+
if (writeCapableProfiles.has(nestedProfile) && nestedRequest.lockKey?.trim()) {
|
|
1472
|
+
nestedReleaseLock = await acquireLocalWriteLock(nestedRequest.lockKey.trim());
|
|
1473
|
+
}
|
|
1474
|
+
if (nestedRequest.isolation === "worktree") {
|
|
1475
|
+
const isolated = provisionWorktree(cwd, requestedNestedCwd, `${runId}_nested_${requestLabel.replace(/\./g, "_")}`);
|
|
1476
|
+
nestedCwd = isolated.runCwd;
|
|
1477
|
+
nestedReleaseIsolation = isolated.cleanup;
|
|
1478
|
+
}
|
|
1479
|
+
const nestedPromptWithInstructions = nestedRequest.prompt;
|
|
1480
|
+
const nestedSharedMemoryGuidance = buildSharedMemoryGuidance(sharedMemoryRunId, sharedMemoryTaskId);
|
|
1481
|
+
const nestedPrompt = `${nestedPromptWithInstructions}\n\n${nestedSharedMemoryGuidance}`;
|
|
1482
|
+
const nestedModelOverride = nestedRequest.model?.trim() || nestedCustomSubagent?.model?.trim() || undefined;
|
|
1483
|
+
const nestedSharedMemoryContext = {
|
|
1484
|
+
rootCwd: cwd,
|
|
1485
|
+
runId: sharedMemoryRunId,
|
|
1486
|
+
taskId: sharedMemoryTaskId,
|
|
1487
|
+
delegateId: requestLabel,
|
|
1488
|
+
profile: nestedProfile,
|
|
1489
|
+
};
|
|
1490
|
+
const nestedResult = await runner({
|
|
1491
|
+
systemPrompt: nestedSystemPrompt,
|
|
1492
|
+
profileName: nestedProfile,
|
|
1493
|
+
tools: nestedTools,
|
|
1494
|
+
prompt: nestedPrompt,
|
|
1495
|
+
cwd: nestedCwd,
|
|
1496
|
+
modelOverride: nestedModelOverride,
|
|
1497
|
+
sharedMemoryContext: nestedSharedMemoryContext,
|
|
1498
|
+
signal: _signal,
|
|
1499
|
+
onProgress: (progress) => {
|
|
1500
|
+
emitProgress({
|
|
1501
|
+
kind: "subagent_progress",
|
|
1502
|
+
phase: "running",
|
|
1503
|
+
message: `delegate ${requestLabel}: ${progress.message}`,
|
|
1504
|
+
cwd: progress.cwd ?? nestedCwd,
|
|
1505
|
+
activeTool: progress.activeTool,
|
|
1506
|
+
});
|
|
1507
|
+
},
|
|
1508
|
+
});
|
|
1509
|
+
throwIfAborted();
|
|
1510
|
+
let nestedOutput = typeof nestedResult === "string" ? nestedResult : nestedResult.output;
|
|
1511
|
+
const nestedStats = typeof nestedResult === "string" ? undefined : nestedResult.stats;
|
|
1512
|
+
delegatedTasks += 1;
|
|
1513
|
+
delegatedSucceeded += 1;
|
|
1514
|
+
statuses[nestedIndex] = "done";
|
|
1515
|
+
delegatedStats.toolCallsStarted += nestedStats?.toolCallsStarted ?? 0;
|
|
1516
|
+
delegatedStats.toolCallsCompleted += nestedStats?.toolCallsCompleted ?? 0;
|
|
1517
|
+
delegatedStats.assistantMessages += nestedStats?.assistantMessages ?? 0;
|
|
1518
|
+
const parsedNestedDelegation = parseDelegationRequests(nestedOutput, depthRemaining > 1 ? effectiveMaxDelegations : 0);
|
|
1519
|
+
nestedOutput = parsedNestedDelegation.cleanedOutput;
|
|
1520
|
+
nestedWarnings.push(...parsedNestedDelegation.warnings.map((warning) => `Nested child ${requestLabel}: ${warning}`));
|
|
1521
|
+
let nestedSection = `###### ${requestLabel}. ${nestedRequest.description} (${nestedProfileLabel})\n${nestedOutput.trim() || "(no output)"}`;
|
|
1522
|
+
if (parsedNestedDelegation.requests.length > 0 && depthRemaining > 1) {
|
|
1523
|
+
const deeper = await executeNestedDelegates(parsedNestedDelegation.requests, nestedCwd, depthRemaining - 1, `${requestLabel}.`);
|
|
1524
|
+
nestedWarnings.push(...deeper.warnings);
|
|
1525
|
+
if (deeper.sections.length > 0) {
|
|
1526
|
+
nestedSection = `${nestedSection}\n\n##### Nested Delegated Subtasks\n\n${deeper.sections.join("\n\n")}`;
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
sectionsByIndex[nestedIndex] = nestedSection;
|
|
1530
|
+
}
|
|
1531
|
+
catch (error) {
|
|
1532
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1533
|
+
const cause = classifyFailureCause(message);
|
|
1534
|
+
markNestedFailed(nestedIndex, requestLabel, nestedRequest, nestedProfileLabel, message, cause);
|
|
1535
|
+
}
|
|
1536
|
+
finally {
|
|
1537
|
+
nestedReleaseIsolation?.();
|
|
1538
|
+
nestedReleaseLock?.();
|
|
1539
|
+
}
|
|
1540
|
+
};
|
|
1541
|
+
const pendingIndices = new Set(Array.from({ length: totalNested }, (_v, i) => i));
|
|
1542
|
+
const runningNested = new Map();
|
|
1543
|
+
const maxNestedParallel = Math.max(1, Math.min(totalNested, effectiveMaxDelegateParallel));
|
|
1544
|
+
const resolveBlockedByFailedDependencies = () => {
|
|
1545
|
+
let changed = false;
|
|
1546
|
+
for (const nestedIndex of Array.from(pendingIndices)) {
|
|
1547
|
+
const deps = normalizedDependsOn[nestedIndex] ?? [];
|
|
1548
|
+
if (deps.length === 0)
|
|
1549
|
+
continue;
|
|
1550
|
+
const failedDep = deps.find((dep) => statusOf(dep - 1) === "failed");
|
|
1551
|
+
if (!failedDep)
|
|
1552
|
+
continue;
|
|
1553
|
+
pendingIndices.delete(nestedIndex);
|
|
1554
|
+
const nestedRequest = requests[nestedIndex];
|
|
1555
|
+
const requestLabel = `${lineage}${nestedIndex + 1}`;
|
|
1556
|
+
markNestedFailed(nestedIndex, requestLabel, nestedRequest, nestedRequest.profile, `nested delegate skipped: dependency ${lineage}${failedDep} failed`, "logic_error");
|
|
1557
|
+
changed = true;
|
|
1558
|
+
}
|
|
1559
|
+
return changed;
|
|
1560
|
+
};
|
|
1561
|
+
const launchReadyNested = () => {
|
|
1562
|
+
let launched = false;
|
|
1563
|
+
while (runningNested.size < maxNestedParallel) {
|
|
1564
|
+
let nextIndex;
|
|
1565
|
+
for (const nestedIndex of pendingIndices) {
|
|
1566
|
+
const deps = normalizedDependsOn[nestedIndex] ?? [];
|
|
1567
|
+
const allDone = deps.every((dep) => statusOf(dep - 1) === "done");
|
|
1568
|
+
if (allDone) {
|
|
1569
|
+
nextIndex = nestedIndex;
|
|
1570
|
+
break;
|
|
1571
|
+
}
|
|
1572
|
+
}
|
|
1573
|
+
if (nextIndex === undefined)
|
|
1574
|
+
break;
|
|
1575
|
+
pendingIndices.delete(nextIndex);
|
|
1576
|
+
const promise = runNestedDelegate(nextIndex).finally(() => {
|
|
1577
|
+
runningNested.delete(nextIndex);
|
|
1578
|
+
});
|
|
1579
|
+
runningNested.set(nextIndex, promise);
|
|
1580
|
+
launched = true;
|
|
1581
|
+
}
|
|
1582
|
+
return launched;
|
|
1583
|
+
};
|
|
1584
|
+
while (pendingIndices.size > 0 || runningNested.size > 0) {
|
|
1585
|
+
throwIfAborted();
|
|
1586
|
+
const changed = resolveBlockedByFailedDependencies();
|
|
1587
|
+
const launched = launchReadyNested();
|
|
1588
|
+
if (runningNested.size === 0) {
|
|
1589
|
+
if (pendingIndices.size > 0 && !changed && !launched) {
|
|
1590
|
+
for (const nestedIndex of Array.from(pendingIndices)) {
|
|
1591
|
+
pendingIndices.delete(nestedIndex);
|
|
1592
|
+
const nestedRequest = requests[nestedIndex];
|
|
1593
|
+
const deps = normalizedDependsOn[nestedIndex] ?? [];
|
|
1594
|
+
const requestLabel = `${lineage}${nestedIndex + 1}`;
|
|
1595
|
+
markNestedFailed(nestedIndex, requestLabel, nestedRequest, nestedRequest.profile, `nested delegate blocked: unresolved depends_on (${deps.join(", ") || "unknown"})`, "logic_error");
|
|
1596
|
+
}
|
|
1597
|
+
}
|
|
1598
|
+
break;
|
|
1599
|
+
}
|
|
1600
|
+
await Promise.race(Array.from(runningNested.values()));
|
|
1601
|
+
}
|
|
1602
|
+
const sections = sectionsByIndex.filter((section) => typeof section === "string" && section.trim().length > 0);
|
|
1603
|
+
return { sections, warnings: nestedWarnings };
|
|
1604
|
+
};
|
|
1040
1605
|
const runDelegate = async (index) => {
|
|
1041
1606
|
throwIfAborted();
|
|
1042
1607
|
const request = parsedDelegation.requests[index];
|
|
1043
|
-
|
|
1044
|
-
|
|
1608
|
+
let requestedChildAgent = request.agent?.trim() || undefined;
|
|
1609
|
+
let childCustomSubagent = resolveCustom(requestedChildAgent);
|
|
1610
|
+
if (!childCustomSubagent && !requestedChildAgent) {
|
|
1611
|
+
const profileAsAgent = resolveCustom(request.profile);
|
|
1612
|
+
if (profileAsAgent) {
|
|
1613
|
+
childCustomSubagent = profileAsAgent;
|
|
1614
|
+
requestedChildAgent = profileAsAgent.name;
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1045
1617
|
if (requestedChildAgent && !childCustomSubagent) {
|
|
1046
1618
|
delegationWarnings.push(`Delegated task "${request.description}" requested unknown agent "${requestedChildAgent}". Falling back to profile "${request.profile}".`);
|
|
1047
1619
|
}
|
|
1048
|
-
const childProfileRaw = childCustomSubagent?.profile ?? request.profile;
|
|
1049
|
-
const normalizedChildProfile = childProfileRaw.
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
: "
|
|
1620
|
+
const childProfileRaw = (childCustomSubagent?.profile ?? request.profile).trim();
|
|
1621
|
+
const normalizedChildProfile = childProfileRaw.toLowerCase();
|
|
1622
|
+
if (!isValidProfileName(normalizedChildProfile)) {
|
|
1623
|
+
recordFailureCause("logic_error");
|
|
1624
|
+
markDelegateFailed(index, `delegate ${index + 1}/${delegateTotal} skipped: unknown profile "${childProfileRaw || request.profile}"`, `Delegated task "${request.description}" requested unknown profile "${childProfileRaw || request.profile}".`, "logic_error");
|
|
1625
|
+
return;
|
|
1626
|
+
}
|
|
1627
|
+
const childProfile = normalizedChildProfile;
|
|
1053
1628
|
const childProfileLabel = childCustomSubagent?.name
|
|
1054
1629
|
? `${childCustomSubagent.name}/${childProfile}`
|
|
1055
1630
|
: childProfile;
|
|
@@ -1070,15 +1645,20 @@ export function createTaskTool(cwd, runner, options) {
|
|
|
1070
1645
|
});
|
|
1071
1646
|
let childTools = childCustomSubagent?.tools
|
|
1072
1647
|
? [...childCustomSubagent.tools]
|
|
1073
|
-
: [...
|
|
1648
|
+
: [...toolsByProfile[childProfile]];
|
|
1074
1649
|
if (childCustomSubagent?.disallowedTools?.length) {
|
|
1075
1650
|
const blocked = new Set(childCustomSubagent.disallowedTools);
|
|
1076
1651
|
childTools = childTools.filter((tool) => !blocked.has(tool));
|
|
1077
1652
|
}
|
|
1078
|
-
const childBaseSystemPrompt = childCustomSubagent?.systemPrompt ??
|
|
1653
|
+
const childBaseSystemPrompt = withSubagentInstructions(childCustomSubagent?.systemPrompt ??
|
|
1079
1654
|
systemPromptByProfile[childProfile] ??
|
|
1080
|
-
systemPromptByProfile.full;
|
|
1081
|
-
const
|
|
1655
|
+
systemPromptByProfile.full, childCustomSubagent?.instructions);
|
|
1656
|
+
const childAutoDelegateParallelHint = deriveAutoDelegateParallelHint(childProfile, requestedChildAgent, normalizedHostProfile, request.description, request.prompt);
|
|
1657
|
+
const childMinDelegationsPreferred = Math.max(0, effectiveDelegationDepth - 1) > 0 &&
|
|
1658
|
+
(childAutoDelegateParallelHint ?? 0) >= 2
|
|
1659
|
+
? Math.min(preferredDelegationFloor, effectiveMaxDelegations, childAutoDelegateParallelHint ?? preferredDelegationFloor)
|
|
1660
|
+
: 0;
|
|
1661
|
+
const childSystemPrompt = withDelegationPrompt(childBaseSystemPrompt, Math.max(0, effectiveDelegationDepth - 1), effectiveMaxDelegations, childMinDelegationsPreferred);
|
|
1082
1662
|
const requestedChildCwd = request.cwd
|
|
1083
1663
|
? path.resolve(subagentCwd, request.cwd)
|
|
1084
1664
|
: childCustomSubagent?.cwd ?? subagentCwd;
|
|
@@ -1094,8 +1674,7 @@ export function createTaskTool(cwd, runner, options) {
|
|
|
1094
1674
|
try {
|
|
1095
1675
|
throwIfAborted();
|
|
1096
1676
|
if (writeCapableProfiles.has(childProfile) && explicitChildLock) {
|
|
1097
|
-
|
|
1098
|
-
childReleaseLock = await lock.acquire();
|
|
1677
|
+
childReleaseLock = await acquireLocalWriteLock(explicitChildLock);
|
|
1099
1678
|
throwIfAborted();
|
|
1100
1679
|
}
|
|
1101
1680
|
if (request.isolation === "worktree") {
|
|
@@ -1104,9 +1683,7 @@ export function createTaskTool(cwd, runner, options) {
|
|
|
1104
1683
|
childReleaseIsolation = isolated.cleanup;
|
|
1105
1684
|
}
|
|
1106
1685
|
const delegateMeta = formatMetaCheckpoint(options?.getMetaMessages?.());
|
|
1107
|
-
const childPromptWithInstructions =
|
|
1108
|
-
? `${childCustomSubagent.instructions.trim()}\n\nUser task:\n${request.prompt}`
|
|
1109
|
-
: request.prompt;
|
|
1686
|
+
const childPromptWithInstructions = request.prompt;
|
|
1110
1687
|
const delegateSharedMemoryGuidance = buildSharedMemoryGuidance(sharedMemoryRunId, sharedMemoryTaskId);
|
|
1111
1688
|
const delegatePromptBase = `${childPromptWithInstructions}\n\n${delegateSharedMemoryGuidance}`;
|
|
1112
1689
|
const delegatePrompt = delegateMeta.section && delegateMeta.appliedCount > 0
|
|
@@ -1136,108 +1713,179 @@ export function createTaskTool(cwd, runner, options) {
|
|
|
1136
1713
|
};
|
|
1137
1714
|
let childOutput = "";
|
|
1138
1715
|
let childStats;
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
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;
|
|
1716
|
+
const runChildPass = async (runPrompt) => {
|
|
1717
|
+
let childEmptyAttempt = 0;
|
|
1718
|
+
let childRetrospectiveAttempt = 0;
|
|
1719
|
+
let childPromptForAttempt = runPrompt;
|
|
1720
|
+
while (true) {
|
|
1721
|
+
try {
|
|
1722
|
+
const childResult = await runner({
|
|
1723
|
+
systemPrompt: childSystemPrompt,
|
|
1724
|
+
profileName: childProfile,
|
|
1725
|
+
tools: childTools,
|
|
1726
|
+
prompt: childPromptForAttempt,
|
|
1727
|
+
cwd: childCwd,
|
|
1728
|
+
modelOverride: childModelOverride,
|
|
1729
|
+
sharedMemoryContext: childSharedMemoryContext,
|
|
1730
|
+
signal: _signal,
|
|
1731
|
+
onProgress: (progress) => {
|
|
1732
|
+
emitProgress({
|
|
1733
|
+
kind: "subagent_progress",
|
|
1734
|
+
phase: "running",
|
|
1735
|
+
message: `delegate ${index + 1}/${delegateTotal}: ${progress.message}`,
|
|
1736
|
+
cwd: progress.cwd ?? childCwd,
|
|
1737
|
+
activeTool: progress.activeTool,
|
|
1738
|
+
delegateIndex: index + 1,
|
|
1739
|
+
delegateTotal,
|
|
1740
|
+
delegateDescription: request.description,
|
|
1741
|
+
delegateProfile: childProfileLabel,
|
|
1742
|
+
delegateItems,
|
|
1743
|
+
});
|
|
1744
|
+
},
|
|
1745
|
+
});
|
|
1746
|
+
throwIfAborted();
|
|
1747
|
+
let attemptOutput;
|
|
1748
|
+
let attemptStats;
|
|
1749
|
+
if (typeof childResult === "string") {
|
|
1750
|
+
attemptOutput = childResult;
|
|
1182
1751
|
}
|
|
1183
|
-
|
|
1752
|
+
else {
|
|
1753
|
+
attemptOutput = childResult.output;
|
|
1754
|
+
attemptStats = childResult.stats;
|
|
1755
|
+
}
|
|
1756
|
+
childStats = mergeRunStats(childStats, attemptStats);
|
|
1757
|
+
if (attemptOutput.trim().length > 0) {
|
|
1758
|
+
if (childRetrospectiveAttempt > 0) {
|
|
1759
|
+
retrospectiveRecovered += 1;
|
|
1760
|
+
}
|
|
1761
|
+
return attemptOutput;
|
|
1762
|
+
}
|
|
1763
|
+
if (childEmptyAttempt >= emptyOutputRetriesFromEnv) {
|
|
1764
|
+
const totalAttempts = childEmptyAttempt + 1;
|
|
1765
|
+
throw new Error(`delegate ${index + 1}/${delegateTotal} returned empty output after ${totalAttempts} attempt${totalAttempts === 1 ? "" : "s"}.`);
|
|
1766
|
+
}
|
|
1767
|
+
childEmptyAttempt += 1;
|
|
1768
|
+
emitProgress({
|
|
1769
|
+
kind: "subagent_progress",
|
|
1770
|
+
phase: "running",
|
|
1771
|
+
message: `delegate ${index + 1}/${delegateTotal}: empty output, retry ${childEmptyAttempt}/${emptyOutputRetriesFromEnv}`,
|
|
1772
|
+
cwd: childCwd,
|
|
1773
|
+
activeTool: undefined,
|
|
1774
|
+
delegateIndex: index + 1,
|
|
1775
|
+
delegateTotal,
|
|
1776
|
+
delegateDescription: request.description,
|
|
1777
|
+
delegateProfile: childProfileLabel,
|
|
1778
|
+
delegateItems,
|
|
1779
|
+
});
|
|
1184
1780
|
}
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1781
|
+
catch (error) {
|
|
1782
|
+
if (_signal?.aborted || isAbortError(error)) {
|
|
1783
|
+
throw new Error("Operation aborted");
|
|
1784
|
+
}
|
|
1785
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1786
|
+
const cause = classifyFailureCause(message);
|
|
1787
|
+
recordFailureCause(cause);
|
|
1788
|
+
const canRetryRetrospective = childRetrospectiveAttempt < retrospectiveRetriesFromEnv &&
|
|
1789
|
+
isRetrospectiveRetryable(cause);
|
|
1790
|
+
if (!canRetryRetrospective) {
|
|
1791
|
+
throw Object.assign(new Error(message), { failureCause: cause });
|
|
1792
|
+
}
|
|
1793
|
+
childRetrospectiveAttempt += 1;
|
|
1794
|
+
retrospectiveAttempts += 1;
|
|
1795
|
+
const directive = buildRetrospectiveDirective({
|
|
1796
|
+
cause,
|
|
1797
|
+
errorMessage: message,
|
|
1798
|
+
attempt: childRetrospectiveAttempt,
|
|
1799
|
+
target: "delegate",
|
|
1800
|
+
});
|
|
1801
|
+
childPromptForAttempt = `${runPrompt}\n\n${directive}`;
|
|
1802
|
+
emitProgress({
|
|
1803
|
+
kind: "subagent_progress",
|
|
1804
|
+
phase: "running",
|
|
1805
|
+
message: `delegate ${index + 1}/${delegateTotal}: retrospective retry ${childRetrospectiveAttempt}/${retrospectiveRetriesFromEnv} (${cause})`,
|
|
1806
|
+
cwd: childCwd,
|
|
1807
|
+
activeTool: undefined,
|
|
1808
|
+
delegateIndex: index + 1,
|
|
1809
|
+
delegateTotal,
|
|
1810
|
+
delegateDescription: request.description,
|
|
1811
|
+
delegateProfile: childProfileLabel,
|
|
1812
|
+
delegateItems,
|
|
1813
|
+
});
|
|
1188
1814
|
}
|
|
1189
|
-
childEmptyAttempt += 1;
|
|
1190
|
-
emitProgress({
|
|
1191
|
-
kind: "subagent_progress",
|
|
1192
|
-
phase: "running",
|
|
1193
|
-
message: `delegate ${index + 1}/${delegateTotal}: empty output, retry ${childEmptyAttempt}/${emptyOutputRetriesFromEnv}`,
|
|
1194
|
-
cwd: childCwd,
|
|
1195
|
-
activeTool: undefined,
|
|
1196
|
-
delegateIndex: index + 1,
|
|
1197
|
-
delegateTotal,
|
|
1198
|
-
delegateDescription: request.description,
|
|
1199
|
-
delegateProfile: childProfileLabel,
|
|
1200
|
-
delegateItems,
|
|
1201
|
-
});
|
|
1202
1815
|
}
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1816
|
+
};
|
|
1817
|
+
childOutput = await runChildPass(delegatePrompt);
|
|
1818
|
+
let parsedChildDelegation = parseDelegationRequests(childOutput, effectiveDelegationDepth > 1 ? effectiveMaxDelegations : 0);
|
|
1819
|
+
if (childMinDelegationsPreferred > 0 &&
|
|
1820
|
+
parsedChildDelegation.requests.length < childMinDelegationsPreferred) {
|
|
1821
|
+
emitProgress({
|
|
1822
|
+
kind: "subagent_progress",
|
|
1823
|
+
phase: "running",
|
|
1824
|
+
message: `delegate ${index + 1}/${delegateTotal}: nested delegation preference unmet (${parsedChildDelegation.requests.length}/${childMinDelegationsPreferred}), retrying with stronger split guidance`,
|
|
1825
|
+
cwd: childCwd,
|
|
1826
|
+
activeTool: undefined,
|
|
1827
|
+
delegateIndex: index + 1,
|
|
1828
|
+
delegateTotal,
|
|
1829
|
+
delegateDescription: request.description,
|
|
1830
|
+
delegateProfile: childProfileLabel,
|
|
1831
|
+
delegateItems,
|
|
1832
|
+
});
|
|
1833
|
+
const enforcedChildPrompt = [
|
|
1834
|
+
delegatePrompt,
|
|
1835
|
+
"",
|
|
1836
|
+
"[DELEGATION_ENFORCEMENT]",
|
|
1837
|
+
`This delegated workstream must emit at least ${childMinDelegationsPreferred} <delegate_task> blocks for independent sub-work when beneficial.`,
|
|
1838
|
+
`Target parallel fan-out: up to ${Math.min(effectiveMaxDelegateParallel, effectiveMaxDelegations)}.`,
|
|
1839
|
+
"For broad audits or implementations, split by subsystem / file cluster / verification stream instead of doing everything in one pass.",
|
|
1840
|
+
"If safe decomposition is impossible, output exactly one line:",
|
|
1841
|
+
"DELEGATION_IMPOSSIBLE: <reason>",
|
|
1842
|
+
"[/DELEGATION_ENFORCEMENT]",
|
|
1843
|
+
].join("\n");
|
|
1844
|
+
childOutput = await runChildPass(enforcedChildPrompt);
|
|
1845
|
+
parsedChildDelegation = parseDelegationRequests(childOutput, effectiveDelegationDepth > 1 ? effectiveMaxDelegations : 0);
|
|
1846
|
+
}
|
|
1847
|
+
if (childMinDelegationsPreferred > 0 &&
|
|
1848
|
+
parsedChildDelegation.requests.length === 0 &&
|
|
1849
|
+
strictDelegationContract) {
|
|
1850
|
+
const impossibleMatch = childOutput.match(/^\s*DELEGATION_IMPOSSIBLE\s*:\s*(.+)$/im);
|
|
1851
|
+
if (!impossibleMatch) {
|
|
1852
|
+
const synthesizedChildRequests = synthesizeDelegationRequests({
|
|
1853
|
+
description: request.description,
|
|
1854
|
+
prompt: request.prompt,
|
|
1855
|
+
baseProfile: childProfile,
|
|
1856
|
+
currentDelegates: parsedChildDelegation.requests.length,
|
|
1857
|
+
minDelegationsPreferred: childMinDelegationsPreferred,
|
|
1858
|
+
maxDelegations: effectiveMaxDelegations,
|
|
1859
|
+
availableCustomNames,
|
|
1235
1860
|
});
|
|
1861
|
+
if (synthesizedChildRequests.length > 0) {
|
|
1862
|
+
parsedChildDelegation.requests.push(...synthesizedChildRequests);
|
|
1863
|
+
delegationWarnings.push(`Child ${index + 1}: delegation auto-fanout synthesized ${synthesizedChildRequests.length} nested delegate(s).`);
|
|
1864
|
+
}
|
|
1236
1865
|
}
|
|
1237
1866
|
}
|
|
1238
|
-
|
|
1867
|
+
if (childMinDelegationsPreferred > 0 &&
|
|
1868
|
+
parsedChildDelegation.requests.length < childMinDelegationsPreferred) {
|
|
1869
|
+
const impossibleMatch = childOutput.match(/^\s*DELEGATION_IMPOSSIBLE\s*:\s*(.+)$/im);
|
|
1870
|
+
const impossibleReason = impossibleMatch?.[1]?.trim() ?? "not provided";
|
|
1871
|
+
if (strictDelegationContract &&
|
|
1872
|
+
parsedChildDelegation.requests.length === 0 &&
|
|
1873
|
+
!impossibleMatch) {
|
|
1874
|
+
throw new Error(`Delegation contract violated for child ${index + 1}: expected >=${childMinDelegationsPreferred} nested delegates, got ${parsedChildDelegation.requests.length}. ` +
|
|
1875
|
+
`Provide nested <delegate_task> fan-out or "DELEGATION_IMPOSSIBLE: <reason>".`);
|
|
1876
|
+
}
|
|
1877
|
+
delegationWarnings.push(`Child ${index + 1}: delegation fallback (preferred >=${childMinDelegationsPreferred}, got ${parsedChildDelegation.requests.length}). Reason: ${impossibleReason}.`);
|
|
1878
|
+
}
|
|
1239
1879
|
childOutput = parsedChildDelegation.cleanedOutput;
|
|
1240
1880
|
delegationWarnings.push(...parsedChildDelegation.warnings.map((warning) => `Child ${index + 1}: ${warning}`));
|
|
1881
|
+
let nestedSection = "";
|
|
1882
|
+
if (parsedChildDelegation.requests.length > 0 && effectiveDelegationDepth > 1) {
|
|
1883
|
+
const nested = await executeNestedDelegates(parsedChildDelegation.requests, childCwd, effectiveDelegationDepth - 1, `${index + 1}.`);
|
|
1884
|
+
delegationWarnings.push(...nested.warnings);
|
|
1885
|
+
if (nested.sections.length > 0) {
|
|
1886
|
+
nestedSection = `\n\n##### Nested Delegated Subtasks\n\n${nested.sections.join("\n\n")}`;
|
|
1887
|
+
}
|
|
1888
|
+
}
|
|
1241
1889
|
delegatedSucceeded += 1;
|
|
1242
1890
|
if (delegateItems[index]) {
|
|
1243
1891
|
delegateItems[index].status = "done";
|
|
@@ -1250,7 +1898,7 @@ export function createTaskTool(cwd, runner, options) {
|
|
|
1250
1898
|
? `${normalizedChildOutput.slice(0, Math.max(1, maxDelegatedOutputCharsFromEnv - 3))}...`
|
|
1251
1899
|
: normalizedChildOutput;
|
|
1252
1900
|
delegatedSections[index] =
|
|
1253
|
-
`#### ${index + 1}. ${request.description} (${childProfileLabel})\n${childOutputExcerpt}`;
|
|
1901
|
+
`#### ${index + 1}. ${request.description} (${childProfileLabel})\n${childOutputExcerpt}${nestedSection}`;
|
|
1254
1902
|
emitProgress({
|
|
1255
1903
|
kind: "subagent_progress",
|
|
1256
1904
|
phase: "running",
|
|
@@ -1280,7 +1928,6 @@ export function createTaskTool(cwd, runner, options) {
|
|
|
1280
1928
|
finally {
|
|
1281
1929
|
childReleaseIsolation?.();
|
|
1282
1930
|
childReleaseLock?.();
|
|
1283
|
-
cleanupWriteLock(explicitChildLock);
|
|
1284
1931
|
}
|
|
1285
1932
|
};
|
|
1286
1933
|
const resolveBlockedByFailedDependencies = () => {
|
|
@@ -1505,7 +2152,6 @@ export function createTaskTool(cwd, runner, options) {
|
|
|
1505
2152
|
finally {
|
|
1506
2153
|
releaseIsolation?.();
|
|
1507
2154
|
releaseWriteLock?.();
|
|
1508
|
-
cleanupWriteLock(explicitRootLockKey);
|
|
1509
2155
|
releaseSlot?.();
|
|
1510
2156
|
releaseRunSlot?.();
|
|
1511
2157
|
if (orchestrationRunId) {
|