pi-subagents 0.45.2 → 0.46.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 +24 -0
- package/README.md +2 -0
- package/docs/agents.md +342 -0
- package/docs/configuration.md +320 -0
- package/docs/extension-api.md +308 -0
- package/docs/missions.md +117 -0
- package/docs/models.md +190 -0
- package/docs/observability.md +174 -0
- package/docs/tool-reference.md +343 -0
- package/docs/watchdog.md +176 -0
- package/docs/workflows.md +163 -0
- package/package.json +4 -2
- package/skills/pi-subagents/references/execution-controls.md +2 -2
- package/src/agents/agents.ts +17 -8
- package/src/agents/frontmatter.ts +7 -3
- package/src/agents/skills.ts +2 -9
- package/src/api/project-panes.ts +30 -0
- package/src/extension/config.ts +15 -1
- package/src/extension/index.ts +36 -16
- package/src/extension/schemas.ts +3 -2
- package/src/extension/subagent-guide.ts +39 -0
- package/src/extension/tool-description.ts +4 -4
- package/src/inspectors/herdr/project-panes.ts +457 -62
- package/src/missions/actions.ts +25 -2
- package/src/missions/lifecycle.ts +21 -2
- package/src/missions/store.ts +77 -1
- package/src/missions/types.ts +33 -0
- package/src/runs/background/async-execution.ts +7 -1
- package/src/runs/background/completion-replay.ts +267 -0
- package/src/runs/background/result-watcher.ts +12 -4
- package/src/runs/background/wait-completions.ts +39 -5
- package/src/runs/background/wait-subscriptions.ts +18 -3
- package/src/runs/foreground/execution.ts +4 -0
- package/src/runs/foreground/foreground-history.ts +137 -0
- package/src/runs/foreground/subagent-executor.ts +310 -44
- package/src/shared/fork-context.ts +13 -0
- package/src/shared/prompt-resources.ts +51 -0
- package/src/shared/types.ts +30 -1
- package/src/shared/utf8.ts +11 -0
- package/src/slash/prompt-workflows.ts +2 -15
- package/src/slash/slash-commands.ts +19 -1
- package/src/tui/fleet-status.ts +8 -2
- package/src/tui/fleet.ts +135 -25
- package/src/tui/render.ts +120 -7
- package/src/workflows/scripted-workflow.ts +167 -10
|
@@ -17,10 +17,12 @@ import {
|
|
|
17
17
|
settleForegroundSchedulingOwner,
|
|
18
18
|
updateForegroundChild,
|
|
19
19
|
} from "./foreground-control.ts";
|
|
20
|
+
import { persistForegroundRunHistory, MAX_REMEMBERED_FOREGROUND_RUNS } from "./foreground-history.ts";
|
|
20
21
|
import { resolveExecutionAgentScope } from "../../agents/agent-scope.ts";
|
|
21
22
|
import { handleManagementAction } from "../../agents/agent-management.ts";
|
|
22
23
|
import { handleRefinementAction } from "../../agents/agent-refinements.ts";
|
|
23
24
|
import { buildDoctorReport } from "../../extension/doctor.ts";
|
|
25
|
+
import { readSubagentGuide } from "../../extension/subagent-guide.ts";
|
|
24
26
|
import { normalizePublicSubagentExecution } from "../../extension/public-execution.ts";
|
|
25
27
|
import { runSync } from "./execution.ts";
|
|
26
28
|
import { handleWatchdogToolAction, WATCHDOG_TOOL_ACTIONS } from "../../watchdog/tool-actions.ts";
|
|
@@ -49,8 +51,8 @@ import {
|
|
|
49
51
|
type StepOverrides,
|
|
50
52
|
} from "../../shared/settings.ts";
|
|
51
53
|
import { discoverAvailableSkills, normalizeSkillInput } from "../../agents/skills.ts";
|
|
52
|
-
import { buildAsyncRunnerSteps, executeAsyncChain, executeAsyncSingle, formatAsyncStartedMessage, isAsyncAvailable } from "../background/async-execution.ts";
|
|
53
|
-
import type
|
|
54
|
+
import { buildAsyncRunnerSteps, executeAsyncChain, executeAsyncSingle, formatAsyncStartedMessage, isAsyncAvailable, workflowAwaitedAsyncResultPath } from "../background/async-execution.ts";
|
|
55
|
+
import { isScheduledRunAction, type ScheduledRunAction } from "../background/scheduled-runs.ts";
|
|
54
56
|
import { enqueueChainAppendRequest, readPendingChainAppendRequests, runnerStepOutputNames } from "../background/chain-append.ts";
|
|
55
57
|
import { ChainOutputValidationError, validateChainOutputBindingsWithContext } from "../shared/chain-outputs.ts";
|
|
56
58
|
import { normalizeGateAcceptance, validateExecutionAcceptance } from "../shared/acceptance.ts";
|
|
@@ -84,20 +86,22 @@ import { updateSteeringTarget, waitForSteeringAction } from "../background/steer
|
|
|
84
86
|
import { steerAsyncRun } from "./async-steering-action.ts";
|
|
85
87
|
import { stopAsyncRun } from "./async-stop-action.ts";
|
|
86
88
|
import { reconcileAsyncRun } from "../background/stale-run-reconciler.ts";
|
|
87
|
-
import { resolveAsyncRootResultPath } from "../background/chain-root-attachment.ts";
|
|
89
|
+
import { resolveAsyncRootResultPath, waitForImportedAsyncRoot } from "../background/chain-root-attachment.ts";
|
|
88
90
|
import { attachRootChildrenToSteps, createNestedRoute, findNestedControlResult, resolveInheritedNestedRouteFromEnv, resolveNestedAsyncDir, resolveNestedParentAddressFromEnv, snapshotNestedEventFiles, updateForegroundNestedProjection, writeNestedControlRequest, writeNestedEvent, type NestedRunResolutionScope } from "../shared/nested-events.ts";
|
|
89
91
|
import { resolveSubagentRunId, type ResolvedSubagentRunId } from "../background/run-id-resolver.ts";
|
|
90
92
|
import { formatNestedRunStatusLines } from "../shared/nested-render.ts";
|
|
91
93
|
import { inspectSubagentStatus } from "../background/run-status.ts";
|
|
92
94
|
import { applyForceTopLevelAsyncOverride } from "../background/top-level-async.ts";
|
|
93
95
|
import { handleMissionAction, MISSION_ACTIONS } from "../../missions/actions.ts";
|
|
94
|
-
import { attachMissionToLaunchResult, prepareMissionLaunch, type MissionLaunchBinding } from "../../missions/lifecycle.ts";
|
|
96
|
+
import { attachMissionToLaunchResult, prepareMissionLaunch, writeMissionAsyncBinding, type MissionLaunchBinding } from "../../missions/lifecycle.ts";
|
|
95
97
|
import { updateMission } from "../../missions/store.ts";
|
|
98
|
+
import type { MissionWorkflowChildUpdate } from "../../missions/types.ts";
|
|
96
99
|
import { createMissionWorkflowState } from "../../missions/workflow-state.ts";
|
|
97
100
|
import { resolveAuthorityDecision } from "../../policy/authority.ts";
|
|
98
101
|
import { handleHerdrInspectorAction, HERDR_INSPECTOR_ACTIONS } from "../../inspectors/herdr/actions.ts";
|
|
99
102
|
import { handleHerdrProjectPaneAction, HERDR_PROJECT_PANE_ACTIONS } from "../../inspectors/herdr/project-panes.ts";
|
|
100
103
|
import { previewSimpleWorkflowRun, runWorkflowScript, WorkflowScriptError, type WorkflowScriptChildResult } from "../../workflows/scripted-workflow.ts";
|
|
104
|
+
import { renderWorkflowPrompt } from "../../shared/prompt-resources.ts";
|
|
101
105
|
import { resolveWorkflowChatProgress, type WorkflowChatProgressProjection } from "../../workflows/chat-progress.ts";
|
|
102
106
|
import {
|
|
103
107
|
cleanupWorktrees,
|
|
@@ -150,7 +154,48 @@ import {
|
|
|
150
154
|
wrapForkTask,
|
|
151
155
|
} from "../../shared/types.ts";
|
|
152
156
|
|
|
153
|
-
const MUTATING_MANAGEMENT_ACTIONS = new Set(["create", "update", "delete", "eject", "disable", "enable", "reset", "grant-spawn-budget", "watchdog.configure", "mission.create", "mission.update", "mission.attach-run", "mission.close", "inspector.open", "inspector.close", "project.open", "project.close", "worktree.discard", "refine", "refine.rollback", "schedule.create", "schedule.pause", "schedule.resume", "schedule.run", "schedule.run-due", "schedule.delete"]);
|
|
157
|
+
const MUTATING_MANAGEMENT_ACTIONS = new Set(["create", "update", "delete", "eject", "disable", "enable", "reset", "grant-spawn-budget", "watchdog.configure", "mission.create", "mission.update", "mission.resolve-decision", "mission.attach-run", "mission.close", "inspector.open", "inspector.close", "project.open", "project.close", "worktree.discard", "refine", "refine.rollback", "schedule.create", "schedule.pause", "schedule.resume", "schedule.run", "schedule.run-due", "schedule.delete"]);
|
|
158
|
+
const DESTRUCTIVE_MANAGEMENT_ACTIONS = new Set(["delete", "eject", "disable", "reset", "mission.close", "worktree.discard", "refine.rollback", "inspector.close", "project.close", "stop", "interrupt", "reject-checkpoint", "schedule.delete"]);
|
|
159
|
+
|
|
160
|
+
function editDistance(left: string, right: string): number {
|
|
161
|
+
const previous = Array.from({ length: right.length + 1 }, (_, index) => index);
|
|
162
|
+
for (let leftIndex = 1; leftIndex <= left.length; leftIndex += 1) {
|
|
163
|
+
let diagonal = previous[0]!;
|
|
164
|
+
previous[0] = leftIndex;
|
|
165
|
+
for (let rightIndex = 1; rightIndex <= right.length; rightIndex += 1) {
|
|
166
|
+
const above = previous[rightIndex]!;
|
|
167
|
+
previous[rightIndex] = left[leftIndex - 1] === right[rightIndex - 1]
|
|
168
|
+
? diagonal
|
|
169
|
+
: Math.min(diagonal, above, previous[rightIndex - 1]!) + 1;
|
|
170
|
+
diagonal = above;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return previous[right.length]!;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function hasSingleAdjacentTransposition(left: string, right: string): boolean {
|
|
177
|
+
if (left.length !== right.length) return false;
|
|
178
|
+
const mismatch = [...left].findIndex((character, index) => character !== right[index]);
|
|
179
|
+
return mismatch >= 0
|
|
180
|
+
&& left[mismatch] === right[mismatch + 1]
|
|
181
|
+
&& left[mismatch + 1] === right[mismatch]
|
|
182
|
+
&& left.slice(mismatch + 2) === right.slice(mismatch + 2);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export function unknownSubagentActionMessage(action: string): string {
|
|
186
|
+
const requested = action.toLowerCase();
|
|
187
|
+
const suggestion = SUBAGENT_ACTIONS.find((candidate) => {
|
|
188
|
+
const distance = editDistance(requested, candidate);
|
|
189
|
+
const closeMatch = distance <= Math.max(1, Math.floor(candidate.length / 4)) || hasSingleAdjacentTransposition(requested, candidate);
|
|
190
|
+
if (DESTRUCTIVE_MANAGEMENT_ACTIONS.has(candidate)) return distance === 1 && requested.length >= candidate.length - 1;
|
|
191
|
+
return closeMatch;
|
|
192
|
+
});
|
|
193
|
+
const nextStep = 'Use subagent({ action: "status" }) to inspect runs or subagent({ action: "list" }) to inspect agents.';
|
|
194
|
+
const validActions = `Valid: ${SUBAGENT_ACTIONS.join(", ")}.`;
|
|
195
|
+
return suggestion
|
|
196
|
+
? `Unknown action: ${action}. Did you mean ${suggestion}? ${nextStep} ${validActions}`
|
|
197
|
+
: `Unknown action: ${action}. ${nextStep} ${validActions}`;
|
|
198
|
+
}
|
|
154
199
|
|
|
155
200
|
type UndefinedOmitted<T extends object> = {
|
|
156
201
|
[K in keyof T as undefined extends T[K] ? never : K]: T[K];
|
|
@@ -209,6 +254,7 @@ export interface SubagentParamsLike {
|
|
|
209
254
|
index?: number;
|
|
210
255
|
view?: "fleet" | "transcript";
|
|
211
256
|
lines?: number;
|
|
257
|
+
topic?: string;
|
|
212
258
|
chainName?: string;
|
|
213
259
|
config?: unknown;
|
|
214
260
|
name?: string;
|
|
@@ -226,6 +272,7 @@ export interface SubagentParamsLike {
|
|
|
226
272
|
/** Internal workflow ownership metadata; not part of the public schema. */
|
|
227
273
|
workflowParentRunId?: string;
|
|
228
274
|
workflowKey?: string;
|
|
275
|
+
workflowChildAsyncId?: string;
|
|
229
276
|
suppressRoutineResultIntercom?: boolean;
|
|
230
277
|
/** Internal durable-run compatibility fields. Public callers must use workflowScript. */
|
|
231
278
|
chain?: ChainStep[];
|
|
@@ -473,7 +520,7 @@ function foregroundStatusResult(control: SubagentState["foregroundControls"] ext
|
|
|
473
520
|
|
|
474
521
|
function trimRememberedForegroundRuns(state: SubagentState): void {
|
|
475
522
|
if (!state.foregroundRuns) return;
|
|
476
|
-
while (state.foregroundRuns.size >
|
|
523
|
+
while (state.foregroundRuns.size > MAX_REMEMBERED_FOREGROUND_RUNS) {
|
|
477
524
|
const oldestTerminal = [...state.foregroundRuns.values()]
|
|
478
525
|
.filter((run) => !run.children.some((child) => child.status === "detached"))
|
|
479
526
|
.sort((left, right) => left.updatedAt - right.updatedAt)[0];
|
|
@@ -482,6 +529,14 @@ function trimRememberedForegroundRuns(state: SubagentState): void {
|
|
|
482
529
|
}
|
|
483
530
|
}
|
|
484
531
|
|
|
532
|
+
function persistRememberedForegroundRuns(state: SubagentState): void {
|
|
533
|
+
try {
|
|
534
|
+
persistForegroundRunHistory(state);
|
|
535
|
+
} catch (error) {
|
|
536
|
+
console.error("Failed to persist foreground run history:", error);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
485
540
|
function foregroundChildActivityFromProgress(progress: SingleResult["progress"] | undefined) {
|
|
486
541
|
return {
|
|
487
542
|
...(progress?.activityState ? { activityState: progress.activityState } : {}),
|
|
@@ -548,6 +603,7 @@ function rememberForegroundRun(state: SubagentState, input: { runId: string; mod
|
|
|
548
603
|
}),
|
|
549
604
|
});
|
|
550
605
|
trimRememberedForegroundRuns(state);
|
|
606
|
+
persistRememberedForegroundRuns(state);
|
|
551
607
|
}
|
|
552
608
|
|
|
553
609
|
function applyControlEventToRememberedForegroundRun(state: SubagentState, event: ControlEvent): void {
|
|
@@ -623,6 +679,7 @@ function updateRememberedForegroundChild(state: SubagentState, input: { runId: s
|
|
|
623
679
|
...(input.result.capabilityAudit ? { capabilityAudit: input.result.capabilityAudit } : {}),
|
|
624
680
|
});
|
|
625
681
|
trimRememberedForegroundRuns(state);
|
|
682
|
+
persistRememberedForegroundRuns(state);
|
|
626
683
|
const output = getSingleResultOutput(input.result).trim();
|
|
627
684
|
const success = terminalStatus === "completed";
|
|
628
685
|
const summary = !success && input.result.error
|
|
@@ -1277,6 +1334,7 @@ async function resumeAsyncRun(input: {
|
|
|
1277
1334
|
deps: ExecutorDeps;
|
|
1278
1335
|
parentModel?: ParentModel;
|
|
1279
1336
|
absoluteDeadlineAt?: number;
|
|
1337
|
+
signal?: AbortSignal;
|
|
1280
1338
|
}): Promise<AgentToolResult<Details>> {
|
|
1281
1339
|
const followUp = (input.params.message ?? input.params.task ?? "").trim();
|
|
1282
1340
|
const attachChain = (input.params.chain?.length ?? 0) > 0 ? input.params.chain as ChainStep[] : undefined;
|
|
@@ -1563,6 +1621,58 @@ async function resumeAsyncRun(input: {
|
|
|
1563
1621
|
}
|
|
1564
1622
|
|
|
1565
1623
|
const revivedId = result.details.asyncId ?? runId;
|
|
1624
|
+
if (input.params.workflowParentRunId !== undefined && result.details.asyncDir) {
|
|
1625
|
+
const asyncDir = result.details.asyncDir;
|
|
1626
|
+
const resultPath = workflowAwaitedAsyncResultPath(asyncDir);
|
|
1627
|
+
const stopOnAbort = () => { stopAsyncRun(input.deps.state, revivedId, input.deps.kill, { asyncDir, resolvedId: revivedId }); };
|
|
1628
|
+
if (input.signal?.aborted) stopOnAbort();
|
|
1629
|
+
else input.signal?.addEventListener("abort", stopOnAbort, { once: true });
|
|
1630
|
+
let completed: Awaited<ReturnType<typeof waitForImportedAsyncRoot>>;
|
|
1631
|
+
try {
|
|
1632
|
+
completed = await waitForImportedAsyncRoot({ runId: revivedId, asyncDir, resultPath, index: 0 });
|
|
1633
|
+
} finally {
|
|
1634
|
+
input.signal?.removeEventListener("abort", stopOnAbort);
|
|
1635
|
+
}
|
|
1636
|
+
fs.rmSync(resultPath, { force: true });
|
|
1637
|
+
const totalCost = completed.totalCost;
|
|
1638
|
+
const childResult: SingleResult = {
|
|
1639
|
+
index: 0,
|
|
1640
|
+
agent: completed.agent,
|
|
1641
|
+
task: effectiveFollowUp,
|
|
1642
|
+
exitCode: completed.exitCode,
|
|
1643
|
+
usage: {
|
|
1644
|
+
input: totalCost?.inputTokens ?? 0,
|
|
1645
|
+
output: totalCost?.outputTokens ?? 0,
|
|
1646
|
+
cacheRead: 0,
|
|
1647
|
+
cacheWrite: 0,
|
|
1648
|
+
cost: totalCost?.costUsd ?? 0,
|
|
1649
|
+
turns: 0,
|
|
1650
|
+
},
|
|
1651
|
+
finalOutput: completed.output,
|
|
1652
|
+
outputState: completed.output.trim() ? "present" : "absent",
|
|
1653
|
+
...(completed.error ? { error: completed.error } : {}),
|
|
1654
|
+
...(completed.timedOut ? { timedOut: true } : {}),
|
|
1655
|
+
...(completed.stopped ? { stopped: true } : {}),
|
|
1656
|
+
...(completed.sessionFile ? { sessionFile: completed.sessionFile } : {}),
|
|
1657
|
+
...(completed.model ? { model: completed.model } : {}),
|
|
1658
|
+
...(completed.attemptedModels ? { attemptedModels: completed.attemptedModels } : {}),
|
|
1659
|
+
...(completed.modelAttempts ? { modelAttempts: completed.modelAttempts } : {}),
|
|
1660
|
+
...(completed.structuredOutput !== undefined ? { structuredOutput: completed.structuredOutput } : {}),
|
|
1661
|
+
...(completed.structuredOutputPath ? { structuredOutputPath: completed.structuredOutputPath } : {}),
|
|
1662
|
+
...(completed.structuredOutputSchemaPath ? { structuredOutputSchemaPath: completed.structuredOutputSchemaPath } : {}),
|
|
1663
|
+
...(completed.acceptance ? { acceptance: completed.acceptance } : {}),
|
|
1664
|
+
};
|
|
1665
|
+
return {
|
|
1666
|
+
content: [{ type: "text", text: completed.output || completed.error || `Revived ${target.source} subagent ${revivedId} completed without output.` }],
|
|
1667
|
+
...(completed.success ? {} : { isError: true }),
|
|
1668
|
+
details: {
|
|
1669
|
+
...result.details,
|
|
1670
|
+
runId: revivedId,
|
|
1671
|
+
results: [childResult],
|
|
1672
|
+
...(target.launchContractDigest ? { sourceLaunchContractDigest: target.launchContractDigest } : {}),
|
|
1673
|
+
},
|
|
1674
|
+
};
|
|
1675
|
+
}
|
|
1566
1676
|
const revivedTarget = intercomBridge.active ? resolveSubagentIntercomTarget(revivedId, target.agent, 0) : undefined;
|
|
1567
1677
|
const sourceLabel = target.source;
|
|
1568
1678
|
const lines = [
|
|
@@ -2414,7 +2524,8 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
2414
2524
|
details: { mode: "single" as const, results: [] },
|
|
2415
2525
|
};
|
|
2416
2526
|
}
|
|
2417
|
-
const
|
|
2527
|
+
const requestedWorkflowChildAsyncId = typeof params.workflowChildAsyncId === "string" ? params.workflowChildAsyncId.trim() : "";
|
|
2528
|
+
const id = requestedWorkflowChildAsyncId && path.basename(requestedWorkflowChildAsyncId) === requestedWorkflowChildAsyncId ? requestedWorkflowChildAsyncId : randomUUID();
|
|
2418
2529
|
const parentModel = data.parentModel;
|
|
2419
2530
|
const asyncCtx = compactOptional<Parameters<typeof executeAsyncSingle>[1]["ctx"]>({
|
|
2420
2531
|
pi: deps.pi,
|
|
@@ -3944,6 +4055,61 @@ function duplicateSubagentCallResult(params: SubagentParamsLike): AgentToolResul
|
|
|
3944
4055
|
|
|
3945
4056
|
const workflowLaunchObservers = new WeakMap<object, (launch: { agent: string; sessionFile?: string }) => void>();
|
|
3946
4057
|
|
|
4058
|
+
function recordMissionWorkflowChild(
|
|
4059
|
+
binding: MissionLaunchBinding | undefined,
|
|
4060
|
+
workflowRunId: string,
|
|
4061
|
+
key: string,
|
|
4062
|
+
update: Omit<MissionWorkflowChildUpdate, "workflowRunId" | "key">,
|
|
4063
|
+
): void {
|
|
4064
|
+
if (!binding) return;
|
|
4065
|
+
try {
|
|
4066
|
+
updateMission(binding.location, binding.missionId, { upsertWorkflowChildren: [{ workflowRunId, key, ...update }] });
|
|
4067
|
+
} catch (error) {
|
|
4068
|
+
console.warn(`[pi-subagents] Failed to record mission workflow child '${key}': ${error instanceof Error ? error.message : String(error)}`);
|
|
4069
|
+
}
|
|
4070
|
+
}
|
|
4071
|
+
|
|
4072
|
+
export function missionWorkflowChildStatus(result: AgentToolResult<Details>): string {
|
|
4073
|
+
const childResults = result.details.results;
|
|
4074
|
+
if (childResults.some((child) => child.detached || child.interrupted)) return "paused";
|
|
4075
|
+
if (result.isError === true || childResults.some((child) => child.exitCode !== 0)) return "failed";
|
|
4076
|
+
if (childResults.length === 0 && (result.details.asyncId || result.details.asyncDir)) return "running";
|
|
4077
|
+
return "completed";
|
|
4078
|
+
}
|
|
4079
|
+
|
|
4080
|
+
export async function runMissionWorkflowChild(
|
|
4081
|
+
binding: MissionLaunchBinding | undefined,
|
|
4082
|
+
workflowRunId: string,
|
|
4083
|
+
key: string,
|
|
4084
|
+
phase: string | undefined,
|
|
4085
|
+
run: () => Promise<AgentToolResult<Details>>,
|
|
4086
|
+
): Promise<AgentToolResult<Details>> {
|
|
4087
|
+
try {
|
|
4088
|
+
return await run();
|
|
4089
|
+
} catch (error) {
|
|
4090
|
+
recordMissionWorkflowChild(binding, workflowRunId, key, {
|
|
4091
|
+
status: "failed",
|
|
4092
|
+
completedAt: new Date().toISOString(),
|
|
4093
|
+
heartbeat: { status: "failed", ...(phase ? { phase } : {}), message: error instanceof Error ? error.message : String(error) },
|
|
4094
|
+
});
|
|
4095
|
+
throw error;
|
|
4096
|
+
}
|
|
4097
|
+
}
|
|
4098
|
+
|
|
4099
|
+
export function bindMissionWorkflowChildAsyncLaunch(
|
|
4100
|
+
params: SubagentParamsLike,
|
|
4101
|
+
binding: MissionLaunchBinding | undefined,
|
|
4102
|
+
asyncByDefault: boolean,
|
|
4103
|
+
asyncId: string = randomUUID(),
|
|
4104
|
+
): SubagentParamsLike {
|
|
4105
|
+
const requestedAsync = params.async ?? asyncByDefault;
|
|
4106
|
+
if (!binding || !requestedAsync || params.clarify === true) return params;
|
|
4107
|
+
const id = asyncId.trim();
|
|
4108
|
+
if (!id || path.basename(id) !== id) throw new Error("workflow child async id must be a single path segment");
|
|
4109
|
+
writeMissionAsyncBinding(path.join(DIRS.async, id), binding);
|
|
4110
|
+
return { ...params, workflowChildAsyncId: id };
|
|
4111
|
+
}
|
|
4112
|
+
|
|
3947
4113
|
function workflowChildResult(key: string, result: AgentToolResult<Details>): WorkflowScriptChildResult {
|
|
3948
4114
|
const receiptOutput = result.content.map((part) => part.type === "text" ? part.text : "").filter(Boolean).join("\n");
|
|
3949
4115
|
const output = result.details.results.length === 1 && result.details.results[0]?.finalOutput !== undefined
|
|
@@ -3960,9 +4126,11 @@ function workflowChildResult(key: string, result: AgentToolResult<Details>): Wor
|
|
|
3960
4126
|
if (child.sessionFile) artifactPaths.add(child.sessionFile);
|
|
3961
4127
|
}
|
|
3962
4128
|
const structured = result.details.results.map((child) => child.structuredOutput).filter((value) => value !== undefined);
|
|
4129
|
+
const resolvedAgents = [...new Set(result.details.results.map((child) => child.agent).filter((agent): agent is string => Boolean(agent)))];
|
|
3963
4130
|
return {
|
|
3964
4131
|
key,
|
|
3965
4132
|
ok,
|
|
4133
|
+
...(resolvedAgents.length === 1 ? { agent: resolvedAgents[0] } : {}),
|
|
3966
4134
|
...(result.details.runId || result.details.asyncId ? { runId: result.details.runId ?? result.details.asyncId } : {}),
|
|
3967
4135
|
output,
|
|
3968
4136
|
...(!ok ? { error: receiptOutput || output || "Child run failed." } : {}),
|
|
@@ -4000,6 +4168,7 @@ export function prepareWorkflowLaunchParams(
|
|
|
4000
4168
|
}
|
|
4001
4169
|
const launchParams = {
|
|
4002
4170
|
...workflowDefaults,
|
|
4171
|
+
async: false,
|
|
4003
4172
|
...childParams,
|
|
4004
4173
|
...(options.missionDetached ? { mission: false } : {}),
|
|
4005
4174
|
workflowParentRunId: parentWorkflowRunId,
|
|
@@ -4178,6 +4347,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4178
4347
|
const workflowUsageBudget = validateUsageBudgetConfig(requestParams.usageBudget ?? deps.config.usageBudget, requestParams.usageBudget ? "usageBudget" : "config.usageBudget");
|
|
4179
4348
|
if (workflowUsageBudget.error) return buildRequestedModeError(requestParams, workflowUsageBudget.error);
|
|
4180
4349
|
const workflowCwd = resolveRequestedCwd(parentCwd, requestParams.cwd);
|
|
4350
|
+
const workflowPrompts = { render: (ref: string, vars?: unknown) => renderWorkflowPrompt(ref, vars, workflowCwd) };
|
|
4181
4351
|
const chatProgressResult = resolveWorkflowChatProgress({ requested: requestParams.chatProgress, parentCwd, workflowCwd, background: requestParams.async !== false });
|
|
4182
4352
|
if (chatProgressResult.error) return { content: [{ type: "text", text: chatProgressResult.error }], isError: true, details: { mode: "workflow", results: [] } };
|
|
4183
4353
|
const chatProgress = chatProgressResult.projection!;
|
|
@@ -4321,12 +4491,13 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4321
4491
|
const mapped = entry.state === "started" || entry.state === "reused" ? "running" : entry.state === "completed" ? "completed" : "failed";
|
|
4322
4492
|
if (existing) {
|
|
4323
4493
|
existing.status = mapped;
|
|
4494
|
+
if (entry.agent) existing.agent = entry.agent;
|
|
4324
4495
|
if (entry.error === undefined) delete existing.error;
|
|
4325
4496
|
else existing.error = entry.error;
|
|
4326
4497
|
if (entry.durationMs === undefined) delete existing.durationMs;
|
|
4327
4498
|
else existing.durationMs = entry.durationMs;
|
|
4328
4499
|
} else {
|
|
4329
|
-
status.steps?.push({ agent: entry.key, label: entry.key, workflowKey: entry.key, parentWorkflowRunId: workflowRunId, status: mapped, startedAt: Date.now() });
|
|
4500
|
+
status.steps?.push({ agent: entry.agent ?? entry.key, label: entry.key, workflowKey: entry.key, parentWorkflowRunId: workflowRunId, status: mapped, startedAt: Date.now() });
|
|
4330
4501
|
}
|
|
4331
4502
|
}
|
|
4332
4503
|
projectWorkflowActivity();
|
|
@@ -4338,6 +4509,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4338
4509
|
script: workflowScript,
|
|
4339
4510
|
timeoutMs: timeout,
|
|
4340
4511
|
signal: controller.signal,
|
|
4512
|
+
prompts: workflowPrompts,
|
|
4341
4513
|
...(workflowState ? { state: workflowState } : {}),
|
|
4342
4514
|
onTrace: updateTrace,
|
|
4343
4515
|
onEmit: (emits) => {
|
|
@@ -4351,37 +4523,70 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4351
4523
|
const budgetState = usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults));
|
|
4352
4524
|
if (budgetState?.exhausted) return workflowChildResult(key, buildRequestedModeError(childParams as SubagentParamsLike, usageBudgetExceededMessage(budgetState)));
|
|
4353
4525
|
patchMissionObjective(childParams.task);
|
|
4354
|
-
const
|
|
4355
|
-
|
|
4356
|
-
|
|
4357
|
-
|
|
4358
|
-
|
|
4359
|
-
|
|
4360
|
-
|
|
4526
|
+
const childPhase = typeof childParams.phase === "string" && childParams.phase.trim() ? childParams.phase.trim() : undefined;
|
|
4527
|
+
const childLabel = typeof childParams.label === "string" && childParams.label.trim() ? childParams.label.trim() : undefined;
|
|
4528
|
+
recordMissionWorkflowChild(missionBinding, workflowRunId, key, {
|
|
4529
|
+
status: "running",
|
|
4530
|
+
...(typeof childParams.agent === "string" && childParams.agent.trim() ? { agent: childParams.agent.trim() } : {}),
|
|
4531
|
+
...(typeof childParams.task === "string" && childParams.task.trim() ? { task: childParams.task.trim() } : {}),
|
|
4532
|
+
...(childLabel ? { label: childLabel } : {}),
|
|
4533
|
+
...(childPhase ? { phase: childPhase } : {}),
|
|
4534
|
+
heartbeat: { status: "running", ...(childPhase ? { phase: childPhase } : {}) },
|
|
4535
|
+
});
|
|
4536
|
+
const result = await runMissionWorkflowChild(missionBinding, workflowRunId, key, childPhase, () => {
|
|
4537
|
+
const childRequest = bindMissionWorkflowChildAsyncLaunch(
|
|
4538
|
+
prepareWorkflowLaunchParams(workflowChildDefaults, childParams, workflowRunId, key, { missionDetached: detachWorkflowChildMissions }),
|
|
4539
|
+
missionBinding,
|
|
4540
|
+
deps.asyncByDefault,
|
|
4541
|
+
);
|
|
4542
|
+
workflowLaunchObservers.set(childRequest, (launch) => {
|
|
4543
|
+
const step = status.steps?.find((candidate) => candidate.workflowKey === key);
|
|
4544
|
+
if (step) {
|
|
4545
|
+
step.agent = launch.agent;
|
|
4546
|
+
step.sessionFile = launch.sessionFile;
|
|
4547
|
+
persist();
|
|
4548
|
+
}
|
|
4549
|
+
recordMissionWorkflowChild(missionBinding, workflowRunId, key, { status: "running", agent: launch.agent, ...(launch.sessionFile ? { sessionPath: launch.sessionFile } : {}) });
|
|
4550
|
+
});
|
|
4551
|
+
return execute(randomUUID(), childRequest, workflowSignal, (update) => {
|
|
4552
|
+
const progress = update.details.progress?.[0];
|
|
4553
|
+
const step = status.steps?.find((candidate) => candidate.workflowKey === key);
|
|
4554
|
+
if (!progress || !step) return;
|
|
4555
|
+
step.status = progress.status === "completed" ? "completed" : progress.status === "failed" ? "failed" : "running";
|
|
4556
|
+
step.activityState = progress.activityState;
|
|
4557
|
+
step.lastActivityAt = progress.lastActivityAt;
|
|
4558
|
+
step.currentTool = progress.currentTool;
|
|
4559
|
+
step.currentToolArgs = progress.currentToolArgs;
|
|
4560
|
+
step.currentToolStartedAt = progress.currentToolStartedAt;
|
|
4561
|
+
step.currentPath = progress.currentPath;
|
|
4562
|
+
step.recentTools = progress.recentTools.map((tool) => ({ ...tool }));
|
|
4563
|
+
step.recentOutput = [...progress.recentOutput];
|
|
4564
|
+
step.turnCount = progress.turnCount;
|
|
4565
|
+
step.toolCount = progress.toolCount;
|
|
4566
|
+
step.model = progress.model;
|
|
4567
|
+
step.thinking = progress.thinking;
|
|
4568
|
+
step.error = progress.error;
|
|
4569
|
+
projectWorkflowActivity();
|
|
4570
|
+
persist();
|
|
4571
|
+
recordMissionWorkflowChild(missionBinding, workflowRunId, key, {
|
|
4572
|
+
status: step.status,
|
|
4573
|
+
heartbeat: { status: step.status, ...(childPhase ? { phase: childPhase } : {}) },
|
|
4574
|
+
});
|
|
4575
|
+
}, ctx, preserveActiveSession);
|
|
4361
4576
|
});
|
|
4362
|
-
const result = await execute(randomUUID(), childRequest, workflowSignal, (update) => {
|
|
4363
|
-
const progress = update.details.progress?.[0];
|
|
4364
|
-
const step = status.steps?.find((candidate) => candidate.workflowKey === key);
|
|
4365
|
-
if (!progress || !step) return;
|
|
4366
|
-
step.status = progress.status === "completed" ? "completed" : progress.status === "failed" ? "failed" : "running";
|
|
4367
|
-
step.activityState = progress.activityState;
|
|
4368
|
-
step.lastActivityAt = progress.lastActivityAt;
|
|
4369
|
-
step.currentTool = progress.currentTool;
|
|
4370
|
-
step.currentToolArgs = progress.currentToolArgs;
|
|
4371
|
-
step.currentToolStartedAt = progress.currentToolStartedAt;
|
|
4372
|
-
step.currentPath = progress.currentPath;
|
|
4373
|
-
step.recentTools = progress.recentTools.map((tool) => ({ ...tool }));
|
|
4374
|
-
step.recentOutput = [...progress.recentOutput];
|
|
4375
|
-
step.turnCount = progress.turnCount;
|
|
4376
|
-
step.toolCount = progress.toolCount;
|
|
4377
|
-
step.model = progress.model;
|
|
4378
|
-
step.thinking = progress.thinking;
|
|
4379
|
-
step.error = progress.error;
|
|
4380
|
-
projectWorkflowActivity();
|
|
4381
|
-
persist();
|
|
4382
|
-
}, ctx, preserveActiveSession);
|
|
4383
4577
|
workflowResults.push(...result.details.results);
|
|
4578
|
+
if (result.details.asyncDir && missionBinding) writeMissionAsyncBinding(result.details.asyncDir, missionBinding);
|
|
4384
4579
|
const child = workflowChildResult(key, result);
|
|
4580
|
+
const childStatus = missionWorkflowChildStatus(result);
|
|
4581
|
+
recordMissionWorkflowChild(missionBinding, workflowRunId, key, {
|
|
4582
|
+
status: childStatus,
|
|
4583
|
+
...(child.runId ? { runId: child.runId } : {}),
|
|
4584
|
+
...(result.details.results[0]?.agent ? { agent: result.details.results[0].agent } : {}),
|
|
4585
|
+
...(result.details.results[0]?.sessionFile ? { sessionPath: result.details.results[0].sessionFile } : {}),
|
|
4586
|
+
artifactPaths: child.artifactPaths,
|
|
4587
|
+
...(["completed", "failed"].includes(childStatus) ? { completedAt: new Date().toISOString() } : {}),
|
|
4588
|
+
heartbeat: { status: childStatus, ...(childPhase ? { phase: childPhase } : {}) },
|
|
4589
|
+
});
|
|
4385
4590
|
if (result.details.asyncId) {
|
|
4386
4591
|
const childJob = deps.state.asyncJobs.get(result.details.asyncId);
|
|
4387
4592
|
if (childJob) { childJob.parentWorkflowRunId = workflowRunId; childJob.workflowKey = key; }
|
|
@@ -4395,14 +4600,14 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4395
4600
|
const summary = `Workflow completed with ${workflow.children.length} child run(s). Return: ${returnPreview}${emitPreview} Trace: ${workflow.trace.length} event(s).`;
|
|
4396
4601
|
const workflowUsage = sumResultsUsage(workflowResults);
|
|
4397
4602
|
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) };
|
|
4398
|
-
writeAtomicJson(resultPath, { id: workflowRunId, runId: workflowRunId, toolCallId, agent: "workflow", mode: "workflow", success: true, state: "complete", summary, output: summary, results: workflow.children.map((child) => ({
|
|
4603
|
+
writeAtomicJson(resultPath, { id: workflowRunId, runId: workflowRunId, toolCallId, agent: "workflow", mode: "workflow", success: true, state: "complete", summary, output: summary, 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 });
|
|
4399
4604
|
persist();
|
|
4400
4605
|
appendWorkflowEvent({ type: "subagent.workflow.completed", state: "complete" });
|
|
4401
4606
|
} catch (error) {
|
|
4402
4607
|
const partial = error instanceof WorkflowScriptError ? error.partial : { trace: [], emits: [], console: [], children: [] };
|
|
4403
4608
|
const stopped = controller.signal.aborted;
|
|
4404
4609
|
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 } });
|
|
4405
|
-
writeAtomicJson(resultPath, { id: workflowRunId, runId: workflowRunId, toolCallId, agent: "workflow", mode: "workflow", success: false, state: status.state, summary: status.error, error: status.error, stopped: status.stopped, results: partial.children.map((child) => ({
|
|
4610
|
+
writeAtomicJson(resultPath, { id: workflowRunId, runId: workflowRunId, toolCallId, agent: "workflow", mode: "workflow", success: false, state: status.state, summary: status.error, 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 });
|
|
4406
4611
|
persist();
|
|
4407
4612
|
appendWorkflowEvent({ type: "subagent.workflow.completed", state: status.state, error: status.error });
|
|
4408
4613
|
} finally {
|
|
@@ -4426,6 +4631,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4426
4631
|
script: requestParams.workflowScript,
|
|
4427
4632
|
timeoutMs: timeout,
|
|
4428
4633
|
signal,
|
|
4634
|
+
prompts: workflowPrompts,
|
|
4429
4635
|
...(workflowState ? { state: workflowState } : {}),
|
|
4430
4636
|
onTrace: (trace) => {
|
|
4431
4637
|
liveWorkflow = { ...liveWorkflow, trace };
|
|
@@ -4440,10 +4646,51 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4440
4646
|
const budgetState = usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults));
|
|
4441
4647
|
if (budgetState?.exhausted) return workflowChildResult(key, buildRequestedModeError(childParams as SubagentParamsLike, usageBudgetExceededMessage(budgetState)));
|
|
4442
4648
|
patchMissionObjective(childParams.task);
|
|
4443
|
-
const
|
|
4444
|
-
const
|
|
4649
|
+
const childPhase = typeof childParams.phase === "string" && childParams.phase.trim() ? childParams.phase.trim() : undefined;
|
|
4650
|
+
const childLabel = typeof childParams.label === "string" && childParams.label.trim() ? childParams.label.trim() : undefined;
|
|
4651
|
+
recordMissionWorkflowChild(missionBinding, _id, key, {
|
|
4652
|
+
status: "running",
|
|
4653
|
+
...(typeof childParams.agent === "string" && childParams.agent.trim() ? { agent: childParams.agent.trim() } : {}),
|
|
4654
|
+
...(typeof childParams.task === "string" && childParams.task.trim() ? { task: childParams.task.trim() } : {}),
|
|
4655
|
+
...(childLabel ? { label: childLabel } : {}),
|
|
4656
|
+
...(childPhase ? { phase: childPhase } : {}),
|
|
4657
|
+
heartbeat: { status: "running", ...(childPhase ? { phase: childPhase } : {}) },
|
|
4658
|
+
});
|
|
4659
|
+
const result = await runMissionWorkflowChild(missionBinding, _id, key, childPhase, () => {
|
|
4660
|
+
const childRequest = bindMissionWorkflowChildAsyncLaunch(
|
|
4661
|
+
prepareWorkflowLaunchParams(workflowChildDefaults, childParams, _id, key, { missionDetached: detachWorkflowChildMissions, suppressRoutineResultIntercom: chatProgress.mode === "live-card" }),
|
|
4662
|
+
missionBinding,
|
|
4663
|
+
deps.asyncByDefault,
|
|
4664
|
+
);
|
|
4665
|
+
workflowLaunchObservers.set(childRequest, (launch) => recordMissionWorkflowChild(missionBinding, _id, key, {
|
|
4666
|
+
status: "running",
|
|
4667
|
+
agent: launch.agent,
|
|
4668
|
+
...(launch.sessionFile ? { sessionPath: launch.sessionFile } : {}),
|
|
4669
|
+
}));
|
|
4670
|
+
return execute(randomUUID(), childRequest, workflowSignal, (update) => {
|
|
4671
|
+
const progress = update.details.progress?.[0];
|
|
4672
|
+
if (!progress) return;
|
|
4673
|
+
const progressStatus = progress.status === "completed" ? "completed" : progress.status === "failed" ? "failed" : "running";
|
|
4674
|
+
recordMissionWorkflowChild(missionBinding, _id, key, {
|
|
4675
|
+
status: progressStatus,
|
|
4676
|
+
heartbeat: { status: progressStatus, ...(childPhase ? { phase: childPhase } : {}) },
|
|
4677
|
+
});
|
|
4678
|
+
}, ctx, preserveActiveSession);
|
|
4679
|
+
});
|
|
4445
4680
|
workflowResults.push(...result.details.results);
|
|
4446
|
-
|
|
4681
|
+
if (result.details.asyncDir && missionBinding) writeMissionAsyncBinding(result.details.asyncDir, missionBinding);
|
|
4682
|
+
const child = workflowChildResult(key, result);
|
|
4683
|
+
const childStatus = missionWorkflowChildStatus(result);
|
|
4684
|
+
recordMissionWorkflowChild(missionBinding, _id, key, {
|
|
4685
|
+
status: childStatus,
|
|
4686
|
+
...(child.runId ? { runId: child.runId } : {}),
|
|
4687
|
+
...(result.details.results[0]?.agent ? { agent: result.details.results[0].agent } : {}),
|
|
4688
|
+
...(result.details.results[0]?.sessionFile ? { sessionPath: result.details.results[0].sessionFile } : {}),
|
|
4689
|
+
artifactPaths: child.artifactPaths,
|
|
4690
|
+
...(["completed", "failed"].includes(childStatus) ? { completedAt: new Date().toISOString() } : {}),
|
|
4691
|
+
heartbeat: { status: childStatus, ...(childPhase ? { phase: childPhase } : {}) },
|
|
4692
|
+
});
|
|
4693
|
+
return child;
|
|
4447
4694
|
},
|
|
4448
4695
|
status: async (keyOrRunId, workflowSignal) => workflowChildResult(keyOrRunId, await execute(randomUUID(), { action: "status", id: keyOrRunId }, workflowSignal, undefined, ctx, preserveActiveSession)),
|
|
4449
4696
|
});
|
|
@@ -4485,7 +4732,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4485
4732
|
? normalizeParentModel(ctx.model)
|
|
4486
4733
|
: rememberParentModel(deps.state, requestSessionId, ctx.model);
|
|
4487
4734
|
} catch (error) {
|
|
4488
|
-
if (action?.toLowerCase() !== "doctor") throw error;
|
|
4735
|
+
if (action?.toLowerCase() !== "doctor" && action?.toLowerCase() !== "guide") throw error;
|
|
4489
4736
|
requestParentModel = normalizeParentModel(ctx.model);
|
|
4490
4737
|
}
|
|
4491
4738
|
if (action) {
|
|
@@ -4667,6 +4914,20 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4667
4914
|
details: { mode: "management", results: [], spawnBudget: granted.snapshot },
|
|
4668
4915
|
};
|
|
4669
4916
|
}
|
|
4917
|
+
if (action === "guide") {
|
|
4918
|
+
try {
|
|
4919
|
+
return {
|
|
4920
|
+
content: [{ type: "text", text: readSubagentGuide(paramsWithResolvedCwd.topic) }],
|
|
4921
|
+
details: { mode: "management", results: [] },
|
|
4922
|
+
};
|
|
4923
|
+
} catch (error) {
|
|
4924
|
+
return {
|
|
4925
|
+
content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }],
|
|
4926
|
+
isError: true,
|
|
4927
|
+
details: { mode: "management", results: [] },
|
|
4928
|
+
};
|
|
4929
|
+
}
|
|
4930
|
+
}
|
|
4670
4931
|
if (action === "children.list") {
|
|
4671
4932
|
deps.state.currentSessionId = resolveCurrentSessionId(ctx.sessionManager);
|
|
4672
4933
|
const children = listRetainedChildren(DIRS.async, deps.state.currentSessionId);
|
|
@@ -4817,7 +5078,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4817
5078
|
}
|
|
4818
5079
|
}
|
|
4819
5080
|
if (action === "resume") {
|
|
4820
|
-
return resumeAsyncRun(omitUndefinedProperties({ params: paramsWithResolvedCwd, requestCwd, ctx, deps, parentModel: requestParentModel }));
|
|
5081
|
+
return resumeAsyncRun(omitUndefinedProperties({ params: paramsWithResolvedCwd, requestCwd, ctx, deps, parentModel: requestParentModel, signal }));
|
|
4821
5082
|
}
|
|
4822
5083
|
if (action === "steer") {
|
|
4823
5084
|
deps.state.currentSessionId = resolveCurrentSessionId(ctx.sessionManager);
|
|
@@ -4898,6 +5159,9 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4898
5159
|
return appendStepToAsyncChain(omitUndefinedProperties({ params: paramsWithResolvedCwd, requestCwd, ctx, deps, parentModel: requestParentModel }));
|
|
4899
5160
|
}
|
|
4900
5161
|
if (action.startsWith("schedule.")) {
|
|
5162
|
+
if (!isScheduledRunAction(action)) {
|
|
5163
|
+
return { content: [{ type: "text", text: unknownSubagentActionMessage(action) }], isError: true, details: { mode: "management", results: [] } };
|
|
5164
|
+
}
|
|
4901
5165
|
if (deps.allowMutatingManagementActions === false && MUTATING_MANAGEMENT_ACTIONS.has(action)) {
|
|
4902
5166
|
return {
|
|
4903
5167
|
content: [{ type: "text", text: `Action '${action}' is not available from child-safe subagent fanout mode.` }],
|
|
@@ -5004,7 +5268,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5004
5268
|
}
|
|
5005
5269
|
if (!(SUBAGENT_ACTIONS as readonly string[]).includes(action)) {
|
|
5006
5270
|
return {
|
|
5007
|
-
content: [{ type: "text", text:
|
|
5271
|
+
content: [{ type: "text", text: unknownSubagentActionMessage(action) }],
|
|
5008
5272
|
isError: true,
|
|
5009
5273
|
details: { mode: "management" as const, results: [] },
|
|
5010
5274
|
};
|
|
@@ -5326,6 +5590,8 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5326
5590
|
runId,
|
|
5327
5591
|
sessionId: requestSessionId,
|
|
5328
5592
|
mode: foregroundMode,
|
|
5593
|
+
...(effectiveParams.workflowParentRunId ? { parentWorkflowRunId: effectiveParams.workflowParentRunId } : {}),
|
|
5594
|
+
...(effectiveParams.workflowKey ? { workflowKey: effectiveParams.workflowKey } : {}),
|
|
5329
5595
|
startedAt: Date.now(),
|
|
5330
5596
|
updatedAt: Date.now(),
|
|
5331
5597
|
cwd: effectiveCwd,
|
|
@@ -9,6 +9,7 @@ type SubagentExecutionContext = "fresh" | "fork";
|
|
|
9
9
|
interface BranchSessionEntry {
|
|
10
10
|
type: string;
|
|
11
11
|
id?: string;
|
|
12
|
+
cwd?: string;
|
|
12
13
|
parentId?: string | null;
|
|
13
14
|
timestamp?: string;
|
|
14
15
|
message?: {
|
|
@@ -128,6 +129,18 @@ function readSessionEntries(sessionFile: string): BranchSessionEntry[] {
|
|
|
128
129
|
});
|
|
129
130
|
}
|
|
130
131
|
|
|
132
|
+
/** Keep Pi from restoring a forked session into the parent's cwd instead of the child launch cwd. */
|
|
133
|
+
export function alignForkedSessionCwd(sessionFile: string, cwd: string): void {
|
|
134
|
+
const entries = readSessionEntries(sessionFile);
|
|
135
|
+
const header = entries[0];
|
|
136
|
+
if (header?.type !== "session") throw new Error(`Forked session ${sessionFile} does not start with a session header.`);
|
|
137
|
+
const resolvedCwd = path.resolve(cwd);
|
|
138
|
+
const effectiveCwd = fs.realpathSync.native(resolvedCwd);
|
|
139
|
+
if (header.cwd === effectiveCwd) return;
|
|
140
|
+
header.cwd = effectiveCwd;
|
|
141
|
+
fs.writeFileSync(sessionFile, `${entries.map((entry) => JSON.stringify(entry)).join("\n")}\n`, "utf-8");
|
|
142
|
+
}
|
|
143
|
+
|
|
131
144
|
export function createForkContextResolver(
|
|
132
145
|
sessionManager: ForkableSessionManager,
|
|
133
146
|
requestedContext: unknown,
|