pi-subagents 0.36.0 → 0.37.1
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 +38 -0
- package/README.md +111 -16
- package/agents/delegate.md +2 -0
- package/agents/planner.md +2 -1
- package/agents/worker.md +2 -0
- package/package.json +6 -4
- package/skills/pi-subagents/SKILL.md +18 -973
- package/skills/pi-subagents/references/constraints-and-recipes.md +256 -0
- package/skills/pi-subagents/references/execution-controls.md +411 -0
- package/skills/pi-subagents/references/management-authoring-rpc.md +140 -0
- package/skills/pi-subagents/references/prompting-and-roles.md +268 -0
- package/src/agents/agent-management.ts +7 -1
- package/src/agents/agents.ts +99 -12
- package/src/api/capability-ceiling.ts +17 -0
- package/src/api/delegation.ts +6 -1
- package/src/api/preflight.ts +399 -0
- package/src/extension/index.ts +16 -7
- package/src/extension/rpc.ts +29 -2
- package/src/extension/schemas.ts +10 -4
- package/src/extension/tool-description.ts +4 -2
- package/src/intercom/intercom-bridge.ts +5 -2
- package/src/runs/background/async-execution.ts +125 -14
- package/src/runs/background/async-job-tracker.ts +19 -12
- package/src/runs/background/async-resume.ts +27 -9
- package/src/runs/background/async-status.ts +18 -0
- package/src/runs/background/notify.ts +3 -0
- package/src/runs/background/process-terminal.ts +280 -0
- package/src/runs/background/result-watcher.ts +9 -3
- package/src/runs/background/run-status.ts +7 -1
- package/src/runs/background/scheduled-runs.ts +6 -1
- package/src/runs/background/stale-run-reconciler.ts +6 -0
- package/src/runs/background/subagent-runner.ts +182 -12
- package/src/runs/foreground/chain-execution.ts +5 -0
- package/src/runs/foreground/execution.ts +59 -13
- package/src/runs/foreground/subagent-executor.ts +142 -47
- package/src/runs/shared/acceptance.ts +41 -30
- package/src/runs/shared/capability-ceiling.ts +177 -0
- package/src/runs/shared/dynamic-fanout.ts +1 -1
- package/src/runs/shared/mcp-direct-tool-allowlist.ts +56 -17
- package/src/runs/shared/model-fallback.ts +8 -0
- package/src/runs/shared/nested-events.ts +8 -1
- package/src/runs/shared/parallel-utils.ts +5 -0
- package/src/runs/shared/pi-args.ts +141 -58
- package/src/runs/shared/session-lease.ts +25 -5
- package/src/runs/shared/subagent-prompt-runtime.ts +14 -1
- package/src/runs/shared/task-intent.ts +1 -1
- package/src/runs/shared/tool-availability.ts +18 -2
- package/src/shared/launch-contract.ts +123 -0
- package/src/shared/types.ts +118 -8
- package/src/shared/utils.ts +17 -42
- package/src/slash/delegation-adapters.ts +6 -1
- package/src/slash/slash-commands.ts +1 -1
- package/src/tui/fleet-status.ts +62 -16
- package/src/tui/fleet.ts +1 -1
package/src/shared/types.ts
CHANGED
|
@@ -9,6 +9,7 @@ import type { AgentConfig } from "../agents/agents.ts";
|
|
|
9
9
|
import type { FSWatcher } from "node:fs";
|
|
10
10
|
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
11
11
|
import type { ModelScopeConfig } from "../runs/shared/model-scope.ts";
|
|
12
|
+
import type { ResolvedSubagentCapabilityCeiling, SubagentCapabilityAudit } from "../runs/shared/capability-ceiling.ts";
|
|
12
13
|
|
|
13
14
|
// ============================================================================
|
|
14
15
|
// Basic Types
|
|
@@ -302,7 +303,7 @@ export interface ExecutionProjection {
|
|
|
302
303
|
}
|
|
303
304
|
|
|
304
305
|
export interface ReviewProjection {
|
|
305
|
-
status: "not-requested" | "
|
|
306
|
+
status: "not-requested" | "review-required" | "reviewed" | "blockers";
|
|
306
307
|
findings?: AcceptanceReviewResult["findings"];
|
|
307
308
|
}
|
|
308
309
|
|
|
@@ -317,9 +318,69 @@ export interface EffectsProjection {
|
|
|
317
318
|
fileMutation?: FileMutationEffect;
|
|
318
319
|
}
|
|
319
320
|
|
|
320
|
-
export const SUBAGENT_LIFECYCLE_ARTIFACT_VERSION =
|
|
321
|
+
export const SUBAGENT_LIFECYCLE_ARTIFACT_VERSION = 3;
|
|
321
322
|
export type SubagentLifecycleArtifactVersion = typeof SUBAGENT_LIFECYCLE_ARTIFACT_VERSION;
|
|
322
323
|
|
|
324
|
+
export type ProcessTerminalState = "pending" | "observed" | "unknown" | "not-started";
|
|
325
|
+
export type ProcessTerminalReason =
|
|
326
|
+
| "observer-unavailable"
|
|
327
|
+
| "runner-candidate-missing"
|
|
328
|
+
| "runner-instance-mismatch"
|
|
329
|
+
| "writer-close-unverified"
|
|
330
|
+
| "canonical-session-unavailable"
|
|
331
|
+
| "canonical-session-lease-active"
|
|
332
|
+
| "canonical-session-release-unverified"
|
|
333
|
+
| "proof-write-failed"
|
|
334
|
+
| "stale-repair";
|
|
335
|
+
|
|
336
|
+
export interface RunnerProcessInstanceExitV1 {
|
|
337
|
+
processInstanceId: string;
|
|
338
|
+
kind: "runner";
|
|
339
|
+
closeObservedAt: number;
|
|
340
|
+
exitCode: number | null;
|
|
341
|
+
signal: string | null;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
export interface PiWriterProcessInstanceExitV1 {
|
|
345
|
+
processInstanceId: string;
|
|
346
|
+
kind: "pi-writer";
|
|
347
|
+
attempt: number;
|
|
348
|
+
closeObservedAt: number;
|
|
349
|
+
exitCode: number | null;
|
|
350
|
+
signal: string | null;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
export type ProcessInstanceExitV1 = RunnerProcessInstanceExitV1 | PiWriterProcessInstanceExitV1;
|
|
354
|
+
|
|
355
|
+
export interface CanonicalSessionTerminalV1 {
|
|
356
|
+
canonicalSessionId: string;
|
|
357
|
+
leaseDisposition: "released" | "not-held";
|
|
358
|
+
freeAtObservation: true;
|
|
359
|
+
canonicalSessionLeaseReleased?: true;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
interface ProcessTerminalBaseV1 {
|
|
363
|
+
version: 1;
|
|
364
|
+
runId: string;
|
|
365
|
+
childIndex?: number;
|
|
366
|
+
runnerProcessInstanceId: string;
|
|
367
|
+
resumeDisposition?: "resumable" | "non-resumable" | "unavailable";
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
export type ProcessTerminalV1 =
|
|
371
|
+
| (ProcessTerminalBaseV1 & { state: "pending" | "not-started" })
|
|
372
|
+
| (ProcessTerminalBaseV1 & {
|
|
373
|
+
state: "observed";
|
|
374
|
+
observedAt: number;
|
|
375
|
+
instances: ProcessInstanceExitV1[];
|
|
376
|
+
canonicalSession?: CanonicalSessionTerminalV1;
|
|
377
|
+
})
|
|
378
|
+
| (ProcessTerminalBaseV1 & {
|
|
379
|
+
state: "unknown";
|
|
380
|
+
reason: ProcessTerminalReason;
|
|
381
|
+
diagnostic?: string;
|
|
382
|
+
});
|
|
383
|
+
|
|
323
384
|
export type SteeringActionState = "delivered" | "scheduled" | "pending" | "partial" | "recovered" | "failed";
|
|
324
385
|
export type SteeringTargetState = "scheduled" | "routed" | "delivered" | "late" | "failed" | "recovered";
|
|
325
386
|
|
|
@@ -384,6 +445,7 @@ export interface SteeringNotice {
|
|
|
384
445
|
|
|
385
446
|
export interface SteeringRecoveryDescriptor {
|
|
386
447
|
version: 1;
|
|
448
|
+
launchContractDigest?: string;
|
|
387
449
|
sourceRunId: string;
|
|
388
450
|
agentContract?: AgentContract;
|
|
389
451
|
agent: string;
|
|
@@ -415,6 +477,7 @@ export interface SteeringRecoveryDescriptor {
|
|
|
415
477
|
initialToolBudget?: ResolvedToolBudget;
|
|
416
478
|
maxSubagentDepth: number;
|
|
417
479
|
maxOutput?: MaxOutputConfig;
|
|
480
|
+
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
418
481
|
share: boolean;
|
|
419
482
|
sessionDir?: string;
|
|
420
483
|
artifactsDir?: string;
|
|
@@ -532,7 +595,7 @@ export interface ModelAttempt {
|
|
|
532
595
|
usage?: Usage;
|
|
533
596
|
}
|
|
534
597
|
|
|
535
|
-
export type AcceptanceLevel = "auto" | "none" | "attested" | "checked" | "verified"
|
|
598
|
+
export type AcceptanceLevel = "auto" | "none" | "attested" | "checked" | "verified";
|
|
536
599
|
|
|
537
600
|
export type AcceptanceEvidenceKind =
|
|
538
601
|
| "changed-files"
|
|
@@ -641,7 +704,7 @@ export interface AcceptanceVerifyResult {
|
|
|
641
704
|
}
|
|
642
705
|
|
|
643
706
|
export interface AcceptanceReviewResult {
|
|
644
|
-
status: "
|
|
707
|
+
status: "review-required" | "reviewed" | "blockers";
|
|
645
708
|
findings: Array<{
|
|
646
709
|
severity: "blocker" | "non-blocking";
|
|
647
710
|
file?: string;
|
|
@@ -650,19 +713,24 @@ export interface AcceptanceReviewResult {
|
|
|
650
713
|
}>;
|
|
651
714
|
}
|
|
652
715
|
|
|
653
|
-
export type
|
|
716
|
+
export type AcceptanceEvidenceStatus =
|
|
654
717
|
| "pending"
|
|
655
718
|
| "not-required"
|
|
656
719
|
| "claimed"
|
|
657
720
|
| "attested"
|
|
658
721
|
| "checked"
|
|
659
722
|
| "verified"
|
|
660
|
-
| "reviewed"
|
|
661
|
-
| "accepted"
|
|
662
723
|
| "rejected";
|
|
663
724
|
|
|
725
|
+
export type AcceptanceLedgerStatus =
|
|
726
|
+
| AcceptanceEvidenceStatus
|
|
727
|
+
| "review-required"
|
|
728
|
+
| "reviewed"
|
|
729
|
+
| "accepted";
|
|
730
|
+
|
|
664
731
|
export interface AcceptanceLedger {
|
|
665
732
|
status: AcceptanceLedgerStatus;
|
|
733
|
+
evidenceStatus: AcceptanceEvidenceStatus;
|
|
666
734
|
explicit: boolean;
|
|
667
735
|
effectiveAcceptance: ResolvedAcceptanceConfig;
|
|
668
736
|
inferredReason: string[];
|
|
@@ -733,6 +801,7 @@ export interface SingleResult {
|
|
|
733
801
|
structuredOutputSchemaPath?: string;
|
|
734
802
|
acceptance?: AcceptanceLedger;
|
|
735
803
|
agentContract?: AgentContract;
|
|
804
|
+
launchContractDigest?: string;
|
|
736
805
|
execution?: ExecutionProjection;
|
|
737
806
|
review?: ReviewProjection;
|
|
738
807
|
effects?: EffectsProjection;
|
|
@@ -740,6 +809,8 @@ export interface SingleResult {
|
|
|
740
809
|
transcriptError?: string;
|
|
741
810
|
children?: NestedRunSummary[];
|
|
742
811
|
watchdog?: ChildWatchdogProgress;
|
|
812
|
+
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
813
|
+
capabilityAudit?: SubagentCapabilityAudit;
|
|
743
814
|
}
|
|
744
815
|
|
|
745
816
|
export interface SpawnBudgetGrant {
|
|
@@ -799,7 +870,15 @@ export interface Details {
|
|
|
799
870
|
// Aggregated cost across all agents in the run
|
|
800
871
|
totalCost?: CostSummary;
|
|
801
872
|
spawnBudget?: SpawnBudgetSnapshot;
|
|
873
|
+
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
874
|
+
capabilityAudit?: SubagentCapabilityAudit;
|
|
802
875
|
parallelHandoff?: ParallelHandoffReference;
|
|
876
|
+
lifecycleStatus?: {
|
|
877
|
+
processTerminal?: ProcessTerminalV1;
|
|
878
|
+
};
|
|
879
|
+
launchContractDigest?: string;
|
|
880
|
+
/** Original launch contract whose persisted session is being revived. */
|
|
881
|
+
sourceLaunchContractDigest?: string;
|
|
803
882
|
}
|
|
804
883
|
|
|
805
884
|
// ============================================================================
|
|
@@ -873,6 +952,9 @@ export interface NestedStepSummary {
|
|
|
873
952
|
wrapUpRequested?: boolean;
|
|
874
953
|
toolBudget?: ToolBudgetState;
|
|
875
954
|
toolBudgetBlocked?: boolean;
|
|
955
|
+
processTerminal?: ProcessTerminalV1;
|
|
956
|
+
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
957
|
+
capabilityAudit?: SubagentCapabilityAudit;
|
|
876
958
|
children?: NestedRunSummary[];
|
|
877
959
|
}
|
|
878
960
|
|
|
@@ -888,6 +970,9 @@ export interface NestedRunSummary extends NestedRunAddress {
|
|
|
888
970
|
controlInbox?: string;
|
|
889
971
|
capabilityToken?: string;
|
|
890
972
|
mode?: SubagentRunMode;
|
|
973
|
+
processTerminal?: ProcessTerminalV1;
|
|
974
|
+
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
975
|
+
capabilityAudit?: SubagentCapabilityAudit;
|
|
891
976
|
state: NestedRunState;
|
|
892
977
|
agent?: string;
|
|
893
978
|
agents?: string[];
|
|
@@ -946,10 +1031,12 @@ export interface AsyncStartedEvent {
|
|
|
946
1031
|
chainStepCount?: number;
|
|
947
1032
|
parallelGroups?: AsyncParallelGroupStatus[];
|
|
948
1033
|
workflowGraph?: WorkflowGraphSnapshot;
|
|
1034
|
+
launchContractDigest?: string;
|
|
949
1035
|
timeoutMs?: number;
|
|
950
1036
|
deadlineAt?: number;
|
|
951
1037
|
turnBudget?: TurnBudgetState;
|
|
952
1038
|
nestedRoute?: NestedRouteInfo;
|
|
1039
|
+
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
953
1040
|
}
|
|
954
1041
|
|
|
955
1042
|
export interface AsyncStatus {
|
|
@@ -987,6 +1074,10 @@ export interface AsyncStatus {
|
|
|
987
1074
|
pendingAppends?: number;
|
|
988
1075
|
parallelGroups?: AsyncParallelGroupStatus[];
|
|
989
1076
|
workflowGraph?: WorkflowGraphSnapshot;
|
|
1077
|
+
processTerminal?: ProcessTerminalV1;
|
|
1078
|
+
launchContractDigest?: string;
|
|
1079
|
+
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
1080
|
+
capabilityAudit?: SubagentCapabilityAudit;
|
|
990
1081
|
steps?: Array<{
|
|
991
1082
|
agent: string;
|
|
992
1083
|
/** Resolved launch context for this child step. */
|
|
@@ -1035,10 +1126,14 @@ export interface AsyncStatus {
|
|
|
1035
1126
|
structuredOutputSchemaPath?: string;
|
|
1036
1127
|
acceptance?: AcceptanceLedger;
|
|
1037
1128
|
agentContract?: AgentContract;
|
|
1129
|
+
launchContractDigest?: string;
|
|
1038
1130
|
execution?: ExecutionProjection;
|
|
1039
1131
|
review?: ReviewProjection;
|
|
1040
1132
|
effects?: EffectsProjection;
|
|
1041
1133
|
watchdog?: ChildWatchdogProgress;
|
|
1134
|
+
processTerminal?: ProcessTerminalV1;
|
|
1135
|
+
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
1136
|
+
capabilityAudit?: SubagentCapabilityAudit;
|
|
1042
1137
|
}>;
|
|
1043
1138
|
sessionDir?: string;
|
|
1044
1139
|
outputFile?: string;
|
|
@@ -1109,6 +1204,8 @@ export interface ForegroundResumeChild {
|
|
|
1109
1204
|
index: number;
|
|
1110
1205
|
context?: "fresh" | "fork";
|
|
1111
1206
|
sessionFile?: string;
|
|
1207
|
+
model?: string;
|
|
1208
|
+
thinking?: string;
|
|
1112
1209
|
status: SubagentResultStatus;
|
|
1113
1210
|
activityState?: ActivityState;
|
|
1114
1211
|
lastActivityAt?: number;
|
|
@@ -1130,9 +1227,12 @@ export interface ForegroundResumeChild {
|
|
|
1130
1227
|
detachedReason?: string;
|
|
1131
1228
|
acceptance?: AcceptanceLedger;
|
|
1132
1229
|
agentContract?: AgentContract;
|
|
1230
|
+
launchContractDigest?: string;
|
|
1133
1231
|
execution?: ExecutionProjection;
|
|
1134
1232
|
review?: ReviewProjection;
|
|
1135
1233
|
effects?: EffectsProjection;
|
|
1234
|
+
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
1235
|
+
capabilityAudit?: SubagentCapabilityAudit;
|
|
1136
1236
|
updatedAt?: number;
|
|
1137
1237
|
}
|
|
1138
1238
|
|
|
@@ -1195,6 +1295,8 @@ export interface SubagentState {
|
|
|
1195
1295
|
/** Runtime-owned artifact resolution inputs used by Fleet transcript targeting. */
|
|
1196
1296
|
artifactDirPreference?: ArtifactDirPreference;
|
|
1197
1297
|
parentSessionFile?: string | null;
|
|
1298
|
+
/** Last valid parent session model observed for this session; used when continuation contexts omit ctx.model. */
|
|
1299
|
+
lastParentModel?: { provider: string; id: string };
|
|
1198
1300
|
subagentInProgress?: boolean;
|
|
1199
1301
|
subagentSpawns?: {
|
|
1200
1302
|
sessionId: string | null;
|
|
@@ -1252,6 +1354,7 @@ export const INTERCOM_DETACH_REQUEST_EVENT = "pi-intercom:detach-request";
|
|
|
1252
1354
|
export const INTERCOM_DETACH_RESPONSE_EVENT = "pi-intercom:detach-response";
|
|
1253
1355
|
export const SUBAGENT_ASYNC_STARTED_EVENT = "subagent:async-started";
|
|
1254
1356
|
export const SUBAGENT_ASYNC_COMPLETE_EVENT = "subagent:async-complete";
|
|
1357
|
+
export const SUBAGENT_PROCESS_TERMINAL_EVENT = "subagent:process-terminal";
|
|
1255
1358
|
export const SUBAGENT_FOREGROUND_COMPLETE_EVENT = "subagent:foreground-complete";
|
|
1256
1359
|
export const SUBAGENT_CONTROL_EVENT = "subagent:control-event";
|
|
1257
1360
|
export const SUBAGENT_CONTROL_INTERCOM_EVENT = "subagent:control-intercom";
|
|
@@ -1299,6 +1402,7 @@ export interface RunSyncOptions {
|
|
|
1299
1402
|
maxSubagentDepth?: number;
|
|
1300
1403
|
/** Effective parent wait-tool setting propagated to the child runtime. */
|
|
1301
1404
|
waitToolEnabled?: boolean;
|
|
1405
|
+
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
1302
1406
|
nestedRoute?: NestedRouteInfo;
|
|
1303
1407
|
/** Override the agent's default model (format: "provider/id" or just "id") */
|
|
1304
1408
|
modelOverride?: string;
|
|
@@ -1332,6 +1436,8 @@ export type IntercomBridgeMode = "off" | "fork-only" | "always";
|
|
|
1332
1436
|
export interface IntercomBridgeConfig {
|
|
1333
1437
|
mode?: IntercomBridgeMode;
|
|
1334
1438
|
instructionFile?: string;
|
|
1439
|
+
/** Deliver grouped completion messages through an external acknowledged intercom listener. */
|
|
1440
|
+
resultDelivery?: boolean;
|
|
1335
1441
|
}
|
|
1336
1442
|
|
|
1337
1443
|
interface TopLevelParallelConfig {
|
|
@@ -1360,10 +1466,14 @@ export interface ScheduledRunsConfig {
|
|
|
1360
1466
|
maxPending?: number;
|
|
1361
1467
|
}
|
|
1362
1468
|
|
|
1469
|
+
export type FleetViewPlacement = "aboveEditor" | "belowEditor";
|
|
1470
|
+
|
|
1363
1471
|
export interface ExtensionConfig {
|
|
1364
1472
|
asyncByDefault?: boolean;
|
|
1365
|
-
/** Show the Claude Code-style navigable fleet
|
|
1473
|
+
/** Show the Claude Code-style navigable fleet. Defaults to true. */
|
|
1366
1474
|
fleetView?: boolean;
|
|
1475
|
+
/** Place the persistent FleetView above or below the editor. Defaults to belowEditor. */
|
|
1476
|
+
fleetViewPlacement?: FleetViewPlacement;
|
|
1367
1477
|
/** Show the legacy above-editor async runs widget. Defaults to true only when fleetView is disabled. */
|
|
1368
1478
|
asyncWidget?: boolean;
|
|
1369
1479
|
/** Tool description variant registered for the parent-facing subagent tool. Defaults to full. */
|
package/src/shared/utils.ts
CHANGED
|
@@ -412,6 +412,14 @@ export function compactForegroundDetails(details: Details): Details {
|
|
|
412
412
|
};
|
|
413
413
|
}
|
|
414
414
|
|
|
415
|
+
export function hasEmptyTerminalAssistantResponse(messages: Message[]): boolean {
|
|
416
|
+
const lastAssistant = messages.findLast((message) => message.role === "assistant");
|
|
417
|
+
return lastAssistant?.role === "assistant"
|
|
418
|
+
&& Array.isArray(lastAssistant.content)
|
|
419
|
+
&& lastAssistant.content.length === 0
|
|
420
|
+
&& lastAssistant.usage.output === 0;
|
|
421
|
+
}
|
|
422
|
+
|
|
415
423
|
/**
|
|
416
424
|
* Detect errors in subagent execution from messages (only errors with no subsequent success)
|
|
417
425
|
*/
|
|
@@ -438,50 +446,17 @@ export function detectSubagentError(messages: Message[]): ErrorInfo {
|
|
|
438
446
|
const toolName = "toolName" in msg && typeof msg.toolName === "string" ? msg.toolName : undefined;
|
|
439
447
|
const isError = "isError" in msg && msg.isError === true;
|
|
440
448
|
|
|
441
|
-
if (isError)
|
|
442
|
-
const text = msg.content.find((c) => c.type === "text");
|
|
443
|
-
const details = text && "text" in text ? text.text : undefined;
|
|
444
|
-
const exitMatch = details?.match(/exit(?:ed)?\s*(?:with\s*)?(?:code|status)?\s*[:\s]?\s*(\d+)/i);
|
|
445
|
-
return {
|
|
446
|
-
hasError: true,
|
|
447
|
-
exitCode: exitMatch ? parseInt(exitMatch[1], 10) : 1,
|
|
448
|
-
errorType: toolName || "tool",
|
|
449
|
-
details: details?.slice(0, 200),
|
|
450
|
-
};
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
if (toolName !== "bash") continue;
|
|
449
|
+
if (!isError) continue;
|
|
454
450
|
|
|
455
451
|
const text = msg.content.find((c) => c.type === "text");
|
|
456
|
-
|
|
457
|
-
const
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
// NOTE: These patterns can match legitimate output (grep results, logs,
|
|
468
|
-
// testing). With the assistant-message check above, most false positives
|
|
469
|
-
// are mitigated since the agent will have responded after routine errors.
|
|
470
|
-
const fatalPatterns = [
|
|
471
|
-
/command not found/i,
|
|
472
|
-
/permission denied/i,
|
|
473
|
-
/no such file or directory/i,
|
|
474
|
-
/segmentation fault/i,
|
|
475
|
-
/killed|terminated/i,
|
|
476
|
-
/out of memory/i,
|
|
477
|
-
/connection refused/i,
|
|
478
|
-
/timeout/i,
|
|
479
|
-
];
|
|
480
|
-
for (const pattern of fatalPatterns) {
|
|
481
|
-
if (pattern.test(output)) {
|
|
482
|
-
return { hasError: true, exitCode: 1, errorType: "bash", details: output.slice(0, 200) };
|
|
483
|
-
}
|
|
484
|
-
}
|
|
452
|
+
const details = text && "text" in text ? text.text : undefined;
|
|
453
|
+
const exitMatch = details?.match(/exit(?:ed)?\s*(?:with\s*)?(?:code|status)?\s*[:\s]?\s*(\d+)/i);
|
|
454
|
+
return {
|
|
455
|
+
hasError: true,
|
|
456
|
+
exitCode: exitMatch ? parseInt(exitMatch[1], 10) : 1,
|
|
457
|
+
errorType: toolName || "tool",
|
|
458
|
+
details: details?.slice(0, 200),
|
|
459
|
+
};
|
|
485
460
|
}
|
|
486
461
|
|
|
487
462
|
return { hasError: false };
|
|
@@ -69,6 +69,7 @@ interface PromptTemplateDelegationTaskProgress {
|
|
|
69
69
|
|
|
70
70
|
export interface PromptTemplateDelegationUpdate {
|
|
71
71
|
requestId: string;
|
|
72
|
+
runId?: string;
|
|
72
73
|
currentTool?: string;
|
|
73
74
|
currentToolArgs?: string;
|
|
74
75
|
recentOutput?: string;
|
|
@@ -323,6 +324,7 @@ export function toDelegationUpdate(requestId: string, update: PromptTemplateBrid
|
|
|
323
324
|
: undefined;
|
|
324
325
|
return {
|
|
325
326
|
requestId,
|
|
327
|
+
...(update.details?.runId ? { runId: update.details.runId } : {}),
|
|
326
328
|
currentTool: progress?.currentTool,
|
|
327
329
|
currentToolArgs: progress?.currentToolArgs,
|
|
328
330
|
recentOutput: safeLastOutput,
|
|
@@ -414,6 +416,7 @@ export function toSubagentDelegationUpdate(requestId: string, result: PromptTemp
|
|
|
414
416
|
return {
|
|
415
417
|
version: SUBAGENT_DELEGATION_PROTOCOL_VERSION,
|
|
416
418
|
requestId,
|
|
419
|
+
...(legacy.runId ? { runId: legacy.runId } : {}),
|
|
417
420
|
...(legacy.currentTool ? { currentTool: legacy.currentTool } : {}),
|
|
418
421
|
...(legacy.currentToolArgs ? { currentToolArgs: legacy.currentToolArgs } : {}),
|
|
419
422
|
...(legacy.recentOutput ? { recentOutput: legacy.recentOutput } : {}),
|
|
@@ -437,6 +440,7 @@ export function toSubagentDelegationV2Update(
|
|
|
437
440
|
requestId: request.requestId,
|
|
438
441
|
ownerRunId: request.ownerRunId,
|
|
439
442
|
nodeId: request.nodeId,
|
|
443
|
+
...(legacy.runId ? { runId: legacy.runId } : {}),
|
|
440
444
|
...(legacy.currentTool ? { currentTool: legacy.currentTool } : {}),
|
|
441
445
|
...(legacy.currentToolArgs ? { currentToolArgs: legacy.currentToolArgs } : {}),
|
|
442
446
|
...(legacy.recentOutput ? { recentOutput: legacy.recentOutput } : {}),
|
|
@@ -491,7 +495,7 @@ export function toSubagentDelegationResponse(
|
|
|
491
495
|
...(child?.finalOutput ? { output: child.finalOutput } : {}),
|
|
492
496
|
...(child?.savedOutputPath ? { outputPath: child.savedOutputPath } : {}),
|
|
493
497
|
...(child?.sessionFile ? { sessionFile: child.sessionFile } : {}),
|
|
494
|
-
...(child?.acceptance ? { acceptance: { status: child.acceptance.status, explicit: child.acceptance.explicit } } : {}),
|
|
498
|
+
...(child?.acceptance ? { acceptance: { status: child.acceptance.status, evidenceStatus: child.acceptance.evidenceStatus, explicit: child.acceptance.explicit } } : {}),
|
|
495
499
|
...(child?.review ? { review: child.review as SubagentDelegationReviewResult } : {}),
|
|
496
500
|
...(child?.effects ? { effects: child.effects as SubagentDelegationEffectsResult } : {}),
|
|
497
501
|
...(typeof child?.usage?.turns === "number" ? { turns: child.usage.turns } : {}),
|
|
@@ -553,6 +557,7 @@ export function toSubagentDelegationV2Response(
|
|
|
553
557
|
...(child?.model ? { model: child.model } : {}),
|
|
554
558
|
...(child?.thinking ? { thinking: child.thinking } : {}),
|
|
555
559
|
...(typeof child?.exitCode === "number" ? { exitCode: child.exitCode } : {}),
|
|
560
|
+
...(child?.launchContractDigest ? { launchContractDigest: child.launchContractDigest } : {}),
|
|
556
561
|
...(projectedResult ? { result: projectedResult } : {}),
|
|
557
562
|
...(usage ? {
|
|
558
563
|
usage: {
|
|
@@ -1008,7 +1008,7 @@ function validateInlineAcceptanceInput(value: string, agent: string): void {
|
|
|
1008
1008
|
const errors = validateAcceptanceInput(value, `acceptance for step '${agent}'`);
|
|
1009
1009
|
if (errors.length > 0) throw new SlashParseError(errors[0]!);
|
|
1010
1010
|
if (!INLINE_ACCEPTANCE_LEVELS.has(value)) {
|
|
1011
|
-
throw new SlashParseError(`Inline acceptance for step '${agent}' supports auto, attested, or checked. Use the subagent tool API or a saved .chain.json file for none or
|
|
1011
|
+
throw new SlashParseError(`Inline acceptance for step '${agent}' supports auto, attested, or checked. Use the subagent tool API or a saved .chain.json file for none, verified, or review requirements; reviewed is an achieved status, not an input level.`);
|
|
1012
1012
|
}
|
|
1013
1013
|
}
|
|
1014
1014
|
|
package/src/tui/fleet-status.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { Editor, isKeyRelease, Key, matchesKey, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
3
|
-
import type { AsyncJobStep, SubagentState } from "../shared/types.ts";
|
|
3
|
+
import type { AsyncJobStep, FleetViewPlacement, SubagentState } from "../shared/types.ts";
|
|
4
4
|
|
|
5
5
|
export const FLEET_STATUS_WIDGET_KEY = "subagent-fleet-status";
|
|
6
6
|
|
|
@@ -22,6 +22,11 @@ type FleetStatusEntry = {
|
|
|
22
22
|
export interface FleetStatusOptions {
|
|
23
23
|
refreshMs?: number;
|
|
24
24
|
maxAgentRows?: number;
|
|
25
|
+
placement?: FleetViewPlacement;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function resolveFleetViewPlacement(value: unknown): FleetViewPlacement {
|
|
29
|
+
return value === "aboveEditor" ? "aboveEditor" : "belowEditor";
|
|
25
30
|
}
|
|
26
31
|
|
|
27
32
|
export function formatFleetElapsed(ms: number): string {
|
|
@@ -48,6 +53,13 @@ function isActiveState(value: string): boolean {
|
|
|
48
53
|
return value === "running" || value === "queued" || value === "pending";
|
|
49
54
|
}
|
|
50
55
|
|
|
56
|
+
function isStaleExtensionContextError(error: unknown): boolean {
|
|
57
|
+
// Pi currently exposes stale contexts as plain Errors without a stable code or subtype.
|
|
58
|
+
return error instanceof Error
|
|
59
|
+
&& (error.message.includes("This extension ctx is stale")
|
|
60
|
+
|| error.message.includes("Extension context no longer active"));
|
|
61
|
+
}
|
|
62
|
+
|
|
51
63
|
export function collectFleetStatusEntries(state: SubagentState): FleetStatusEntry[] {
|
|
52
64
|
const entries: FleetStatusEntry[] = [];
|
|
53
65
|
for (const control of state.foregroundControls.values()) {
|
|
@@ -111,6 +123,7 @@ export function collectFleetStatusEntries(state: SubagentState): FleetStatusEntr
|
|
|
111
123
|
|
|
112
124
|
export class SubagentFleetStatus {
|
|
113
125
|
private ctx: ExtensionContext | undefined;
|
|
126
|
+
private ui: ExtensionContext["ui"] | undefined;
|
|
114
127
|
private tui: FleetStatusTui | undefined;
|
|
115
128
|
private inputUnsubscribe: (() => void) | undefined;
|
|
116
129
|
private timer: ReturnType<typeof setInterval> | undefined;
|
|
@@ -124,6 +137,7 @@ export class SubagentFleetStatus {
|
|
|
124
137
|
private readonly openInspector: (itemKey: string) => Promise<void> | void;
|
|
125
138
|
private readonly refreshMs: number;
|
|
126
139
|
private readonly maxAgentRows: number;
|
|
140
|
+
private readonly placement: FleetViewPlacement;
|
|
127
141
|
|
|
128
142
|
constructor(
|
|
129
143
|
state: SubagentState,
|
|
@@ -134,19 +148,22 @@ export class SubagentFleetStatus {
|
|
|
134
148
|
this.openInspector = openInspector;
|
|
135
149
|
this.refreshMs = options.refreshMs ?? REFRESH_MS;
|
|
136
150
|
this.maxAgentRows = options.maxAgentRows ?? MAX_AGENT_ROWS;
|
|
151
|
+
this.placement = options.placement ?? "belowEditor";
|
|
137
152
|
}
|
|
138
153
|
|
|
139
154
|
setContext(ctx: ExtensionContext): void {
|
|
140
155
|
if (!ctx.hasUI) return;
|
|
141
|
-
|
|
156
|
+
const ui = ctx.ui;
|
|
157
|
+
if (this.ui === ui) {
|
|
142
158
|
this.ctx = ctx;
|
|
143
159
|
this.refresh();
|
|
144
160
|
return;
|
|
145
161
|
}
|
|
146
162
|
this.clearUiRegistration();
|
|
147
163
|
this.ctx = ctx;
|
|
148
|
-
|
|
149
|
-
|
|
164
|
+
this.ui = ui;
|
|
165
|
+
if (typeof ui.onTerminalInput === "function") {
|
|
166
|
+
this.inputUnsubscribe = ui.onTerminalInput((data) => this.handleKey(data));
|
|
150
167
|
}
|
|
151
168
|
this.timer = setInterval(() => this.refresh(), this.refreshMs);
|
|
152
169
|
this.timer.unref?.();
|
|
@@ -156,6 +173,7 @@ export class SubagentFleetStatus {
|
|
|
156
173
|
dispose(): void {
|
|
157
174
|
this.clearUiRegistration();
|
|
158
175
|
this.ctx = undefined;
|
|
176
|
+
this.ui = undefined;
|
|
159
177
|
this.entries = [];
|
|
160
178
|
this.active = false;
|
|
161
179
|
this.selectedKey = "main";
|
|
@@ -164,8 +182,8 @@ export class SubagentFleetStatus {
|
|
|
164
182
|
}
|
|
165
183
|
|
|
166
184
|
refresh(): void {
|
|
167
|
-
const ctx = this.
|
|
168
|
-
if (!ctx
|
|
185
|
+
const ctx = this.getActiveUiContext();
|
|
186
|
+
if (!ctx) return;
|
|
169
187
|
this.entries = collectFleetStatusEntries(this.state);
|
|
170
188
|
this.clampSelection();
|
|
171
189
|
if (this.inspectorOpen || this.state.fleetInspectorOpen) {
|
|
@@ -204,7 +222,7 @@ export class SubagentFleetStatus {
|
|
|
204
222
|
this.tui = undefined;
|
|
205
223
|
},
|
|
206
224
|
};
|
|
207
|
-
}, { placement:
|
|
225
|
+
}, { placement: this.placement });
|
|
208
226
|
this.widgetRegistered = true;
|
|
209
227
|
this.lastRenderKey = renderKey;
|
|
210
228
|
return;
|
|
@@ -215,8 +233,8 @@ export class SubagentFleetStatus {
|
|
|
215
233
|
}
|
|
216
234
|
|
|
217
235
|
handleKey(data: string): { consume?: boolean; data?: string } | undefined {
|
|
218
|
-
const ctx = this.
|
|
219
|
-
if (!ctx
|
|
236
|
+
const ctx = this.getActiveUiContext();
|
|
237
|
+
if (!ctx || this.entries.length === 0 || isKeyRelease(data)) return undefined;
|
|
220
238
|
if (this.inspectorOpen) return undefined;
|
|
221
239
|
if (!this.editorHasFocus()) {
|
|
222
240
|
if (this.active) this.deactivate();
|
|
@@ -344,19 +362,47 @@ export class SubagentFleetStatus {
|
|
|
344
362
|
});
|
|
345
363
|
}
|
|
346
364
|
|
|
365
|
+
private getActiveUiContext(): ExtensionContext | undefined {
|
|
366
|
+
const ctx = this.ctx;
|
|
367
|
+
if (!ctx) return undefined;
|
|
368
|
+
try {
|
|
369
|
+
return ctx.hasUI ? ctx : undefined;
|
|
370
|
+
} catch (error) {
|
|
371
|
+
if (!isStaleExtensionContextError(error)) throw error;
|
|
372
|
+
this.clearUiRegistration();
|
|
373
|
+
return undefined;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
347
377
|
private clearUiRegistration(): void {
|
|
348
378
|
if (this.timer) clearInterval(this.timer);
|
|
349
379
|
this.timer = undefined;
|
|
350
|
-
|
|
380
|
+
|
|
381
|
+
const inputUnsubscribe = this.inputUnsubscribe;
|
|
382
|
+
const ui = this.ui;
|
|
383
|
+
const widgetRegistered = this.widgetRegistered;
|
|
351
384
|
this.inputUnsubscribe = undefined;
|
|
352
|
-
|
|
385
|
+
this.ctx = undefined;
|
|
386
|
+
this.ui = undefined;
|
|
387
|
+
this.widgetRegistered = false;
|
|
388
|
+
this.tui = undefined;
|
|
389
|
+
|
|
390
|
+
const cleanupErrors: unknown[] = [];
|
|
391
|
+
try {
|
|
392
|
+
inputUnsubscribe?.();
|
|
393
|
+
} catch (error) {
|
|
394
|
+
if (!isStaleExtensionContextError(error)) cleanupErrors.push(error);
|
|
395
|
+
}
|
|
396
|
+
if (ui && widgetRegistered) {
|
|
353
397
|
try {
|
|
354
|
-
|
|
355
|
-
} catch {
|
|
356
|
-
|
|
398
|
+
ui.setWidget(FLEET_STATUS_WIDGET_KEY, undefined);
|
|
399
|
+
} catch (error) {
|
|
400
|
+
if (!isStaleExtensionContextError(error)) cleanupErrors.push(error);
|
|
357
401
|
}
|
|
358
402
|
}
|
|
359
|
-
|
|
360
|
-
|
|
403
|
+
if (cleanupErrors.length === 1) throw cleanupErrors[0];
|
|
404
|
+
if (cleanupErrors.length > 1) {
|
|
405
|
+
throw new AggregateError(cleanupErrors, "Failed to clean up FleetView UI registration");
|
|
406
|
+
}
|
|
361
407
|
}
|
|
362
408
|
}
|
package/src/tui/fleet.ts
CHANGED
|
@@ -463,7 +463,7 @@ export class SubagentFleetComponent implements Component {
|
|
|
463
463
|
this.refresh();
|
|
464
464
|
this.timer = setInterval(() => {
|
|
465
465
|
if (this.disposed) return;
|
|
466
|
-
this.
|
|
466
|
+
this.invalidate();
|
|
467
467
|
this.tui.requestRender();
|
|
468
468
|
}, options.refreshMs ?? REFRESH_MS);
|
|
469
469
|
this.timer.unref?.();
|