pi-subagents 0.48.0 → 0.49.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +47 -19
- package/docs/agents.md +1 -0
- package/docs/configuration.md +28 -5
- package/docs/missions.md +4 -2
- package/docs/observability.md +26 -2
- package/package.json +1 -1
- package/src/agents/agents.ts +27 -12
- package/src/api/preflight.ts +1 -1
- package/src/extension/config.ts +20 -0
- package/src/extension/index.ts +18 -8
- package/src/extension/public-execution.ts +30 -4
- package/src/extension/schemas.ts +8 -7
- package/src/extension/tool-description.ts +10 -8
- package/src/inspectors/herdr/actions.ts +11 -2
- package/src/inspectors/herdr/inspector-runner.ts +16 -3
- package/src/intercom/intercom-bridge.ts +3 -1
- package/src/missions/store.ts +8 -3
- package/src/runs/background/active-async-capacity.ts +82 -25
- package/src/runs/background/async-execution.ts +24 -36
- package/src/runs/background/async-job-tracker.ts +4 -0
- package/src/runs/background/async-resume.ts +11 -1
- package/src/runs/background/fleet-view.ts +18 -5
- package/src/runs/background/resume-guidance.ts +27 -7
- package/src/runs/background/retained-children.ts +14 -6
- package/src/runs/background/run-status.ts +96 -4
- package/src/runs/background/subagent-runner.ts +4 -1
- package/src/runs/foreground/prompt-audit.ts +4 -3
- package/src/runs/foreground/subagent-executor.ts +219 -20
- package/src/runs/shared/completion-guard.ts +17 -1
- package/src/runs/shared/llm-intent-arbiter.ts +39 -23
- package/src/shared/agent-stream-options.ts +5 -0
- package/src/shared/artifacts.ts +2 -6
- package/src/shared/display-text.ts +50 -0
- package/src/shared/node-executable.ts +21 -0
- package/src/shared/types.ts +23 -4
- package/src/tui/fleet-transcript.ts +1 -48
- package/src/tui/fleet.ts +3 -1
- package/src/tui/render.ts +86 -39
- package/src/watchdog/permission-arbiter.ts +2 -1
- package/src/watchdog/review.ts +4 -3
- package/src/workflows/chat-progress.ts +8 -2
- package/src/workflows/scripted-workflow.ts +46 -7
|
@@ -139,6 +139,7 @@ import {
|
|
|
139
139
|
type Details,
|
|
140
140
|
type ExtensionConfig,
|
|
141
141
|
type ForegroundRunControl,
|
|
142
|
+
type IntercomBridgeConfig,
|
|
142
143
|
type IntercomEventBus,
|
|
143
144
|
type JsonSchemaObject,
|
|
144
145
|
type MaxOutputConfig,
|
|
@@ -300,6 +301,8 @@ export interface SubagentParamsLike {
|
|
|
300
301
|
concurrency?: number;
|
|
301
302
|
worktree?: boolean;
|
|
302
303
|
context?: "fresh" | "fork";
|
|
304
|
+
/** Per-run intercom bridge config. It replaces the global config for this launch only. */
|
|
305
|
+
intercomBridge?: IntercomBridgeConfig;
|
|
303
306
|
async?: boolean;
|
|
304
307
|
foregroundOnly?: boolean;
|
|
305
308
|
timeoutMs?: number;
|
|
@@ -387,6 +390,8 @@ interface ExecutionContextData {
|
|
|
387
390
|
signal: AbortSignal;
|
|
388
391
|
onUpdate?: (r: AgentToolResult<Details>) => void;
|
|
389
392
|
agents: AgentConfig[];
|
|
393
|
+
/** Discovered agent definitions before per-run bridge injection. */
|
|
394
|
+
recoveryAgents: AgentConfig[];
|
|
390
395
|
runId: string;
|
|
391
396
|
shareEnabled: boolean;
|
|
392
397
|
sessionRoot: string;
|
|
@@ -1511,17 +1516,18 @@ async function resumeAsyncRun(input: {
|
|
|
1511
1516
|
const discoveredAgents = discovered.agents;
|
|
1512
1517
|
const modelScope = discovered.modelScope;
|
|
1513
1518
|
const sessionName = resolveIntercomSessionTarget(input.deps.pi.getSessionName(), input.ctx.sessionManager.getSessionId());
|
|
1519
|
+
const recoveryDescriptor = "recoveryDescriptor" in target ? target.recoveryDescriptor : undefined;
|
|
1514
1520
|
const intercomBridge = resolveIntercomBridge({
|
|
1515
1521
|
config: input.deps.config.intercomBridge,
|
|
1522
|
+
override: input.params.intercomBridge ?? recoveryDescriptor?.intercomBridge,
|
|
1516
1523
|
context: input.params.context,
|
|
1517
1524
|
orchestratorTarget: sessionName,
|
|
1518
1525
|
});
|
|
1519
1526
|
const agents = intercomBridge.active
|
|
1520
1527
|
? discoveredAgents.map((agent) => applyIntercomBridgeToAgent(agent, intercomBridge))
|
|
1521
1528
|
: discoveredAgents;
|
|
1522
|
-
const
|
|
1523
|
-
const
|
|
1524
|
-
const agentConfig: AgentConfig | undefined = discoveredAgentConfig ?? (recoveryDescriptor ? {
|
|
1529
|
+
const discoveredAgentConfig = discoveredAgents.find((agent) => agent.name === target.agent);
|
|
1530
|
+
const baseAgentConfig: AgentConfig | undefined = discoveredAgentConfig ?? (recoveryDescriptor ? {
|
|
1525
1531
|
name: recoveryDescriptor.agent,
|
|
1526
1532
|
description: "Persisted async recovery contract",
|
|
1527
1533
|
systemPrompt: "",
|
|
@@ -1531,7 +1537,7 @@ async function resumeAsyncRun(input: {
|
|
|
1531
1537
|
source: "project",
|
|
1532
1538
|
filePath: recoveryDescriptor.agentFilePath ?? path.join(getProjectSubagentsDir(recoveryDescriptor.cwd), "recovery-agent"),
|
|
1533
1539
|
} : undefined);
|
|
1534
|
-
if (!
|
|
1540
|
+
if (!baseAgentConfig) {
|
|
1535
1541
|
return {
|
|
1536
1542
|
content: [{ type: "text", text: `Unknown agent for resume: ${target.agent}` }],
|
|
1537
1543
|
isError: true,
|
|
@@ -1672,7 +1678,8 @@ async function resumeAsyncRun(input: {
|
|
|
1672
1678
|
if (error instanceof ActiveAsyncCapacityError) return { content: [{ type: "text", text: error.message }], isError: true, details: { mode: "single", results: [], activeAsyncCapacity: error.snapshot } };
|
|
1673
1679
|
return { content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }], isError: true, details: { mode: "single", results: [] } };
|
|
1674
1680
|
}
|
|
1675
|
-
const recoveryAgentConfig = recoveryDescriptor ? applySteeringRecoveryAgentConfig(
|
|
1681
|
+
const recoveryAgentConfig = recoveryDescriptor ? applySteeringRecoveryAgentConfig(baseAgentConfig, recoveryDescriptor) : baseAgentConfig;
|
|
1682
|
+
const agentConfig = intercomBridge.active ? applyIntercomBridgeToAgent(recoveryAgentConfig, intercomBridge) : recoveryAgentConfig;
|
|
1676
1683
|
const artifactConfig: ArtifactConfig = recoveryDescriptor?.artifactConfig ?? omitUndefinedProperties({ ...DEFAULT_ARTIFACT_CONFIG, enabled: input.params.artifacts !== false, dir: input.deps.config.artifactDir ?? DEFAULT_ARTIFACT_CONFIG.dir });
|
|
1677
1684
|
const artifactsDir = recoveryDescriptor?.artifactsDir ?? getArtifactsDir(parentSessionFile, effectiveCwd, artifactConfig.dir);
|
|
1678
1685
|
const availableModels = input.ctx.modelRegistry.getAvailable().map(toModelInfo);
|
|
@@ -1682,7 +1689,8 @@ async function resumeAsyncRun(input: {
|
|
|
1682
1689
|
agent: target.agent,
|
|
1683
1690
|
task: buildRevivedAsyncTask(target as Parameters<typeof buildRevivedAsyncTask>[0], effectiveFollowUp),
|
|
1684
1691
|
goal: effectiveFollowUp,
|
|
1685
|
-
agentConfig
|
|
1692
|
+
agentConfig,
|
|
1693
|
+
recoveryAgentConfig,
|
|
1686
1694
|
ctx: compactOptional<Parameters<typeof executeAsyncSingle>[1]["ctx"]>({
|
|
1687
1695
|
pi: input.deps.pi,
|
|
1688
1696
|
cwd: input.requestCwd,
|
|
@@ -1717,6 +1725,7 @@ async function resumeAsyncRun(input: {
|
|
|
1717
1725
|
worktreeSetupHookTimeoutMs: input.deps.config.worktreeSetupHookTimeoutMs,
|
|
1718
1726
|
worktreeBaseDir: input.deps.config.worktreeBaseDir,
|
|
1719
1727
|
controlConfig: recoveryDescriptor?.controlConfig ?? resolveControlConfig(input.deps.config.control, input.params.control),
|
|
1728
|
+
intercomBridge: input.params.intercomBridge ?? recoveryDescriptor?.intercomBridge,
|
|
1720
1729
|
controlIntercomTarget: intercomBridge.active ? intercomBridge.orchestratorTarget : undefined,
|
|
1721
1730
|
childIntercomTarget: intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(runId, agent, index) : undefined,
|
|
1722
1731
|
availableModels,
|
|
@@ -1731,7 +1740,7 @@ async function resumeAsyncRun(input: {
|
|
|
1731
1740
|
...(input.params.turnBudget !== undefined ? { turnBudget: input.params.turnBudget } : {}),
|
|
1732
1741
|
...(input.params.toolBudget !== undefined ? { toolBudget: input.params.toolBudget } : {}),
|
|
1733
1742
|
capabilityCeiling: intersectSubagentCapabilityCeilings("capabilityCeiling" in target ? target.capabilityCeiling : undefined, recoveryDescriptor?.capabilityCeiling, resolveCurrentSubagentCapabilityCeiling(input.deps.state.currentSessionId)),
|
|
1734
|
-
runFanoutBudget: recoveryDescriptor?.runFanoutBudget ?? createRunFanoutBudget(runId, resolveMaxSubagentSpawnsPerRun(input.deps.config.maxSubagentSpawnsPerRun)),
|
|
1743
|
+
runFanoutBudget: input.params.runFanoutBudget ?? recoveryDescriptor?.runFanoutBudget ?? createRunFanoutBudget(runId, resolveMaxSubagentSpawnsPerRun(input.deps.config.maxSubagentSpawnsPerRun)),
|
|
1735
1744
|
parentWorkflowRunId: input.params.workflowParentRunId,
|
|
1736
1745
|
workflowKey: input.params.workflowKey,
|
|
1737
1746
|
activeAsyncCapacity,
|
|
@@ -2870,6 +2879,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
2870
2879
|
task: shouldForkAgent(contextPolicy, params.agent!) ? wrapForkTask(params.task ?? "") : (params.task ?? ""),
|
|
2871
2880
|
goal: params.task ?? "",
|
|
2872
2881
|
agentConfig: a,
|
|
2882
|
+
recoveryAgentConfig: data.recoveryAgents.find((agent) => agent.name === params.agent),
|
|
2873
2883
|
ctx: asyncCtx,
|
|
2874
2884
|
availableModels,
|
|
2875
2885
|
cwd: effectiveCwd,
|
|
@@ -2894,6 +2904,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
2894
2904
|
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
2895
2905
|
worktreeBaseDir: deps.config.worktreeBaseDir,
|
|
2896
2906
|
controlConfig,
|
|
2907
|
+
intercomBridge: params.intercomBridge,
|
|
2897
2908
|
controlIntercomTarget,
|
|
2898
2909
|
childIntercomTarget: childIntercomTarget ? (agent, index) => childIntercomTarget(agent, index) : undefined,
|
|
2899
2910
|
nestedRoute,
|
|
@@ -3239,10 +3250,128 @@ function buildParallelWorktreeTaskCwdError(
|
|
|
3239
3250
|
return formatWorktreeTaskCwdConflict(conflict, sharedCwd);
|
|
3240
3251
|
}
|
|
3241
3252
|
|
|
3242
|
-
function
|
|
3253
|
+
function resolveConfiguredSingleRunOutputBaseDir(deps: ExecutorDeps): string | undefined {
|
|
3243
3254
|
return deps.config.singleRunOutputBaseDir
|
|
3244
3255
|
? path.resolve(deps.expandTilde(deps.config.singleRunOutputBaseDir))
|
|
3245
|
-
:
|
|
3256
|
+
: undefined;
|
|
3257
|
+
}
|
|
3258
|
+
|
|
3259
|
+
function resolveSingleRunOutputBaseDir(deps: ExecutorDeps, artifactsDir: string, runId: string): string {
|
|
3260
|
+
return resolveConfiguredSingleRunOutputBaseDir(deps) ?? path.join(artifactsDir, "outputs", runId);
|
|
3261
|
+
}
|
|
3262
|
+
|
|
3263
|
+
function resolveWorkflowAggregateOutputPath(
|
|
3264
|
+
output: string | boolean | undefined,
|
|
3265
|
+
ctxCwd: string,
|
|
3266
|
+
workflowCwd: string,
|
|
3267
|
+
outputBaseDir: string,
|
|
3268
|
+
configuredOutputBaseDir: string | undefined,
|
|
3269
|
+
): string | undefined {
|
|
3270
|
+
if (typeof output === "string" && output && !path.isAbsolute(output) && !configuredOutputBaseDir) return path.resolve(workflowCwd, output);
|
|
3271
|
+
return resolveSingleOutputPath(output, ctxCwd, workflowCwd, outputBaseDir);
|
|
3272
|
+
}
|
|
3273
|
+
|
|
3274
|
+
function workflowChildDefaultOutput(aggregateOutputPath: string | undefined, artifactsDir: string, workflowRunId: string, workflowKey: string): string {
|
|
3275
|
+
if (aggregateOutputPath) {
|
|
3276
|
+
const parsed = path.parse(aggregateOutputPath);
|
|
3277
|
+
return path.join(parsed.dir, `${parsed.name}.${workflowKey}${parsed.ext || ".md"}`);
|
|
3278
|
+
}
|
|
3279
|
+
return path.join(artifactsDir, "outputs", workflowRunId, `${workflowKey}.md`);
|
|
3280
|
+
}
|
|
3281
|
+
|
|
3282
|
+
function writeWorkflowAggregateOutput(outputPath: string | undefined, text: string): string | undefined {
|
|
3283
|
+
if (!outputPath) return undefined;
|
|
3284
|
+
try {
|
|
3285
|
+
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
|
|
3286
|
+
fs.writeFileSync(outputPath, text, "utf-8");
|
|
3287
|
+
return undefined;
|
|
3288
|
+
} catch (error) {
|
|
3289
|
+
return `Output file error: ${outputPath}\n${error instanceof Error ? error.message : String(error)}`;
|
|
3290
|
+
}
|
|
3291
|
+
}
|
|
3292
|
+
|
|
3293
|
+
function appendWorkflowOutputWarning(text: string, warning: string | undefined): string {
|
|
3294
|
+
return warning ? `${text}\n\n${warning}` : text;
|
|
3295
|
+
}
|
|
3296
|
+
|
|
3297
|
+
function resolveWorkflowChildOutputPath(input: {
|
|
3298
|
+
ctxCwd: string;
|
|
3299
|
+
workflowCwd: string;
|
|
3300
|
+
artifactsDir: string;
|
|
3301
|
+
workflowRunId: string;
|
|
3302
|
+
aggregateOutputPath?: string;
|
|
3303
|
+
configuredOutputBaseDir?: string;
|
|
3304
|
+
discoverAgents: (cwd: string, scope: AgentScope) => { agents: AgentConfig[] };
|
|
3305
|
+
workflowAgentScope?: unknown;
|
|
3306
|
+
key: string;
|
|
3307
|
+
params: Record<string, unknown>;
|
|
3308
|
+
}): string | undefined {
|
|
3309
|
+
const rawOutput = input.params.output;
|
|
3310
|
+
const hasExplicitOutput = typeof rawOutput === "string" || typeof rawOutput === "boolean";
|
|
3311
|
+
const childCwd = typeof input.params.cwd === "string" ? resolveChildCwd(input.workflowCwd, input.params.cwd) : input.workflowCwd;
|
|
3312
|
+
const agentScope = resolveExecutionAgentScope(input.params.agentScope ?? input.workflowAgentScope);
|
|
3313
|
+
const agents = input.discoverAgents(childCwd, agentScope).agents;
|
|
3314
|
+
const agent = typeof input.params.agent === "string" ? resolveAgentName(input.params.agent, agents).agent : undefined;
|
|
3315
|
+
const agentOutput = typeof agent?.output === "string" ? agent.output : undefined;
|
|
3316
|
+
const output = rawOutput === true || rawOutput === "true"
|
|
3317
|
+
? agentOutput
|
|
3318
|
+
: hasExplicitOutput
|
|
3319
|
+
? rawOutput
|
|
3320
|
+
: workflowChildDefaultOutput(input.aggregateOutputPath, input.artifactsDir, input.workflowRunId, input.key);
|
|
3321
|
+
return resolveSingleOutputPath(output, input.ctxCwd, childCwd, input.configuredOutputBaseDir);
|
|
3322
|
+
}
|
|
3323
|
+
|
|
3324
|
+
function workflowChildOutputClaims(input: {
|
|
3325
|
+
ctxCwd: string;
|
|
3326
|
+
workflowCwd: string;
|
|
3327
|
+
artifactsDir: string;
|
|
3328
|
+
workflowRunId: string;
|
|
3329
|
+
aggregateOutputPath?: string;
|
|
3330
|
+
configuredOutputBaseDir?: string;
|
|
3331
|
+
discoverAgents: (cwd: string, scope: AgentScope) => { agents: AgentConfig[] };
|
|
3332
|
+
workflowAgentScope?: unknown;
|
|
3333
|
+
claimedOutputPaths: Map<string, string>;
|
|
3334
|
+
entries: Array<{ key: string; params: Record<string, unknown> }>;
|
|
3335
|
+
}): { error?: string; claims?: Map<string, string> } {
|
|
3336
|
+
const claims = new Map(input.claimedOutputPaths);
|
|
3337
|
+
for (const { key, params } of input.entries) {
|
|
3338
|
+
const resolved = resolveWorkflowChildOutputPath({ ...input, key, params });
|
|
3339
|
+
if (!resolved) continue;
|
|
3340
|
+
if (input.aggregateOutputPath && resolved === input.aggregateOutputPath) return { error: `Workflow child '${key}' output resolves to the workflow aggregate output path: ${resolved}. Use a distinct child output path.` };
|
|
3341
|
+
const previous = claims.get(resolved);
|
|
3342
|
+
if (previous) return { error: `Workflow children '${previous}' and '${key}' resolve output to the same path: ${resolved}. Use distinct child output paths.` };
|
|
3343
|
+
claims.set(resolved, key);
|
|
3344
|
+
}
|
|
3345
|
+
return { claims };
|
|
3346
|
+
}
|
|
3347
|
+
|
|
3348
|
+
function applyWorkflowChildOutputClaims(target: Map<string, string>, claims: Map<string, string>): void {
|
|
3349
|
+
target.clear();
|
|
3350
|
+
for (const [resolved, key] of claims) target.set(resolved, key);
|
|
3351
|
+
}
|
|
3352
|
+
|
|
3353
|
+
function prepareWorkflowChildLaunchParams(input: {
|
|
3354
|
+
workflowDefaults: SubagentParamsLike;
|
|
3355
|
+
childParams: Record<string, unknown>;
|
|
3356
|
+
parentWorkflowRunId: string;
|
|
3357
|
+
workflowKey: string;
|
|
3358
|
+
ctxCwd: string;
|
|
3359
|
+
workflowCwd: string;
|
|
3360
|
+
artifactsDir: string;
|
|
3361
|
+
aggregateOutputPath?: string;
|
|
3362
|
+
configuredOutputBaseDir?: string;
|
|
3363
|
+
discoverAgents: (cwd: string, scope: AgentScope) => { agents: AgentConfig[] };
|
|
3364
|
+
workflowAgentScope?: unknown;
|
|
3365
|
+
options?: { missionDetached?: boolean; suppressRoutineResultIntercom?: boolean; runFanoutBudget?: RunFanoutBudgetDescriptor };
|
|
3366
|
+
}): SubagentParamsLike {
|
|
3367
|
+
let childParams = input.childParams;
|
|
3368
|
+
if (input.childParams.output === undefined && input.childParams.resume === undefined) {
|
|
3369
|
+
childParams = { ...input.childParams, output: workflowChildDefaultOutput(input.aggregateOutputPath, input.artifactsDir, input.parentWorkflowRunId, input.workflowKey) };
|
|
3370
|
+
} else if (input.childParams.resume === undefined) {
|
|
3371
|
+
const resolvedOutput = resolveWorkflowChildOutputPath({ ctxCwd: input.ctxCwd, workflowCwd: input.workflowCwd, artifactsDir: input.artifactsDir, workflowRunId: input.parentWorkflowRunId, aggregateOutputPath: input.aggregateOutputPath, configuredOutputBaseDir: input.configuredOutputBaseDir, discoverAgents: input.discoverAgents, workflowAgentScope: input.workflowAgentScope, key: input.workflowKey, params: input.childParams });
|
|
3372
|
+
if (resolvedOutput) childParams = { ...input.childParams, output: resolvedOutput };
|
|
3373
|
+
}
|
|
3374
|
+
return prepareWorkflowLaunchParams(input.workflowDefaults, childParams, input.parentWorkflowRunId, input.workflowKey, input.options);
|
|
3246
3375
|
}
|
|
3247
3376
|
|
|
3248
3377
|
function buildChainWorktreeTaskCwdError(chain: ChainStep[], sharedCwd: string): string | undefined {
|
|
@@ -4073,6 +4202,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
4073
4202
|
task: shouldForkAgent(contextPolicy, params.agent!) ? wrapForkTask(task) : task,
|
|
4074
4203
|
goal: task,
|
|
4075
4204
|
agentConfig,
|
|
4205
|
+
recoveryAgentConfig: data.recoveryAgents.find((agent) => agent.name === params.agent),
|
|
4076
4206
|
ctx: asyncCtx,
|
|
4077
4207
|
availableModels,
|
|
4078
4208
|
cwd: effectiveCwd,
|
|
@@ -4097,6 +4227,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
4097
4227
|
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
4098
4228
|
worktreeBaseDir: deps.config.worktreeBaseDir,
|
|
4099
4229
|
controlConfig,
|
|
4230
|
+
intercomBridge: params.intercomBridge,
|
|
4100
4231
|
controlIntercomTarget: data.intercomBridge.active ? data.intercomBridge.orchestratorTarget : undefined,
|
|
4101
4232
|
childIntercomTarget: data.intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(id, agent, index) : undefined,
|
|
4102
4233
|
nestedRoute: data.nestedRoute,
|
|
@@ -4469,16 +4600,19 @@ export function prepareWorkflowLaunchParams(
|
|
|
4469
4600
|
const timeoutMs = childParams.timeoutMs ?? childParams.maxRuntimeMs ?? workflowDefaults.timeoutMs ?? workflowDefaults.maxRuntimeMs;
|
|
4470
4601
|
const turnBudget = childParams.turnBudget ?? workflowDefaults.turnBudget;
|
|
4471
4602
|
const toolBudget = childParams.toolBudget ?? workflowDefaults.toolBudget;
|
|
4603
|
+
const intercomBridge = childParams.intercomBridge ?? workflowDefaults.intercomBridge;
|
|
4472
4604
|
return {
|
|
4473
4605
|
action: "resume",
|
|
4474
4606
|
id: childParams.resume.trim(),
|
|
4475
4607
|
message: typeof childParams.task === "string" ? childParams.task.trim() : "",
|
|
4476
4608
|
workflowParentRunId: parentWorkflowRunId,
|
|
4477
4609
|
workflowKey,
|
|
4610
|
+
...(options.runFanoutBudget ? { runFanoutBudget: { ...options.runFanoutBudget, parentPath: `${options.runFanoutBudget.parentPath ? `${options.runFanoutBudget.parentPath}/` : ""}workflow[${workflowKey}]` } } : {}),
|
|
4478
4611
|
...(options.missionDetached ? { mission: false } : {}),
|
|
4479
4612
|
...(timeoutMs !== undefined ? { timeoutMs: timeoutMs as number } : {}),
|
|
4480
4613
|
...(turnBudget !== undefined ? { turnBudget: turnBudget as TurnBudgetConfig } : {}),
|
|
4481
4614
|
...(toolBudget !== undefined ? { toolBudget: toolBudget as ToolBudgetConfig } : {}),
|
|
4615
|
+
...(intercomBridge !== undefined ? { intercomBridge: intercomBridge as IntercomBridgeConfig } : {}),
|
|
4482
4616
|
};
|
|
4483
4617
|
}
|
|
4484
4618
|
const launchParams = {
|
|
@@ -4665,6 +4799,17 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4665
4799
|
const workflowUsageBudget = validateUsageBudgetConfig(requestParams.usageBudget ?? deps.config.usageBudget, requestParams.usageBudget ? "usageBudget" : "config.usageBudget");
|
|
4666
4800
|
if (workflowUsageBudget.error) return buildRequestedModeError(requestParams, workflowUsageBudget.error);
|
|
4667
4801
|
const workflowCwd = resolveRequestedCwd(parentCwd, requestParams.cwd);
|
|
4802
|
+
const workflowArtifactConfig: ArtifactConfig = omitUndefinedProperties({
|
|
4803
|
+
...DEFAULT_ARTIFACT_CONFIG,
|
|
4804
|
+
enabled: requestParams.artifacts !== false,
|
|
4805
|
+
dir: deps.config.artifactDir ?? DEFAULT_ARTIFACT_CONFIG.dir,
|
|
4806
|
+
});
|
|
4807
|
+
const workflowArtifactsDir = getArtifactsDir(ctx.sessionManager.getSessionFile() ?? null, workflowCwd, workflowArtifactConfig.dir);
|
|
4808
|
+
if (workflowArtifactConfig.dir === "project" && !warnedArtifactPackageDirs.has(workflowCwd)) {
|
|
4809
|
+
warnedArtifactPackageDirs.add(workflowCwd);
|
|
4810
|
+
const warning = getProjectArtifactPackagingWarning(workflowCwd);
|
|
4811
|
+
if (warning) console.warn(`[pi-subagents] ${warning}`);
|
|
4812
|
+
}
|
|
4668
4813
|
const workflowPrompts = { render: (ref: string, vars?: unknown) => renderWorkflowPrompt(ref, vars, workflowCwd) };
|
|
4669
4814
|
const chatProgressResult = resolveWorkflowChatProgress({ requested: requestParams.chatProgress, parentCwd, workflowCwd, background: requestParams.async !== false });
|
|
4670
4815
|
if (chatProgressResult.error) return { content: [{ type: "text", text: chatProgressResult.error }], isError: true, details: { mode: "workflow", results: [] } };
|
|
@@ -4838,6 +4983,10 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4838
4983
|
void Promise.resolve().then(async () => {
|
|
4839
4984
|
const workflowResults: SingleResult[] = [];
|
|
4840
4985
|
const { action: _action, agent: _agent, task: _task, resume: _resume, tasks: _tasks, chain: _chain, concurrency: _concurrency, foregroundOnly: _foregroundOnly, clarify: _clarify, timeoutMs: _timeoutMs, maxRuntimeMs: _maxRuntimeMs, usageBudget: _usageBudget, missionId: _missionId, mission: _mission, ...workflowChildDefaults } = workflowRequest;
|
|
4986
|
+
const workflowOutput = typeof workflowChildDefaults.output === "string" || typeof workflowChildDefaults.output === "boolean" ? workflowChildDefaults.output : undefined;
|
|
4987
|
+
const configuredOutputBaseDir = resolveConfiguredSingleRunOutputBaseDir(deps);
|
|
4988
|
+
const workflowAggregateOutputPath = resolveWorkflowAggregateOutputPath(workflowOutput, ctx.cwd, workflowCwd, resolveSingleRunOutputBaseDir(deps, workflowArtifactsDir, workflowRunId), configuredOutputBaseDir);
|
|
4989
|
+
const claimedOutputPaths = new Map<string, string>();
|
|
4841
4990
|
const workflowSteps = new Map<string, NonNullable<AsyncStatus["steps"]>[number]>();
|
|
4842
4991
|
let projectedTraceLength = 0;
|
|
4843
4992
|
let projectedTraceTail: NonNullable<Details["workflow"]>["trace"][number] | undefined;
|
|
@@ -4857,12 +5006,20 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4857
5006
|
if (entry.operation !== "run") continue;
|
|
4858
5007
|
const existing = workflowSteps.get(entry.key);
|
|
4859
5008
|
if (entry.state === "reused" && existing) continue;
|
|
4860
|
-
const mapped = entry.state === "started" || entry.state === "reused"
|
|
5009
|
+
const mapped = entry.state === "started" || entry.state === "reused"
|
|
5010
|
+
? "running"
|
|
5011
|
+
: entry.state === "completed"
|
|
5012
|
+
? "completed"
|
|
5013
|
+
: entry.state === "stopped"
|
|
5014
|
+
? "stopped"
|
|
5015
|
+
: "failed";
|
|
4861
5016
|
if (existing) {
|
|
4862
5017
|
existing.status = mapped;
|
|
4863
5018
|
if (entry.agent) existing.agent = entry.agent;
|
|
4864
5019
|
if (entry.runId) existing.runId = entry.runId;
|
|
4865
5020
|
if (entry.state === "failed" && !entry.runId && existing.async === undefined) existing.async = false;
|
|
5021
|
+
if (entry.state === "stopped") existing.stopped = true;
|
|
5022
|
+
else delete existing.stopped;
|
|
4866
5023
|
if (entry.error === undefined) delete existing.error;
|
|
4867
5024
|
else existing.error = entry.error;
|
|
4868
5025
|
if (entry.durationMs === undefined) delete existing.durationMs;
|
|
@@ -4877,6 +5034,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4877
5034
|
startedAt: Date.now(),
|
|
4878
5035
|
...(entry.runId ? { runId: entry.runId } : {}),
|
|
4879
5036
|
...(entry.state === "failed" && !entry.runId ? { async: false } : {}),
|
|
5037
|
+
...(entry.state === "stopped" ? { stopped: true } : {}),
|
|
4880
5038
|
};
|
|
4881
5039
|
status.steps?.push(step);
|
|
4882
5040
|
workflowSteps.set(entry.key, step);
|
|
@@ -4897,7 +5055,10 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4897
5055
|
...(workflowState ? { state: workflowState } : {}),
|
|
4898
5056
|
onTrace: updateTrace,
|
|
4899
5057
|
admit: (calls) => {
|
|
5058
|
+
const outputClaims = workflowChildOutputClaims({ ctxCwd: ctx.cwd, workflowCwd, artifactsDir: workflowArtifactsDir, workflowRunId, aggregateOutputPath: workflowAggregateOutputPath, configuredOutputBaseDir, discoverAgents: deps.discoverAgents, workflowAgentScope: workflowChildDefaults.agentScope, claimedOutputPaths, entries: calls });
|
|
5059
|
+
if (outputClaims.error) throw new Error(outputClaims.error);
|
|
4900
5060
|
status.runFanoutBudget = claimRunFanoutBatch(workflowFanoutBudget, calls.map(({ key }) => `workflow[${key}]`));
|
|
5061
|
+
if (outputClaims.claims) applyWorkflowChildOutputClaims(claimedOutputPaths, outputClaims.claims);
|
|
4901
5062
|
persist();
|
|
4902
5063
|
},
|
|
4903
5064
|
onEmit: (emits) => {
|
|
@@ -4921,7 +5082,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4921
5082
|
});
|
|
4922
5083
|
const result = await runMissionWorkflowChild(missionBinding, workflowRunId, key, childPhase, () => {
|
|
4923
5084
|
const childRequest = bindMissionWorkflowChildAsyncLaunch(
|
|
4924
|
-
{ ...
|
|
5085
|
+
{ ...prepareWorkflowChildLaunchParams({ workflowDefaults: workflowChildDefaults, childParams, parentWorkflowRunId: workflowRunId, workflowKey: key, ctxCwd: ctx.cwd, workflowCwd, artifactsDir: workflowArtifactsDir, aggregateOutputPath: workflowAggregateOutputPath, configuredOutputBaseDir, discoverAgents: deps.discoverAgents, workflowAgentScope: workflowChildDefaults.agentScope, options: { missionDetached: detachWorkflowChildMissions, runFanoutBudget: workflowFanoutBudget } }), runFanoutAdmitted: admission.admitted },
|
|
4925
5086
|
missionBinding,
|
|
4926
5087
|
deps.asyncByDefault,
|
|
4927
5088
|
);
|
|
@@ -4939,7 +5100,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4939
5100
|
return execute(randomUUID(), childRequest, workflowSignal, (update) => {
|
|
4940
5101
|
const progress = update.details.progress?.[0];
|
|
4941
5102
|
const step = status.steps?.find((candidate) => candidate.workflowKey === key);
|
|
4942
|
-
if (!progress || !step) return;
|
|
5103
|
+
if (!progress || !step || step.stopped) return;
|
|
4943
5104
|
step.status = progress.status === "completed" ? "completed" : progress.status === "failed" ? "failed" : "running";
|
|
4944
5105
|
step.activityState = progress.activityState;
|
|
4945
5106
|
step.lastActivityAt = progress.lastActivityAt;
|
|
@@ -4991,16 +5152,20 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4991
5152
|
const returnPreview = formatWorkflowValue(workflow.value).slice(0, 1_000);
|
|
4992
5153
|
const emitPreview = workflow.emits.length > 0 ? ` Emitted: ${workflow.emits.map(formatWorkflowValue).join(", ").slice(0, 1_000)}` : "";
|
|
4993
5154
|
const summary = `Workflow completed with ${workflow.children.length} child run(s). Return: ${returnPreview}${emitPreview} Trace: ${workflow.trace.length} event(s).`;
|
|
5155
|
+
const outputWarning = writeWorkflowAggregateOutput(workflowAggregateOutputPath, summary);
|
|
5156
|
+
const resultSummary = appendWorkflowOutputWarning(summary, outputWarning);
|
|
4994
5157
|
const workflowUsage = sumResultsUsage(workflowResults);
|
|
4995
5158
|
status = { ...status, state: "complete", endedAt: Date.now(), workflow: { value: workflow.value, trace: workflow.trace, emits: workflow.emits, console: workflow.console }, totalTokens: { input: workflowUsage.input, output: workflowUsage.output, total: workflowUsage.input + workflowUsage.output }, totalCost: sumResultsCost(workflowResults) };
|
|
4996
|
-
writeAtomicJson(resultPath, { id: workflowRunId, runId: workflowRunId, toolCallId, agent: "workflow", mode: "workflow", success: true, state: "complete", summary, output:
|
|
5159
|
+
writeAtomicJson(resultPath, { id: workflowRunId, runId: workflowRunId, toolCallId, agent: "workflow", mode: "workflow", success: true, state: "complete", summary: resultSummary, output: resultSummary, results: workflow.children.map((child) => ({ workflowKey: child.key, ...(child.agent ? { agent: child.agent } : {}), ...(child.runId ? { runId: child.runId } : {}), output: child.output, outputState: child.output.trim() || child.structuredOutput !== undefined ? "present" : "absent", structuredOutput: child.structuredOutput, success: child.ok, ...(child.artifactPaths[0] ? { artifactPaths: { outputPath: child.artifactPaths[0] } } : {}) })), workflow: status.workflow, asyncDir, cwd: workflowCwd, sessionId: currentSessionId, timestamp: Date.now(), durationMs: Date.now() - startedAt });
|
|
4997
5160
|
persist();
|
|
4998
5161
|
appendWorkflowEvent({ type: "subagent.workflow.completed", state: "complete" });
|
|
4999
5162
|
} catch (error) {
|
|
5000
5163
|
const partial = error instanceof WorkflowScriptError ? error.partial : { trace: [], emits: [], console: [], children: [] };
|
|
5001
5164
|
const stopped = controller.signal.aborted;
|
|
5002
5165
|
status = compactOptional<AsyncStatus>({ ...status, state: stopped ? "stopped" : "failed", stopped: stopped || undefined, error: error instanceof Error ? error.message : String(error), endedAt: Date.now(), workflow: { trace: partial.trace, emits: partial.emits, console: partial.console } });
|
|
5003
|
-
|
|
5166
|
+
const outputWarning = writeWorkflowAggregateOutput(workflowAggregateOutputPath, status.error ?? "Workflow failed.");
|
|
5167
|
+
const resultSummary = appendWorkflowOutputWarning(status.error ?? "Workflow failed.", outputWarning);
|
|
5168
|
+
writeAtomicJson(resultPath, { id: workflowRunId, runId: workflowRunId, toolCallId, agent: "workflow", mode: "workflow", success: false, state: status.state, summary: resultSummary, error: status.error, stopped: status.stopped, results: partial.children.map((child) => ({ workflowKey: child.key, ...(child.agent ? { agent: child.agent } : {}), ...(child.runId ? { runId: child.runId } : {}), output: child.output, outputState: child.output.trim() || child.structuredOutput !== undefined ? "present" : "absent", structuredOutput: child.structuredOutput, success: child.ok, ...(child.artifactPaths[0] ? { artifactPaths: { outputPath: child.artifactPaths[0] } } : {}) })), workflow: status.workflow, asyncDir, cwd: workflowCwd, sessionId: currentSessionId, timestamp: Date.now(), durationMs: Date.now() - startedAt });
|
|
5004
5169
|
persist();
|
|
5005
5170
|
appendWorkflowEvent({ type: "subagent.workflow.completed", state: status.state, error: status.error });
|
|
5006
5171
|
} finally {
|
|
@@ -5015,6 +5180,10 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5015
5180
|
}, workflowFanoutBudget));
|
|
5016
5181
|
}
|
|
5017
5182
|
const { workflowScript: _workflowScript, action: _action, agent: _agent, task: _task, resume: _resume, tasks: _tasks, chain: _chain, concurrency: _concurrency, async: _async, foregroundOnly: _foregroundOnly, clarify: _clarify, timeoutMs: _timeoutMs, maxRuntimeMs: _maxRuntimeMs, usageBudget: _usageBudget, chatProgress: _chatProgress, missionId: _missionId, mission: _mission, ...workflowChildDefaults } = requestParams;
|
|
5183
|
+
const workflowOutput = typeof workflowChildDefaults.output === "string" || typeof workflowChildDefaults.output === "boolean" ? workflowChildDefaults.output : undefined;
|
|
5184
|
+
const configuredOutputBaseDir = resolveConfiguredSingleRunOutputBaseDir(deps);
|
|
5185
|
+
const workflowAggregateOutputPath = resolveWorkflowAggregateOutputPath(workflowOutput, ctx.cwd, workflowCwd, resolveSingleRunOutputBaseDir(deps, workflowArtifactsDir, _id), configuredOutputBaseDir);
|
|
5186
|
+
const claimedOutputPaths = new Map<string, string>();
|
|
5018
5187
|
const workflowResults: SingleResult[] = [];
|
|
5019
5188
|
let liveWorkflow: NonNullable<Details["workflow"]> = { trace: [], emits: [], console: [] };
|
|
5020
5189
|
const sendWorkflowProgress = () => {
|
|
@@ -5033,7 +5202,10 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5033
5202
|
sendWorkflowProgress();
|
|
5034
5203
|
},
|
|
5035
5204
|
admit: (calls) => {
|
|
5205
|
+
const outputClaims = workflowChildOutputClaims({ ctxCwd: ctx.cwd, workflowCwd, artifactsDir: workflowArtifactsDir, workflowRunId: _id, aggregateOutputPath: workflowAggregateOutputPath, configuredOutputBaseDir, discoverAgents: deps.discoverAgents, workflowAgentScope: workflowChildDefaults.agentScope, claimedOutputPaths, entries: calls });
|
|
5206
|
+
if (outputClaims.error) throw new Error(outputClaims.error);
|
|
5036
5207
|
claimRunFanoutBatch(workflowFanoutBudget, calls.map(({ key }) => `workflow[${key}]`));
|
|
5208
|
+
if (outputClaims.claims) applyWorkflowChildOutputClaims(claimedOutputPaths, outputClaims.claims);
|
|
5037
5209
|
},
|
|
5038
5210
|
onEmit: (emits) => {
|
|
5039
5211
|
liveWorkflow = { ...liveWorkflow, emits };
|
|
@@ -5054,7 +5226,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5054
5226
|
});
|
|
5055
5227
|
const result = await runMissionWorkflowChild(missionBinding, _id, key, childPhase, () => {
|
|
5056
5228
|
const childRequest = bindMissionWorkflowChildAsyncLaunch(
|
|
5057
|
-
{ ...
|
|
5229
|
+
{ ...prepareWorkflowChildLaunchParams({ workflowDefaults: workflowChildDefaults, childParams, parentWorkflowRunId: _id, workflowKey: key, ctxCwd: ctx.cwd, workflowCwd, artifactsDir: workflowArtifactsDir, aggregateOutputPath: workflowAggregateOutputPath, configuredOutputBaseDir, discoverAgents: deps.discoverAgents, workflowAgentScope: workflowChildDefaults.agentScope, options: { missionDetached: detachWorkflowChildMissions, suppressRoutineResultIntercom: chatProgress.mode === "live-card", runFanoutBudget: workflowFanoutBudget } }), runFanoutAdmitted: admission.admitted },
|
|
5058
5230
|
missionBinding,
|
|
5059
5231
|
deps.asyncByDefault,
|
|
5060
5232
|
);
|
|
@@ -5095,8 +5267,11 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5095
5267
|
if (workflow.emits.length > 0) sections.push(`Emitted:\n${workflow.emits.map(formatWorkflowValue).join("\n")}`);
|
|
5096
5268
|
if (workflow.console.length > 0) sections.push(`Console:\n${workflow.console.map((entry) => `[${entry.level}] ${entry.text}`).join("\n")}`);
|
|
5097
5269
|
if (traceLines.length > 0) sections.push(`Call trace:\n${traceLines.join("\n")}`);
|
|
5270
|
+
const workflowText = sections.join("\n\n");
|
|
5271
|
+
const outputWarning = writeWorkflowAggregateOutput(workflowAggregateOutputPath, workflowText);
|
|
5272
|
+
const displayText = appendWorkflowOutputWarning(workflowText, outputWarning);
|
|
5098
5273
|
return attachWorkflowMission(withRunFanoutBudget({
|
|
5099
|
-
content: [{ type: "text", text:
|
|
5274
|
+
content: [{ type: "text", text: displayText }],
|
|
5100
5275
|
details: compactOptional<Details>({ mode: "workflow", runId: _id, results: workflow.children.flatMap((child) => (child.results ?? []) as SingleResult[]), totalChildUsage: sumResultsUsage(workflowResults), totalCost: sumResultsCost(workflowResults), usageBudget: usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults)), workflow: { value: workflow.value, trace: workflow.trace, emits: workflow.emits, console: workflow.console }, chatProgress }),
|
|
5101
5276
|
}, workflowFanoutBudget));
|
|
5102
5277
|
} catch (error) {
|
|
@@ -5107,8 +5282,11 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5107
5282
|
if (partial.emits.length > 0) sections.push(`Emitted:\n${partial.emits.map(formatWorkflowValue).join("\n")}`);
|
|
5108
5283
|
if (partial.console.length > 0) sections.push(`Console:\n${partial.console.map((entry) => `[${entry.level}] ${entry.text}`).join("\n")}`);
|
|
5109
5284
|
if (traceLines.length > 0) sections.push(`Call trace:\n${traceLines.join("\n")}`);
|
|
5285
|
+
const workflowText = sections.join("\n\n");
|
|
5286
|
+
const outputWarning = writeWorkflowAggregateOutput(workflowAggregateOutputPath, workflowText);
|
|
5287
|
+
const displayText = appendWorkflowOutputWarning(workflowText, outputWarning);
|
|
5110
5288
|
return attachWorkflowMission(withRunFanoutBudget({
|
|
5111
|
-
content: [{ type: "text", text:
|
|
5289
|
+
content: [{ type: "text", text: displayText }],
|
|
5112
5290
|
isError: true,
|
|
5113
5291
|
details: compactOptional<Details>({ mode: "workflow", runId: _id, results: partial.children.flatMap((child) => (child.results ?? []) as SingleResult[]), totalChildUsage: sumResultsUsage(workflowResults), totalCost: sumResultsCost(workflowResults), usageBudget: usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults)), workflow: { trace: partial.trace, emits: partial.emits, console: partial.console }, chatProgress }),
|
|
5114
5292
|
}, workflowFanoutBudget));
|
|
@@ -5372,11 +5550,11 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5372
5550
|
details: { mode: "management", results: [], spawnBudget, activeAsyncCapacity },
|
|
5373
5551
|
};
|
|
5374
5552
|
}
|
|
5375
|
-
if (action === "status") {
|
|
5553
|
+
if (action === "status" || action === "debug.run") {
|
|
5376
5554
|
if (!preserveActiveSession) deps.state.currentSessionId = resolveCurrentSessionId(ctx.sessionManager);
|
|
5377
5555
|
const targetRunId = paramsWithResolvedCwd.id ?? paramsWithResolvedCwd.runId;
|
|
5378
5556
|
const hasDirectoryTarget = Boolean(paramsWithResolvedCwd.dir);
|
|
5379
|
-
const targetLabel = formatStatusTargetLabel(paramsWithResolvedCwd, targetRunId);
|
|
5557
|
+
const targetLabel = action === "debug.run" ? "Debug run" : formatStatusTargetLabel(paramsWithResolvedCwd, targetRunId);
|
|
5380
5558
|
const withBudget = (result: AgentToolResult<Details>) => {
|
|
5381
5559
|
const budgeted = withSpawnBudgetStatus(result, deps.state, deps.config, deps.state.currentSessionId);
|
|
5382
5560
|
return {
|
|
@@ -5388,6 +5566,15 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5388
5566
|
};
|
|
5389
5567
|
const nestedScope = nestedResolutionScopeForExecutor(deps);
|
|
5390
5568
|
const sessionRoots = trustedSessionRootsForStatus(ctx, deps);
|
|
5569
|
+
if (action === "debug.run") {
|
|
5570
|
+
if (!targetRunId && !hasDirectoryTarget) {
|
|
5571
|
+
return withBudget({ content: [{ type: "text", text: "action='debug.run' requires id, runId, or dir." }], isError: true, details: { mode: "management", results: [] } });
|
|
5572
|
+
}
|
|
5573
|
+
if (paramsWithResolvedCwd.view) {
|
|
5574
|
+
return withBudget({ content: [{ type: "text", text: "action='debug.run' does not support status views." }], isError: true, details: { mode: "management", results: [] } });
|
|
5575
|
+
}
|
|
5576
|
+
return withBudget(inspectSubagentStatus(paramsWithResolvedCwd, omitUndefinedProperties({ state: deps.state, nested: nestedScope, sessionRoots })));
|
|
5577
|
+
}
|
|
5391
5578
|
if (paramsWithResolvedCwd.view === "fleet") {
|
|
5392
5579
|
return withBudget(inspectSubagentStatus(paramsWithResolvedCwd, omitUndefinedProperties({ state: deps.state, nested: nestedScope, sessionRoots })));
|
|
5393
5580
|
}
|
|
@@ -5777,6 +5964,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5777
5964
|
const sessionName = resolveIntercomSessionTarget(deps.pi.getSessionName(), ctx.sessionManager.getSessionId());
|
|
5778
5965
|
const intercomBridge = resolveIntercomBridge({
|
|
5779
5966
|
config: deps.config.intercomBridge,
|
|
5967
|
+
override: effectiveParams.intercomBridge,
|
|
5780
5968
|
context: effectiveParams.context ?? (contextPolicy.usesFork ? "fork" : undefined),
|
|
5781
5969
|
orchestratorTarget: sessionName,
|
|
5782
5970
|
});
|
|
@@ -6039,6 +6227,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6039
6227
|
signal,
|
|
6040
6228
|
onUpdate: onUpdateWithContext,
|
|
6041
6229
|
agents,
|
|
6230
|
+
recoveryAgents: discoveredAgents,
|
|
6042
6231
|
runId,
|
|
6043
6232
|
shareEnabled,
|
|
6044
6233
|
sessionRoot,
|
|
@@ -6229,7 +6418,12 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6229
6418
|
};
|
|
6230
6419
|
|
|
6231
6420
|
let nestedForegroundStarted = false;
|
|
6421
|
+
let asyncLaunchFailed = false;
|
|
6232
6422
|
try {
|
|
6423
|
+
if (effectiveAsync) {
|
|
6424
|
+
deps.state.liveAsyncSessionRoots ??= new Map();
|
|
6425
|
+
deps.state.liveAsyncSessionRoots.set(asyncRunId, sessionRoot);
|
|
6426
|
+
}
|
|
6233
6427
|
if (workflowLaunchObserver) {
|
|
6234
6428
|
const singleTask = hasTasks && effectiveParams.tasks?.length === 1 ? effectiveParams.tasks[0] : undefined;
|
|
6235
6429
|
const launch = hasSingle
|
|
@@ -6243,7 +6437,10 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6243
6437
|
}
|
|
6244
6438
|
}
|
|
6245
6439
|
const asyncResult = runAsyncPath(execData, deps);
|
|
6246
|
-
if (asyncResult)
|
|
6440
|
+
if (asyncResult) {
|
|
6441
|
+
asyncLaunchFailed = asyncResult.isError === true;
|
|
6442
|
+
return attachMission(withRunFanoutBudget(withResolvedContext(withForkThinkingNotes(asyncResult, forkThinkingDowngrades), contextPolicy.contextSummary), runFanoutBudget));
|
|
6443
|
+
}
|
|
6247
6444
|
if (foregroundControl) {
|
|
6248
6445
|
writeNestedForegroundEvent("subagent.nested.started");
|
|
6249
6446
|
nestedForegroundStarted = true;
|
|
@@ -6265,10 +6462,12 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6265
6462
|
return attachMission(withRunFanoutBudget(withResolvedContext(withForkThinkingNotes(result, forkThinkingDowngrades), contextPolicy.contextSummary), runFanoutBudget, { annotateContent: runFanoutAnnotateContent }));
|
|
6266
6463
|
}
|
|
6267
6464
|
} catch (error) {
|
|
6465
|
+
asyncLaunchFailed = effectiveAsync;
|
|
6268
6466
|
const errorResult = withForkThinkingNotes(toExecutionErrorResult(effectiveParams, error, contextPolicy.contextSummary), forkThinkingDowngrades);
|
|
6269
6467
|
if (nestedForegroundStarted) writeNestedForegroundEvent("subagent.nested.completed", errorResult);
|
|
6270
6468
|
return attachMission(errorResult);
|
|
6271
6469
|
} finally {
|
|
6470
|
+
if (effectiveAsync && (asyncLaunchFailed || (activeAsyncCapacity && !activeAsyncCapacity.owner.runnerStartedAt))) deps.state.liveAsyncSessionRoots?.delete(asyncRunId);
|
|
6272
6471
|
if (activeAsyncCapacity && !activeAsyncCapacity.owner.runnerStartedAt) activeAsyncCapacity.rollback();
|
|
6273
6472
|
if (foregroundControl) {
|
|
6274
6473
|
settleForegroundSchedulingOwner(foregroundControl);
|
|
@@ -22,6 +22,10 @@ const READ_ONLY_BUILTIN_TOOLS = new Set([
|
|
|
22
22
|
const CURSOR_FILE_MUTATION_THINKING =
|
|
23
23
|
/(?:^|\n)\s*Cursor (?:edit|write)\s*:/i;
|
|
24
24
|
|
|
25
|
+
const IMPLEMENTATION_CHALLENGE_TASK_PATTERN = /^You are reviving a previous subagent conversation\.\n\nOriginal run: .+\nOriginal agent: .+(?:\nOriginal session file: .+)?\n\nUse the stored session context as background\. Answer the orchestrator's follow-up below\. Do not assume the original child process is still alive\.\n\nFollow-up:\nRun implementation challenge pass (?:one|two|\d+) and implement any better current-scope change\.$/;
|
|
26
|
+
const NO_BETTER_CHANGE_NEEDED_PATTERN = /^\s*no (?:better|further|additional) (?:current[- ]scope )?(?:code |source |file )?(?:change|changes|edit|edits|patch|patches) (?:is|are) needed[.!]?\s*$/i;
|
|
27
|
+
const NO_BETTER_CHANGE_QUALIFIER_PATTERN = /\b(?:do\s+not|don't|dont|not|never|cannot|can't|cant|unable|uncertain|unsure|unclear|maybe|might|may|\w+n['’]t)\b/i;
|
|
28
|
+
|
|
25
29
|
interface CompletionMutationGuardInput {
|
|
26
30
|
agent: string;
|
|
27
31
|
task: string;
|
|
@@ -82,14 +86,26 @@ export function hasMutationToolCall(messages: Message[]): boolean {
|
|
|
82
86
|
return false;
|
|
83
87
|
}
|
|
84
88
|
|
|
89
|
+
function reportsNoBetterChallengeChange(messages: Message[]): boolean {
|
|
90
|
+
const report = messages
|
|
91
|
+
.filter((message) => message.role === "assistant")
|
|
92
|
+
.flatMap((message) => message.content)
|
|
93
|
+
.flatMap((part) => part.type === "text" ? [part.text] : [])
|
|
94
|
+
.join("\n");
|
|
95
|
+
return NO_BETTER_CHANGE_NEEDED_PATTERN.test(report)
|
|
96
|
+
&& !NO_BETTER_CHANGE_QUALIFIER_PATTERN.test(report);
|
|
97
|
+
}
|
|
98
|
+
|
|
85
99
|
export function evaluateCompletionMutationGuard(input: CompletionMutationGuardInput): CompletionMutationGuardResult {
|
|
86
100
|
const expectedMutation = hasMutationToolCapability(input.tools, input.mcpDirectTools)
|
|
87
101
|
? expectsImplementationMutation(input.agent, input.task)
|
|
88
102
|
: false;
|
|
89
103
|
const attemptedMutation = hasMutationToolCall(input.messages);
|
|
104
|
+
const noEditChallengeComplete = IMPLEMENTATION_CHALLENGE_TASK_PATTERN.test(input.task)
|
|
105
|
+
&& reportsNoBetterChallengeChange(input.messages);
|
|
90
106
|
return {
|
|
91
107
|
expectedMutation,
|
|
92
108
|
attemptedMutation,
|
|
93
|
-
triggered: expectedMutation && !attemptedMutation,
|
|
109
|
+
triggered: expectedMutation && !attemptedMutation && !noEditChallengeComplete,
|
|
94
110
|
};
|
|
95
111
|
}
|