iosm-cli 0.2.4 → 0.2.6
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 +60 -0
- package/README.md +447 -285
- package/dist/core/agent-profiles.d.ts +1 -0
- package/dist/core/agent-profiles.d.ts.map +1 -1
- package/dist/core/agent-profiles.js +10 -6
- package/dist/core/agent-profiles.js.map +1 -1
- package/dist/core/agent-session.d.ts +1 -1
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +6 -2
- 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/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/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/shared-memory.d.ts +16 -2
- package/dist/core/shared-memory.d.ts.map +1 -1
- package/dist/core/shared-memory.js +283 -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 +11 -3
- 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/shared-memory.d.ts.map +1 -1
- package/dist/core/tools/shared-memory.js +79 -11
- package/dist/core/tools/shared-memory.js.map +1 -1
- package/dist/core/tools/task.d.ts +12 -1
- package/dist/core/tools/task.d.ts.map +1 -1
- package/dist/core/tools/task.js +1023 -76
- package/dist/core/tools/task.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.map +1 -1
- package/dist/modes/interactive/components/footer.js +2 -1
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +13 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +881 -74
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/docs/cli-reference.md +4 -0
- package/docs/interactive-mode.md +2 -0
- package/docs/orchestration-and-subagents.md +5 -0
- package/package.json +1 -1
package/dist/core/tools/task.js
CHANGED
|
@@ -5,6 +5,8 @@ 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
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";
|
|
9
|
+
import { readSharedMemory, summarizeSharedMemoryUsage, writeSharedMemory, } from "../shared-memory.js";
|
|
8
10
|
const taskSchema = Type.Object({
|
|
9
11
|
description: Type.Optional(Type.String({
|
|
10
12
|
description: "Optional short 3-5 word description of what the subagent will do. If omitted, it is derived from prompt.",
|
|
@@ -22,7 +24,7 @@ const taskSchema = Type.Object({
|
|
|
22
24
|
description: "Optional custom subagent name loaded from .iosm/agents or global agents directory.",
|
|
23
25
|
})),
|
|
24
26
|
profile: Type.Optional(Type.String({
|
|
25
|
-
description: "Optional subagent capability profile. Defaults to
|
|
27
|
+
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`.",
|
|
26
28
|
})),
|
|
27
29
|
cwd: Type.Optional(Type.String({
|
|
28
30
|
description: "Optional working directory for this subagent. Relative paths are resolved from the current workspace.",
|
|
@@ -51,29 +53,26 @@ const taskSchema = Type.Object({
|
|
|
51
53
|
description: "Optional hint for intra-task delegation fan-out. Higher value allows more delegated subtasks to run in parallel inside a single task execution.",
|
|
52
54
|
})),
|
|
53
55
|
});
|
|
54
|
-
/** Tool names available per profile */
|
|
55
|
-
const toolsByProfile = {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
meta: ["read", "bash", "edit", "write", "grep", "find", "ls"],
|
|
60
|
-
iosm_analyst: ["read", "bash", "grep", "find", "ls"],
|
|
61
|
-
iosm_verifier: ["read", "bash", "write"],
|
|
62
|
-
cycle_planner: ["read", "bash", "write"],
|
|
63
|
-
full: ["read", "bash", "edit", "write", "grep", "find", "ls"],
|
|
64
|
-
};
|
|
56
|
+
/** Tool names available per profile (kept in sync with AGENT_PROFILES). */
|
|
57
|
+
const toolsByProfile = Object.values(AGENT_PROFILES).reduce((acc, profile) => {
|
|
58
|
+
acc[profile.name] = [...profile.tools];
|
|
59
|
+
return acc;
|
|
60
|
+
}, {});
|
|
65
61
|
/** System prompt injected per profile */
|
|
66
62
|
const systemPromptByProfile = {
|
|
67
63
|
explore: "You are a fast read-only codebase explorer. Answer concisely. Never write or edit files.",
|
|
68
64
|
plan: "You are a technical architect. Analyze the codebase and produce a clear implementation plan. Do not write or edit files.",
|
|
69
65
|
iosm: "You are an IOSM execution agent. Use IOSM methodology and keep IOSM artifacts synchronized with implementation.",
|
|
70
|
-
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. 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.",
|
|
66
|
+
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. For any metrics (speedup, compliance, conflict counts, quality scores), report only values backed by observed runtime evidence; if evidence is missing, mark the metric as unknown. Do not claim report files/artifacts unless they were produced in this run or verified on disk.",
|
|
71
67
|
iosm_analyst: "You are an IOSM metrics analyst. Analyze .iosm/ artifacts and codebase metrics. Be precise and evidence-based.",
|
|
72
68
|
iosm_verifier: "You are an IOSM verifier. Validate checks and update only required IOSM artifacts with deterministic reasoning.",
|
|
73
69
|
cycle_planner: "You are an IOSM cycle planner. Propose and align cycle goals with measurable outcomes and concrete risks.",
|
|
74
70
|
full: "You are a software engineering agent. Execute the task end-to-end.",
|
|
75
71
|
};
|
|
76
|
-
const
|
|
72
|
+
const writeCapableTools = new Set(["bash", "edit", "write"]);
|
|
73
|
+
const backgroundUnsafeTools = new Set(writeCapableTools);
|
|
74
|
+
const writeCapableProfiles = new Set(Object.keys(toolsByProfile).filter((profileName) => toolsByProfile[profileName].some((tool) => writeCapableTools.has(tool))));
|
|
75
|
+
const backgroundSafeProfiles = Object.keys(toolsByProfile).filter((profileName) => toolsByProfile[profileName].every((tool) => !backgroundUnsafeTools.has(tool)));
|
|
77
76
|
const delegationTagName = "delegate_task";
|
|
78
77
|
class Semaphore {
|
|
79
78
|
constructor(limit) {
|
|
@@ -132,7 +131,7 @@ class Mutex {
|
|
|
132
131
|
}
|
|
133
132
|
const maxParallelFromEnv = parseBoundedInt(process.env.IOSM_SUBAGENT_MAX_PARALLEL, MAX_ORCHESTRATION_PARALLEL, 1, MAX_ORCHESTRATION_PARALLEL);
|
|
134
133
|
const subagentSemaphore = new Semaphore(maxParallelFromEnv);
|
|
135
|
-
const maxDelegationDepthFromEnv = parseBoundedInt(process.env.IOSM_SUBAGENT_MAX_DELEGATION_DEPTH,
|
|
134
|
+
const maxDelegationDepthFromEnv = parseBoundedInt(process.env.IOSM_SUBAGENT_MAX_DELEGATION_DEPTH, 2, 0, MAX_SUBAGENT_DELEGATION_DEPTH);
|
|
136
135
|
const maxDelegationsPerTaskFromEnv = parseBoundedInt(process.env.IOSM_SUBAGENT_MAX_DELEGATIONS_PER_TASK, MAX_SUBAGENT_DELEGATIONS_PER_TASK, 0, MAX_SUBAGENT_DELEGATIONS_PER_TASK);
|
|
137
136
|
const maxDelegatedParallelFromEnv = parseBoundedInt(process.env.IOSM_SUBAGENT_MAX_DELEGATE_PARALLEL, MAX_SUBAGENT_DELEGATE_PARALLEL, 1, MAX_SUBAGENT_DELEGATE_PARALLEL);
|
|
138
137
|
const emptyOutputRetriesFromEnv = parseBoundedInt(process.env.IOSM_SUBAGENT_EMPTY_OUTPUT_RETRIES, 1, 0, 2);
|
|
@@ -179,6 +178,9 @@ function deriveAutoDelegateParallelHint(profile, agentName, hostProfile, descrip
|
|
|
179
178
|
const fileLikeMatches = normalized.match(/\b[A-Za-z0-9_.-]+\.[A-Za-z0-9]{1,8}\b/g) ?? [];
|
|
180
179
|
const listMarkers = text.match(/(?:^|\n)\s*(?:[-*]|\d+[.)])\s+/g)?.length ?? 0;
|
|
181
180
|
const hasCodeBlock = text.includes("```");
|
|
181
|
+
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) ?? [];
|
|
182
|
+
const strongActionSignal = new Set(actionTokenMatches.map((token) => token.toLowerCase())).size >= 2;
|
|
183
|
+
const metaOrchestratorContext = isMetaProfile || isMetaHost;
|
|
182
184
|
let score = 0;
|
|
183
185
|
if (words >= 40) {
|
|
184
186
|
score += 2;
|
|
@@ -196,15 +198,32 @@ function deriveAutoDelegateParallelHint(profile, agentName, hostProfile, descrip
|
|
|
196
198
|
score += 1;
|
|
197
199
|
}
|
|
198
200
|
const referenceCount = pathLikeMatches.length + fileLikeMatches.length;
|
|
201
|
+
const metaNonTrivialSignal = words >= 12 ||
|
|
202
|
+
clauses >= 3 ||
|
|
203
|
+
listMarkers >= 1 ||
|
|
204
|
+
referenceCount >= 1 ||
|
|
205
|
+
hasCodeBlock ||
|
|
206
|
+
(strongActionSignal && words >= 4);
|
|
199
207
|
if (referenceCount >= 3 || (referenceCount >= 1 && words >= 20)) {
|
|
200
208
|
score += 1;
|
|
201
209
|
}
|
|
202
210
|
if (hasCodeBlock) {
|
|
203
211
|
score += 1;
|
|
204
212
|
}
|
|
205
|
-
if (
|
|
206
|
-
//
|
|
207
|
-
|
|
213
|
+
if (metaOrchestratorContext) {
|
|
214
|
+
// In meta orchestration, require delegation pressure for non-trivial prompts
|
|
215
|
+
// even when lexical scoring is still low.
|
|
216
|
+
if (score === 0) {
|
|
217
|
+
if (strongActionSignal && words >= 4) {
|
|
218
|
+
score = 1;
|
|
219
|
+
}
|
|
220
|
+
else if (metaNonTrivialSignal) {
|
|
221
|
+
score = 2;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
else if (score > 0) {
|
|
225
|
+
score += 1;
|
|
226
|
+
}
|
|
208
227
|
}
|
|
209
228
|
if (score >= 6)
|
|
210
229
|
return 10;
|
|
@@ -320,6 +339,9 @@ function buildDelegationProtocolPrompt(depthRemaining, maxDelegations, minDelega
|
|
|
320
339
|
`Keep a brief coordinator note outside the blocks, but do not collapse the full workload into one monolithic answer.`,
|
|
321
340
|
`If safe decomposition is truly impossible, output exactly one line: DELEGATION_IMPOSSIBLE: <precise reason>.`,
|
|
322
341
|
`When shared_memory tools are available, exchange intermediate state through shared_memory_write/shared_memory_read instead of repeating large context.`,
|
|
342
|
+
`Shared-memory protocol: use stable namespaced keys (findings/<stream>, plan/<stream>, risks/<stream>).`,
|
|
343
|
+
`Use scope=run for cross-stream coordination, scope=task for local scratch state, read before overwrite, and use if_version for contested updates.`,
|
|
344
|
+
`Reserve mode=append for timeline/log keys only; avoid append on canonical state keys.`,
|
|
323
345
|
].join("\n");
|
|
324
346
|
}
|
|
325
347
|
return [
|
|
@@ -329,8 +351,270 @@ function buildDelegationProtocolPrompt(depthRemaining, maxDelegations, minDelega
|
|
|
329
351
|
`</${delegationTagName}>`,
|
|
330
352
|
`Only emit blocks when necessary. Keep normal analysis/answer text outside those blocks.`,
|
|
331
353
|
`When shared_memory tools are available, exchange intermediate state through shared_memory_write/shared_memory_read instead of repeating large context.`,
|
|
354
|
+
`Shared-memory protocol: prefer namespaced keys and read-before-write discipline; use CAS (if_version) on shared state updates.`,
|
|
355
|
+
`Reserve mode=append for timeline/log keys only.`,
|
|
332
356
|
].join("\n");
|
|
333
357
|
}
|
|
358
|
+
function truncateForDelegationContext(text, maxChars = 2200) {
|
|
359
|
+
const normalized = normalizeSpacing(text);
|
|
360
|
+
if (normalized.length <= maxChars)
|
|
361
|
+
return normalized;
|
|
362
|
+
return `${normalized.slice(0, Math.max(100, maxChars - 3)).trimEnd()}...`;
|
|
363
|
+
}
|
|
364
|
+
function stripDelegatedSectionHeading(section) {
|
|
365
|
+
return section.replace(/^####\s+[^\n]*\n?/i, "").trim();
|
|
366
|
+
}
|
|
367
|
+
function normalizeDelegatedSectionBody(section) {
|
|
368
|
+
return stripDelegatedSectionHeading(section)
|
|
369
|
+
.toLowerCase()
|
|
370
|
+
.replace(/[`"'*_#~[\](){}<>\\|]/g, " ")
|
|
371
|
+
.replace(/[^\w\s]/g, " ")
|
|
372
|
+
.replace(/\s+/g, " ")
|
|
373
|
+
.trim();
|
|
374
|
+
}
|
|
375
|
+
function detectDuplicateDelegatedSections(sections) {
|
|
376
|
+
const duplicatePairs = [];
|
|
377
|
+
const normalizedSections = sections.map((section) => normalizeDelegatedSectionBody(section));
|
|
378
|
+
for (let index = 0; index < normalizedSections.length; index += 1) {
|
|
379
|
+
const current = normalizedSections[index] ?? "";
|
|
380
|
+
if (current.length < 60)
|
|
381
|
+
continue;
|
|
382
|
+
for (let previous = 0; previous < index; previous += 1) {
|
|
383
|
+
const baseline = normalizedSections[previous] ?? "";
|
|
384
|
+
if (baseline.length < 60)
|
|
385
|
+
continue;
|
|
386
|
+
if (current === baseline) {
|
|
387
|
+
duplicatePairs.push({ duplicate: index + 1, original: previous + 1 });
|
|
388
|
+
break;
|
|
389
|
+
}
|
|
390
|
+
const shorter = current.length <= baseline.length ? current : baseline;
|
|
391
|
+
const longer = current.length > baseline.length ? current : baseline;
|
|
392
|
+
if (shorter.length >= 100 && longer.includes(shorter)) {
|
|
393
|
+
const coverage = shorter.length / Math.max(1, longer.length);
|
|
394
|
+
if (coverage >= 0.92) {
|
|
395
|
+
duplicatePairs.push({ duplicate: index + 1, original: previous + 1 });
|
|
396
|
+
break;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
return {
|
|
402
|
+
duplicates: duplicatePairs.length,
|
|
403
|
+
duplicatePairs,
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
function extractDelegationWorkstreams(text, maxItems) {
|
|
407
|
+
if (maxItems <= 0)
|
|
408
|
+
return [];
|
|
409
|
+
const seen = new Set();
|
|
410
|
+
const pushUnique = (raw) => {
|
|
411
|
+
const cleaned = raw
|
|
412
|
+
.replace(/^[-*]\s+/, "")
|
|
413
|
+
.replace(/^\d+[.)]\s+/, "")
|
|
414
|
+
.replace(/\s+/g, " ")
|
|
415
|
+
.trim();
|
|
416
|
+
if (cleaned.length < 5)
|
|
417
|
+
return;
|
|
418
|
+
const key = cleaned.toLowerCase();
|
|
419
|
+
if (seen.has(key))
|
|
420
|
+
return;
|
|
421
|
+
seen.add(key);
|
|
422
|
+
};
|
|
423
|
+
for (const line of text.split("\n")) {
|
|
424
|
+
if (!/^\s*(?:[-*]|\d+[.)])\s+/.test(line))
|
|
425
|
+
continue;
|
|
426
|
+
pushUnique(line);
|
|
427
|
+
if (seen.size >= maxItems)
|
|
428
|
+
break;
|
|
429
|
+
}
|
|
430
|
+
if (seen.size < maxItems) {
|
|
431
|
+
const fragments = text
|
|
432
|
+
.split(/[\n.;:]+/g)
|
|
433
|
+
.map((fragment) => fragment.trim())
|
|
434
|
+
.filter((fragment) => fragment.length >= 10)
|
|
435
|
+
.slice(0, maxItems * 3);
|
|
436
|
+
for (const fragment of fragments) {
|
|
437
|
+
pushUnique(fragment);
|
|
438
|
+
if (seen.size >= maxItems)
|
|
439
|
+
break;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
return Array.from(seen).slice(0, maxItems);
|
|
443
|
+
}
|
|
444
|
+
function deriveAutoDelegateProfile(baseProfile, description, prompt) {
|
|
445
|
+
const signal = `${description}\n${prompt}`.toLowerCase();
|
|
446
|
+
const writeIntent = /\b(?:implement|fix|patch|refactor|rewrite|edit|update|migrate|change|write|apply)\b/.test(signal);
|
|
447
|
+
if (baseProfile === "full")
|
|
448
|
+
return writeIntent ? "full" : "explore";
|
|
449
|
+
if (baseProfile === "meta")
|
|
450
|
+
return writeIntent ? "full" : "explore";
|
|
451
|
+
if (baseProfile === "iosm")
|
|
452
|
+
return writeIntent ? "full" : "explore";
|
|
453
|
+
if (baseProfile === "iosm_verifier")
|
|
454
|
+
return "iosm_verifier";
|
|
455
|
+
if (baseProfile === "cycle_planner")
|
|
456
|
+
return "cycle_planner";
|
|
457
|
+
if (baseProfile === "plan" || baseProfile === "iosm_analyst")
|
|
458
|
+
return "explore";
|
|
459
|
+
return "explore";
|
|
460
|
+
}
|
|
461
|
+
function pickAutoDelegateAgent(workstream, availableCustomNames) {
|
|
462
|
+
if (availableCustomNames.length === 0)
|
|
463
|
+
return undefined;
|
|
464
|
+
const normalizedWorkstream = workstream.toLowerCase();
|
|
465
|
+
const names = availableCustomNames.map((name) => ({ raw: name, normalized: name.toLowerCase() }));
|
|
466
|
+
const findByHint = (hints) => {
|
|
467
|
+
for (const hint of hints) {
|
|
468
|
+
const exact = names.find((item) => item.normalized === hint);
|
|
469
|
+
if (exact)
|
|
470
|
+
return exact.raw;
|
|
471
|
+
const contains = names.find((item) => item.normalized.includes(hint));
|
|
472
|
+
if (contains)
|
|
473
|
+
return contains.raw;
|
|
474
|
+
}
|
|
475
|
+
return undefined;
|
|
476
|
+
};
|
|
477
|
+
if (/\b(?:test|qa|coverage|verification|regression)\b/.test(normalizedWorkstream)) {
|
|
478
|
+
return findByHint(["qa_test_engineer", "qa", "tester", "verification"]);
|
|
479
|
+
}
|
|
480
|
+
if (/\b(?:ui|ux|design|layout|accessibility)\b/.test(normalizedWorkstream)) {
|
|
481
|
+
return findByHint(["uiux_top_senior", "uiux", "ui", "ux", "design"]);
|
|
482
|
+
}
|
|
483
|
+
if (/\b(?:architecture|codebase|refactor|security|rbac|auth|database|api)\b/.test(normalizedWorkstream)) {
|
|
484
|
+
return findByHint(["codebase_auditor", "architect", "security", "backend"]);
|
|
485
|
+
}
|
|
486
|
+
return undefined;
|
|
487
|
+
}
|
|
488
|
+
function buildAutoDelegationPrompt(input) {
|
|
489
|
+
const objective = truncateForDelegationContext(`${input.rootDescription}\n\n${input.rootPrompt}`);
|
|
490
|
+
return normalizeSpacing([
|
|
491
|
+
`Workstream ${input.ordinal}/${input.total}: ${input.streamTitle}`,
|
|
492
|
+
"Scope:",
|
|
493
|
+
`- Own this stream end-to-end and avoid duplicating sibling streams.`,
|
|
494
|
+
`- Produce concrete findings/changes for this stream only.`,
|
|
495
|
+
"Coordinator objective:",
|
|
496
|
+
objective,
|
|
497
|
+
].join("\n"));
|
|
498
|
+
}
|
|
499
|
+
function uniquifyWorkstreamTitles(titles) {
|
|
500
|
+
const counts = new Map();
|
|
501
|
+
return titles.map((title) => {
|
|
502
|
+
const key = title
|
|
503
|
+
.toLowerCase()
|
|
504
|
+
.replace(/[^a-z0-9]+/g, " ")
|
|
505
|
+
.trim();
|
|
506
|
+
const next = (counts.get(key) ?? 0) + 1;
|
|
507
|
+
counts.set(key, next);
|
|
508
|
+
if (next <= 1)
|
|
509
|
+
return title;
|
|
510
|
+
return `${title} (${next})`;
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
function semanticallyDeduplicateWorkstreamTitles(titles) {
|
|
514
|
+
const kept = [];
|
|
515
|
+
const tokenized = (value) => new Set(value
|
|
516
|
+
.toLowerCase()
|
|
517
|
+
.replace(/[^a-z0-9]+/g, " ")
|
|
518
|
+
.split(/\s+/)
|
|
519
|
+
.map((token) => token.trim())
|
|
520
|
+
.filter((token) => token.length >= 3));
|
|
521
|
+
const jaccard = (left, right) => {
|
|
522
|
+
if (left.size === 0 || right.size === 0)
|
|
523
|
+
return 0;
|
|
524
|
+
let intersection = 0;
|
|
525
|
+
for (const token of left) {
|
|
526
|
+
if (right.has(token))
|
|
527
|
+
intersection += 1;
|
|
528
|
+
}
|
|
529
|
+
const union = left.size + right.size - intersection;
|
|
530
|
+
return union > 0 ? intersection / union : 0;
|
|
531
|
+
};
|
|
532
|
+
for (const candidate of titles) {
|
|
533
|
+
const candidateTokens = tokenized(candidate);
|
|
534
|
+
const duplicate = kept.some((existing) => {
|
|
535
|
+
const existingTokens = tokenized(existing);
|
|
536
|
+
const similarity = jaccard(existingTokens, candidateTokens);
|
|
537
|
+
return similarity >= 0.82;
|
|
538
|
+
});
|
|
539
|
+
if (!duplicate) {
|
|
540
|
+
kept.push(candidate);
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
return kept;
|
|
544
|
+
}
|
|
545
|
+
function deriveAutoSynthLockKey(streamTitle, ordinal) {
|
|
546
|
+
const pathLike = streamTitle.match(/\b(?:[A-Za-z0-9_.-]+\/)+[A-Za-z0-9_.-]+\b/)?.[0];
|
|
547
|
+
if (pathLike) {
|
|
548
|
+
const normalizedPath = pathLike
|
|
549
|
+
.replace(/^[./]+/, "")
|
|
550
|
+
.split("/")
|
|
551
|
+
.filter((segment) => segment.length > 0)
|
|
552
|
+
.slice(0, 2)
|
|
553
|
+
.join("/");
|
|
554
|
+
return `auto-synth:${toSharedMemoryKeySegment(normalizedPath, `stream-${ordinal}`)}`;
|
|
555
|
+
}
|
|
556
|
+
if (/\b(auth|rbac|acl|permission)\b/i.test(streamTitle))
|
|
557
|
+
return "auto-synth:auth";
|
|
558
|
+
if (/\b(db|database|sql|storage|schema)\b/i.test(streamTitle))
|
|
559
|
+
return "auto-synth:data";
|
|
560
|
+
if (/\b(ui|ux|frontend|view|component)\b/i.test(streamTitle))
|
|
561
|
+
return "auto-synth:ui";
|
|
562
|
+
if (/\b(test|qa|verification|coverage)\b/i.test(streamTitle))
|
|
563
|
+
return "auto-synth:test";
|
|
564
|
+
if (/\b(api|gateway|route|http)\b/i.test(streamTitle))
|
|
565
|
+
return "auto-synth:api";
|
|
566
|
+
return `auto-synth:${toSharedMemoryKeySegment(streamTitle, `stream-${ordinal}`)}`;
|
|
567
|
+
}
|
|
568
|
+
function synthesizeDelegationRequests(input) {
|
|
569
|
+
const desiredTotal = Math.max(0, Math.min(input.maxDelegations, input.minDelegationsPreferred));
|
|
570
|
+
const missing = Math.max(0, desiredTotal - input.currentDelegates);
|
|
571
|
+
if (missing <= 0)
|
|
572
|
+
return [];
|
|
573
|
+
const combined = `${input.description}\n${input.prompt}`.trim();
|
|
574
|
+
const extracted = extractDelegationWorkstreams(combined, Math.max(missing, desiredTotal));
|
|
575
|
+
const fallbackByIndex = [
|
|
576
|
+
"Architecture and structure analysis",
|
|
577
|
+
"Behavioral verification and tests",
|
|
578
|
+
"Risk, regressions, and remediation",
|
|
579
|
+
"Integration and dependency checks",
|
|
580
|
+
"Delivery summary and rollout constraints",
|
|
581
|
+
];
|
|
582
|
+
const titles = [];
|
|
583
|
+
for (const stream of extracted) {
|
|
584
|
+
titles.push(stream);
|
|
585
|
+
if (titles.length >= missing)
|
|
586
|
+
break;
|
|
587
|
+
}
|
|
588
|
+
for (let index = 0; titles.length < missing && index < fallbackByIndex.length; index += 1) {
|
|
589
|
+
titles.push(fallbackByIndex[index]);
|
|
590
|
+
}
|
|
591
|
+
while (titles.length < missing) {
|
|
592
|
+
titles.push(`Independent workstream ${titles.length + 1}`);
|
|
593
|
+
}
|
|
594
|
+
const semanticallyDeduped = semanticallyDeduplicateWorkstreamTitles(titles);
|
|
595
|
+
while (semanticallyDeduped.length < missing) {
|
|
596
|
+
semanticallyDeduped.push(`Independent workstream ${semanticallyDeduped.length + 1}`);
|
|
597
|
+
}
|
|
598
|
+
const uniqueTitles = uniquifyWorkstreamTitles(semanticallyDeduped);
|
|
599
|
+
const defaultProfile = deriveAutoDelegateProfile(input.baseProfile, input.description, input.prompt);
|
|
600
|
+
return uniqueTitles.map((streamTitle, index) => ({
|
|
601
|
+
description: `Auto: ${streamTitle}`,
|
|
602
|
+
profile: defaultProfile,
|
|
603
|
+
agent: pickAutoDelegateAgent(streamTitle, input.availableCustomNames),
|
|
604
|
+
prompt: buildAutoDelegationPrompt({
|
|
605
|
+
streamTitle,
|
|
606
|
+
rootDescription: input.description,
|
|
607
|
+
rootPrompt: input.prompt,
|
|
608
|
+
ordinal: input.currentDelegates + index + 1,
|
|
609
|
+
total: input.currentDelegates + uniqueTitles.length,
|
|
610
|
+
}),
|
|
611
|
+
cwd: undefined,
|
|
612
|
+
lockKey: writeCapableProfiles.has(defaultProfile) ? deriveAutoSynthLockKey(streamTitle, index + 1) : undefined,
|
|
613
|
+
model: undefined,
|
|
614
|
+
isolation: undefined,
|
|
615
|
+
dependsOn: undefined,
|
|
616
|
+
}));
|
|
617
|
+
}
|
|
334
618
|
function withDelegationPrompt(basePrompt, depthRemaining, maxDelegations, minDelegationsPreferred = 0) {
|
|
335
619
|
const protocol = buildDelegationProtocolPrompt(depthRemaining, maxDelegations, minDelegationsPreferred);
|
|
336
620
|
return `${basePrompt}\n\n${protocol}`;
|
|
@@ -348,10 +632,78 @@ function buildSharedMemoryGuidance(runId, taskId) {
|
|
|
348
632
|
"Guidelines:",
|
|
349
633
|
"- Use scope=run for cross-agent data and scope=task for task-local notes.",
|
|
350
634
|
"- Keep entries compact and key-based (for example: findings/auth, plan/step-1, risks/session).",
|
|
635
|
+
"- Prefer one canonical key per stream and deduplicate updates; avoid redundant writes in loops.",
|
|
351
636
|
"- Read before overwrite when collaborating on the same key.",
|
|
637
|
+
"- Use if_version CAS for contested updates on shared keys.",
|
|
638
|
+
"- Use mode=append only for log/timeline keys; use mode=set for canonical state.",
|
|
352
639
|
"[/SHARED_MEMORY]",
|
|
353
640
|
].join("\n");
|
|
354
641
|
}
|
|
642
|
+
function toSharedMemoryKeySegment(raw, fallback) {
|
|
643
|
+
const normalized = (raw ?? "")
|
|
644
|
+
.trim()
|
|
645
|
+
.toLowerCase()
|
|
646
|
+
.replace(/[^a-z0-9._-]+/g, "-")
|
|
647
|
+
.replace(/-+/g, "-")
|
|
648
|
+
.replace(/^-+|-+$/g, "");
|
|
649
|
+
if (!normalized)
|
|
650
|
+
return fallback;
|
|
651
|
+
return normalized.slice(0, 64);
|
|
652
|
+
}
|
|
653
|
+
function buildTaskPlanSharedMemoryKey(taskId) {
|
|
654
|
+
return `plan/${toSharedMemoryKeySegment(taskId, "task")}`;
|
|
655
|
+
}
|
|
656
|
+
function buildDelegateFindingSharedMemoryKey(taskId, delegateLabel) {
|
|
657
|
+
return `findings/${toSharedMemoryKeySegment(taskId, "task")}/${toSharedMemoryKeySegment(delegateLabel, "stream")}`;
|
|
658
|
+
}
|
|
659
|
+
function extractClaimCandidates(text, maxItems = 3) {
|
|
660
|
+
const matches = text.match(/\b(?:[A-Za-z0-9_.-]+\/)+[A-Za-z0-9_.-]+\b/g) ?? [];
|
|
661
|
+
const normalized = new Set();
|
|
662
|
+
for (const match of matches) {
|
|
663
|
+
const cleaned = match
|
|
664
|
+
.replace(/^[./]+/, "")
|
|
665
|
+
.replace(/\/+/g, "/")
|
|
666
|
+
.trim();
|
|
667
|
+
if (!cleaned)
|
|
668
|
+
continue;
|
|
669
|
+
normalized.add(cleaned);
|
|
670
|
+
if (normalized.size >= maxItems)
|
|
671
|
+
break;
|
|
672
|
+
}
|
|
673
|
+
return Array.from(normalized);
|
|
674
|
+
}
|
|
675
|
+
function buildClaimKey(pathLike) {
|
|
676
|
+
const segments = pathLike
|
|
677
|
+
.split("/")
|
|
678
|
+
.map((segment) => toSharedMemoryKeySegment(segment, "segment"))
|
|
679
|
+
.filter((segment) => segment.length > 0);
|
|
680
|
+
if (segments.length === 0) {
|
|
681
|
+
return "claims/unknown";
|
|
682
|
+
}
|
|
683
|
+
return `claims/${segments.slice(0, 6).join("/")}`;
|
|
684
|
+
}
|
|
685
|
+
function buildDelegateCoordinationGuidance(input) {
|
|
686
|
+
const planKey = buildTaskPlanSharedMemoryKey(input.taskId);
|
|
687
|
+
const findingKey = buildDelegateFindingSharedMemoryKey(input.taskId, input.delegateLabel);
|
|
688
|
+
return [
|
|
689
|
+
"[DELEGATE_COORDINATION]",
|
|
690
|
+
`delegate_label: ${input.delegateLabel}`,
|
|
691
|
+
`delegate_description: ${input.delegateDescription}`,
|
|
692
|
+
`read_first_key: ${planKey}`,
|
|
693
|
+
`publish_key: ${findingKey}`,
|
|
694
|
+
"Before heavy repository reads, check current coordination state via shared_memory_read.",
|
|
695
|
+
"Use claims/<path> run-scoped keys with CAS (if_version) to announce file ownership and reduce duplicate reads.",
|
|
696
|
+
"Before responding, publish concise stream findings via shared_memory_write.",
|
|
697
|
+
"Keep ownership strict: do not duplicate sibling streams.",
|
|
698
|
+
"[/DELEGATE_COORDINATION]",
|
|
699
|
+
].join("\n");
|
|
700
|
+
}
|
|
701
|
+
function createSharedMemoryExcerpt(value, maxChars = 1200) {
|
|
702
|
+
const normalized = normalizeSpacing(value);
|
|
703
|
+
if (normalized.length <= maxChars)
|
|
704
|
+
return normalized;
|
|
705
|
+
return `${normalized.slice(0, Math.max(120, maxChars - 3)).trimEnd()}...`;
|
|
706
|
+
}
|
|
355
707
|
function parseDelegationRequests(output, maxRequests) {
|
|
356
708
|
const requests = [];
|
|
357
709
|
const warnings = [];
|
|
@@ -366,19 +718,21 @@ function parseDelegationRequests(output, maxRequests) {
|
|
|
366
718
|
return "";
|
|
367
719
|
}
|
|
368
720
|
const attrs = {};
|
|
369
|
-
for (const match of attrsRaw.matchAll(/([A-Za-z_][A-Za-z0-9_-]*)
|
|
370
|
-
attrs[match[1].toLowerCase()] = match[2];
|
|
721
|
+
for (const match of attrsRaw.matchAll(/([A-Za-z_][A-Za-z0-9_-]*)\s*=\s*(?:"([^"]*)"|'([^']*)')/g)) {
|
|
722
|
+
attrs[match[1].toLowerCase()] = (match[2] ?? match[3] ?? "").trim();
|
|
371
723
|
}
|
|
372
724
|
const prompt = normalizeSpacing(bodyRaw ?? "");
|
|
373
725
|
if (!prompt) {
|
|
374
726
|
warnings.push(`Ignored delegation block with empty prompt.`);
|
|
375
727
|
return "";
|
|
376
728
|
}
|
|
377
|
-
const
|
|
378
|
-
if (!
|
|
379
|
-
warnings.push(`Ignored delegation block with
|
|
729
|
+
const profileRaw = (attrs.profile ?? "explore").trim();
|
|
730
|
+
if (!profileRaw) {
|
|
731
|
+
warnings.push(`Ignored delegation block with empty profile.`);
|
|
380
732
|
return "";
|
|
381
733
|
}
|
|
734
|
+
const normalizedProfile = profileRaw.toLowerCase();
|
|
735
|
+
const profile = isValidProfileName(normalizedProfile) ? normalizedProfile : profileRaw;
|
|
382
736
|
const isolationRaw = (attrs.isolation ?? "").trim().toLowerCase();
|
|
383
737
|
const isolation = isolationRaw === "worktree" ? "worktree" : isolationRaw === "none" ? "none" : undefined;
|
|
384
738
|
if (isolationRaw && !isolation) {
|
|
@@ -409,6 +763,9 @@ function parseDelegationRequests(output, maxRequests) {
|
|
|
409
763
|
cleanedOutput: normalizeSpacing(cleaned),
|
|
410
764
|
};
|
|
411
765
|
}
|
|
766
|
+
function isBackgroundSafeToolset(tools) {
|
|
767
|
+
return tools.every((toolName) => !backgroundUnsafeTools.has(toolName));
|
|
768
|
+
}
|
|
412
769
|
function getCwdLockKey(cwd) {
|
|
413
770
|
// Normalize lock key to keep behavior consistent across path aliases.
|
|
414
771
|
return path.resolve(cwd).toLowerCase();
|
|
@@ -694,12 +1051,26 @@ export function createTaskTool(cwd, runner, options) {
|
|
|
694
1051
|
const resolveCustom = (name) => {
|
|
695
1052
|
if (!name || !options?.resolveCustomSubagent)
|
|
696
1053
|
return undefined;
|
|
697
|
-
|
|
1054
|
+
const trimmed = name.trim();
|
|
1055
|
+
if (!trimmed)
|
|
1056
|
+
return undefined;
|
|
1057
|
+
const resolved = options.resolveCustomSubagent(trimmed);
|
|
1058
|
+
if (resolved)
|
|
1059
|
+
return resolved;
|
|
1060
|
+
const lowered = trimmed.toLowerCase();
|
|
1061
|
+
if (lowered !== trimmed) {
|
|
1062
|
+
return options.resolveCustomSubagent(lowered);
|
|
1063
|
+
}
|
|
1064
|
+
return undefined;
|
|
698
1065
|
};
|
|
699
1066
|
let normalizedAgentName = agentName?.trim() || undefined;
|
|
700
1067
|
let customSubagent = resolveCustom(normalizedAgentName);
|
|
701
|
-
|
|
1068
|
+
const requestedProfileRaw = profile?.trim() || undefined;
|
|
702
1069
|
const normalizedHostProfile = options?.getHostProfileName?.()?.trim().toLowerCase() ?? options?.hostProfileName?.trim().toLowerCase();
|
|
1070
|
+
const hostProfileFallback = normalizedHostProfile && isValidProfileName(normalizedHostProfile) ? normalizedHostProfile : "full";
|
|
1071
|
+
let normalizedProfile = requestedProfileRaw?.toLowerCase() ||
|
|
1072
|
+
customSubagent?.profile?.trim().toLowerCase() ||
|
|
1073
|
+
hostProfileFallback;
|
|
703
1074
|
if (normalizedAgentName && !customSubagent) {
|
|
704
1075
|
const available = availableCustomNames.length > 0 ? ` Available custom agents: ${availableCustomNames.join(", ")}.` : "";
|
|
705
1076
|
throw new Error(`Unknown subagent: ${normalizedAgentName}.${available}`);
|
|
@@ -713,17 +1084,24 @@ export function createTaskTool(cwd, runner, options) {
|
|
|
713
1084
|
normalizedProfile = (profileAsAgent.profile ?? "full").trim().toLowerCase();
|
|
714
1085
|
}
|
|
715
1086
|
}
|
|
716
|
-
if (!
|
|
717
|
-
normalizedProfile
|
|
1087
|
+
if (!customSubagent && !isValidProfileName(normalizedProfile)) {
|
|
1088
|
+
throw new Error(`Unknown profile "${requestedProfileRaw ?? normalizedProfile}". Valid profiles: ${Object.keys(toolsByProfile).join(", ")}.`);
|
|
718
1089
|
}
|
|
719
|
-
const
|
|
1090
|
+
const effectiveProfileCandidate = (customSubagent?.profile ?? normalizedProfile).trim().toLowerCase();
|
|
1091
|
+
if (!isValidProfileName(effectiveProfileCandidate)) {
|
|
1092
|
+
throw new Error(`Invalid resolved profile "${effectiveProfileCandidate}". Valid profiles: ${Object.keys(toolsByProfile).join(", ")}.`);
|
|
1093
|
+
}
|
|
1094
|
+
const effectiveProfile = effectiveProfileCandidate;
|
|
720
1095
|
let tools = customSubagent?.tools
|
|
721
1096
|
? [...customSubagent.tools]
|
|
722
|
-
: [...
|
|
1097
|
+
: [...toolsByProfile[effectiveProfile]];
|
|
723
1098
|
if (customSubagent?.disallowedTools?.length) {
|
|
724
1099
|
const blocked = new Set(customSubagent.disallowedTools);
|
|
725
1100
|
tools = tools.filter((tool) => !blocked.has(tool));
|
|
726
1101
|
}
|
|
1102
|
+
if (isReadOnlyProfileName(normalizedHostProfile) && tools.some((tool) => writeCapableTools.has(tool))) {
|
|
1103
|
+
throw new Error(`Host profile "${normalizedHostProfile}" is read-only. Switch to full/meta/iosm to launch write-capable subtasks.`);
|
|
1104
|
+
}
|
|
727
1105
|
const delegationDepth = maxDelegationDepthFromEnv;
|
|
728
1106
|
const requestedDelegateParallelHint = typeof delegateParallelHint === "number" && Number.isInteger(delegateParallelHint)
|
|
729
1107
|
? Math.max(1, Math.min(MAX_SUBAGENT_DELEGATE_PARALLEL, delegateParallelHint))
|
|
@@ -735,13 +1113,22 @@ export function createTaskTool(cwd, runner, options) {
|
|
|
735
1113
|
const effectiveDelegationDepth = effectiveProfile === "meta" || normalizedHostProfile === "meta" || normalizedAgentName?.toLowerCase().includes("orchestrator")
|
|
736
1114
|
? Math.max(delegationDepth, 2)
|
|
737
1115
|
: delegationDepth;
|
|
1116
|
+
const orchestratedRunContext = !!(orchestrationRunId && orchestrationTaskId);
|
|
1117
|
+
const strictDelegationContract = effectiveProfile === "meta" ||
|
|
1118
|
+
normalizedHostProfile === "meta" ||
|
|
1119
|
+
normalizedAgentName?.toLowerCase().includes("orchestrator") ||
|
|
1120
|
+
(orchestratedRunContext && (effectiveDelegateParallelHint ?? 0) >= 2);
|
|
738
1121
|
let effectiveMaxDelegations = Math.max(0, Math.min(maxDelegationsPerTaskFromEnv, effectiveDelegateParallelHint ?? maxDelegationsPerTaskFromEnv));
|
|
739
1122
|
let effectiveMaxDelegateParallel = Math.max(1, Math.min(maxDelegatedParallelFromEnv, effectiveDelegateParallelHint ?? maxDelegatedParallelFromEnv));
|
|
740
|
-
const
|
|
1123
|
+
const isMetaDelegationContext = effectiveProfile === "meta" || normalizedHostProfile === "meta";
|
|
1124
|
+
const preferredDelegationFloorBase = isMetaDelegationContext ? 3 : 2;
|
|
1125
|
+
const preferredDelegationFloorMin = isMetaDelegationContext ? 2 : 1;
|
|
1126
|
+
const metaDelegationCapacityFloor = 3;
|
|
1127
|
+
const preferredDelegationFloor = Math.max(preferredDelegationFloorMin, Math.min(preferredDelegationFloorBase, effectiveDelegateParallelHint ?? preferredDelegationFloorBase));
|
|
741
1128
|
const applyMetaDelegationFloor = requestedDelegateParallelHint === undefined &&
|
|
742
1129
|
(effectiveProfile === "meta" || normalizedHostProfile === "meta");
|
|
743
1130
|
if (applyMetaDelegationFloor) {
|
|
744
|
-
effectiveMaxDelegations = Math.max(effectiveMaxDelegations, Math.min(maxDelegationsPerTaskFromEnv,
|
|
1131
|
+
effectiveMaxDelegations = Math.max(effectiveMaxDelegations, Math.min(maxDelegationsPerTaskFromEnv, metaDelegationCapacityFloor));
|
|
745
1132
|
effectiveMaxDelegateParallel = Math.max(effectiveMaxDelegateParallel, Math.min(maxDelegatedParallelFromEnv, preferredDelegationFloor));
|
|
746
1133
|
}
|
|
747
1134
|
const minDelegationsPreferred = (effectiveDelegateParallelHint ?? 0) >= 2 && effectiveMaxDelegations >= 2
|
|
@@ -764,8 +1151,10 @@ export function createTaskTool(cwd, runner, options) {
|
|
|
764
1151
|
if (!existsSync(requestedSubagentCwd) || !statSync(requestedSubagentCwd).isDirectory()) {
|
|
765
1152
|
throw new Error(`Subagent cwd does not exist or is not a directory: ${requestedSubagentCwd}`);
|
|
766
1153
|
}
|
|
767
|
-
if (runInBackground &&
|
|
768
|
-
throw new Error(`Background policy violation: profile "${effectiveProfile}"
|
|
1154
|
+
if (runInBackground && !isBackgroundSafeToolset(tools)) {
|
|
1155
|
+
throw new Error(`Background policy violation: profile "${effectiveProfile}" has mutable tools (${tools
|
|
1156
|
+
.filter((toolName) => backgroundUnsafeTools.has(toolName))
|
|
1157
|
+
.join(", ")}). Background mode requires read-only toolsets. Safe baseline profiles: ${backgroundSafeProfiles.join(", ")}.`);
|
|
769
1158
|
}
|
|
770
1159
|
const useWorktree = isolation === "worktree";
|
|
771
1160
|
const queuedAt = Date.now();
|
|
@@ -821,6 +1210,42 @@ export function createTaskTool(cwd, runner, options) {
|
|
|
821
1210
|
let subagentCwd = requestedSubagentCwd;
|
|
822
1211
|
let worktreePath;
|
|
823
1212
|
let runStats;
|
|
1213
|
+
const heldWriteLocks = new Map();
|
|
1214
|
+
const acquireLocalWriteLock = async (rawLockKey) => {
|
|
1215
|
+
const trimmed = rawLockKey?.trim();
|
|
1216
|
+
if (!trimmed)
|
|
1217
|
+
return undefined;
|
|
1218
|
+
const normalizedKey = getCwdLockKey(trimmed);
|
|
1219
|
+
const existing = heldWriteLocks.get(normalizedKey);
|
|
1220
|
+
if (existing) {
|
|
1221
|
+
existing.count += 1;
|
|
1222
|
+
return () => {
|
|
1223
|
+
const current = heldWriteLocks.get(normalizedKey);
|
|
1224
|
+
if (!current)
|
|
1225
|
+
return;
|
|
1226
|
+
current.count -= 1;
|
|
1227
|
+
if (current.count <= 0) {
|
|
1228
|
+
heldWriteLocks.delete(normalizedKey);
|
|
1229
|
+
current.release();
|
|
1230
|
+
cleanupWriteLock(trimmed);
|
|
1231
|
+
}
|
|
1232
|
+
};
|
|
1233
|
+
}
|
|
1234
|
+
const lock = getOrCreateWriteLock(trimmed);
|
|
1235
|
+
const release = await lock.acquire();
|
|
1236
|
+
heldWriteLocks.set(normalizedKey, { count: 1, release });
|
|
1237
|
+
return () => {
|
|
1238
|
+
const current = heldWriteLocks.get(normalizedKey);
|
|
1239
|
+
if (!current)
|
|
1240
|
+
return;
|
|
1241
|
+
current.count -= 1;
|
|
1242
|
+
if (current.count <= 0) {
|
|
1243
|
+
heldWriteLocks.delete(normalizedKey);
|
|
1244
|
+
current.release();
|
|
1245
|
+
cleanupWriteLock(trimmed);
|
|
1246
|
+
}
|
|
1247
|
+
};
|
|
1248
|
+
};
|
|
824
1249
|
try {
|
|
825
1250
|
throwIfAborted();
|
|
826
1251
|
if (orchestrationRunId && orchestrationTaskId) {
|
|
@@ -882,8 +1307,7 @@ export function createTaskTool(cwd, runner, options) {
|
|
|
882
1307
|
// Parallel orchestration should remain truly parallel by default.
|
|
883
1308
|
// Serialize write-capable agents only when an explicit lock_key is provided.
|
|
884
1309
|
if (explicitRootLockKey) {
|
|
885
|
-
|
|
886
|
-
releaseWriteLock = await lock.acquire();
|
|
1310
|
+
releaseWriteLock = await acquireLocalWriteLock(explicitRootLockKey);
|
|
887
1311
|
}
|
|
888
1312
|
}
|
|
889
1313
|
if (useWorktree) {
|
|
@@ -923,6 +1347,131 @@ export function createTaskTool(cwd, runner, options) {
|
|
|
923
1347
|
taskId: sharedMemoryTaskId,
|
|
924
1348
|
profile: effectiveProfile,
|
|
925
1349
|
};
|
|
1350
|
+
const publishTaskCoordinationPlan = async () => {
|
|
1351
|
+
const key = buildTaskPlanSharedMemoryKey(sharedMemoryTaskId);
|
|
1352
|
+
const payload = JSON.stringify({
|
|
1353
|
+
taskId: sharedMemoryTaskId,
|
|
1354
|
+
description,
|
|
1355
|
+
profile: effectiveProfile,
|
|
1356
|
+
objective: createSharedMemoryExcerpt(prompt, 900),
|
|
1357
|
+
});
|
|
1358
|
+
try {
|
|
1359
|
+
await writeSharedMemory(rootSharedMemoryContext, {
|
|
1360
|
+
key,
|
|
1361
|
+
value: payload,
|
|
1362
|
+
scope: "run",
|
|
1363
|
+
mode: "set",
|
|
1364
|
+
}, _signal);
|
|
1365
|
+
}
|
|
1366
|
+
catch (error) {
|
|
1367
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1368
|
+
delegationWarnings.push(`Shared memory plan publish skipped: ${message}`);
|
|
1369
|
+
}
|
|
1370
|
+
};
|
|
1371
|
+
const publishDelegateFinding = async (input) => {
|
|
1372
|
+
const key = buildDelegateFindingSharedMemoryKey(sharedMemoryTaskId, input.delegateLabel);
|
|
1373
|
+
const payload = JSON.stringify({
|
|
1374
|
+
taskId: sharedMemoryTaskId,
|
|
1375
|
+
delegate: input.delegateLabel,
|
|
1376
|
+
description: input.delegateDescription,
|
|
1377
|
+
profile: input.delegateProfile,
|
|
1378
|
+
status: input.status,
|
|
1379
|
+
summary: createSharedMemoryExcerpt(input.content, 1000),
|
|
1380
|
+
});
|
|
1381
|
+
try {
|
|
1382
|
+
await writeSharedMemory({
|
|
1383
|
+
rootCwd: cwd,
|
|
1384
|
+
runId: sharedMemoryRunId,
|
|
1385
|
+
taskId: sharedMemoryTaskId,
|
|
1386
|
+
delegateId: input.delegateLabel,
|
|
1387
|
+
profile: input.delegateProfile,
|
|
1388
|
+
}, {
|
|
1389
|
+
key,
|
|
1390
|
+
value: payload,
|
|
1391
|
+
scope: "run",
|
|
1392
|
+
mode: "set",
|
|
1393
|
+
}, _signal);
|
|
1394
|
+
}
|
|
1395
|
+
catch (error) {
|
|
1396
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1397
|
+
delegationWarnings.push(`Shared memory finding publish skipped for delegate ${input.delegateLabel}: ${message}`);
|
|
1398
|
+
}
|
|
1399
|
+
};
|
|
1400
|
+
const publishStreamClaims = async (input) => {
|
|
1401
|
+
const claimPaths = extractClaimCandidates(`${input.description}\n${input.promptText}`, 3);
|
|
1402
|
+
if (claimPaths.length === 0)
|
|
1403
|
+
return;
|
|
1404
|
+
for (const claimPath of claimPaths) {
|
|
1405
|
+
const key = buildClaimKey(claimPath);
|
|
1406
|
+
let lastError;
|
|
1407
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
1408
|
+
try {
|
|
1409
|
+
const snapshot = await readSharedMemory(rootSharedMemoryContext, {
|
|
1410
|
+
scope: "run",
|
|
1411
|
+
key,
|
|
1412
|
+
includeValues: true,
|
|
1413
|
+
}, _signal);
|
|
1414
|
+
const current = snapshot.items[0];
|
|
1415
|
+
if (!current) {
|
|
1416
|
+
await writeSharedMemory(rootSharedMemoryContext, {
|
|
1417
|
+
key,
|
|
1418
|
+
value: JSON.stringify({
|
|
1419
|
+
path: claimPath,
|
|
1420
|
+
owners: [input.owner],
|
|
1421
|
+
updatedAt: new Date().toISOString(),
|
|
1422
|
+
}),
|
|
1423
|
+
scope: "run",
|
|
1424
|
+
mode: "set",
|
|
1425
|
+
}, _signal);
|
|
1426
|
+
lastError = undefined;
|
|
1427
|
+
break;
|
|
1428
|
+
}
|
|
1429
|
+
let existingOwners = [];
|
|
1430
|
+
if (current.value) {
|
|
1431
|
+
try {
|
|
1432
|
+
const parsed = JSON.parse(current.value);
|
|
1433
|
+
if (Array.isArray(parsed.owners)) {
|
|
1434
|
+
existingOwners = parsed.owners
|
|
1435
|
+
.filter((value) => typeof value === "string" && value.trim().length > 0)
|
|
1436
|
+
.slice(0, 12);
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
catch {
|
|
1440
|
+
// tolerate malformed payload and overwrite with normalized shape
|
|
1441
|
+
}
|
|
1442
|
+
}
|
|
1443
|
+
if (existingOwners.includes(input.owner)) {
|
|
1444
|
+
lastError = undefined;
|
|
1445
|
+
break;
|
|
1446
|
+
}
|
|
1447
|
+
const nextOwners = [...existingOwners, input.owner].slice(0, 12);
|
|
1448
|
+
await writeSharedMemory(rootSharedMemoryContext, {
|
|
1449
|
+
key,
|
|
1450
|
+
value: JSON.stringify({
|
|
1451
|
+
path: claimPath,
|
|
1452
|
+
owners: nextOwners,
|
|
1453
|
+
updatedAt: new Date().toISOString(),
|
|
1454
|
+
}),
|
|
1455
|
+
scope: "run",
|
|
1456
|
+
mode: "set",
|
|
1457
|
+
ifVersion: current.version,
|
|
1458
|
+
}, _signal);
|
|
1459
|
+
lastError = undefined;
|
|
1460
|
+
break;
|
|
1461
|
+
}
|
|
1462
|
+
catch (error) {
|
|
1463
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1464
|
+
lastError = message;
|
|
1465
|
+
if (!/version mismatch/i.test(message)) {
|
|
1466
|
+
break;
|
|
1467
|
+
}
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1470
|
+
if (lastError) {
|
|
1471
|
+
delegationWarnings.push(`Shared memory claim publish skipped (${key}) for ${input.owner}: ${lastError}`);
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
1474
|
+
};
|
|
926
1475
|
try {
|
|
927
1476
|
const runRootPass = async (runPrompt) => {
|
|
928
1477
|
let emptyAttempt = 0;
|
|
@@ -1023,6 +1572,12 @@ export function createTaskTool(cwd, runner, options) {
|
|
|
1023
1572
|
activeTool: undefined,
|
|
1024
1573
|
});
|
|
1025
1574
|
}
|
|
1575
|
+
await publishTaskCoordinationPlan();
|
|
1576
|
+
await publishStreamClaims({
|
|
1577
|
+
owner: "root",
|
|
1578
|
+
description,
|
|
1579
|
+
promptText: prompt,
|
|
1580
|
+
});
|
|
1026
1581
|
const firstPass = await runRootPass(rootPrompt);
|
|
1027
1582
|
output = firstPass.output;
|
|
1028
1583
|
subagentSessionId = firstPass.sessionId;
|
|
@@ -1052,8 +1607,33 @@ export function createTaskTool(cwd, runner, options) {
|
|
|
1052
1607
|
runStats = secondPass.stats ?? runStats;
|
|
1053
1608
|
parsedDelegation = parseDelegationRequests(output, effectiveDelegationDepth > 0 ? effectiveMaxDelegations : 0);
|
|
1054
1609
|
}
|
|
1610
|
+
if (minDelegationsPreferred > 0 &&
|
|
1611
|
+
parsedDelegation.requests.length === 0 &&
|
|
1612
|
+
strictDelegationContract) {
|
|
1613
|
+
const impossibleMatch = output.match(/^\s*DELEGATION_IMPOSSIBLE\s*:\s*(.+)$/im);
|
|
1614
|
+
if (!impossibleMatch) {
|
|
1615
|
+
const synthesizedRequests = synthesizeDelegationRequests({
|
|
1616
|
+
description,
|
|
1617
|
+
prompt,
|
|
1618
|
+
baseProfile: effectiveProfile,
|
|
1619
|
+
currentDelegates: parsedDelegation.requests.length,
|
|
1620
|
+
minDelegationsPreferred,
|
|
1621
|
+
maxDelegations: effectiveMaxDelegations,
|
|
1622
|
+
availableCustomNames,
|
|
1623
|
+
});
|
|
1624
|
+
if (synthesizedRequests.length > 0) {
|
|
1625
|
+
parsedDelegation.requests.push(...synthesizedRequests);
|
|
1626
|
+
delegationWarnings.push(`Delegation auto-fanout: synthesized ${synthesizedRequests.length} delegate(s) to satisfy parallelism contract.`);
|
|
1627
|
+
}
|
|
1628
|
+
}
|
|
1629
|
+
}
|
|
1055
1630
|
if (minDelegationsPreferred > 0 && parsedDelegation.requests.length < minDelegationsPreferred) {
|
|
1056
|
-
const
|
|
1631
|
+
const impossibleMatch = output.match(/^\s*DELEGATION_IMPOSSIBLE\s*:\s*(.+)$/im);
|
|
1632
|
+
const impossibleReason = impossibleMatch?.[1]?.trim() ?? "not provided";
|
|
1633
|
+
if (strictDelegationContract && parsedDelegation.requests.length === 0 && !impossibleMatch) {
|
|
1634
|
+
throw new Error(`Delegation contract violated: expected >=${minDelegationsPreferred} delegates, got ${parsedDelegation.requests.length}. ` +
|
|
1635
|
+
`Provide nested <delegate_task> fan-out or an explicit "DELEGATION_IMPOSSIBLE: <reason>" line.`);
|
|
1636
|
+
}
|
|
1057
1637
|
delegationWarnings.push(`Delegation fallback: kept single-agent execution (preferred >=${minDelegationsPreferred} delegates, got ${parsedDelegation.requests.length}). Reason: ${impossibleReason}.`);
|
|
1058
1638
|
}
|
|
1059
1639
|
output = parsedDelegation.cleanedOutput;
|
|
@@ -1109,6 +1689,13 @@ export function createTaskTool(cwd, runner, options) {
|
|
|
1109
1689
|
}
|
|
1110
1690
|
const causeLabel = cause ? ` [cause=${cause}]` : "";
|
|
1111
1691
|
delegatedSections[index] = `#### ${index + 1}. ${request.description} (${formatDelegateTarget(request)})\nERROR${causeLabel}: ${message}`;
|
|
1692
|
+
void publishDelegateFinding({
|
|
1693
|
+
delegateLabel: String(index + 1),
|
|
1694
|
+
delegateDescription: request.description,
|
|
1695
|
+
delegateProfile: formatDelegateTarget(request),
|
|
1696
|
+
status: "failed",
|
|
1697
|
+
content: message,
|
|
1698
|
+
});
|
|
1112
1699
|
emitProgress({
|
|
1113
1700
|
kind: "subagent_progress",
|
|
1114
1701
|
phase: "running",
|
|
@@ -1127,25 +1714,67 @@ export function createTaskTool(cwd, runner, options) {
|
|
|
1127
1714
|
return { sections: [], warnings: [] };
|
|
1128
1715
|
}
|
|
1129
1716
|
const nestedWarnings = [];
|
|
1130
|
-
const
|
|
1131
|
-
|
|
1717
|
+
const sectionsByIndex = new Array(requests.length);
|
|
1718
|
+
const statuses = Array.from({ length: requests.length }, () => "pending");
|
|
1719
|
+
const totalNested = requests.length;
|
|
1720
|
+
const normalizedDependsOn = requests.map((request, index) => {
|
|
1721
|
+
const current = index + 1;
|
|
1722
|
+
const raw = request.dependsOn ?? [];
|
|
1723
|
+
const unique = new Set();
|
|
1724
|
+
for (const dep of raw) {
|
|
1725
|
+
if (!Number.isInteger(dep) || dep <= 0 || dep > totalNested || dep === current) {
|
|
1726
|
+
nestedWarnings.push(`Nested delegated task ${lineage}${current} has invalid depends_on reference "${dep}" and it was ignored.`);
|
|
1727
|
+
continue;
|
|
1728
|
+
}
|
|
1729
|
+
unique.add(dep);
|
|
1730
|
+
}
|
|
1731
|
+
return Array.from(unique).sort((a, b) => a - b);
|
|
1732
|
+
});
|
|
1733
|
+
const statusOf = (index) => statuses[index] ?? "pending";
|
|
1734
|
+
const markNestedFailed = (nestedIndex, requestLabel, nestedRequest, nestedProfileLabel, message, cause) => {
|
|
1735
|
+
statuses[nestedIndex] = "failed";
|
|
1736
|
+
recordFailureCause(cause);
|
|
1737
|
+
delegatedTasks += 1;
|
|
1738
|
+
delegatedFailed += 1;
|
|
1739
|
+
sectionsByIndex[nestedIndex] =
|
|
1740
|
+
`###### ${requestLabel}. ${nestedRequest.description} (${nestedProfileLabel})\nERROR [cause=${cause}]: ${message}`;
|
|
1741
|
+
void publishDelegateFinding({
|
|
1742
|
+
delegateLabel: requestLabel,
|
|
1743
|
+
delegateDescription: nestedRequest.description,
|
|
1744
|
+
delegateProfile: nestedProfileLabel,
|
|
1745
|
+
status: "failed",
|
|
1746
|
+
content: message,
|
|
1747
|
+
});
|
|
1748
|
+
};
|
|
1749
|
+
const runNestedDelegate = async (nestedIndex) => {
|
|
1750
|
+
const nestedRequest = requests[nestedIndex];
|
|
1132
1751
|
const requestLabel = `${lineage}${nestedIndex + 1}`;
|
|
1133
|
-
|
|
1134
|
-
|
|
1752
|
+
statuses[nestedIndex] = "running";
|
|
1753
|
+
let requestedNestedAgent = nestedRequest.agent?.trim() || undefined;
|
|
1754
|
+
let nestedCustomSubagent = resolveCustom(requestedNestedAgent);
|
|
1755
|
+
if (!nestedCustomSubagent && !requestedNestedAgent) {
|
|
1756
|
+
const profileAsAgent = resolveCustom(nestedRequest.profile);
|
|
1757
|
+
if (profileAsAgent) {
|
|
1758
|
+
nestedCustomSubagent = profileAsAgent;
|
|
1759
|
+
requestedNestedAgent = profileAsAgent.name;
|
|
1760
|
+
}
|
|
1761
|
+
}
|
|
1135
1762
|
if (requestedNestedAgent && !nestedCustomSubagent) {
|
|
1136
1763
|
nestedWarnings.push(`Nested delegated task "${nestedRequest.description}" requested unknown agent "${requestedNestedAgent}". Falling back to profile "${nestedRequest.profile}".`);
|
|
1137
1764
|
}
|
|
1138
|
-
const
|
|
1139
|
-
const normalizedNestedProfile =
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1765
|
+
const nestedProfileCandidate = (nestedCustomSubagent?.profile ?? nestedRequest.profile).trim();
|
|
1766
|
+
const normalizedNestedProfile = nestedProfileCandidate.toLowerCase();
|
|
1767
|
+
if (!isValidProfileName(normalizedNestedProfile)) {
|
|
1768
|
+
markNestedFailed(nestedIndex, requestLabel, nestedRequest, nestedProfileCandidate || "unknown", `nested delegate skipped: unknown profile "${nestedProfileCandidate || nestedRequest.profile}"`, "logic_error");
|
|
1769
|
+
return;
|
|
1770
|
+
}
|
|
1771
|
+
const nestedProfile = normalizedNestedProfile;
|
|
1143
1772
|
const nestedProfileLabel = nestedCustomSubagent?.name
|
|
1144
1773
|
? `${nestedCustomSubagent.name}/${nestedProfile}`
|
|
1145
1774
|
: nestedProfile;
|
|
1146
1775
|
let nestedTools = nestedCustomSubagent?.tools
|
|
1147
1776
|
? [...nestedCustomSubagent.tools]
|
|
1148
|
-
: [...
|
|
1777
|
+
: [...toolsByProfile[nestedProfile]];
|
|
1149
1778
|
if (nestedCustomSubagent?.disallowedTools?.length) {
|
|
1150
1779
|
const blocked = new Set(nestedCustomSubagent.disallowedTools);
|
|
1151
1780
|
nestedTools = nestedTools.filter((tool) => !blocked.has(tool));
|
|
@@ -1158,19 +1787,15 @@ export function createTaskTool(cwd, runner, options) {
|
|
|
1158
1787
|
? path.resolve(parentCwd, nestedRequest.cwd)
|
|
1159
1788
|
: nestedCustomSubagent?.cwd ?? parentCwd;
|
|
1160
1789
|
if (!existsSync(requestedNestedCwd) || !statSync(requestedNestedCwd).isDirectory()) {
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
delegatedTasks += 1;
|
|
1164
|
-
sections.push(`###### ${requestLabel}. ${nestedRequest.description} (${nestedProfileLabel})\nERROR [cause=dependency_env]: nested delegate skipped: missing cwd`);
|
|
1165
|
-
continue;
|
|
1790
|
+
markNestedFailed(nestedIndex, requestLabel, nestedRequest, nestedProfileLabel, "nested delegate skipped: missing cwd", "dependency_env");
|
|
1791
|
+
return;
|
|
1166
1792
|
}
|
|
1167
1793
|
let nestedReleaseLock;
|
|
1168
1794
|
let nestedReleaseIsolation;
|
|
1169
1795
|
let nestedCwd = requestedNestedCwd;
|
|
1170
1796
|
try {
|
|
1171
1797
|
if (writeCapableProfiles.has(nestedProfile) && nestedRequest.lockKey?.trim()) {
|
|
1172
|
-
|
|
1173
|
-
nestedReleaseLock = await lock.acquire();
|
|
1798
|
+
nestedReleaseLock = await acquireLocalWriteLock(nestedRequest.lockKey.trim());
|
|
1174
1799
|
}
|
|
1175
1800
|
if (nestedRequest.isolation === "worktree") {
|
|
1176
1801
|
const isolated = provisionWorktree(cwd, requestedNestedCwd, `${runId}_nested_${requestLabel.replace(/\./g, "_")}`);
|
|
@@ -1179,7 +1804,17 @@ export function createTaskTool(cwd, runner, options) {
|
|
|
1179
1804
|
}
|
|
1180
1805
|
const nestedPromptWithInstructions = nestedRequest.prompt;
|
|
1181
1806
|
const nestedSharedMemoryGuidance = buildSharedMemoryGuidance(sharedMemoryRunId, sharedMemoryTaskId);
|
|
1182
|
-
const
|
|
1807
|
+
const nestedCoordinationGuidance = buildDelegateCoordinationGuidance({
|
|
1808
|
+
taskId: sharedMemoryTaskId,
|
|
1809
|
+
delegateLabel: requestLabel,
|
|
1810
|
+
delegateDescription: nestedRequest.description,
|
|
1811
|
+
});
|
|
1812
|
+
const nestedPrompt = `${nestedPromptWithInstructions}\n\n${nestedSharedMemoryGuidance}\n\n${nestedCoordinationGuidance}`;
|
|
1813
|
+
await publishStreamClaims({
|
|
1814
|
+
owner: requestLabel,
|
|
1815
|
+
description: nestedRequest.description,
|
|
1816
|
+
promptText: nestedRequest.prompt,
|
|
1817
|
+
});
|
|
1183
1818
|
const nestedModelOverride = nestedRequest.model?.trim() || nestedCustomSubagent?.model?.trim() || undefined;
|
|
1184
1819
|
const nestedSharedMemoryContext = {
|
|
1185
1820
|
rootCwd: cwd,
|
|
@@ -1212,6 +1847,7 @@ export function createTaskTool(cwd, runner, options) {
|
|
|
1212
1847
|
const nestedStats = typeof nestedResult === "string" ? undefined : nestedResult.stats;
|
|
1213
1848
|
delegatedTasks += 1;
|
|
1214
1849
|
delegatedSucceeded += 1;
|
|
1850
|
+
statuses[nestedIndex] = "done";
|
|
1215
1851
|
delegatedStats.toolCallsStarted += nestedStats?.toolCallsStarted ?? 0;
|
|
1216
1852
|
delegatedStats.toolCallsCompleted += nestedStats?.toolCallsCompleted ?? 0;
|
|
1217
1853
|
delegatedStats.assistantMessages += nestedStats?.assistantMessages ?? 0;
|
|
@@ -1226,37 +1862,112 @@ export function createTaskTool(cwd, runner, options) {
|
|
|
1226
1862
|
nestedSection = `${nestedSection}\n\n##### Nested Delegated Subtasks\n\n${deeper.sections.join("\n\n")}`;
|
|
1227
1863
|
}
|
|
1228
1864
|
}
|
|
1229
|
-
|
|
1865
|
+
await publishDelegateFinding({
|
|
1866
|
+
delegateLabel: requestLabel,
|
|
1867
|
+
delegateDescription: nestedRequest.description,
|
|
1868
|
+
delegateProfile: nestedProfileLabel,
|
|
1869
|
+
status: "done",
|
|
1870
|
+
content: nestedSection,
|
|
1871
|
+
});
|
|
1872
|
+
sectionsByIndex[nestedIndex] = nestedSection;
|
|
1230
1873
|
}
|
|
1231
1874
|
catch (error) {
|
|
1232
1875
|
const message = error instanceof Error ? error.message : String(error);
|
|
1233
1876
|
const cause = classifyFailureCause(message);
|
|
1234
|
-
|
|
1235
|
-
delegatedTasks += 1;
|
|
1236
|
-
delegatedFailed += 1;
|
|
1237
|
-
sections.push(`###### ${requestLabel}. ${nestedRequest.description} (${nestedProfileLabel})\nERROR [cause=${cause}]: ${message}`);
|
|
1877
|
+
markNestedFailed(nestedIndex, requestLabel, nestedRequest, nestedProfileLabel, message, cause);
|
|
1238
1878
|
}
|
|
1239
1879
|
finally {
|
|
1240
1880
|
nestedReleaseIsolation?.();
|
|
1241
1881
|
nestedReleaseLock?.();
|
|
1242
|
-
cleanupWriteLock(nestedRequest.lockKey?.trim());
|
|
1243
1882
|
}
|
|
1883
|
+
};
|
|
1884
|
+
const pendingIndices = new Set(Array.from({ length: totalNested }, (_v, i) => i));
|
|
1885
|
+
const runningNested = new Map();
|
|
1886
|
+
const maxNestedParallel = Math.max(1, Math.min(totalNested, effectiveMaxDelegateParallel));
|
|
1887
|
+
const resolveBlockedByFailedDependencies = () => {
|
|
1888
|
+
let changed = false;
|
|
1889
|
+
for (const nestedIndex of Array.from(pendingIndices)) {
|
|
1890
|
+
const deps = normalizedDependsOn[nestedIndex] ?? [];
|
|
1891
|
+
if (deps.length === 0)
|
|
1892
|
+
continue;
|
|
1893
|
+
const failedDep = deps.find((dep) => statusOf(dep - 1) === "failed");
|
|
1894
|
+
if (!failedDep)
|
|
1895
|
+
continue;
|
|
1896
|
+
pendingIndices.delete(nestedIndex);
|
|
1897
|
+
const nestedRequest = requests[nestedIndex];
|
|
1898
|
+
const requestLabel = `${lineage}${nestedIndex + 1}`;
|
|
1899
|
+
markNestedFailed(nestedIndex, requestLabel, nestedRequest, nestedRequest.profile, `nested delegate skipped: dependency ${lineage}${failedDep} failed`, "logic_error");
|
|
1900
|
+
changed = true;
|
|
1901
|
+
}
|
|
1902
|
+
return changed;
|
|
1903
|
+
};
|
|
1904
|
+
const launchReadyNested = () => {
|
|
1905
|
+
let launched = false;
|
|
1906
|
+
while (runningNested.size < maxNestedParallel) {
|
|
1907
|
+
let nextIndex;
|
|
1908
|
+
for (const nestedIndex of pendingIndices) {
|
|
1909
|
+
const deps = normalizedDependsOn[nestedIndex] ?? [];
|
|
1910
|
+
const allDone = deps.every((dep) => statusOf(dep - 1) === "done");
|
|
1911
|
+
if (allDone) {
|
|
1912
|
+
nextIndex = nestedIndex;
|
|
1913
|
+
break;
|
|
1914
|
+
}
|
|
1915
|
+
}
|
|
1916
|
+
if (nextIndex === undefined)
|
|
1917
|
+
break;
|
|
1918
|
+
pendingIndices.delete(nextIndex);
|
|
1919
|
+
const promise = runNestedDelegate(nextIndex).finally(() => {
|
|
1920
|
+
runningNested.delete(nextIndex);
|
|
1921
|
+
});
|
|
1922
|
+
runningNested.set(nextIndex, promise);
|
|
1923
|
+
launched = true;
|
|
1924
|
+
}
|
|
1925
|
+
return launched;
|
|
1926
|
+
};
|
|
1927
|
+
while (pendingIndices.size > 0 || runningNested.size > 0) {
|
|
1928
|
+
throwIfAborted();
|
|
1929
|
+
const changed = resolveBlockedByFailedDependencies();
|
|
1930
|
+
const launched = launchReadyNested();
|
|
1931
|
+
if (runningNested.size === 0) {
|
|
1932
|
+
if (pendingIndices.size > 0 && !changed && !launched) {
|
|
1933
|
+
for (const nestedIndex of Array.from(pendingIndices)) {
|
|
1934
|
+
pendingIndices.delete(nestedIndex);
|
|
1935
|
+
const nestedRequest = requests[nestedIndex];
|
|
1936
|
+
const deps = normalizedDependsOn[nestedIndex] ?? [];
|
|
1937
|
+
const requestLabel = `${lineage}${nestedIndex + 1}`;
|
|
1938
|
+
markNestedFailed(nestedIndex, requestLabel, nestedRequest, nestedRequest.profile, `nested delegate blocked: unresolved depends_on (${deps.join(", ") || "unknown"})`, "logic_error");
|
|
1939
|
+
}
|
|
1940
|
+
}
|
|
1941
|
+
break;
|
|
1942
|
+
}
|
|
1943
|
+
await Promise.race(Array.from(runningNested.values()));
|
|
1244
1944
|
}
|
|
1945
|
+
const sections = sectionsByIndex.filter((section) => typeof section === "string" && section.trim().length > 0);
|
|
1245
1946
|
return { sections, warnings: nestedWarnings };
|
|
1246
1947
|
};
|
|
1247
1948
|
const runDelegate = async (index) => {
|
|
1248
1949
|
throwIfAborted();
|
|
1249
1950
|
const request = parsedDelegation.requests[index];
|
|
1250
|
-
|
|
1251
|
-
|
|
1951
|
+
let requestedChildAgent = request.agent?.trim() || undefined;
|
|
1952
|
+
let childCustomSubagent = resolveCustom(requestedChildAgent);
|
|
1953
|
+
if (!childCustomSubagent && !requestedChildAgent) {
|
|
1954
|
+
const profileAsAgent = resolveCustom(request.profile);
|
|
1955
|
+
if (profileAsAgent) {
|
|
1956
|
+
childCustomSubagent = profileAsAgent;
|
|
1957
|
+
requestedChildAgent = profileAsAgent.name;
|
|
1958
|
+
}
|
|
1959
|
+
}
|
|
1252
1960
|
if (requestedChildAgent && !childCustomSubagent) {
|
|
1253
1961
|
delegationWarnings.push(`Delegated task "${request.description}" requested unknown agent "${requestedChildAgent}". Falling back to profile "${request.profile}".`);
|
|
1254
1962
|
}
|
|
1255
|
-
const childProfileRaw = childCustomSubagent?.profile ?? request.profile;
|
|
1256
|
-
const normalizedChildProfile = childProfileRaw.
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
: "
|
|
1963
|
+
const childProfileRaw = (childCustomSubagent?.profile ?? request.profile).trim();
|
|
1964
|
+
const normalizedChildProfile = childProfileRaw.toLowerCase();
|
|
1965
|
+
if (!isValidProfileName(normalizedChildProfile)) {
|
|
1966
|
+
recordFailureCause("logic_error");
|
|
1967
|
+
markDelegateFailed(index, `delegate ${index + 1}/${delegateTotal} skipped: unknown profile "${childProfileRaw || request.profile}"`, `Delegated task "${request.description}" requested unknown profile "${childProfileRaw || request.profile}".`, "logic_error");
|
|
1968
|
+
return;
|
|
1969
|
+
}
|
|
1970
|
+
const childProfile = normalizedChildProfile;
|
|
1260
1971
|
const childProfileLabel = childCustomSubagent?.name
|
|
1261
1972
|
? `${childCustomSubagent.name}/${childProfile}`
|
|
1262
1973
|
: childProfile;
|
|
@@ -1277,7 +1988,7 @@ export function createTaskTool(cwd, runner, options) {
|
|
|
1277
1988
|
});
|
|
1278
1989
|
let childTools = childCustomSubagent?.tools
|
|
1279
1990
|
? [...childCustomSubagent.tools]
|
|
1280
|
-
: [...
|
|
1991
|
+
: [...toolsByProfile[childProfile]];
|
|
1281
1992
|
if (childCustomSubagent?.disallowedTools?.length) {
|
|
1282
1993
|
const blocked = new Set(childCustomSubagent.disallowedTools);
|
|
1283
1994
|
childTools = childTools.filter((tool) => !blocked.has(tool));
|
|
@@ -1306,8 +2017,7 @@ export function createTaskTool(cwd, runner, options) {
|
|
|
1306
2017
|
try {
|
|
1307
2018
|
throwIfAborted();
|
|
1308
2019
|
if (writeCapableProfiles.has(childProfile) && explicitChildLock) {
|
|
1309
|
-
|
|
1310
|
-
childReleaseLock = await lock.acquire();
|
|
2020
|
+
childReleaseLock = await acquireLocalWriteLock(explicitChildLock);
|
|
1311
2021
|
throwIfAborted();
|
|
1312
2022
|
}
|
|
1313
2023
|
if (request.isolation === "worktree") {
|
|
@@ -1318,7 +2028,12 @@ export function createTaskTool(cwd, runner, options) {
|
|
|
1318
2028
|
const delegateMeta = formatMetaCheckpoint(options?.getMetaMessages?.());
|
|
1319
2029
|
const childPromptWithInstructions = request.prompt;
|
|
1320
2030
|
const delegateSharedMemoryGuidance = buildSharedMemoryGuidance(sharedMemoryRunId, sharedMemoryTaskId);
|
|
1321
|
-
const
|
|
2031
|
+
const delegateCoordinationGuidance = buildDelegateCoordinationGuidance({
|
|
2032
|
+
taskId: sharedMemoryTaskId,
|
|
2033
|
+
delegateLabel: String(index + 1),
|
|
2034
|
+
delegateDescription: request.description,
|
|
2035
|
+
});
|
|
2036
|
+
const delegatePromptBase = `${childPromptWithInstructions}\n\n${delegateSharedMemoryGuidance}\n\n${delegateCoordinationGuidance}`;
|
|
1322
2037
|
const delegatePrompt = delegateMeta.section && delegateMeta.appliedCount > 0
|
|
1323
2038
|
? `${delegatePromptBase}\n\n${delegateMeta.section}`
|
|
1324
2039
|
: delegatePromptBase;
|
|
@@ -1336,6 +2051,11 @@ export function createTaskTool(cwd, runner, options) {
|
|
|
1336
2051
|
delegateItems,
|
|
1337
2052
|
});
|
|
1338
2053
|
}
|
|
2054
|
+
await publishStreamClaims({
|
|
2055
|
+
owner: String(index + 1),
|
|
2056
|
+
description: request.description,
|
|
2057
|
+
promptText: request.prompt,
|
|
2058
|
+
});
|
|
1339
2059
|
const childModelOverride = request.model?.trim() || childCustomSubagent?.model?.trim() || undefined;
|
|
1340
2060
|
const childSharedMemoryContext = {
|
|
1341
2061
|
rootCwd: cwd,
|
|
@@ -1477,10 +2197,36 @@ export function createTaskTool(cwd, runner, options) {
|
|
|
1477
2197
|
childOutput = await runChildPass(enforcedChildPrompt);
|
|
1478
2198
|
parsedChildDelegation = parseDelegationRequests(childOutput, effectiveDelegationDepth > 1 ? effectiveMaxDelegations : 0);
|
|
1479
2199
|
}
|
|
2200
|
+
if (childMinDelegationsPreferred > 0 &&
|
|
2201
|
+
parsedChildDelegation.requests.length === 0 &&
|
|
2202
|
+
strictDelegationContract) {
|
|
2203
|
+
const impossibleMatch = childOutput.match(/^\s*DELEGATION_IMPOSSIBLE\s*:\s*(.+)$/im);
|
|
2204
|
+
if (!impossibleMatch) {
|
|
2205
|
+
const synthesizedChildRequests = synthesizeDelegationRequests({
|
|
2206
|
+
description: request.description,
|
|
2207
|
+
prompt: request.prompt,
|
|
2208
|
+
baseProfile: childProfile,
|
|
2209
|
+
currentDelegates: parsedChildDelegation.requests.length,
|
|
2210
|
+
minDelegationsPreferred: childMinDelegationsPreferred,
|
|
2211
|
+
maxDelegations: effectiveMaxDelegations,
|
|
2212
|
+
availableCustomNames,
|
|
2213
|
+
});
|
|
2214
|
+
if (synthesizedChildRequests.length > 0) {
|
|
2215
|
+
parsedChildDelegation.requests.push(...synthesizedChildRequests);
|
|
2216
|
+
delegationWarnings.push(`Child ${index + 1}: delegation auto-fanout synthesized ${synthesizedChildRequests.length} nested delegate(s).`);
|
|
2217
|
+
}
|
|
2218
|
+
}
|
|
2219
|
+
}
|
|
1480
2220
|
if (childMinDelegationsPreferred > 0 &&
|
|
1481
2221
|
parsedChildDelegation.requests.length < childMinDelegationsPreferred) {
|
|
1482
|
-
const
|
|
1483
|
-
|
|
2222
|
+
const impossibleMatch = childOutput.match(/^\s*DELEGATION_IMPOSSIBLE\s*:\s*(.+)$/im);
|
|
2223
|
+
const impossibleReason = impossibleMatch?.[1]?.trim() ?? "not provided";
|
|
2224
|
+
if (strictDelegationContract &&
|
|
2225
|
+
parsedChildDelegation.requests.length === 0 &&
|
|
2226
|
+
!impossibleMatch) {
|
|
2227
|
+
throw new Error(`Delegation contract violated for child ${index + 1}: expected >=${childMinDelegationsPreferred} nested delegates, got ${parsedChildDelegation.requests.length}. ` +
|
|
2228
|
+
`Provide nested <delegate_task> fan-out or "DELEGATION_IMPOSSIBLE: <reason>".`);
|
|
2229
|
+
}
|
|
1484
2230
|
delegationWarnings.push(`Child ${index + 1}: delegation fallback (preferred >=${childMinDelegationsPreferred}, got ${parsedChildDelegation.requests.length}). Reason: ${impossibleReason}.`);
|
|
1485
2231
|
}
|
|
1486
2232
|
childOutput = parsedChildDelegation.cleanedOutput;
|
|
@@ -1506,6 +2252,13 @@ export function createTaskTool(cwd, runner, options) {
|
|
|
1506
2252
|
: normalizedChildOutput;
|
|
1507
2253
|
delegatedSections[index] =
|
|
1508
2254
|
`#### ${index + 1}. ${request.description} (${childProfileLabel})\n${childOutputExcerpt}${nestedSection}`;
|
|
2255
|
+
await publishDelegateFinding({
|
|
2256
|
+
delegateLabel: String(index + 1),
|
|
2257
|
+
delegateDescription: request.description,
|
|
2258
|
+
delegateProfile: childProfileLabel,
|
|
2259
|
+
status: "done",
|
|
2260
|
+
content: `${childOutputExcerpt}${nestedSection}`,
|
|
2261
|
+
});
|
|
1509
2262
|
emitProgress({
|
|
1510
2263
|
kind: "subagent_progress",
|
|
1511
2264
|
phase: "running",
|
|
@@ -1535,7 +2288,6 @@ export function createTaskTool(cwd, runner, options) {
|
|
|
1535
2288
|
finally {
|
|
1536
2289
|
childReleaseIsolation?.();
|
|
1537
2290
|
childReleaseLock?.();
|
|
1538
|
-
cleanupWriteLock(explicitChildLock);
|
|
1539
2291
|
}
|
|
1540
2292
|
};
|
|
1541
2293
|
const resolveBlockedByFailedDependencies = () => {
|
|
@@ -1667,11 +2419,205 @@ export function createTaskTool(cwd, runner, options) {
|
|
|
1667
2419
|
}
|
|
1668
2420
|
const normalizedOutput = output.trim().length > 0 ? output.trim() : "(Subagent completed with no output)";
|
|
1669
2421
|
const finalSections = [normalizedOutput];
|
|
2422
|
+
const delegatedBlocks = delegatedSections.filter((section) => typeof section === "string" && section.trim().length > 0);
|
|
2423
|
+
let duplicateDelegatedOutputs = 0;
|
|
1670
2424
|
if (delegatedTasks > 0) {
|
|
1671
2425
|
const header = `### Delegated Subtasks (${delegatedSucceeded}/${delegatedTasks} done)`;
|
|
1672
|
-
|
|
2426
|
+
if (delegatedBlocks.length > 1) {
|
|
2427
|
+
const duplicateReport = detectDuplicateDelegatedSections(delegatedBlocks);
|
|
2428
|
+
duplicateDelegatedOutputs = duplicateReport.duplicates;
|
|
2429
|
+
if (duplicateReport.duplicates > 0) {
|
|
2430
|
+
const duplicateHints = duplicateReport.duplicatePairs
|
|
2431
|
+
.slice(0, 5)
|
|
2432
|
+
.map((pair) => `${pair.duplicate}->${pair.original}`)
|
|
2433
|
+
.join(", ");
|
|
2434
|
+
delegationWarnings.push(`Delegation quality: detected ${duplicateReport.duplicates} near-duplicate delegated output(s) (${duplicateHints}). Consider stricter stream partitioning or stronger shared-memory coordination keys.`);
|
|
2435
|
+
}
|
|
2436
|
+
}
|
|
1673
2437
|
finalSections.push([header, ...delegatedBlocks].join("\n\n"));
|
|
1674
2438
|
}
|
|
2439
|
+
let sharedMemorySummaryKey;
|
|
2440
|
+
if (orchestrationRunId && orchestrationTaskId) {
|
|
2441
|
+
const summaryExcerpt = normalizedOutput.length > 1200
|
|
2442
|
+
? `${normalizedOutput.slice(0, 1197).trimEnd()}...`
|
|
2443
|
+
: normalizedOutput;
|
|
2444
|
+
const summaryPayload = JSON.stringify({
|
|
2445
|
+
taskId: orchestrationTaskId,
|
|
2446
|
+
description,
|
|
2447
|
+
profile: effectiveProfile,
|
|
2448
|
+
delegated: {
|
|
2449
|
+
total: delegatedTasks,
|
|
2450
|
+
succeeded: delegatedSucceeded,
|
|
2451
|
+
failed: delegatedFailed,
|
|
2452
|
+
duplicatesDetected: duplicateDelegatedOutputs,
|
|
2453
|
+
},
|
|
2454
|
+
retrospective: {
|
|
2455
|
+
attempts: retrospectiveAttempts,
|
|
2456
|
+
recovered: retrospectiveRecovered,
|
|
2457
|
+
failureCauses: failureCauses,
|
|
2458
|
+
},
|
|
2459
|
+
summary: summaryExcerpt,
|
|
2460
|
+
});
|
|
2461
|
+
try {
|
|
2462
|
+
const summaryWrite = await writeSharedMemory({
|
|
2463
|
+
rootCwd: cwd,
|
|
2464
|
+
runId: sharedMemoryRunId,
|
|
2465
|
+
taskId: sharedMemoryTaskId,
|
|
2466
|
+
profile: effectiveProfile,
|
|
2467
|
+
}, {
|
|
2468
|
+
key: `results/${orchestrationTaskId}`,
|
|
2469
|
+
value: summaryPayload,
|
|
2470
|
+
scope: "run",
|
|
2471
|
+
mode: "set",
|
|
2472
|
+
}, _signal);
|
|
2473
|
+
sharedMemorySummaryKey = summaryWrite.key;
|
|
2474
|
+
}
|
|
2475
|
+
catch (error) {
|
|
2476
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2477
|
+
delegationWarnings.push(`Shared memory summary write skipped: ${message}`);
|
|
2478
|
+
}
|
|
2479
|
+
}
|
|
2480
|
+
let coordinationSummary;
|
|
2481
|
+
let claimSummary;
|
|
2482
|
+
let sharedFindingsSnapshot;
|
|
2483
|
+
if (delegatedTasks > 0 || (orchestrationRunId && orchestrationTaskId)) {
|
|
2484
|
+
try {
|
|
2485
|
+
const usage = await summarizeSharedMemoryUsage({
|
|
2486
|
+
rootCwd: cwd,
|
|
2487
|
+
runId: sharedMemoryRunId,
|
|
2488
|
+
taskId: sharedMemoryTaskId,
|
|
2489
|
+
}, _signal);
|
|
2490
|
+
if (delegatedTasks > 1 && usage.currentTaskDelegateWrites === 0) {
|
|
2491
|
+
delegationWarnings.push("No shared_memory writes detected from delegates in this task. Cross-stream coordination may be weak; use stable keys (findings/<stream>, risks/<stream>, plan/<stream>).");
|
|
2492
|
+
}
|
|
2493
|
+
coordinationSummary = {
|
|
2494
|
+
sharedMemoryWrites: usage.totalWrites,
|
|
2495
|
+
currentTaskWrites: usage.currentTaskWrites,
|
|
2496
|
+
currentTaskDelegateWrites: usage.currentTaskDelegateWrites,
|
|
2497
|
+
runScopeWrites: usage.runScopeWrites,
|
|
2498
|
+
taskScopeWrites: usage.taskScopeWrites,
|
|
2499
|
+
duplicatesDetected: duplicateDelegatedOutputs,
|
|
2500
|
+
claimKeysMatched: 0,
|
|
2501
|
+
claimCollisions: 0,
|
|
2502
|
+
};
|
|
2503
|
+
}
|
|
2504
|
+
catch {
|
|
2505
|
+
// shared-memory summary is advisory; never fail task completion on analytics path.
|
|
2506
|
+
}
|
|
2507
|
+
}
|
|
2508
|
+
if (delegatedTasks > 0) {
|
|
2509
|
+
try {
|
|
2510
|
+
const claims = await readSharedMemory({
|
|
2511
|
+
rootCwd: cwd,
|
|
2512
|
+
runId: sharedMemoryRunId,
|
|
2513
|
+
taskId: sharedMemoryTaskId,
|
|
2514
|
+
}, {
|
|
2515
|
+
scope: "run",
|
|
2516
|
+
prefix: "claims/",
|
|
2517
|
+
includeValues: true,
|
|
2518
|
+
limit: Math.max(40, delegatedTasks * 8),
|
|
2519
|
+
}, _signal);
|
|
2520
|
+
const collisions = [];
|
|
2521
|
+
for (const item of claims.items) {
|
|
2522
|
+
if (!item.value)
|
|
2523
|
+
continue;
|
|
2524
|
+
try {
|
|
2525
|
+
const parsed = JSON.parse(item.value);
|
|
2526
|
+
if (!Array.isArray(parsed.owners))
|
|
2527
|
+
continue;
|
|
2528
|
+
const owners = Array.from(new Set(parsed.owners
|
|
2529
|
+
.filter((value) => typeof value === "string" && value.trim().length > 0)
|
|
2530
|
+
.map((value) => value.trim())));
|
|
2531
|
+
if (owners.length > 1) {
|
|
2532
|
+
collisions.push({ key: item.key, owners: owners.slice(0, 8) });
|
|
2533
|
+
}
|
|
2534
|
+
}
|
|
2535
|
+
catch {
|
|
2536
|
+
// ignore malformed claim entries and continue best-effort aggregation
|
|
2537
|
+
}
|
|
2538
|
+
}
|
|
2539
|
+
claimSummary = {
|
|
2540
|
+
keysMatched: claims.totalMatched,
|
|
2541
|
+
collisions: collisions.length,
|
|
2542
|
+
examples: collisions.slice(0, 6).map((item) => `${item.key}: ${item.owners.join(", ")}`),
|
|
2543
|
+
};
|
|
2544
|
+
if (coordinationSummary) {
|
|
2545
|
+
coordinationSummary.claimKeysMatched = claimSummary.keysMatched;
|
|
2546
|
+
coordinationSummary.claimCollisions = claimSummary.collisions;
|
|
2547
|
+
}
|
|
2548
|
+
}
|
|
2549
|
+
catch {
|
|
2550
|
+
// advisory only
|
|
2551
|
+
}
|
|
2552
|
+
}
|
|
2553
|
+
if (delegatedTasks > 0) {
|
|
2554
|
+
try {
|
|
2555
|
+
const findingsPrefix = `findings/${toSharedMemoryKeySegment(sharedMemoryTaskId, "task")}/`;
|
|
2556
|
+
const findings = await readSharedMemory({
|
|
2557
|
+
rootCwd: cwd,
|
|
2558
|
+
runId: sharedMemoryRunId,
|
|
2559
|
+
taskId: sharedMemoryTaskId,
|
|
2560
|
+
}, {
|
|
2561
|
+
scope: "run",
|
|
2562
|
+
prefix: findingsPrefix,
|
|
2563
|
+
includeValues: true,
|
|
2564
|
+
limit: Math.max(20, delegatedTasks * 4),
|
|
2565
|
+
}, _signal);
|
|
2566
|
+
const examples = findings.items.slice(0, 6).map((item) => {
|
|
2567
|
+
let excerpt = "";
|
|
2568
|
+
if (item.value) {
|
|
2569
|
+
try {
|
|
2570
|
+
const parsed = JSON.parse(item.value);
|
|
2571
|
+
if (typeof parsed.summary === "string" && parsed.summary.trim().length > 0) {
|
|
2572
|
+
excerpt = parsed.summary.trim().replace(/\s+/g, " ").slice(0, 120);
|
|
2573
|
+
}
|
|
2574
|
+
}
|
|
2575
|
+
catch {
|
|
2576
|
+
excerpt = item.value.trim().replace(/\s+/g, " ").slice(0, 120);
|
|
2577
|
+
}
|
|
2578
|
+
}
|
|
2579
|
+
return excerpt.length > 0 ? `${item.key}: ${excerpt}` : item.key;
|
|
2580
|
+
});
|
|
2581
|
+
sharedFindingsSnapshot = {
|
|
2582
|
+
keysMatched: findings.totalMatched,
|
|
2583
|
+
examples,
|
|
2584
|
+
};
|
|
2585
|
+
}
|
|
2586
|
+
catch {
|
|
2587
|
+
// advisory only
|
|
2588
|
+
}
|
|
2589
|
+
}
|
|
2590
|
+
if (coordinationSummary && delegatedTasks > 0) {
|
|
2591
|
+
finalSections.push([
|
|
2592
|
+
"### Orchestration Summary",
|
|
2593
|
+
`- delegated: ${delegatedSucceeded}/${delegatedTasks} succeeded (${delegatedFailed} failed)`,
|
|
2594
|
+
`- duplicate_delegated_outputs: ${coordinationSummary.duplicatesDetected}`,
|
|
2595
|
+
`- shared_memory_writes_total: ${coordinationSummary.sharedMemoryWrites}`,
|
|
2596
|
+
`- shared_memory_writes_current_task: ${coordinationSummary.currentTaskWrites}`,
|
|
2597
|
+
`- shared_memory_delegate_writes_current_task: ${coordinationSummary.currentTaskDelegateWrites}`,
|
|
2598
|
+
`- shared_memory_scope_distribution: run=${coordinationSummary.runScopeWrites}, task=${coordinationSummary.taskScopeWrites}`,
|
|
2599
|
+
`- claims_keys_matched: ${coordinationSummary.claimKeysMatched}`,
|
|
2600
|
+
`- claims_collisions: ${coordinationSummary.claimCollisions}`,
|
|
2601
|
+
sharedMemorySummaryKey ? `- shared_memory_summary_key: ${sharedMemorySummaryKey}` : undefined,
|
|
2602
|
+
]
|
|
2603
|
+
.filter((line) => typeof line === "string" && line.trim().length > 0)
|
|
2604
|
+
.join("\n"));
|
|
2605
|
+
}
|
|
2606
|
+
if (claimSummary && delegatedTasks > 0) {
|
|
2607
|
+
finalSections.push([
|
|
2608
|
+
"### Claim Overlap Snapshot",
|
|
2609
|
+
`- matched_keys: ${claimSummary.keysMatched}`,
|
|
2610
|
+
`- collisions: ${claimSummary.collisions}`,
|
|
2611
|
+
...claimSummary.examples.map((line) => `- ${line}`),
|
|
2612
|
+
].join("\n"));
|
|
2613
|
+
}
|
|
2614
|
+
if (sharedFindingsSnapshot && delegatedTasks > 0) {
|
|
2615
|
+
finalSections.push([
|
|
2616
|
+
"### Shared Findings Snapshot",
|
|
2617
|
+
`- matched_keys: ${sharedFindingsSnapshot.keysMatched}`,
|
|
2618
|
+
...sharedFindingsSnapshot.examples.map((line) => `- ${line}`),
|
|
2619
|
+
].join("\n"));
|
|
2620
|
+
}
|
|
1675
2621
|
if (delegationWarnings.length > 0) {
|
|
1676
2622
|
finalSections.push(`### Delegation Notes\n${delegationWarnings.map((w) => `- ${w}`).join("\n")}`);
|
|
1677
2623
|
}
|
|
@@ -1753,6 +2699,8 @@ export function createTaskTool(cwd, runner, options) {
|
|
|
1753
2699
|
retrospectiveAttempts: retrospectiveAttempts > 0 ? retrospectiveAttempts : undefined,
|
|
1754
2700
|
retrospectiveRecovered: retrospectiveRecovered > 0 ? retrospectiveRecovered : undefined,
|
|
1755
2701
|
failureCauses: hasFailureCauses ? { ...failureCauses } : undefined,
|
|
2702
|
+
coordination: coordinationSummary,
|
|
2703
|
+
sharedMemorySummaryKey,
|
|
1756
2704
|
};
|
|
1757
2705
|
updateTrackedTaskStatus("done");
|
|
1758
2706
|
return { text, details };
|
|
@@ -1760,7 +2708,6 @@ export function createTaskTool(cwd, runner, options) {
|
|
|
1760
2708
|
finally {
|
|
1761
2709
|
releaseIsolation?.();
|
|
1762
2710
|
releaseWriteLock?.();
|
|
1763
|
-
cleanupWriteLock(explicitRootLockKey);
|
|
1764
2711
|
releaseSlot?.();
|
|
1765
2712
|
releaseRunSlot?.();
|
|
1766
2713
|
if (orchestrationRunId) {
|