pi-subagents 0.49.0 → 0.51.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 +97 -0
- package/agents/gpt-pro.md +17 -0
- package/agents/oracle.md +7 -5
- package/agents/researcher.md +1 -1
- package/agents/reviewer.md +2 -2
- package/agents/scout.md +1 -1
- package/agents/worker.md +1 -1
- package/async-retention-discovery-worker.mjs +180 -0
- package/docs/agents.md +37 -2
- package/docs/configuration.md +76 -14
- package/docs/extension-api.md +78 -1
- package/docs/missions.md +1 -1
- package/docs/observability.md +20 -4
- package/docs/tool-reference.md +55 -39
- package/docs/workflows.md +171 -5
- package/package.json +4 -2
- package/skills/pi-subagents/SKILL.md +5 -4
- package/skills/pi-subagents/references/constraints-and-recipes.md +9 -6
- package/skills/pi-subagents/references/execution-controls.md +22 -18
- package/skills/pi-subagents/references/management-authoring-rpc.md +5 -5
- package/skills/pi-subagents/references/prompting-and-roles.md +33 -17
- package/src/agents/agent-management.ts +100 -345
- package/src/agents/agent-serializer.ts +2 -0
- package/src/agents/agents.ts +135 -25
- package/src/api/external-job-provider.ts +185 -0
- package/src/api/external-runs.ts +174 -84
- package/src/api/preflight.ts +42 -11
- package/src/api/shared-types.ts +2 -0
- package/src/extension/config.ts +36 -3
- package/src/extension/doctor.ts +3 -6
- package/src/extension/fanout-child.ts +2 -2
- package/src/extension/index.ts +210 -90
- package/src/extension/public-execution.ts +31 -2
- package/src/extension/rpc.ts +5 -1
- package/src/extension/schemas.ts +14 -36
- package/src/extension/tool-description.ts +37 -24
- package/src/inspectors/herdr/actions.ts +5 -9
- package/src/inspectors/herdr/inspector-runner.ts +2 -1
- package/src/inspectors/herdr/project-panes.ts +4 -8
- package/src/inspectors/herdr/shell-command.ts +16 -0
- package/src/intercom/intercom-bridge.ts +2 -3
- package/src/intercom/native-supervisor-channel.ts +49 -51
- package/src/missions/goal-driver.ts +3 -1
- package/src/missions/lifecycle.ts +6 -1
- package/src/missions/store.ts +4 -9
- package/src/profiles/profiles.ts +3 -1
- package/src/runs/background/active-run-index.ts +94 -1
- package/src/runs/background/async-execution.ts +98 -38
- package/src/runs/background/async-job-tracker.ts +21 -4
- package/src/runs/background/async-resume.ts +30 -17
- package/src/runs/background/async-retention.ts +888 -0
- package/src/runs/background/async-status-snapshot.ts +277 -0
- package/src/runs/background/async-status.ts +47 -56
- package/src/runs/background/chain-append.ts +3 -33
- package/src/runs/background/chain-root-attachment.ts +2 -2
- package/src/runs/background/completion-replay.ts +11 -1
- package/src/runs/background/control-channel.ts +14 -68
- package/src/runs/background/fleet-view.ts +3 -1
- package/src/runs/background/index-segment.ts +59 -0
- package/src/runs/background/notify.ts +3 -1
- package/src/runs/background/result-files.ts +505 -0
- package/src/runs/background/result-watcher.ts +250 -51
- package/src/runs/background/retained-children.ts +79 -20
- package/src/runs/background/run-id-query.ts +7 -0
- package/src/runs/background/run-id-resolver.ts +37 -29
- package/src/runs/background/run-status.ts +34 -20
- package/src/runs/background/scheduled-runs.ts +71 -27
- package/src/runs/background/stale-run-reconciler.ts +33 -16
- package/src/runs/background/steering.ts +11 -1
- package/src/runs/background/subagent-runner.ts +535 -161
- package/src/runs/background/subagent-wait.ts +9 -7
- package/src/runs/background/terminal-run-index.ts +129 -0
- package/src/runs/background/wait-completions.ts +22 -2
- package/src/runs/background/wait-subscriptions.ts +80 -1
- package/src/runs/foreground/async-dismiss-action.ts +2 -1
- package/src/runs/foreground/async-steering-action.ts +21 -14
- package/src/runs/foreground/execution.ts +221 -15
- package/src/runs/foreground/subagent-executor.ts +529 -1519
- package/src/runs/foreground/workflow-foreground-steering.ts +6 -5
- package/src/runs/shared/chain-outputs.ts +1 -3
- package/src/runs/shared/completion-guard.ts +96 -6
- package/src/runs/shared/external-cli-runner.ts +4 -0
- package/src/runs/shared/external-job-bridge.ts +450 -0
- package/src/runs/shared/external-job-runner.ts +286 -0
- package/src/runs/shared/mcp-direct-tool-allowlist.ts +14 -0
- package/src/runs/shared/model-fallback.ts +34 -3
- package/src/runs/shared/nested-events.ts +66 -62
- package/src/runs/shared/orca-progress-tabs.ts +437 -0
- package/src/runs/shared/parallel-handoff.ts +46 -4
- package/src/runs/shared/parallel-utils.ts +6 -15
- package/src/runs/shared/permissions.ts +5 -1
- package/src/runs/shared/pi-args.ts +8 -1
- package/src/runs/shared/subagent-control.ts +41 -4
- package/src/runs/shared/subagent-prompt-runtime.ts +13 -15
- package/src/runs/shared/subagent-startup-retry.ts +12 -0
- package/src/runs/shared/tool-timeout.ts +93 -0
- package/src/runs/shared/workflow-graph.ts +1 -23
- package/src/runs/shared/worktree.ts +12 -1
- package/src/shared/atomic-json.ts +22 -2
- package/src/shared/capacity-resilient-json.ts +102 -0
- package/src/shared/completion-owner.ts +14 -0
- package/src/shared/file-system-retry.ts +49 -1
- package/src/shared/fork-context.ts +42 -0
- package/src/shared/prompt-resources.ts +0 -40
- package/src/shared/settings.ts +3 -27
- package/src/shared/types.ts +88 -27
- package/src/shared/utils.ts +8 -0
- package/src/shared/watch-strategy.ts +10 -0
- package/src/slash/slash-commands.ts +45 -28
- package/src/slash/slash-live-state.ts +3 -0
- package/src/tui/fleet-status.ts +160 -45
- package/src/tui/fleet.ts +186 -28
- package/src/tui/render.ts +41 -10
- package/src/workflows/chat-progress.ts +7 -5
- package/src/workflows/scripted-workflow.ts +424 -125
- package/src/runs/foreground/chain-clarify.ts +0 -1354
- package/src/runs/foreground/chain-execution.ts +0 -1565
|
@@ -18,6 +18,7 @@ import * as fs from "node:fs";
|
|
|
18
18
|
import * as path from "node:path";
|
|
19
19
|
import { writeAtomicJson } from "../../shared/atomic-json.ts";
|
|
20
20
|
import { POLL_INTERVAL_MS } from "../../shared/types.ts";
|
|
21
|
+
import { shouldUseNativeFsWatch } from "../../shared/watch-strategy.ts";
|
|
21
22
|
import { resolveWatchPath } from "../../shared/utils.ts";
|
|
22
23
|
|
|
23
24
|
export type ControlChannelFs = Pick<typeof fs, "mkdirSync" | "existsSync" | "rmSync" | "watch" | "readdirSync" | "readFileSync" | "realpathSync">;
|
|
@@ -56,13 +57,6 @@ export interface StopRequest {
|
|
|
56
57
|
reason?: string;
|
|
57
58
|
}
|
|
58
59
|
|
|
59
|
-
export interface CheckpointDecisionRequest {
|
|
60
|
-
type: "approve-checkpoint" | "reject-checkpoint";
|
|
61
|
-
ts?: number;
|
|
62
|
-
source?: string;
|
|
63
|
-
reason?: string;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
60
|
export type SteerDeliveryMode = "steer" | "follow_up" | "auto";
|
|
67
61
|
export type SteerDeliveryStatus = "delivered" | "queued";
|
|
68
62
|
|
|
@@ -127,14 +121,6 @@ export function stopRequestPath(asyncDir: string): string {
|
|
|
127
121
|
return path.join(controlInboxDir(asyncDir), "stop.json");
|
|
128
122
|
}
|
|
129
123
|
|
|
130
|
-
export function approveCheckpointRequestPath(asyncDir: string): string {
|
|
131
|
-
return path.join(controlInboxDir(asyncDir), "approve-checkpoint.json");
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
export function rejectCheckpointRequestPath(asyncDir: string): string {
|
|
135
|
-
return path.join(controlInboxDir(asyncDir), "reject-checkpoint.json");
|
|
136
|
-
}
|
|
137
|
-
|
|
138
124
|
/** Directory of parent-to-runner steering requests. */
|
|
139
125
|
export function steerRequestsDir(asyncDir: string): string {
|
|
140
126
|
return path.join(controlInboxDir(asyncDir), STEER_REQUESTS_DIR);
|
|
@@ -144,8 +130,8 @@ export function steerInboxClosedPath(asyncDir: string): string {
|
|
|
144
130
|
return path.join(controlInboxDir(asyncDir), STEER_INBOX_CLOSED_FILE);
|
|
145
131
|
}
|
|
146
132
|
|
|
147
|
-
export function closeSteerInbox(asyncDir: string, state: string): void {
|
|
148
|
-
|
|
133
|
+
export function closeSteerInbox(asyncDir: string, state: string, write: (filePath: string, payload: object) => void = writeAtomicJson): void {
|
|
134
|
+
write(steerInboxClosedPath(asyncDir), { version: 1, closedAt: Date.now(), state });
|
|
149
135
|
}
|
|
150
136
|
|
|
151
137
|
/** Per-child inbox consumed by the child prompt runtime inside the Pi process. */
|
|
@@ -299,18 +285,6 @@ export function requestAsyncStop(
|
|
|
299
285
|
return requestPath;
|
|
300
286
|
}
|
|
301
287
|
|
|
302
|
-
export function requestAsyncCheckpointDecision(
|
|
303
|
-
asyncDir: string,
|
|
304
|
-
type: CheckpointDecisionRequest["type"],
|
|
305
|
-
payload: Omit<CheckpointDecisionRequest, "type"> = {},
|
|
306
|
-
deps: { now?: () => number } = {},
|
|
307
|
-
): string {
|
|
308
|
-
const requestPath = type === "approve-checkpoint" ? approveCheckpointRequestPath(asyncDir) : rejectCheckpointRequestPath(asyncDir);
|
|
309
|
-
const request: CheckpointDecisionRequest = { ...payload, ts: payload.ts ?? deps.now?.() ?? Date.now(), type };
|
|
310
|
-
writeAtomicJson(requestPath, request);
|
|
311
|
-
return requestPath;
|
|
312
|
-
}
|
|
313
|
-
|
|
314
288
|
export function requestAsyncSteer(
|
|
315
289
|
asyncDir: string,
|
|
316
290
|
payload: { message: string; mode?: SteerDeliveryMode; targetIndex?: number; targetIndexes?: number[]; source?: string; id?: string; ts?: number },
|
|
@@ -559,21 +533,6 @@ export function consumeStopRequest(
|
|
|
559
533
|
return true;
|
|
560
534
|
}
|
|
561
535
|
|
|
562
|
-
export function consumeCheckpointDecisionRequest(
|
|
563
|
-
asyncDir: string,
|
|
564
|
-
fsImpl: Pick<typeof fs, "existsSync" | "rmSync"> = fs,
|
|
565
|
-
): "approved" | "rejected" | undefined {
|
|
566
|
-
if (fsImpl.existsSync(rejectCheckpointRequestPath(asyncDir))) {
|
|
567
|
-
try { fsImpl.rmSync(rejectCheckpointRequestPath(asyncDir), { force: true, recursive: true }); } catch {}
|
|
568
|
-
return "rejected";
|
|
569
|
-
}
|
|
570
|
-
if (fsImpl.existsSync(approveCheckpointRequestPath(asyncDir))) {
|
|
571
|
-
try { fsImpl.rmSync(approveCheckpointRequestPath(asyncDir), { force: true, recursive: true }); } catch {}
|
|
572
|
-
return "approved";
|
|
573
|
-
}
|
|
574
|
-
return undefined;
|
|
575
|
-
}
|
|
576
|
-
|
|
577
536
|
/** Parent side: write the authoritative portable interrupt request. */
|
|
578
537
|
export function deliverInterruptRequest(input: {
|
|
579
538
|
asyncDir: string;
|
|
@@ -605,21 +564,6 @@ export function deliverStopRequest(input: {
|
|
|
605
564
|
requestAsyncStop(input.asyncDir, input.source ? { source: input.source } : {}, { now: input.now });
|
|
606
565
|
}
|
|
607
566
|
|
|
608
|
-
export function deliverCheckpointDecisionRequest(input: {
|
|
609
|
-
asyncDir: string;
|
|
610
|
-
decision: "approved" | "rejected";
|
|
611
|
-
now?: () => number;
|
|
612
|
-
source?: string;
|
|
613
|
-
reason?: string;
|
|
614
|
-
}): void {
|
|
615
|
-
requestAsyncCheckpointDecision(
|
|
616
|
-
input.asyncDir,
|
|
617
|
-
input.decision === "approved" ? "approve-checkpoint" : "reject-checkpoint",
|
|
618
|
-
{ ...(input.source ? { source: input.source } : {}), ...(input.reason ? { reason: input.reason } : {}) },
|
|
619
|
-
{ now: input.now },
|
|
620
|
-
);
|
|
621
|
-
}
|
|
622
|
-
|
|
623
567
|
/**
|
|
624
568
|
* Runner side: watch the control inbox and route interrupt requests into
|
|
625
569
|
* `onInterrupt`. Uses `fs.watch` when available and starts interval polling
|
|
@@ -633,11 +577,11 @@ export function watchAsyncControlInbox(
|
|
|
633
577
|
onTimeout?: () => void;
|
|
634
578
|
onStop?: () => void;
|
|
635
579
|
onSteer?: (request: SteerRequest) => void;
|
|
636
|
-
onCheckpointDecision?: (decision: "approved" | "rejected") => void;
|
|
637
580
|
onSteerCapability?: (capability: SteerCapability) => void;
|
|
638
581
|
onSteerAck?: (ack: SteerAck) => void;
|
|
639
582
|
pollIntervalMs?: number;
|
|
640
583
|
safetyPollIntervalMs?: number;
|
|
584
|
+
platform?: NodeJS.Platform;
|
|
641
585
|
fs?: ControlChannelFs;
|
|
642
586
|
timers?: ControlChannelTimers;
|
|
643
587
|
},
|
|
@@ -658,8 +602,6 @@ export function watchAsyncControlInbox(
|
|
|
658
602
|
if (consumeStopRequest(asyncDir, fsImpl)) opts.onStop?.();
|
|
659
603
|
if (consumeTimeoutRequest(asyncDir, fsImpl)) opts.onTimeout?.();
|
|
660
604
|
if (consumeInterruptRequest(asyncDir, fsImpl)) opts.onInterrupt();
|
|
661
|
-
const checkpointDecision = consumeCheckpointDecisionRequest(asyncDir, fsImpl);
|
|
662
|
-
if (checkpointDecision) opts.onCheckpointDecision?.(checkpointDecision);
|
|
663
605
|
for (const request of consumeSteerRequests(asyncDir, fsImpl)) opts.onSteer?.(request);
|
|
664
606
|
for (const capability of consumeSteerCapabilities(asyncDir, fsImpl)) opts.onSteerCapability?.(capability);
|
|
665
607
|
for (const ack of consumeSteerAcks(asyncDir, fsImpl)) opts.onSteerAck?.(ack);
|
|
@@ -707,12 +649,16 @@ export function watchAsyncControlInbox(
|
|
|
707
649
|
for (const entry of entries) watchDir(path.join(ackRoot, entry));
|
|
708
650
|
};
|
|
709
651
|
try {
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
652
|
+
if (shouldUseNativeFsWatch("runner-control-inbox", opts.platform)) {
|
|
653
|
+
watchDir(dir);
|
|
654
|
+
watchDir(steerRequestsDir(asyncDir), true);
|
|
655
|
+
watchDir(steerCapabilitiesDir(asyncDir), true);
|
|
656
|
+
watchDir(path.join(dir, STEER_ACKS_DIR), true);
|
|
657
|
+
watchExistingSteerAckDirs();
|
|
658
|
+
startSafetyPolling();
|
|
659
|
+
} else {
|
|
660
|
+
startPolling();
|
|
661
|
+
}
|
|
716
662
|
} catch {
|
|
717
663
|
startPolling();
|
|
718
664
|
}
|
|
@@ -443,6 +443,8 @@ export function formatAsyncRunTranscript(status: AsyncStatus, asyncDir: string,
|
|
|
443
443
|
: uniqueStrings([runOutputPath]);
|
|
444
444
|
const sessionFile = selected.index !== undefined ? selected.step?.sessionFile : status.sessionFile;
|
|
445
445
|
const eventsPath = path.join(asyncDir, "events.jsonl");
|
|
446
|
+
// Synthesized output paths can name files that were never written. Keep the unfiltered paths
|
|
447
|
+
// below so a present-but-unreadable file still reports its read error.
|
|
446
448
|
|
|
447
449
|
const context = contextModeLabel(status.context ?? summarizeContextModes((status.steps ?? []).map((step) => step.context)));
|
|
448
450
|
const lines = [
|
|
@@ -452,7 +454,7 @@ export function formatAsyncRunTranscript(status: AsyncStatus, asyncDir: string,
|
|
|
452
454
|
stepStateLine(status.mode, selected.index, selected.step),
|
|
453
455
|
selected.hint,
|
|
454
456
|
].filter((line): line is string => Boolean(line));
|
|
455
|
-
appendKnownArtifacts(lines, { outputPaths, sessionFile, eventsPath: fs.existsSync(eventsPath) ? eventsPath : undefined, logPath: fs.existsSync(logPath) ? logPath : undefined });
|
|
457
|
+
appendKnownArtifacts(lines, { outputPaths: outputPaths.filter((outputPath) => fs.existsSync(outputPath)), sessionFile, eventsPath: fs.existsSync(eventsPath) ? eventsPath : undefined, logPath: fs.existsSync(logPath) ? logPath : undefined });
|
|
456
458
|
|
|
457
459
|
const warnings: string[] = [];
|
|
458
460
|
let transcriptLines: string[] = [];
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Keep opaque index keys below common filesystem component limits.
|
|
5
|
+
*
|
|
6
|
+
* Portable short values retain the historical URI-encoded representation.
|
|
7
|
+
* Oversized or non-portable values use a deterministic digest so callers can
|
|
8
|
+
* resolve the same index without persisting a separate lookup table.
|
|
9
|
+
*
|
|
10
|
+
* Encoded backslashes and trailing file extensions are not portable. Pi session
|
|
11
|
+
* ids are often the full session `.jsonl` path. On Windows, `readdir` of a
|
|
12
|
+
* directory whose name ends in `.jsonl` or contains `%5C` can fail with EPERM.
|
|
13
|
+
*/
|
|
14
|
+
export const MAX_INDEX_SEGMENT_BYTES = 255;
|
|
15
|
+
const HASHED_INDEX_SEGMENT_PREFIX = "~sha256-";
|
|
16
|
+
const WINDOWS_RESERVED_NAME = /^(?:con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\.|$)/i;
|
|
17
|
+
|
|
18
|
+
function hashedSegment(value: string): string {
|
|
19
|
+
return `${HASHED_INDEX_SEGMENT_PREFIX}${createHash("sha256").update(value).digest("hex")}`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function isPortableSegment(value: string): boolean {
|
|
23
|
+
return value.length > 0
|
|
24
|
+
&& value !== "."
|
|
25
|
+
&& value !== ".."
|
|
26
|
+
&& !value.endsWith(".")
|
|
27
|
+
&& !WINDOWS_RESERVED_NAME.test(value)
|
|
28
|
+
&& !/%5C/i.test(value)
|
|
29
|
+
&& !/\.[A-Za-z][A-Za-z0-9]{0,7}$/.test(value);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function encodeIndexSegment(value: string, maxBytes = MAX_INDEX_SEGMENT_BYTES): string {
|
|
33
|
+
let encoded: string;
|
|
34
|
+
try {
|
|
35
|
+
encoded = encodeURIComponent(value);
|
|
36
|
+
} catch {
|
|
37
|
+
return hashedSegment(value);
|
|
38
|
+
}
|
|
39
|
+
if (Buffer.byteLength(encoded, "utf-8") <= maxBytes && isPortableSegment(encoded)) return encoded;
|
|
40
|
+
return hashedSegment(value);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function historicallyReadableSegment(value: string, maxBytes: number): string | undefined {
|
|
44
|
+
try {
|
|
45
|
+
const encoded = encodeURIComponent(value);
|
|
46
|
+
if (encoded.length === 0 || encoded === "." || encoded === ".." || encoded.endsWith(".") || WINDOWS_RESERVED_NAME.test(encoded)) return undefined;
|
|
47
|
+
if (Buffer.byteLength(encoded, "utf-8") > maxBytes) return undefined;
|
|
48
|
+
return encoded;
|
|
49
|
+
} catch {
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Current write key first, then the pre-hash URI-encoded key when it still fits. */
|
|
55
|
+
export function indexSegmentAliases(value: string, maxBytes = MAX_INDEX_SEGMENT_BYTES): string[] {
|
|
56
|
+
const current = encodeIndexSegment(value, maxBytes);
|
|
57
|
+
const historical = historicallyReadableSegment(value, maxBytes);
|
|
58
|
+
return historical && historical !== current ? [current, historical] : [current];
|
|
59
|
+
}
|
|
@@ -63,6 +63,7 @@ export interface CompletionNotification {
|
|
|
63
63
|
taskIndex?: number;
|
|
64
64
|
totalTasks?: number;
|
|
65
65
|
sessionId?: string | null;
|
|
66
|
+
completionOwnerId?: string | null;
|
|
66
67
|
triggerTurn?: boolean;
|
|
67
68
|
/** True when an acknowledged grouped intercom relay already delivered this run. */
|
|
68
69
|
intercomDelivered?: boolean;
|
|
@@ -239,7 +240,7 @@ export function buildCompletionDetails(result: CompletionNotification): Subagent
|
|
|
239
240
|
|
|
240
241
|
export default function registerSubagentNotify(
|
|
241
242
|
pi: ExtensionAPI,
|
|
242
|
-
state: Pick<SubagentState, "currentSessionId">,
|
|
243
|
+
state: Pick<SubagentState, "currentSessionId" | "completionOwnerId">,
|
|
243
244
|
options: RegisterSubagentNotifyOptions = {},
|
|
244
245
|
): CompletionNotifier {
|
|
245
246
|
const seen = new Map<string, number>();
|
|
@@ -275,6 +276,7 @@ export default function registerSubagentNotify(
|
|
|
275
276
|
|
|
276
277
|
const deliver = (result: CompletionNotification): Promise<boolean> => {
|
|
277
278
|
if (disposed || typeof result.sessionId !== "string" || result.sessionId !== state.currentSessionId) return Promise.resolve(false);
|
|
279
|
+
if (result.source !== "foreground" && (!state.completionOwnerId || result.completionOwnerId !== state.completionOwnerId)) return Promise.resolve(false);
|
|
278
280
|
if (result.intercomDelivered === true) return Promise.resolve(true);
|
|
279
281
|
const key = buildCompletionKey(result, "notify");
|
|
280
282
|
const seenAt = seen.get(key);
|