pi-subagents 0.63.0 → 0.65.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 +57 -1
- package/README.md +2 -2
- package/agents/reviewer.md +1 -1
- package/agents/scout.md +1 -1
- package/docs/agents.md +18 -16
- package/docs/configuration.md +7 -15
- package/docs/extension-api.md +15 -6
- package/docs/missions.md +2 -0
- package/docs/observability.md +10 -12
- package/docs/tool-reference.md +7 -6
- package/docs/watchdog.md +92 -114
- package/docs/workflows.md +5 -3
- package/package.json +3 -4
- package/skills/pi-subagents/references/constraints-and-recipes.md +1 -1
- package/skills/pi-subagents/references/execution-controls.md +6 -5
- package/src/agents/agent-management.ts +47 -15
- package/src/api/capability-ceiling.ts +0 -1
- package/src/api/{pi-args.ts → child-tool-plan.ts} +1 -1
- package/src/api/preflight.ts +2 -3
- package/src/extension/doctor.ts +2 -10
- package/src/extension/fanout-child.ts +9 -11
- package/src/extension/index.ts +27 -5
- package/src/extension/public-execution.ts +14 -0
- package/src/extension/rpc.ts +3 -2
- package/src/extension/schemas.ts +2 -1
- package/src/extension/tool-description.ts +9 -8
- package/src/intercom/native-supervisor-channel.ts +138 -60
- package/src/intercom/supervisor-ui.ts +243 -0
- package/src/runs/background/async-execution.ts +46 -24
- package/src/runs/background/async-job-tracker.ts +11 -0
- package/src/runs/background/async-resume.ts +11 -2
- package/src/runs/background/control-channel.ts +2 -204
- package/src/runs/background/notify.ts +41 -2
- package/src/runs/background/process-terminal.ts +1 -1
- package/src/runs/background/run-child-session.ts +613 -0
- package/src/runs/background/run-status.ts +0 -1
- package/src/runs/background/runner-aliases.ts +125 -0
- package/src/runs/background/runner-child-sessions.ts +31 -0
- package/src/runs/background/scheduled-runs.ts +18 -4
- package/src/runs/background/subagent-runner.ts +203 -898
- package/src/runs/foreground/async-steering-action.ts +1 -17
- package/src/runs/foreground/execution.ts +196 -378
- package/src/runs/foreground/foreground-control.ts +4 -0
- package/src/runs/foreground/subagent-executor.ts +121 -58
- package/src/runs/foreground/workflow-foreground-steering.ts +24 -98
- package/src/runs/shared/abort-recovery.ts +3 -3
- package/src/runs/shared/acceptance.ts +10 -0
- package/src/runs/shared/async-status-projection.ts +11 -43
- package/src/runs/shared/capability-ceiling.ts +1 -2
- package/src/runs/shared/child-hooks.ts +25 -0
- package/src/runs/shared/child-identity.ts +13 -2
- package/src/runs/shared/child-launch.ts +314 -0
- package/src/runs/shared/child-lifecycle.ts +25 -0
- package/src/runs/shared/child-runtime-config.ts +126 -0
- package/src/runs/shared/child-session.ts +342 -0
- package/src/runs/shared/child-tool-plan.ts +530 -0
- package/src/runs/shared/claude-code-adapter.ts +5 -1
- package/src/runs/shared/completion-guard.ts +1 -1
- package/src/runs/shared/external-cli-preflight.ts +16 -0
- package/src/runs/shared/mcp-direct-tool-allowlist.ts +5 -4
- package/src/runs/shared/model-exclusions.ts +82 -14
- package/src/runs/shared/model-fallback.ts +47 -4
- package/src/runs/shared/nested-events.ts +29 -45
- package/src/runs/shared/nested-path.ts +0 -14
- package/src/runs/shared/orca-progress-tabs.ts +12 -7
- package/src/runs/shared/parallel-utils.ts +0 -2
- package/src/runs/shared/permissions.ts +0 -13
- package/src/runs/shared/process-signal.ts +4 -1
- package/src/runs/shared/run-fanout-budget.ts +0 -13
- package/src/runs/shared/runtime-acknowledged-extensions.ts +0 -27
- package/src/runs/shared/structured-output.ts +17 -4
- package/src/runs/shared/subagent-control.ts +6 -2
- package/src/runs/shared/subagent-prompt-runtime.ts +87 -384
- package/src/runs/shared/tool-availability.ts +18 -62
- package/src/runs/shared/tool-budget.ts +0 -14
- package/src/runs/shared/worktree-cleanup-plan.ts +25 -6
- package/src/runs/shared/worktree.ts +117 -30
- package/src/shared/child-session-name.ts +1 -1
- package/src/shared/jsonl-writer.ts +11 -0
- package/src/shared/thinking-ceiling.ts +0 -6
- package/src/shared/types.ts +64 -30
- package/src/shared/utils.ts +3 -4
- package/src/slash/slash-commands.ts +0 -6
- package/src/tui/fleet.ts +0 -1
- package/src/tui/render.ts +238 -31
- package/src/watchdog/child-status.ts +54 -34
- package/src/watchdog/diff-tool.ts +77 -0
- package/src/watchdog/emission-guard.ts +5 -3
- package/src/watchdog/guidance.ts +20 -0
- package/src/watchdog/register-child.ts +28 -25
- package/src/watchdog/register-main.ts +10 -9
- package/src/watchdog/render.ts +4 -5
- package/src/watchdog/review.ts +15 -4
- package/src/watchdog/rules.ts +70 -0
- package/src/watchdog/runtime.ts +75 -92
- package/src/watchdog/scope.ts +0 -11
- package/src/watchdog/settings.ts +48 -104
- package/src/watchdog/types.ts +18 -32
- package/src/watchdog/warning-format.ts +0 -1
- package/src/workflows/chat-progress.ts +3 -2
- package/src/workflows/scripted-workflow.ts +48 -1
- package/src/workflows/workflow-checklist.ts +10 -12
- package/src/workflows/workflow-preflight.ts +28 -1
- package/src/runs/shared/child-protocol.ts +0 -415
- package/src/runs/shared/pi-args.ts +0 -1062
- package/src/runs/shared/subagent-startup-retry.ts +0 -116
- package/src/shared/post-exit-stdio-guard.ts +0 -85
|
@@ -1,20 +1,10 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
-
import * as fs from "node:fs";
|
|
3
2
|
import * as path from "node:path";
|
|
4
3
|
import type { AgentToolResult } from "@earendil-works/pi-agent-core";
|
|
5
4
|
import type { Details, ForegroundRunControl, SubagentState } from "../../shared/types.ts";
|
|
6
5
|
import { readStatus } from "../../shared/utils.ts";
|
|
7
6
|
import { steeringReceipt } from "../background/steering.ts";
|
|
8
|
-
import {
|
|
9
|
-
consumeSteerAckFromDir,
|
|
10
|
-
readSteerCapability,
|
|
11
|
-
steerAcksDir,
|
|
12
|
-
steerCapabilityPath,
|
|
13
|
-
stepSteerInboxDir,
|
|
14
|
-
writeSteerRequestToExistingDir,
|
|
15
|
-
type SteerDeliveryMode,
|
|
16
|
-
type SteerRequest,
|
|
17
|
-
} from "../background/control-channel.ts";
|
|
7
|
+
import type { SteerDeliveryMode } from "../background/control-channel.ts";
|
|
18
8
|
|
|
19
9
|
export interface WorkflowForegroundSteeringTarget {
|
|
20
10
|
control: ForegroundRunControl;
|
|
@@ -40,7 +30,6 @@ function activeWorkflowError(state: SubagentState, workflowRunId: string, asyncD
|
|
|
40
30
|
function controlIsLiveInWorkflow(control: ForegroundRunControl, workflowRunId: string, sessionId: string): boolean {
|
|
41
31
|
return control.parentWorkflowRunId === workflowRunId
|
|
42
32
|
&& control.sessionId === sessionId
|
|
43
|
-
&& Boolean(control.workflowSteeringDir)
|
|
44
33
|
&& (control.activeChildren?.size ?? 0) > 0;
|
|
45
34
|
}
|
|
46
35
|
|
|
@@ -77,17 +66,18 @@ function managementError(message: string): AgentToolResult<Details> {
|
|
|
77
66
|
return { content: [{ type: "text", text: message }], isError: true, details: { mode: "management", results: [] } };
|
|
78
67
|
}
|
|
79
68
|
|
|
69
|
+
/**
|
|
70
|
+
* Steer a live workflow-owned foreground child through its in-process session.
|
|
71
|
+
* `steer` (and `auto`) interrupt the child at its next safe point; `follow_up`
|
|
72
|
+
* queues the message until the current run settles.
|
|
73
|
+
*/
|
|
80
74
|
export async function steerWorkflowForegroundTarget(input: {
|
|
81
75
|
target: WorkflowForegroundSteeringTarget;
|
|
82
76
|
message: string;
|
|
83
77
|
mode?: SteerDeliveryMode;
|
|
84
78
|
index?: number;
|
|
85
|
-
signal?: AbortSignal;
|
|
86
|
-
ackTimeoutMs?: number;
|
|
87
79
|
}): Promise<AgentToolResult<Details>> {
|
|
88
80
|
const { control, sourceRunId } = input.target;
|
|
89
|
-
const routeDir = control.workflowSteeringDir;
|
|
90
|
-
if (!routeDir || !fs.existsSync(routeDir)) return managementError(`Foreground run '${control.runId}' has no live workflow steering route.`);
|
|
91
81
|
const activeIndexes = [...(control.activeChildren?.keys() ?? [])].sort((left, right) => left - right);
|
|
92
82
|
const index = input.index ?? (activeIndexes.length === 1 ? activeIndexes[0] : undefined);
|
|
93
83
|
if (index === undefined) {
|
|
@@ -95,94 +85,30 @@ export async function steerWorkflowForegroundTarget(input: {
|
|
|
95
85
|
? `Foreground run '${control.runId}' has no live child session.`
|
|
96
86
|
: `Foreground run '${control.runId}' has ${activeIndexes.length} live child sessions; provide index.`);
|
|
97
87
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
if (
|
|
88
|
+
const child = control.activeChildren?.get(index);
|
|
89
|
+
if (!child) return managementError(`Foreground run '${control.runId}' child ${index} is not live.`);
|
|
90
|
+
if (!child.steer) return managementError(`Foreground run '${control.runId}' child ${index} does not support steering.`);
|
|
101
91
|
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
targetIndex: index,
|
|
109
|
-
source: "steer-action",
|
|
110
|
-
};
|
|
111
|
-
try {
|
|
112
|
-
writeSteerRequestToExistingDir(stepSteerInboxDir(routeDir, index), request);
|
|
113
|
-
} catch (error) {
|
|
114
|
-
if (typeof error === "object" && error !== null && "code" in error && (error as NodeJS.ErrnoException).code === "ENOENT") {
|
|
115
|
-
return managementError(`Foreground run '${control.runId}' has no live workflow steering route.`);
|
|
116
|
-
}
|
|
117
|
-
return managementError(`Failed to queue steering for foreground run ${control.runId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
const deadline = Date.now() + (input.ackTimeoutMs ?? 3_000);
|
|
121
|
-
let ack;
|
|
122
|
-
let routeRemoved = false;
|
|
123
|
-
while (Date.now() <= deadline) {
|
|
124
|
-
ack = consumeSteerAckFromDir(steerAcksDir(routeDir, index), request.id);
|
|
125
|
-
if (ack || input.signal?.aborted) break;
|
|
126
|
-
if (!fs.existsSync(routeDir)) {
|
|
127
|
-
routeRemoved = true;
|
|
128
|
-
break;
|
|
129
|
-
}
|
|
130
|
-
await new Promise<void>((resolve) => setTimeout(resolve, Math.min(50, Math.max(1, deadline - Date.now()))));
|
|
131
|
-
}
|
|
132
|
-
if (routeRemoved || (!ack && !input.signal?.aborted && !fs.existsSync(routeDir))) {
|
|
133
|
-
return managementError(`Foreground run '${control.runId}' has no live child session.`);
|
|
134
|
-
}
|
|
135
|
-
const target = ack?.state === "delivered"
|
|
136
|
-
? { index, state: "delivered" as const, deliveredAt: ack.ts }
|
|
137
|
-
: ack?.state === "queued"
|
|
92
|
+
const message = input.message.trim();
|
|
93
|
+
const requestId = randomUUID();
|
|
94
|
+
const outcome = await child.steer({ message, ...(input.mode && input.mode !== "steer" ? { mode: input.mode } : {}) });
|
|
95
|
+
const target = outcome.state === "delivered"
|
|
96
|
+
? { index, state: "delivered" as const, deliveredAt: Date.now() }
|
|
97
|
+
: outcome.state === "queued"
|
|
138
98
|
? { index, state: "queued" as const }
|
|
139
|
-
:
|
|
140
|
-
? { index, state: "failed" as const, reason: ack.message }
|
|
141
|
-
: { index, state: "pending" as const };
|
|
99
|
+
: { index, state: "failed" as const, reason: outcome.reason };
|
|
142
100
|
const steering = {
|
|
143
|
-
requestId
|
|
144
|
-
state:
|
|
145
|
-
deliveryStatus:
|
|
101
|
+
requestId,
|
|
102
|
+
state: outcome.state === "delivered" ? "delivered" as const : outcome.state === "failed" ? "failed" as const : "pending" as const,
|
|
103
|
+
deliveryStatus: outcome.state === "delivered" ? "delivered" as const : "queued" as const,
|
|
146
104
|
sourceRunId,
|
|
147
105
|
targets: [target],
|
|
148
106
|
};
|
|
149
|
-
if (
|
|
150
|
-
return { content: [{ type: "text", text: steeringReceipt(
|
|
151
|
-
}
|
|
152
|
-
if (ack?.state === "delivered") {
|
|
153
|
-
return { content: [{ type: "text", text: steeringReceipt(request.message, `Steering delivered for foreground run ${control.runId} (request ${request.id}).`) }], details: { mode: "management", results: [], steering } };
|
|
154
|
-
}
|
|
155
|
-
if (ack?.state === "queued") {
|
|
156
|
-
return { content: [{ type: "text", text: steeringReceipt(request.message, `Steering queued for foreground run ${control.runId} (request ${request.id}).`) }], details: { mode: "management", results: [], steering } };
|
|
107
|
+
if (outcome.state === "delivered") {
|
|
108
|
+
return { content: [{ type: "text", text: steeringReceipt(message, `Steering delivered for foreground run ${control.runId} (request ${requestId}).`) }], details: { mode: "management", results: [], steering } };
|
|
157
109
|
}
|
|
158
|
-
if (
|
|
159
|
-
return { content: [{ type: "text", text: steeringReceipt(
|
|
110
|
+
if (outcome.state === "queued") {
|
|
111
|
+
return { content: [{ type: "text", text: steeringReceipt(message, `Steering queued for foreground run ${control.runId} (request ${requestId}).`) }], details: { mode: "management", results: [], steering } };
|
|
160
112
|
}
|
|
161
|
-
return { content: [{ type: "text", text: steeringReceipt(
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
export function workflowForegroundSteeringDir(asyncDirRoot: string, workflowRunId: string, childRunId: string): string {
|
|
165
|
-
return path.join(asyncDirRoot, workflowRunId, "control", "workflow-foreground", childRunId);
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
export function removeWorkflowForegroundSteeringRoute(control: ForegroundRunControl): void {
|
|
169
|
-
if (!control.workflowSteeringDir) return;
|
|
170
|
-
try {
|
|
171
|
-
fs.rmSync(control.workflowSteeringDir, { recursive: true, force: true });
|
|
172
|
-
} catch (error) {
|
|
173
|
-
console.warn(`[pi-subagents] Failed to remove workflow foreground steering route '${control.workflowSteeringDir}': ${error instanceof Error ? error.message : String(error)}`);
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
export function workflowForegroundSteeringLaunchOptions(control: ForegroundRunControl | undefined, index: number): Pick<import("../../shared/types.ts").RunSyncOptions, "steerInboxDir" | "steerCapabilityPath" | "steerAckDir"> {
|
|
178
|
-
if (!control?.workflowSteeringDir) return {};
|
|
179
|
-
const steerInboxDir = stepSteerInboxDir(control.workflowSteeringDir, index);
|
|
180
|
-
const steerAckDir = steerAcksDir(control.workflowSteeringDir, index);
|
|
181
|
-
fs.mkdirSync(steerInboxDir, { recursive: true });
|
|
182
|
-
fs.mkdirSync(steerAckDir, { recursive: true });
|
|
183
|
-
return {
|
|
184
|
-
steerInboxDir,
|
|
185
|
-
steerCapabilityPath: steerCapabilityPath(control.workflowSteeringDir, index),
|
|
186
|
-
steerAckDir,
|
|
187
|
-
};
|
|
113
|
+
return { content: [{ type: "text", text: steeringReceipt(message, `Steering failed for foreground run ${control.runId} (request ${requestId}): ${outcome.reason ?? "unknown error"}`) }], isError: true, details: { mode: "management", results: [], steering } };
|
|
188
114
|
}
|
|
@@ -101,9 +101,9 @@ export function planAbortRecovery(input: {
|
|
|
101
101
|
if (input.alreadyResumed) return settle("resume already attempted");
|
|
102
102
|
if (!input.sessionAvailable) return settle("retained session unavailable");
|
|
103
103
|
if (input.stopped || input.interrupted) return settle("explicit stop or interrupt");
|
|
104
|
-
//
|
|
105
|
-
//
|
|
106
|
-
// that cleanup signal non-authoritative; explicit stop/interrupt still wins.
|
|
104
|
+
// An external-job runner reports a process signal when its terminal drain
|
|
105
|
+
// ends a child left open by a compaction abort. The compaction + settlement
|
|
106
|
+
// markers make that cleanup signal non-authoritative; explicit stop/interrupt still wins.
|
|
107
107
|
if (input.processSignal && !compactionAbortCandidate) return settle("process terminated by signal");
|
|
108
108
|
if (input.timedOut) return settle("elapsed timeout");
|
|
109
109
|
if (input.toolBudgetExhausted || input.usageBudgetExhausted) return settle("budget exhausted");
|
|
@@ -21,7 +21,9 @@ import type {
|
|
|
21
21
|
ResolvedAcceptanceGate,
|
|
22
22
|
SingleResult,
|
|
23
23
|
SubagentRunMode,
|
|
24
|
+
ChildWatchdogProgress,
|
|
24
25
|
} from "../../shared/types.ts";
|
|
26
|
+
import { unresolvedChildWatchdogBlockers } from "../../watchdog/child-status.ts";
|
|
25
27
|
import { isAgentContractV1 } from "./agent-contract.ts";
|
|
26
28
|
import { classifyTaskMutationIntent, stripSeverityCompounds, taskMayMutate } from "./task-intent.ts";
|
|
27
29
|
|
|
@@ -1360,6 +1362,7 @@ export async function evaluateAcceptance(input: {
|
|
|
1360
1362
|
reportOptional?: boolean;
|
|
1361
1363
|
artifactsDir?: string;
|
|
1362
1364
|
runId?: string;
|
|
1365
|
+
watchdog?: ChildWatchdogProgress;
|
|
1363
1366
|
}): Promise<AcceptanceLedger> {
|
|
1364
1367
|
const acceptance = input.acceptance;
|
|
1365
1368
|
const initialStatus = acceptance.level === "none" ? "not-required" : "claimed";
|
|
@@ -1375,6 +1378,13 @@ export async function evaluateAcceptance(input: {
|
|
|
1375
1378
|
};
|
|
1376
1379
|
if (acceptance.level === "none") return ledger;
|
|
1377
1380
|
|
|
1381
|
+
if (input.watchdog) {
|
|
1382
|
+
const unresolved = unresolvedChildWatchdogBlockers(input.watchdog);
|
|
1383
|
+
ledger.runtimeChecks.push(unresolved.length
|
|
1384
|
+
? { id: "watchdog-blocker", status: "failed", message: `Unresolved watchdog blocker: ${unresolved[0]!.summary}` }
|
|
1385
|
+
: { id: "watchdog-blocker", status: "passed", message: "No unresolved watchdog blockers." });
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1378
1388
|
const parsed: AcceptanceReportParseResult = input.reportError
|
|
1379
1389
|
? { error: input.reportError }
|
|
1380
1390
|
: input.report !== undefined
|
|
@@ -2,6 +2,7 @@ import { sanitizeDisplayText, truncateDisplayText } from "../../shared/display-t
|
|
|
2
2
|
import { formatModelThinking } from "../../shared/formatters.ts";
|
|
3
3
|
import type { AsyncJobState, AsyncJobStep, HostStepFreshnessV1, HostStepMonitorKind, HostStepNodeV1, HostStepState, HostStepVerdict, NestedRunSummary, NestedStepSummary, SubagentRunMode, WorkflowGraphSnapshot, WorkflowPreflightLaneV1, WorkflowPreflightV1 } from "../../shared/types.ts";
|
|
4
4
|
import { HOST_STEP_MAX_COUNT, HOST_STEP_MAX_DETAIL_CHARS, HOST_STEP_MAX_LABEL_CHARS, HOST_STEP_MAX_PROVIDER_CHARS, HOST_STEP_MAX_REASON_CHARS, HOST_STEP_MAX_REF_CHARS, HOST_STEP_MAX_ROLE_CHARS, HOST_STEP_MAX_TARGET_CHARS, hostStepReportName, parseHostStepNode, validHostStepNodes } from "./host-step-status.ts";
|
|
5
|
+
import { workflowPreflightLaneForRuntimeKey } from "../../workflows/workflow-preflight.ts";
|
|
5
6
|
import { workflowGraphStageNodes } from "./workflow-graph.ts";
|
|
6
7
|
|
|
7
8
|
export const ASYNC_STATUS_SNAPSHOT_KIND = "pi-subagents.async-status-snapshot";
|
|
@@ -472,7 +473,7 @@ function projectWorkflowGraphRow(node: WorkflowGraphSnapshot["nodes"][number], p
|
|
|
472
473
|
};
|
|
473
474
|
}
|
|
474
475
|
|
|
475
|
-
/** Project
|
|
476
|
+
/** Project authoritative workflow facts into compact rows, annotated by preflight hints. */
|
|
476
477
|
export function projectAsyncWorkflowRows(
|
|
477
478
|
steps: readonly AsyncJobStep[] | undefined,
|
|
478
479
|
hostStepsOrPreflight?: readonly HostStepNodeV1[] | WorkflowGraphSnapshot | WorkflowPreflightV1,
|
|
@@ -482,7 +483,8 @@ export function projectAsyncWorkflowRows(
|
|
|
482
483
|
const graph = isWorkflowGraph(hostStepsOrPreflight) ? hostStepsOrPreflight : undefined;
|
|
483
484
|
const hostSteps = isWorkflowPreflight(hostStepsOrPreflight) || graph ? undefined : hostStepsOrPreflight;
|
|
484
485
|
const loaded = steps ?? [];
|
|
485
|
-
const
|
|
486
|
+
const preflightForKey = (key: string, groupKeys: readonly (string | undefined)[] = []): WorkflowPreflightLaneV1 | undefined =>
|
|
487
|
+
workflowPreflightLaneForRuntimeKey(preflight, key, groupKeys);
|
|
486
488
|
if (graph) {
|
|
487
489
|
const loadedIndexesByKey = new Map<string, number[]>();
|
|
488
490
|
for (const [index, step] of loaded.entries()) {
|
|
@@ -493,7 +495,6 @@ export function projectAsyncWorkflowRows(
|
|
|
493
495
|
}
|
|
494
496
|
const consumed = new Set<number>();
|
|
495
497
|
const childRows: AsyncStatusWorkflowRow[] = [];
|
|
496
|
-
for (const lane of preflight?.lanes ?? []) declared.set(lane.key, lane);
|
|
497
498
|
const graphStages = workflowGraphStageNodes(graph);
|
|
498
499
|
const graphKeys = new Set(graphStages.map((node) => node.id));
|
|
499
500
|
const graphPhaseByNodeId = new Map<string, string>();
|
|
@@ -502,60 +503,27 @@ export function projectAsyncWorkflowRows(
|
|
|
502
503
|
if (graphKeys.has(nodeId) && !graphPhaseByNodeId.has(nodeId)) graphPhaseByNodeId.set(nodeId, phase.title);
|
|
503
504
|
}
|
|
504
505
|
}
|
|
505
|
-
const graphLaneKeys = new Set(graphPhaseByNodeId.values());
|
|
506
|
-
const preflightForNode = (node: WorkflowGraphSnapshot["nodes"][number]): WorkflowPreflightLaneV1 | undefined => {
|
|
507
|
-
const phaseTitle = graphPhaseByNodeId.get(node.id);
|
|
508
|
-
return declared.get(node.id) ?? (node.phase ? declared.get(node.phase) : undefined) ?? (phaseTitle ? declared.get(phaseTitle) : undefined);
|
|
509
|
-
};
|
|
510
506
|
for (const node of graphStages) {
|
|
507
|
+
const lane = preflightForKey(node.id, [node.phase, graphPhaseByNodeId.get(node.id)]);
|
|
511
508
|
const indexes = (loadedIndexesByKey.get(node.id) ?? []).filter((index) => !consumed.has(index));
|
|
512
|
-
if (indexes.length > 0) {
|
|
513
|
-
for (const index of indexes) {
|
|
514
|
-
consumed.add(index);
|
|
515
|
-
childRows.push(projectLoadedWorkflowRow(loaded[index]!, index, preflightForNode(node)));
|
|
516
|
-
}
|
|
517
|
-
} else {
|
|
518
|
-
childRows.push(projectWorkflowGraphRow(node, preflightForNode(node)));
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
for (const lane of preflight?.lanes ?? []) {
|
|
522
|
-
if (graphKeys.has(lane.key) || graphLaneKeys.has(lane.key)) continue;
|
|
523
|
-
const indexes = (loadedIndexesByKey.get(lane.key) ?? []).filter((index) => !consumed.has(index));
|
|
524
509
|
if (indexes.length > 0) {
|
|
525
510
|
for (const index of indexes) {
|
|
526
511
|
consumed.add(index);
|
|
527
512
|
childRows.push(projectLoadedWorkflowRow(loaded[index]!, index, lane));
|
|
528
513
|
}
|
|
529
514
|
} else {
|
|
530
|
-
childRows.push(
|
|
515
|
+
childRows.push(projectWorkflowGraphRow(node, lane));
|
|
531
516
|
}
|
|
532
517
|
}
|
|
533
518
|
for (const [index, step] of loaded.entries()) {
|
|
534
|
-
if (!consumed.has(index)) childRows.push(projectLoadedWorkflowRow(step, index, step.workflowKey ?
|
|
519
|
+
if (!consumed.has(index)) childRows.push(projectLoadedWorkflowRow(step, index, step.workflowKey ? preflightForKey(step.workflowKey, [step.phase]) : undefined));
|
|
535
520
|
}
|
|
536
521
|
return [...childRows, ...validHostStepList(graph).map(hostStepRow)];
|
|
537
522
|
}
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
const consumed = new Set<number>();
|
|
543
|
-
const childRows: AsyncStatusWorkflowRow[] = [];
|
|
544
|
-
for (const lane of preflight?.lanes ?? []) {
|
|
545
|
-
declared.set(lane.key, lane);
|
|
546
|
-
const index = loadedIndexByKey.get(lane.key);
|
|
547
|
-
if (index === undefined) {
|
|
548
|
-
childRows.push({ name: lane.key, state: "planned", preflight: lane });
|
|
549
|
-
continue;
|
|
550
|
-
}
|
|
551
|
-
consumed.add(index);
|
|
552
|
-
childRows.push(projectLoadedWorkflowRow(loaded[index]!, index, lane));
|
|
553
|
-
}
|
|
554
|
-
for (const [index, step] of loaded.entries()) {
|
|
555
|
-
if (consumed.has(index)) continue;
|
|
556
|
-
childRows.push(projectLoadedWorkflowRow(step, index, step.workflowKey ? declared.get(step.workflowKey) : undefined));
|
|
557
|
-
}
|
|
558
|
-
return [...childRows, ...validHostStepList(hostSteps).map(hostStepRow)];
|
|
523
|
+
return [
|
|
524
|
+
...loaded.map((step, index) => projectLoadedWorkflowRow(step, index, step.workflowKey ? preflightForKey(step.workflowKey, [step.phase]) : undefined)),
|
|
525
|
+
...validHostStepList(hostSteps).map(hostStepRow),
|
|
526
|
+
];
|
|
559
527
|
}
|
|
560
528
|
|
|
561
529
|
function projectLoadedWorkflowRow(step: AsyncJobStep, index: number, preflight?: WorkflowPreflightLaneV1): AsyncStatusWorkflowRow {
|
|
@@ -2,7 +2,6 @@ import { Buffer } from "node:buffer";
|
|
|
2
2
|
|
|
3
3
|
export const SUBAGENT_CAPABILITY_CEILING_VERSION = 1 as const;
|
|
4
4
|
export const SUBAGENT_CAPABILITY_CEILING_REGISTRY_KEY = "pi-subagents.capability-ceiling.v1";
|
|
5
|
-
export const SUBAGENT_CAPABILITY_CEILING_ENV = "PI_SUBAGENT_CAPABILITY_CEILING_V1";
|
|
6
5
|
|
|
7
6
|
export type SubagentCapabilityCeiling =
|
|
8
7
|
| { allowedTools: readonly string[]; allowedAgents?: readonly string[]; denyExtensions?: boolean }
|
|
@@ -167,7 +166,7 @@ export function resolveSubagentCapabilityCeiling(sessionId: string | undefined,
|
|
|
167
166
|
}
|
|
168
167
|
|
|
169
168
|
export function resolveCurrentSubagentCapabilityCeiling(sessionId: string | undefined): ResolvedSubagentCapabilityCeiling | undefined {
|
|
170
|
-
return resolveSubagentCapabilityCeiling(sessionId,
|
|
169
|
+
return resolveSubagentCapabilityCeiling(sessionId, undefined);
|
|
171
170
|
}
|
|
172
171
|
|
|
173
172
|
export function isAgentAllowedByCapabilityCeiling(agentName: string, ceiling: ResolvedSubagentCapabilityCeiling | undefined): boolean {
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import registerFanoutChildSubagentExtension from "../../extension/fanout-child.ts";
|
|
3
|
+
import registerSubagentFastModeExtension from "./fast-mode-extension.ts";
|
|
4
|
+
import registerSubagentPromptRuntime from "./subagent-prompt-runtime.ts";
|
|
5
|
+
import type { ChildRuntimeConfig } from "./child-runtime-config.ts";
|
|
6
|
+
|
|
7
|
+
/** Inline extension shape accepted by pi's resource loader (`extensionFactories`). */
|
|
8
|
+
export interface ChildHookExtension {
|
|
9
|
+
name: string;
|
|
10
|
+
factory: (pi: ExtensionAPI) => void;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The child-side hooks pi-subagents installs in every child, keyed off the
|
|
15
|
+
* launch config. The registrations live in `subagent-prompt-runtime.ts`,
|
|
16
|
+
* `fast-mode-extension.ts`, and `fanout-child.ts`.
|
|
17
|
+
*/
|
|
18
|
+
export function createChildHooks(config: ChildRuntimeConfig): ChildHookExtension[] {
|
|
19
|
+
const hooks: ChildHookExtension[] = [
|
|
20
|
+
{ name: "pi-subagents:prompt-runtime", factory: (pi) => registerSubagentPromptRuntime(pi, config) },
|
|
21
|
+
];
|
|
22
|
+
if (config.fast) hooks.push({ name: "pi-subagents:fast-mode", factory: (pi) => registerSubagentFastModeExtension(pi) });
|
|
23
|
+
if (config.fanoutChild) hooks.push({ name: "pi-subagents:fanout-child", factory: (pi) => registerFanoutChildSubagentExtension(pi, config) });
|
|
24
|
+
return hooks;
|
|
25
|
+
}
|
|
@@ -14,11 +14,11 @@ export type AsyncStatusChildResolution =
|
|
|
14
14
|
| { ok: false; code: "not_found" | "ambiguous"; message: string };
|
|
15
15
|
|
|
16
16
|
export function asyncStatusChildIdentity(step: AsyncStatusStep, index: number): string {
|
|
17
|
-
return step
|
|
17
|
+
return asyncStatusChildIdentityCandidates(step, index)[0]!;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
export function asyncStatusChildIdentityCandidates(step: AsyncStatusStep, index: number): string[] {
|
|
21
|
-
return [...new Set([step.workflowKey, step.runId, `step:${index}`].filter((value): value is string => typeof value === "string" && value.length > 0))];
|
|
21
|
+
return [...new Set([step.childId, step.workflowKey, step.runId, `step:${index}`].filter((value): value is string => typeof value === "string" && value.length > 0))];
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
export function resolveAsyncStatusChild(
|
|
@@ -49,3 +49,14 @@ export function resolveAsyncStatusChild(
|
|
|
49
49
|
export function isStoppableAsyncStatusStep(step: AsyncStatusStep): boolean {
|
|
50
50
|
return step.status === "pending" || step.status === "running";
|
|
51
51
|
}
|
|
52
|
+
|
|
53
|
+
export function stopStoppableAsyncStatusChildren(
|
|
54
|
+
status: Pick<AsyncStatus, "steps">,
|
|
55
|
+
stopChild: ((childId: string, message?: string) => boolean) | undefined,
|
|
56
|
+
message: string,
|
|
57
|
+
): void {
|
|
58
|
+
if (!stopChild) return;
|
|
59
|
+
for (const [index, step] of (status.steps ?? []).entries()) {
|
|
60
|
+
if (isStoppableAsyncStatusStep(step)) stopChild(asyncStatusChildIdentity(step, index), message);
|
|
61
|
+
}
|
|
62
|
+
}
|