pi-subagents 0.40.0 → 0.41.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 +76 -0
- package/README.md +246 -525
- package/agents/oracle.md +1 -0
- package/package.json +12 -4
- package/prompts/parallel-context-build.md +1 -1
- package/prompts/parallel-handoff-plan.md +1 -1
- package/prompts/review-loop.md +1 -1
- package/skills/pi-subagents/SKILL.md +6 -6
- package/skills/pi-subagents/references/constraints-and-recipes.md +19 -26
- package/skills/pi-subagents/references/execution-controls.md +98 -97
- package/skills/pi-subagents/references/management-authoring-rpc.md +2 -2
- package/skills/pi-subagents/references/prompting-and-roles.md +18 -27
- package/src/agents/agent-management.ts +155 -65
- package/src/agents/agent-serializer.ts +19 -0
- package/src/agents/agents.ts +154 -71
- package/src/agents/chain-serializer.ts +10 -7
- package/src/agents/frontmatter.ts +5 -3
- package/src/agents/identity.ts +1 -1
- package/src/agents/proactive-skills.ts +13 -10
- package/src/agents/skills.ts +23 -6
- package/src/api/control-channel.ts +4 -0
- package/src/api/delegation.ts +26 -194
- package/src/api/external-runs.ts +129 -0
- package/src/api/intercom-bridge.ts +3 -0
- package/src/api/pi-args.ts +5 -0
- package/src/api/preflight.ts +3 -3
- package/src/api/shared-types.ts +19 -0
- package/src/extension/config.ts +10 -0
- package/src/extension/control-notices.ts +5 -39
- package/src/extension/doctor.ts +10 -9
- package/src/extension/fanout-child.ts +7 -4
- package/src/extension/index.ts +232 -68
- package/src/extension/rpc.ts +18 -12
- package/src/extension/schemas.ts +48 -37
- package/src/extension/tool-description.ts +36 -86
- package/src/inspectors/herdr/actions.ts +229 -0
- package/src/inspectors/herdr/client.ts +130 -0
- package/src/inspectors/herdr/inspector-runner.ts +141 -0
- package/src/inspectors/herdr/project-panes.ts +154 -0
- package/src/integrations/herdr-status.ts +330 -0
- package/src/intercom/intercom-bridge.ts +3 -2
- package/src/intercom/result-intercom.ts +5 -1
- package/src/missions/actions.ts +372 -0
- package/src/missions/lifecycle.ts +314 -0
- package/src/missions/store.ts +442 -0
- package/src/missions/types.ts +135 -0
- package/src/policy/authority.ts +46 -0
- package/src/profiles/profiles.ts +29 -3
- package/src/runs/background/async-execution.ts +98 -49
- package/src/runs/background/async-job-tracker.ts +10 -2
- package/src/runs/background/async-resume.ts +6 -6
- package/src/runs/background/async-status.ts +29 -1
- package/src/runs/background/auto-drain.ts +3 -3
- package/src/runs/background/chain-append.ts +3 -2
- package/src/runs/background/control-channel.ts +9 -7
- package/src/runs/background/fleet-view.ts +3 -4
- package/src/runs/background/notify.ts +2 -1
- package/src/runs/background/process-terminal.ts +5 -5
- package/src/runs/background/result-watcher.ts +13 -5
- package/src/runs/background/run-id-resolver.ts +3 -3
- package/src/runs/background/run-status.ts +35 -8
- package/src/runs/background/scheduled-runs.ts +602 -375
- package/src/runs/background/stale-run-reconciler.ts +3 -3
- package/src/runs/background/subagent-runner.ts +608 -445
- package/src/runs/background/subagent-wait.ts +50 -9
- package/src/runs/background/wait-subscriptions.ts +253 -0
- package/src/runs/background/wait-tool.ts +12 -4
- package/src/runs/foreground/async-steering-action.ts +3 -3
- package/src/runs/foreground/chain-clarify.ts +8 -4
- package/src/runs/foreground/chain-execution.ts +56 -30
- package/src/runs/foreground/execution.ts +15 -2
- package/src/runs/foreground/subagent-executor.ts +1023 -273
- package/src/runs/shared/acceptance.ts +28 -6
- package/src/runs/shared/child-protocol.ts +302 -22
- package/src/runs/shared/dynamic-fanout.ts +1 -1
- package/src/runs/shared/external-cli-runner.ts +130 -0
- package/src/runs/shared/long-running-guard.ts +42 -1
- package/src/runs/shared/nested-events.ts +59 -5
- package/src/runs/shared/nested-render.ts +9 -4
- package/src/runs/shared/parallel-handoff.ts +86 -2
- package/src/runs/shared/parallel-utils.ts +11 -2
- package/src/runs/shared/permissions.ts +95 -0
- package/src/runs/shared/pi-args.ts +11 -1
- package/src/runs/shared/pi-spawn.ts +11 -1
- package/src/runs/shared/run-history.ts +1 -1
- package/src/runs/shared/subagent-prompt-runtime.ts +36 -5
- package/src/runs/shared/subagent-startup-retry.ts +5 -2
- package/src/runs/shared/turn-budget.ts +6 -6
- package/src/runs/shared/worktree.ts +122 -12
- package/src/shared/accessible-dir.ts +29 -7
- package/src/shared/artifacts.ts +18 -1
- package/src/shared/fork-context.ts +3 -2
- package/src/shared/launch-contract.ts +1 -0
- package/src/shared/settings.ts +10 -0
- package/src/shared/types.ts +156 -14
- package/src/shared/utils.ts +8 -6
- package/src/slash/delegation-adapters.ts +32 -194
- package/src/slash/delegation-request.ts +43 -126
- package/src/slash/prompt-template-bridge.ts +158 -205
- package/src/slash/prompt-workflows.ts +21 -57
- package/src/slash/slash-bridge.ts +14 -0
- package/src/slash/slash-commands.ts +31 -632
- package/src/slash/subagents-admin.ts +18 -14
- package/src/tui/fleet-status.ts +156 -21
- package/src/tui/fleet-transcript.ts +110 -5
- package/src/tui/fleet.ts +56 -24
- package/src/tui/render.ts +291 -109
- package/src/types/pi-runtime-compat.d.ts +14 -0
- package/src/watchdog/lsp-diagnostics.ts +12 -7
- package/src/watchdog/model-selection.ts +2 -2
- package/src/watchdog/permission-arbiter.ts +145 -0
- package/src/watchdog/register-child.ts +1 -1
- package/src/watchdog/register-main.ts +1 -1
- package/src/watchdog/review.ts +4 -1
- package/src/watchdog/runtime.ts +3 -2
- package/src/workflows/chat-progress.ts +140 -0
- package/src/workflows/scripted-workflow.ts +415 -0
- package/agents/advisor.md +0 -73
- package/src/extension/chain-validation.ts +0 -181
package/src/api/delegation.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
//
|
|
5
|
-
// intentionally reuses it instead of adding a second event protocol.
|
|
1
|
+
// This is the established extension-to-extension transport. The structured
|
|
2
|
+
// delegation API intentionally reuses it instead of adding a second event
|
|
3
|
+
// protocol. Unstructured payloads on the same events remain the legacy
|
|
4
|
+
// prompt-template bridge while we validate whether any integrations still use it.
|
|
6
5
|
export const SUBAGENT_DELEGATION_REQUEST_EVENT = "prompt-template:subagent:request";
|
|
7
6
|
export const SUBAGENT_DELEGATION_STARTED_EVENT = "prompt-template:subagent:started";
|
|
8
7
|
export const SUBAGENT_DELEGATION_UPDATE_EVENT = "prompt-template:subagent:update";
|
|
@@ -20,114 +19,36 @@ export interface SubagentDelegationToolBudget {
|
|
|
20
19
|
block?: string[] | "*";
|
|
21
20
|
}
|
|
22
21
|
|
|
23
|
-
export interface SubagentDelegationAgentContract {
|
|
24
|
-
version: 1;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
22
|
export type SubagentDelegationJsonSchemaObject = Record<string, unknown>;
|
|
28
23
|
|
|
29
|
-
export
|
|
30
|
-
status: "completed" | "failed" | "paused" | "stopped" | "detached";
|
|
31
|
-
success: boolean;
|
|
32
|
-
exitCode: number;
|
|
33
|
-
error?: string;
|
|
34
|
-
interrupted?: boolean;
|
|
35
|
-
timedOut?: boolean;
|
|
36
|
-
stopped?: boolean;
|
|
37
|
-
detached?: boolean;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export interface SubagentDelegationReviewResult {
|
|
41
|
-
status: "not-requested" | "review-required" | "reviewed" | "blockers";
|
|
42
|
-
findings?: Array<{ severity: "blocker" | "non-blocking"; file?: string; issue: string; rationale: string }>;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export interface SubagentDelegationEffectsResult {
|
|
46
|
-
fileMutation?: {
|
|
47
|
-
status: "not-requested" | "not-applicable" | "observed" | "missing";
|
|
48
|
-
expected: boolean;
|
|
49
|
-
attempted: boolean;
|
|
50
|
-
message?: string;
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export type SubagentDelegationAcceptanceEvidence =
|
|
55
|
-
| "changed-files"
|
|
56
|
-
| "tests-added"
|
|
57
|
-
| "commands-run"
|
|
58
|
-
| "validation-output"
|
|
59
|
-
| "residual-risks"
|
|
60
|
-
| "no-staged-files"
|
|
61
|
-
| "diff-summary"
|
|
62
|
-
| "review-findings"
|
|
63
|
-
| "manual-notes";
|
|
64
|
-
|
|
65
|
-
export interface SubagentDelegationAcceptanceCriterion {
|
|
66
|
-
id: string;
|
|
67
|
-
must: string;
|
|
68
|
-
evidence?: SubagentDelegationAcceptanceEvidence[];
|
|
69
|
-
severity?: "required" | "recommended";
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export interface SubagentDelegationAcceptanceVerifyCommand {
|
|
73
|
-
id: string;
|
|
74
|
-
command: string;
|
|
75
|
-
timeoutMs?: number;
|
|
76
|
-
cwd?: string;
|
|
77
|
-
env?: Record<string, string>;
|
|
78
|
-
allowFailure?: boolean;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
export interface SubagentDelegationAcceptanceReview {
|
|
82
|
-
agent?: string;
|
|
83
|
-
focus?: string;
|
|
84
|
-
required?: boolean;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
interface SubagentDelegationAcceptanceFields {
|
|
88
|
-
criteria?: Array<string | SubagentDelegationAcceptanceCriterion>;
|
|
89
|
-
evidence?: SubagentDelegationAcceptanceEvidence[];
|
|
90
|
-
verify?: SubagentDelegationAcceptanceVerifyCommand[];
|
|
91
|
-
review?: SubagentDelegationAcceptanceReview | false;
|
|
92
|
-
stopRules?: string[];
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
export type SubagentDelegationAcceptanceConfig = SubagentDelegationAcceptanceFields & (
|
|
96
|
-
| { level: "none"; reason: string }
|
|
97
|
-
| { level?: "auto" | "attested" | "checked" | "verified"; reason?: string }
|
|
98
|
-
);
|
|
24
|
+
export type SubagentDelegationThinking = "off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max";
|
|
99
25
|
|
|
100
|
-
export type
|
|
101
|
-
| "
|
|
102
|
-
| "
|
|
103
|
-
| "checked"
|
|
104
|
-
| "verified"
|
|
105
|
-
| false
|
|
106
|
-
| SubagentDelegationAcceptanceConfig;
|
|
26
|
+
export type SubagentDelegationResultRequest =
|
|
27
|
+
| { kind: "text" }
|
|
28
|
+
| { kind: "structured"; schema: SubagentDelegationJsonSchemaObject };
|
|
107
29
|
|
|
108
30
|
export interface SubagentDelegationRequest {
|
|
109
|
-
version: typeof SUBAGENT_DELEGATION_PROTOCOL_VERSION;
|
|
110
31
|
requestId: string;
|
|
32
|
+
ownerRunId: string;
|
|
33
|
+
nodeId: string;
|
|
111
34
|
agent: string;
|
|
112
35
|
task: string;
|
|
113
36
|
context: "fresh" | "fork";
|
|
114
37
|
cwd: string;
|
|
115
38
|
model?: string;
|
|
39
|
+
thinking?: SubagentDelegationThinking;
|
|
116
40
|
timeoutMs?: number;
|
|
117
41
|
turnBudget?: SubagentDelegationTurnBudget;
|
|
118
42
|
toolBudget?: SubagentDelegationToolBudget;
|
|
119
43
|
skill?: string | string[] | boolean;
|
|
120
|
-
output?: string | boolean;
|
|
121
|
-
outputMode?: "inline" | "file-only";
|
|
122
|
-
outputSchema?: SubagentDelegationJsonSchemaObject;
|
|
123
|
-
agentContract?: SubagentDelegationAgentContract;
|
|
124
|
-
acceptance?: SubagentDelegationAcceptance;
|
|
125
44
|
artifacts?: boolean;
|
|
45
|
+
result: SubagentDelegationResultRequest;
|
|
126
46
|
}
|
|
127
47
|
|
|
128
48
|
export interface SubagentDelegationStarted {
|
|
129
|
-
version: typeof SUBAGENT_DELEGATION_PROTOCOL_VERSION;
|
|
130
49
|
requestId: string;
|
|
50
|
+
ownerRunId: string;
|
|
51
|
+
nodeId: string;
|
|
131
52
|
}
|
|
132
53
|
|
|
133
54
|
export interface SubagentDelegationUpdate extends SubagentDelegationStarted {
|
|
@@ -154,102 +75,14 @@ export type SubagentDelegationStatus =
|
|
|
154
75
|
| "structured_output_failed"
|
|
155
76
|
| "acceptance_failed"
|
|
156
77
|
| "invalid_request"
|
|
157
|
-
| "unavailable_context"
|
|
158
|
-
|
|
159
|
-
export type SubagentDelegationAcceptanceStatus =
|
|
160
|
-
| "pending"
|
|
161
|
-
| "not-required"
|
|
162
|
-
| "claimed"
|
|
163
|
-
| "attested"
|
|
164
|
-
| "checked"
|
|
165
|
-
| "verified"
|
|
166
|
-
| "review-required"
|
|
167
|
-
| "reviewed"
|
|
168
|
-
| "accepted"
|
|
169
|
-
| "rejected";
|
|
170
|
-
|
|
171
|
-
export interface SubagentDelegationAcceptanceResult {
|
|
172
|
-
status: SubagentDelegationAcceptanceStatus;
|
|
173
|
-
evidenceStatus: Exclude<SubagentDelegationAcceptanceStatus, "review-required" | "reviewed" | "accepted">;
|
|
174
|
-
explicit: boolean;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
export interface SubagentDelegationResponse extends SubagentDelegationStarted {
|
|
178
|
-
status: SubagentDelegationStatus;
|
|
179
|
-
error?: string;
|
|
180
|
-
runId?: string;
|
|
181
|
-
childIndex?: number;
|
|
182
|
-
agent?: string;
|
|
183
|
-
model?: string;
|
|
184
|
-
exitCode?: number;
|
|
185
|
-
execution?: SubagentDelegationExecutionResult;
|
|
186
|
-
output?: string;
|
|
187
|
-
outputPath?: string;
|
|
188
|
-
sessionFile?: string;
|
|
189
|
-
acceptance?: SubagentDelegationAcceptanceResult;
|
|
190
|
-
review?: SubagentDelegationReviewResult;
|
|
191
|
-
effects?: SubagentDelegationEffectsResult;
|
|
192
|
-
turns?: number;
|
|
193
|
-
toolCount?: number;
|
|
194
|
-
durationMs?: number;
|
|
195
|
-
tokens?: number;
|
|
196
|
-
warnings?: string[];
|
|
197
|
-
}
|
|
78
|
+
| "unavailable_context"
|
|
79
|
+
| "duplicate_node";
|
|
198
80
|
|
|
199
|
-
export
|
|
200
|
-
|
|
201
|
-
export type SubagentDelegationV2Thinking = "off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max";
|
|
202
|
-
|
|
203
|
-
export type SubagentDelegationV2ResultRequest =
|
|
204
|
-
| { kind: "text" }
|
|
205
|
-
| { kind: "structured"; schema: SubagentDelegationJsonSchemaObject };
|
|
206
|
-
|
|
207
|
-
export interface SubagentDelegationV2Request {
|
|
208
|
-
version: typeof SUBAGENT_DELEGATION_V2_PROTOCOL_VERSION;
|
|
209
|
-
requestId: string;
|
|
210
|
-
ownerRunId: string;
|
|
211
|
-
nodeId: string;
|
|
212
|
-
agent: string;
|
|
213
|
-
task: string;
|
|
214
|
-
context: "fresh" | "fork";
|
|
215
|
-
cwd: string;
|
|
216
|
-
model?: string;
|
|
217
|
-
thinking?: SubagentDelegationV2Thinking;
|
|
218
|
-
timeoutMs?: number;
|
|
219
|
-
turnBudget?: SubagentDelegationTurnBudget;
|
|
220
|
-
toolBudget?: SubagentDelegationToolBudget;
|
|
221
|
-
skill?: string | string[] | boolean;
|
|
222
|
-
artifacts?: boolean;
|
|
223
|
-
result: SubagentDelegationV2ResultRequest;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
export interface SubagentDelegationV2Started {
|
|
227
|
-
version: typeof SUBAGENT_DELEGATION_V2_PROTOCOL_VERSION;
|
|
228
|
-
requestId: string;
|
|
229
|
-
ownerRunId: string;
|
|
230
|
-
nodeId: string;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
export interface SubagentDelegationV2Update extends SubagentDelegationV2Started {
|
|
234
|
-
runId?: string;
|
|
235
|
-
currentTool?: string;
|
|
236
|
-
currentToolArgs?: string;
|
|
237
|
-
recentOutput?: string;
|
|
238
|
-
recentOutputLines?: string[];
|
|
239
|
-
recentTools?: Array<{ tool: string; args: string }>;
|
|
240
|
-
model?: string;
|
|
241
|
-
toolCount?: number;
|
|
242
|
-
durationMs?: number;
|
|
243
|
-
tokens?: number;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
export type SubagentDelegationV2Status = SubagentDelegationStatus | "duplicate_node";
|
|
247
|
-
|
|
248
|
-
export type SubagentDelegationV2Value =
|
|
81
|
+
export type SubagentDelegationValue =
|
|
249
82
|
| { kind: "text"; text: string }
|
|
250
83
|
| { kind: "structured"; value: unknown };
|
|
251
84
|
|
|
252
|
-
export interface
|
|
85
|
+
export interface SubagentDelegationUsage {
|
|
253
86
|
input: number;
|
|
254
87
|
output: number;
|
|
255
88
|
cacheRead: number;
|
|
@@ -260,8 +93,8 @@ export interface SubagentDelegationV2Usage {
|
|
|
260
93
|
durationMs: number;
|
|
261
94
|
}
|
|
262
95
|
|
|
263
|
-
export interface
|
|
264
|
-
status: Exclude<
|
|
96
|
+
export interface SubagentDelegationTerminalResponse extends SubagentDelegationStarted {
|
|
97
|
+
status: Exclude<SubagentDelegationStatus, "invalid_request">;
|
|
265
98
|
error?: string;
|
|
266
99
|
runId?: string;
|
|
267
100
|
agent?: string;
|
|
@@ -269,13 +102,12 @@ export interface SubagentDelegationV2TerminalResponse extends SubagentDelegation
|
|
|
269
102
|
thinking?: string;
|
|
270
103
|
exitCode?: number;
|
|
271
104
|
launchContractDigest?: string;
|
|
272
|
-
result?:
|
|
273
|
-
usage?:
|
|
105
|
+
result?: SubagentDelegationValue;
|
|
106
|
+
usage?: SubagentDelegationUsage;
|
|
274
107
|
}
|
|
275
108
|
|
|
276
|
-
/** A malformed
|
|
277
|
-
export interface
|
|
278
|
-
version: typeof SUBAGENT_DELEGATION_V2_PROTOCOL_VERSION;
|
|
109
|
+
/** A malformed structured request can only be correlated by the valid identity fields it supplied. */
|
|
110
|
+
export interface SubagentDelegationInvalidResponse {
|
|
279
111
|
requestId: string;
|
|
280
112
|
ownerRunId?: string;
|
|
281
113
|
nodeId?: string;
|
|
@@ -283,6 +115,6 @@ export interface SubagentDelegationV2InvalidResponse {
|
|
|
283
115
|
error?: string;
|
|
284
116
|
}
|
|
285
117
|
|
|
286
|
-
export type
|
|
118
|
+
export type SubagentDelegationResponse = SubagentDelegationTerminalResponse | SubagentDelegationInvalidResponse;
|
|
287
119
|
|
|
288
|
-
export interface
|
|
120
|
+
export interface SubagentDelegationCancel extends SubagentDelegationStarted {}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
export const EXTERNAL_RUN_REGISTRY_VERSION = 1;
|
|
2
|
+
export const EXTERNAL_RUN_REGISTRY_KEY = "pi-subagents.external-runs.v1";
|
|
3
|
+
|
|
4
|
+
const MAX_PROVIDERS = 100;
|
|
5
|
+
const MAX_RUNS_PER_PROVIDER = 10_000;
|
|
6
|
+
const MAX_TEXT_LENGTH = 4_096;
|
|
7
|
+
|
|
8
|
+
export type ExternalRunState = "running" | "completed" | "failed" | "stopped";
|
|
9
|
+
export type ExternalRunCompletionReason = "exit" | "timeout" | "user-kill" | "auto-close-quiet" | "crash" | "unknown";
|
|
10
|
+
|
|
11
|
+
/** An observational record for work owned by another runtime. */
|
|
12
|
+
export interface ExternalRun {
|
|
13
|
+
id: string;
|
|
14
|
+
sessionId: string;
|
|
15
|
+
source: string;
|
|
16
|
+
state: ExternalRunState;
|
|
17
|
+
command?: string;
|
|
18
|
+
cwd?: string;
|
|
19
|
+
isolationPath?: string;
|
|
20
|
+
owner?: string;
|
|
21
|
+
completionReason?: ExternalRunCompletionReason;
|
|
22
|
+
reportPath?: string;
|
|
23
|
+
startedAt?: number;
|
|
24
|
+
completedAt?: number;
|
|
25
|
+
exitCode?: number | null;
|
|
26
|
+
residualRisks?: string[];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface ExternalRunProvider {
|
|
30
|
+
name: string;
|
|
31
|
+
listExternalRuns(): readonly ExternalRun[];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface RegisteredExternalRun extends ExternalRun {
|
|
35
|
+
provider: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
interface ExternalRunRegistry {
|
|
39
|
+
version: typeof EXTERNAL_RUN_REGISTRY_VERSION;
|
|
40
|
+
providers: Map<string, ExternalRunProvider>;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function registry(): ExternalRunRegistry {
|
|
44
|
+
const key = Symbol.for(EXTERNAL_RUN_REGISTRY_KEY);
|
|
45
|
+
const target = globalThis as Record<PropertyKey, unknown>;
|
|
46
|
+
const existing = target[key];
|
|
47
|
+
if (existing === undefined) {
|
|
48
|
+
const created: ExternalRunRegistry = { version: EXTERNAL_RUN_REGISTRY_VERSION, providers: new Map() };
|
|
49
|
+
target[key] = created;
|
|
50
|
+
return created;
|
|
51
|
+
}
|
|
52
|
+
if (!existing || typeof existing !== "object" || Array.isArray(existing)) throw new Error(`Malformed external-run registry at Symbol.for("${EXTERNAL_RUN_REGISTRY_KEY}").`);
|
|
53
|
+
const candidate = existing as Partial<ExternalRunRegistry>;
|
|
54
|
+
if (candidate.version !== EXTERNAL_RUN_REGISTRY_VERSION || !(candidate.providers instanceof Map)) throw new Error(`Unsupported external-run registry at Symbol.for("${EXTERNAL_RUN_REGISTRY_KEY}").`);
|
|
55
|
+
return candidate as ExternalRunRegistry;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function text(value: unknown, field: string, required = false): string | undefined {
|
|
59
|
+
if (value === undefined && !required) return undefined;
|
|
60
|
+
if (typeof value !== "string" || value.length === 0 || value.trim() !== value || value.includes("\0")) throw new Error(`${field} must be a non-empty trimmed string without NUL characters.`);
|
|
61
|
+
if (value.length > MAX_TEXT_LENGTH) throw new Error(`${field} must be at most ${MAX_TEXT_LENGTH} characters.`);
|
|
62
|
+
return value;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function timestamp(value: unknown, field: string): number | undefined {
|
|
66
|
+
if (value === undefined) return undefined;
|
|
67
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) throw new Error(`${field} must be a non-negative finite number.`);
|
|
68
|
+
return value;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function validateRun(value: unknown, provider: string, index: number): ExternalRun {
|
|
72
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`External-run provider '${provider}' item ${index} must be an object.`);
|
|
73
|
+
const run = value as Record<string, unknown>;
|
|
74
|
+
const fields = new Set(["id", "sessionId", "source", "state", "command", "cwd", "isolationPath", "owner", "completionReason", "reportPath", "startedAt", "completedAt", "exitCode", "residualRisks"]);
|
|
75
|
+
const unknown = Object.keys(run).filter((key) => !fields.has(key));
|
|
76
|
+
if (unknown.length) throw new Error(`External-run provider '${provider}' item ${index} has unknown fields: ${unknown.join(", ")}.`);
|
|
77
|
+
if (!["running", "completed", "failed", "stopped"].includes(run.state as string)) throw new Error(`External-run provider '${provider}' item ${index} state is invalid.`);
|
|
78
|
+
if (run.completionReason !== undefined && !["exit", "timeout", "user-kill", "auto-close-quiet", "crash", "unknown"].includes(run.completionReason as string)) throw new Error(`External-run provider '${provider}' item ${index} completionReason is invalid.`);
|
|
79
|
+
if (run.exitCode !== undefined && run.exitCode !== null && (!Number.isInteger(run.exitCode) || !Number.isFinite(run.exitCode))) throw new Error(`External-run provider '${provider}' item ${index} exitCode must be an integer or null.`);
|
|
80
|
+
if (run.residualRisks !== undefined && (!Array.isArray(run.residualRisks) || run.residualRisks.some((risk, riskIndex) => text(risk, `External-run provider '${provider}' item ${index} residualRisks[${riskIndex}]`, true) === undefined))) throw new Error(`External-run provider '${provider}' item ${index} residualRisks must be an array of strings.`);
|
|
81
|
+
return {
|
|
82
|
+
id: text(run.id, `External-run provider '${provider}' item ${index} id`, true)!,
|
|
83
|
+
sessionId: text(run.sessionId, `External-run provider '${provider}' item ${index} sessionId`, true)!,
|
|
84
|
+
source: text(run.source, `External-run provider '${provider}' item ${index} source`, true)!,
|
|
85
|
+
state: run.state as ExternalRunState,
|
|
86
|
+
...(text(run.command, `External-run provider '${provider}' item ${index} command`) !== undefined ? { command: text(run.command, `External-run provider '${provider}' item ${index} command`) } : {}),
|
|
87
|
+
...(text(run.cwd, `External-run provider '${provider}' item ${index} cwd`) !== undefined ? { cwd: text(run.cwd, `External-run provider '${provider}' item ${index} cwd`) } : {}),
|
|
88
|
+
...(text(run.isolationPath, `External-run provider '${provider}' item ${index} isolationPath`) !== undefined ? { isolationPath: text(run.isolationPath, `External-run provider '${provider}' item ${index} isolationPath`) } : {}),
|
|
89
|
+
...(text(run.owner, `External-run provider '${provider}' item ${index} owner`) !== undefined ? { owner: text(run.owner, `External-run provider '${provider}' item ${index} owner`) } : {}),
|
|
90
|
+
...(run.completionReason !== undefined ? { completionReason: run.completionReason as ExternalRunCompletionReason } : {}),
|
|
91
|
+
...(text(run.reportPath, `External-run provider '${provider}' item ${index} reportPath`) !== undefined ? { reportPath: text(run.reportPath, `External-run provider '${provider}' item ${index} reportPath`) } : {}),
|
|
92
|
+
...(timestamp(run.startedAt, `External-run provider '${provider}' item ${index} startedAt`) !== undefined ? { startedAt: timestamp(run.startedAt, `External-run provider '${provider}' item ${index} startedAt`) } : {}),
|
|
93
|
+
...(timestamp(run.completedAt, `External-run provider '${provider}' item ${index} completedAt`) !== undefined ? { completedAt: timestamp(run.completedAt, `External-run provider '${provider}' item ${index} completedAt`) } : {}),
|
|
94
|
+
...(run.exitCode !== undefined ? { exitCode: run.exitCode as number | null } : {}),
|
|
95
|
+
...(run.residualRisks !== undefined ? { residualRisks: [...run.residualRisks] as string[] } : {}),
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Register an observational provider. pi-subagents never starts, stops, or steers these processes. */
|
|
100
|
+
export function registerExternalRunProvider(provider: ExternalRunProvider): () => void {
|
|
101
|
+
if (!provider || typeof provider !== "object" || Array.isArray(provider) || Object.keys(provider).some((key) => key !== "name" && key !== "listExternalRuns")) throw new Error("External-run provider must contain only name and listExternalRuns.");
|
|
102
|
+
const name = text(provider.name, "External-run provider name", true)!;
|
|
103
|
+
if (typeof provider.listExternalRuns !== "function") throw new Error(`External-run provider '${name}' must expose listExternalRuns().`);
|
|
104
|
+
const current = registry();
|
|
105
|
+
if (!current.providers.has(name) && current.providers.size >= MAX_PROVIDERS) throw new Error(`External-run registry supports at most ${MAX_PROVIDERS} providers.`);
|
|
106
|
+
current.providers.set(name, provider);
|
|
107
|
+
return () => { if (current.providers.get(name) === provider) current.providers.delete(name); };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Snapshot records for one Pi session. Records are read-only observations of foreign process ownership. */
|
|
111
|
+
export function snapshotExternalRuns(sessionId: string): readonly RegisteredExternalRun[] {
|
|
112
|
+
text(sessionId, "External-run snapshot sessionId", true);
|
|
113
|
+
const records: RegisteredExternalRun[] = [];
|
|
114
|
+
const identities = new Set<string>();
|
|
115
|
+
for (const [name, provider] of registry().providers) {
|
|
116
|
+
if (provider.name !== name) throw new Error(`External-run registry key '${name}' does not match provider name '${provider.name}'.`);
|
|
117
|
+
const runs = provider.listExternalRuns();
|
|
118
|
+
if (!Array.isArray(runs)) throw new Error(`External-run provider '${name}' listExternalRuns() must return an array.`);
|
|
119
|
+
if (runs.length > MAX_RUNS_PER_PROVIDER) throw new Error(`External-run provider '${name}' returned more than ${MAX_RUNS_PER_PROVIDER} runs.`);
|
|
120
|
+
runs.forEach((value, index) => {
|
|
121
|
+
const run = validateRun(value, name, index);
|
|
122
|
+
const identity = `${name}\0${run.sessionId}\0${run.id}`;
|
|
123
|
+
if (identities.has(identity)) throw new Error(`External-run provider '${name}' returned duplicate run '${run.id}' for session '${run.sessionId}'.`);
|
|
124
|
+
identities.add(identity);
|
|
125
|
+
if (run.sessionId === sessionId) records.push({ provider: name, ...run });
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
return records;
|
|
129
|
+
}
|
package/src/api/preflight.ts
CHANGED
|
@@ -18,7 +18,7 @@ import type { ResolvedTurnBudget } from "../shared/types.ts";
|
|
|
18
18
|
import type { ResolvedMcpDirectToolSelection } from "../runs/shared/mcp-direct-tool-allowlist.ts";
|
|
19
19
|
import { resolveStepBehavior } from "../shared/settings.ts";
|
|
20
20
|
import { agentDefinitionDigest, AGENT_DEFINITION_PROJECTION_VERSION, launchBindingDigest } from "../shared/launch-contract.ts";
|
|
21
|
-
import {
|
|
21
|
+
import { DIRS, TEMP_ROOT_DIR } from "../shared/types.ts";
|
|
22
22
|
import { processTerminalCandidatePath, processTerminalPath } from "../runs/background/process-terminal.ts";
|
|
23
23
|
import { nestedResultsPath } from "../runs/shared/nested-events.ts";
|
|
24
24
|
|
|
@@ -292,10 +292,10 @@ export async function resolveSubagentLaunchContract(input: SubagentLaunchContrac
|
|
|
292
292
|
const sessionDir = sessionRoot ? path.join(sessionRoot, "run-0") : undefined;
|
|
293
293
|
const lifecycleAsyncDir = input.nestedRootRunId
|
|
294
294
|
? path.join(TEMP_ROOT_DIR, "nested-subagent-runs", input.nestedRootRunId, runId)
|
|
295
|
-
: path.join(
|
|
295
|
+
: path.join(DIRS.async, runId);
|
|
296
296
|
const lifecycleResultPath = input.nestedRootRunId
|
|
297
297
|
? nestedResultsPath(input.nestedRootRunId, runId)
|
|
298
|
-
: path.join(
|
|
298
|
+
: path.join(DIRS.results, `${runId}.json`);
|
|
299
299
|
if (!sessionDir) diagnostics.push({ code: "host_required", severity: "host-required", message: "No sessionRoot/sessionDir was supplied; exact child session paths require the Pi host session-root policy." });
|
|
300
300
|
if (input.availableModels === undefined && (input.model || agent.model || input.parentModel)) {
|
|
301
301
|
diagnostics.push({ code: "host_required", severity: "host-required", message: "No availableModels snapshot was supplied; model resolution may differ from the active Pi host registry." });
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export {
|
|
2
|
+
DEFAULT_FORK_PREAMBLE,
|
|
3
|
+
wrapForkTask,
|
|
4
|
+
type AcceptanceEvidenceKind,
|
|
5
|
+
type AcceptanceInput,
|
|
6
|
+
type AcceptanceLedger,
|
|
7
|
+
type AcceptanceLevel,
|
|
8
|
+
type AsyncStartedEvent,
|
|
9
|
+
type ControlEvent,
|
|
10
|
+
type Details,
|
|
11
|
+
type ExecutionProjection,
|
|
12
|
+
type JsonSchemaObject,
|
|
13
|
+
type OutputMode,
|
|
14
|
+
type ReviewProjection,
|
|
15
|
+
type SingleResult,
|
|
16
|
+
type SubagentResultStatus,
|
|
17
|
+
type SubagentRunMode,
|
|
18
|
+
type Usage,
|
|
19
|
+
} from "../shared/types.ts";
|
package/src/extension/config.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import type { ArtifactDirPreference, ExtensionConfig } from "../shared/types.ts";
|
|
4
|
+
import { validateMissionStoreConfig } from "../missions/store.ts";
|
|
5
|
+
import { validateAuthorityPolicy } from "../policy/authority.ts";
|
|
4
6
|
import { getAgentDir } from "../shared/utils.ts";
|
|
7
|
+
import { validatePermissionConfig } from "../runs/shared/permissions.ts";
|
|
5
8
|
|
|
6
9
|
const ARTIFACT_DIR_PREFERENCES = new Set<ArtifactDirPreference>(["project", "session", "temp"]);
|
|
7
10
|
|
|
@@ -19,6 +22,9 @@ function readConfigForUpdate(configPath = getConfigPath()): ExtensionConfig {
|
|
|
19
22
|
if (config.artifactDir !== undefined && !ARTIFACT_DIR_PREFERENCES.has(config.artifactDir as ArtifactDirPreference)) {
|
|
20
23
|
throw new Error(`config.artifactDir must be "project", "session", or "temp"`);
|
|
21
24
|
}
|
|
25
|
+
validateMissionStoreConfig(config.missions);
|
|
26
|
+
validateAuthorityPolicy(config.authorityPolicy);
|
|
27
|
+
validatePermissionConfig(config.permissions);
|
|
22
28
|
return parsed as ExtensionConfig;
|
|
23
29
|
}
|
|
24
30
|
|
|
@@ -34,6 +40,10 @@ export function updateConfig(updater: (config: ExtensionConfig) => ExtensionConf
|
|
|
34
40
|
return next;
|
|
35
41
|
}
|
|
36
42
|
|
|
43
|
+
export function resolveAsyncByDefault(config: Pick<ExtensionConfig, "asyncByDefault">): boolean {
|
|
44
|
+
return config.asyncByDefault !== false;
|
|
45
|
+
}
|
|
46
|
+
|
|
37
47
|
export function loadConfig(): ExtensionConfig {
|
|
38
48
|
const configPath = getConfigPath();
|
|
39
49
|
try {
|
|
@@ -20,21 +20,6 @@ export function formatSubagentControlNotice(details: SubagentControlMessageDetai
|
|
|
20
20
|
return details.noticeText ?? content ?? formatControlNoticeMessage(details.event, controlNoticeTarget(details));
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
function noticeTimerKey(details: SubagentControlMessageDetails): string {
|
|
24
|
-
const childIntercomTarget = controlNoticeTarget(details);
|
|
25
|
-
return `${details.event.runId}:${controlNotificationKey(details.event, childIntercomTarget)}`;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export function clearPendingForegroundControlNotices(state: SubagentState, runId?: string): void {
|
|
29
|
-
const pending = state.pendingForegroundControlNotices;
|
|
30
|
-
if (!pending) return;
|
|
31
|
-
for (const [key, timer] of pending) {
|
|
32
|
-
if (runId !== undefined && !key.startsWith(`${runId}:`)) continue;
|
|
33
|
-
clearTimeout(timer);
|
|
34
|
-
pending.delete(key);
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
23
|
function deliverControlNotice(input: {
|
|
39
24
|
pi: Pick<ExtensionAPI, "sendMessage">;
|
|
40
25
|
visibleControlNotices: Set<string>;
|
|
@@ -56,37 +41,18 @@ function deliverControlNotice(input: {
|
|
|
56
41
|
);
|
|
57
42
|
}
|
|
58
43
|
|
|
59
|
-
function isForegroundNoticeStillActionable(state: SubagentState, details: SubagentControlMessageDetails): boolean {
|
|
60
|
-
const control = state.foregroundControls.get(details.event.runId);
|
|
61
|
-
if (!control) return false;
|
|
62
|
-
if (control.currentAgent && control.currentAgent !== details.event.agent) return false;
|
|
63
|
-
if (details.event.index !== undefined && control.currentIndex !== details.event.index) return false;
|
|
64
|
-
return control.currentActivityState === "needs_attention";
|
|
65
|
-
}
|
|
66
|
-
|
|
67
44
|
export function handleSubagentControlNotice(input: {
|
|
68
45
|
pi: Pick<ExtensionAPI, "sendMessage">;
|
|
69
46
|
state: SubagentState;
|
|
70
47
|
visibleControlNotices: Set<string>;
|
|
71
48
|
details: SubagentControlMessageDetails;
|
|
72
|
-
foregroundDelayMs?: number;
|
|
73
49
|
}): void {
|
|
74
50
|
if (!input.details?.event || input.details.event.type === "active_long_running") return;
|
|
75
|
-
if (input.details.source
|
|
76
|
-
|
|
51
|
+
if (input.details.source === "foreground") {
|
|
52
|
+
// A foreground tool blocks Pi from displaying this message. The run can
|
|
53
|
+
// finish before Pi flushes it, and queued messages cannot be withdrawn.
|
|
54
|
+
// Foreground control remains available through the live tool and fleet state.
|
|
77
55
|
return;
|
|
78
56
|
}
|
|
79
|
-
|
|
80
|
-
const pending = input.state.pendingForegroundControlNotices ?? new Map<string, ReturnType<typeof setTimeout>>();
|
|
81
|
-
input.state.pendingForegroundControlNotices = pending;
|
|
82
|
-
const timerKey = noticeTimerKey(input.details);
|
|
83
|
-
const existing = pending.get(timerKey);
|
|
84
|
-
if (existing) clearTimeout(existing);
|
|
85
|
-
const timer = setTimeout(() => {
|
|
86
|
-
pending.delete(timerKey);
|
|
87
|
-
if (!isForegroundNoticeStillActionable(input.state, input.details)) return;
|
|
88
|
-
deliverControlNotice(input);
|
|
89
|
-
}, input.foregroundDelayMs ?? 1000);
|
|
90
|
-
timer.unref?.();
|
|
91
|
-
pending.set(timerKey, timer);
|
|
57
|
+
deliverControlNotice(input);
|
|
92
58
|
}
|
package/src/extension/doctor.ts
CHANGED
|
@@ -6,9 +6,8 @@ import { formatSpawnBudgetSummary, getSpawnBudgetSnapshot } from "../runs/shared
|
|
|
6
6
|
import { diagnoseIntercomBridge, type IntercomBridgeDiagnostic } from "../intercom/intercom-bridge.ts";
|
|
7
7
|
import { discoverAvailableSkills, type SkillSource } from "../agents/skills.ts";
|
|
8
8
|
import {
|
|
9
|
-
|
|
9
|
+
DIRS,
|
|
10
10
|
CHAIN_RUNS_DIR,
|
|
11
|
-
RESULTS_DIR,
|
|
12
11
|
TEMP_ROOT_DIR,
|
|
13
12
|
type ExtensionConfig,
|
|
14
13
|
type SubagentState,
|
|
@@ -43,12 +42,14 @@ interface DoctorReportInput {
|
|
|
43
42
|
deps?: Partial<DoctorDeps>;
|
|
44
43
|
}
|
|
45
44
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
45
|
+
function defaultPaths(): DoctorPaths {
|
|
46
|
+
return {
|
|
47
|
+
tempRootDir: TEMP_ROOT_DIR,
|
|
48
|
+
asyncDir: DIRS.async,
|
|
49
|
+
resultsDir: DIRS.results,
|
|
50
|
+
chainRunsDir: CHAIN_RUNS_DIR,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
52
53
|
|
|
53
54
|
const DEFAULT_DEPS: DoctorDeps = {
|
|
54
55
|
isAsyncAvailable,
|
|
@@ -192,7 +193,7 @@ function formatPermissionSystemSection(): string[] {
|
|
|
192
193
|
}
|
|
193
194
|
|
|
194
195
|
export function buildDoctorReport(input: DoctorReportInput): string {
|
|
195
|
-
const paths = input.paths ??
|
|
196
|
+
const paths = input.paths ?? defaultPaths();
|
|
196
197
|
const deps = { ...DEFAULT_DEPS, ...input.deps };
|
|
197
198
|
const lines = [
|
|
198
199
|
"Subagents doctor report",
|
|
@@ -11,7 +11,7 @@ import { readNestedControlRequests, resolveNestedRouteFromEnv, writeNestedContro
|
|
|
11
11
|
import { deliverSubagentIntercomMessageEvent } from "../intercom/result-intercom.ts";
|
|
12
12
|
import { resolveSubagentIntercomTarget } from "../intercom/intercom-bridge.ts";
|
|
13
13
|
import { SubagentParams } from "./schemas.ts";
|
|
14
|
-
import { loadConfig } from "./config.ts";
|
|
14
|
+
import { loadConfig, resolveAsyncByDefault } from "./config.ts";
|
|
15
15
|
import { type Details, type SubagentState } from "../shared/types.ts";
|
|
16
16
|
|
|
17
17
|
function getSubagentSessionRoot(parentSessionFile: string | null): string {
|
|
@@ -37,7 +37,6 @@ function createChildSafeState(): SubagentState {
|
|
|
37
37
|
foregroundRuns: new Map(),
|
|
38
38
|
foregroundControls: new Map(),
|
|
39
39
|
lastForegroundControlId: null,
|
|
40
|
-
pendingForegroundControlNotices: new Map(),
|
|
41
40
|
cleanupTimers: new Map(),
|
|
42
41
|
lastUiContext: null,
|
|
43
42
|
poller: null,
|
|
@@ -146,7 +145,7 @@ export default function registerFanoutChildSubagentExtension(pi: ExtensionAPI):
|
|
|
146
145
|
pi,
|
|
147
146
|
state,
|
|
148
147
|
config,
|
|
149
|
-
asyncByDefault: config
|
|
148
|
+
asyncByDefault: resolveAsyncByDefault(config),
|
|
150
149
|
waitToolEnabled: resolveWaitToolConfig(config.waitTool).enabled,
|
|
151
150
|
tempArtifactsDir: getArtifactsDir(null),
|
|
152
151
|
getSubagentSessionRoot,
|
|
@@ -165,7 +164,11 @@ export default function registerFanoutChildSubagentExtension(pi: ExtensionAPI):
|
|
|
165
164
|
].join("\n"),
|
|
166
165
|
parameters: SubagentParams,
|
|
167
166
|
execute(id, params, signal, onUpdate, ctx) {
|
|
168
|
-
|
|
167
|
+
const input = params as SubagentParamsLike;
|
|
168
|
+
if (input.tasks !== undefined || input.chain !== undefined || input.concurrency !== undefined || input.chainDir !== undefined || (input.worktree !== undefined && !(input.worktree === true && input.agent))) {
|
|
169
|
+
return Promise.resolve({ content: [{ type: "text", text: "Legacy top-level chain and parallel inputs were removed; use workflowScript." }], isError: true, details: { mode: "management", results: [] } });
|
|
170
|
+
}
|
|
171
|
+
return executor.execute(id, input, signal ?? new AbortController().signal, onUpdate, ctx);
|
|
169
172
|
},
|
|
170
173
|
};
|
|
171
174
|
|