pi-subagents 0.65.1 → 0.66.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 +55 -0
- package/README.md +1 -1
- package/agents/researcher.md +23 -13
- package/docs/agents.md +16 -2
- package/docs/configuration.md +18 -0
- package/docs/extension-api.md +91 -0
- package/docs/models.md +58 -1
- package/docs/observability.md +42 -2
- package/docs/tool-reference.md +3 -3
- package/docs/workflows.md +14 -7
- package/package.json +2 -1
- package/skills/pi-subagents/references/execution-controls.md +14 -1
- package/skills/pi-subagents/references/management-authoring-rpc.md +2 -1
- package/src/agents/advertised-agent-prompt.ts +63 -0
- package/src/agents/agent-management.ts +14 -1
- package/src/agents/agent-serializer.ts +2 -0
- package/src/agents/agents.ts +8 -0
- package/src/api/shared-types.ts +1 -1
- package/src/api/workflow-resources.ts +6 -0
- package/src/extension/index.ts +40 -2
- package/src/extension/public-execution.ts +0 -1
- package/src/extension/rpc.ts +4 -21
- package/src/extension/schemas.ts +8 -6
- package/src/extension/tool-description.ts +6 -5
- package/src/intercom/native-supervisor-channel.ts +82 -54
- package/src/runs/background/active-async-capacity.ts +18 -18
- package/src/runs/background/async-job-tracker.ts +35 -3
- package/src/runs/background/async-status-snapshot.ts +10 -12
- package/src/runs/background/async-status.ts +17 -9
- package/src/runs/background/auto-drain.ts +40 -29
- package/src/runs/background/chain-root-attachment.ts +8 -0
- package/src/runs/background/control-channel.ts +78 -44
- package/src/runs/background/notify.ts +86 -12
- package/src/runs/background/owned-process-tree.ts +6 -6
- package/src/runs/background/process-terminal.ts +23 -23
- package/src/runs/background/run-child-session.ts +60 -32
- package/src/runs/background/run-status.ts +75 -5
- package/src/runs/background/runner-aliases.ts +18 -7
- package/src/runs/background/runner-child-launch.ts +86 -0
- package/src/runs/background/stale-run-reconciler.ts +3 -1
- package/src/runs/background/subagent-runner.ts +412 -209
- package/src/runs/background/subagent-wait.ts +3 -0
- package/src/runs/background/wait-completions.ts +4 -0
- package/src/runs/foreground/async-steering-action.ts +19 -0
- package/src/runs/foreground/execution.ts +101 -25
- package/src/runs/foreground/subagent-executor.ts +474 -197
- package/src/runs/foreground/workflow-detach-reconcile.ts +8 -5
- package/src/runs/foreground/workflow-foreground-steering.ts +56 -2
- package/src/runs/shared/acceptance.ts +2 -2
- package/src/runs/shared/agent-contract.ts +1 -1
- package/src/runs/shared/async-status-projection.ts +47 -47
- package/src/runs/shared/child-hooks.ts +151 -2
- package/src/runs/shared/child-launch.ts +18 -13
- package/src/runs/shared/child-session.ts +42 -6
- package/src/runs/shared/child-tool-plan.ts +2 -2
- package/src/runs/shared/completion-evidence.ts +2 -2
- package/src/runs/shared/completion-guard.ts +1 -0
- package/src/runs/shared/host-step-status.ts +11 -11
- package/src/runs/shared/llm-intent-arbiter.ts +10 -9
- package/src/runs/shared/model-fallback.ts +10 -6
- package/src/runs/shared/nested-events.ts +5 -5
- package/src/runs/shared/orca-progress-tabs.ts +6 -0
- package/src/runs/shared/parallel-handoff.ts +57 -12
- package/src/runs/shared/parallel-utils.ts +2 -2
- package/src/runs/shared/readonly-drain-observation.ts +42 -0
- package/src/runs/shared/readonly-model-continuation.ts +69 -0
- package/src/runs/shared/readonly-session-evidence.ts +307 -0
- package/src/runs/shared/run-fanout-budget.ts +8 -8
- package/src/runs/shared/runtime-acknowledged-extensions.ts +3 -3
- package/src/runs/shared/subagent-prompt-runtime.ts +13 -3
- package/src/runs/shared/worktree-setup-command.ts +190 -0
- package/src/runs/shared/worktree.ts +332 -204
- package/src/shared/types.ts +81 -60
- package/src/shared/utils.ts +7 -2
- package/src/shared/workflow-child-permit.ts +18 -13
- package/src/tui/fleet.ts +11 -5
- package/src/tui/render.ts +23 -5
- package/src/workflows/chat-progress.ts +3 -3
- package/src/workflows/scripted-workflow.ts +38 -10
- package/src/workflows/workflow-checklist.ts +11 -15
- package/src/workflows/workflow-child-summary.ts +57 -8
- package/src/workflows/workflow-preflight.ts +19 -19
- package/src/workflows/workflow-receipt.ts +3 -3
- package/src/workflows/workflow-resources.ts +96 -21
- package/src/workflows/workflow-settlement.ts +3 -0
|
@@ -2,12 +2,14 @@ import * as fs from "node:fs";
|
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import type { AgentToolResult } from "@earendil-works/pi-agent-core";
|
|
4
4
|
import { safeTerminalText } from "../../shared/display-text.ts";
|
|
5
|
+
import { getArtifactPaths, getArtifactsDir } from "../../shared/artifacts.ts";
|
|
6
|
+
import { readFleetTranscript } from "../../tui/fleet-transcript.ts";
|
|
5
7
|
import { formatAsyncRunList, formatAsyncRunOutputPath, formatAsyncRunProgressLabel, formatWorkflowStageLine, listAsyncRuns } from "./async-status.ts";
|
|
6
8
|
import { formatAsyncResultTranscript, formatAsyncRunTranscript, formatNestedRunTranscript, inspectSubagentFleet } from "./fleet-view.ts";
|
|
7
9
|
import { formatNestedRunStatusLines } from "../shared/nested-render.ts";
|
|
8
10
|
import { formatModelThinking } from "../../shared/formatters.ts";
|
|
9
11
|
import { formatActivityLabel } from "../../shared/status-format.ts";
|
|
10
|
-
import { DIRS, type AsyncStatus, type Details, type ForegroundResumeRun, type NestedRunSummary, type SteeringStatus, type SubagentState } from "../../shared/types.ts";
|
|
12
|
+
import { DIRS, type AsyncStatus, type Details, type ForegroundRunControl, type ForegroundResumeRun, type NestedRunSummary, type SteeringStatus, type SubagentState } from "../../shared/types.ts";
|
|
11
13
|
import { inspectActiveAsyncCapacityOwner, type ActiveAsyncCapacityInspection } from "./active-async-capacity.ts";
|
|
12
14
|
import { readStatus } from "../../shared/utils.ts";
|
|
13
15
|
import { resolveSubagentIntercomTarget } from "../../intercom/intercom-bridge.ts";
|
|
@@ -88,6 +90,7 @@ function formatRunLifecycleDebug(input: { status: AsyncStatus; asyncDir: string;
|
|
|
88
90
|
`Run: ${status.runId}`,
|
|
89
91
|
`Dir: ${asyncDir}`,
|
|
90
92
|
`Status file: ${path.join(asyncDir, "status.json")}`,
|
|
93
|
+
status.workflowReceiptPath ? `Workflow receipt: ${status.workflowReceiptPath}` : undefined,
|
|
91
94
|
`Process terminal file: ${path.join(asyncDir, "process-terminal.json")}`,
|
|
92
95
|
`Session: ${status.sessionId ?? "unknown"}`,
|
|
93
96
|
`State: ${status.state}`,
|
|
@@ -241,6 +244,54 @@ function formatRememberedForegroundStatus(run: ForegroundResumeRun): string {
|
|
|
241
244
|
return lines.join("\n");
|
|
242
245
|
}
|
|
243
246
|
|
|
247
|
+
/** Request-only snapshot of the same artifact used by Fleet; no session or output fallback. */
|
|
248
|
+
function formatLiveForegroundTranscript(control: ForegroundRunControl, state: SubagentState, options: { index?: number; lines?: number }): string {
|
|
249
|
+
if (!state.currentSessionId || control.sessionId !== state.currentSessionId) {
|
|
250
|
+
throw new Error(`Foreground run '${control.runId}' is not owned by the current session.`);
|
|
251
|
+
}
|
|
252
|
+
if (options.index !== undefined && (!Number.isInteger(options.index) || options.index < 0)) {
|
|
253
|
+
throw new Error("Transcript index must be a non-negative integer.");
|
|
254
|
+
}
|
|
255
|
+
const children = control.activeChildren
|
|
256
|
+
? [...control.activeChildren.values()]
|
|
257
|
+
: control.currentAgent ? [{ index: control.currentIndex ?? 0, agent: control.currentAgent, sessionName: control.sessionName }] : [];
|
|
258
|
+
const header = [`Run: ${control.runId}`, "State: live foreground"];
|
|
259
|
+
if (children.length === 0) return `${header.map((line) => safeTerminalText(line)).join("\n")}\nTranscript unavailable: no active foreground child.`;
|
|
260
|
+
if (options.index === undefined && children.length > 1) {
|
|
261
|
+
throw new Error(`Transcript view requires index for foreground run '${control.runId}'. Active child indexes: ${children.map((child) => child.index).join(", ")}.`);
|
|
262
|
+
}
|
|
263
|
+
const child = options.index === undefined ? children[0]! : children.find((child) => child.index === options.index);
|
|
264
|
+
if (!child) throw new Error(`Transcript index ${options.index} is not an active foreground child of '${control.runId}'.`);
|
|
265
|
+
const root = getArtifactsDir(state.parentSessionFile ?? null, control.cwd ?? state.baseCwd, state.artifactDirPreference);
|
|
266
|
+
const transcriptPath = getArtifactPaths(root, control.runId, child.agent, child.index).transcriptPath;
|
|
267
|
+
const transcript = readFleetTranscript(transcriptPath, { trustedRoots: [root] });
|
|
268
|
+
const lineLimit = Number.isFinite(options.lines) ? Math.max(1, Math.min(500, Math.trunc(options.lines!))) : 80;
|
|
269
|
+
const allLines = transcript.events.flatMap((event) => {
|
|
270
|
+
const text = event.kind === "tool"
|
|
271
|
+
? [`Tool: ${event.name} (${event.status})`, event.argsPayload ?? event.args, event.output ?? event.error].filter(Boolean).join("\n")
|
|
272
|
+
: event.kind === "notice" ? event.text : `${event.kind === "assistant" ? "Assistant" : "Supervisor"}: ${event.text}`;
|
|
273
|
+
return text.split(/\r?\n/);
|
|
274
|
+
});
|
|
275
|
+
let body = allLines.slice(-lineLimit).join("\n");
|
|
276
|
+
let truncated = transcript.truncated || allLines.length > lineLimit
|
|
277
|
+
|| transcript.events.some((event) => event.kind === "tool" && event.outputTruncated)
|
|
278
|
+
|| body.includes("… message truncated");
|
|
279
|
+
const maxOutputBytes = 32 * 1024;
|
|
280
|
+
const bytes = Buffer.from(body);
|
|
281
|
+
if (bytes.length > maxOutputBytes) {
|
|
282
|
+
// Skip UTF-8 continuation bytes at the beginning of the bounded tail.
|
|
283
|
+
let start = bytes.length - maxOutputBytes;
|
|
284
|
+
while ((bytes[start]! & 0xc0) === 0x80) start++;
|
|
285
|
+
body = bytes.subarray(start).toString("utf-8");
|
|
286
|
+
truncated = true;
|
|
287
|
+
}
|
|
288
|
+
header.push(`Child: ${child.index} (${child.sessionName?.trim() || child.agent})`, `Transcript: ${transcriptPath}`);
|
|
289
|
+
if (transcript.warning) header.push(`Transcript warning: ${transcript.warning}`);
|
|
290
|
+
header.push(`Live transcript tail${truncated ? " (tail truncated)" : ""}:`);
|
|
291
|
+
if (!body) header.push("Transcript unavailable: no readable activity in the bounded artifact tail yet.");
|
|
292
|
+
return [...header.map((line) => safeTerminalText(line)), body].filter(Boolean).join("\n");
|
|
293
|
+
}
|
|
294
|
+
|
|
244
295
|
function formatRememberedForegroundTranscript(run: ForegroundResumeRun, options: { index?: number; lines?: number }): string {
|
|
245
296
|
let index = options.index;
|
|
246
297
|
if (index !== undefined && !Number.isInteger(index)) throw new Error("Transcript index must be an integer.");
|
|
@@ -313,6 +364,12 @@ export function inspectSubagentStatus(params: RunStatusParams, deps: RunStatusDe
|
|
|
313
364
|
return inspectSubagentFleet(params, { asyncDirRoot, resultsDir, kill: deps.kill, now: deps.now, state: deps.state, childSafe: Boolean(deps.nested) });
|
|
314
365
|
}
|
|
315
366
|
if (!params.id && !params.runId && !params.dir) {
|
|
367
|
+
if (params.view === "transcript" && deps.state?.currentSessionId) {
|
|
368
|
+
const controls = [...deps.state.foregroundControls.values()].filter((control) => control.sessionId === currentSessionId);
|
|
369
|
+
const foreground = controls.find((control) => control.runId === deps.state?.lastForegroundControlId)
|
|
370
|
+
?? controls.sort((left, right) => right.updatedAt - left.updatedAt)[0];
|
|
371
|
+
if (foreground) return inspectSubagentStatus({ ...params, id: foreground.runId }, deps);
|
|
372
|
+
}
|
|
316
373
|
if (deps.nested) {
|
|
317
374
|
return {
|
|
318
375
|
content: [{ type: "text", text: "Child-safe subagent status requires an id when no foreground run is active." }],
|
|
@@ -353,6 +410,13 @@ export function inspectSubagentStatus(params: RunStatusParams, deps: RunStatusDe
|
|
|
353
410
|
} else if (!params.dir && requestedId) {
|
|
354
411
|
const resolved = resolveSubagentRunId(requestedId, { asyncDirRoot, resultsDir, state: deps.state, nested: deps.nested });
|
|
355
412
|
if (resolved?.kind === "foreground") {
|
|
413
|
+
const control = deps.state?.foregroundControls.get(resolved.id);
|
|
414
|
+
if (control && deps.state && params.view === "transcript") {
|
|
415
|
+
return {
|
|
416
|
+
content: [{ type: "text", text: formatLiveForegroundTranscript(control, deps.state, params) }],
|
|
417
|
+
details: { mode: "management", results: [] },
|
|
418
|
+
};
|
|
419
|
+
}
|
|
356
420
|
const run = deps.state?.foregroundRuns?.get(resolved.id);
|
|
357
421
|
if (run) {
|
|
358
422
|
try {
|
|
@@ -423,7 +487,7 @@ export function inspectSubagentStatus(params: RunStatusParams, deps: RunStatusDe
|
|
|
423
487
|
const capacity = inspectActiveAsyncCapacityOwner({ runId: diskStatus.runId, sessionId: diskStatus.sessionId, asyncDir }, { rootDir: deps.activeCapacityRoot, liveWorkflowRunIds: new Set(deps.state?.workflowControllers?.keys() ?? []), abandonedSlotReleaseAfterMs: deps.abandonedSlotReleaseAfterMs });
|
|
424
488
|
return {
|
|
425
489
|
content: [{ type: "text", text: formatRunLifecycleDebug({ status: diskStatus, asyncDir, sidecarProcessTerminal: sidecar, overlayProcessTerminal: overlay, capacity }) }],
|
|
426
|
-
details: { mode: "single", results: [], ...((sidecar ?? overlay) ? { lifecycleStatus: { processTerminal: sidecar ?? overlay } } : {}) },
|
|
490
|
+
details: { mode: "single", results: [], ...(diskStatus.workflowReceiptPath ? { workflowReceiptPath: diskStatus.workflowReceiptPath } : {}), ...((sidecar ?? overlay) ? { lifecycleStatus: { processTerminal: sidecar ?? overlay } } : {}) },
|
|
427
491
|
};
|
|
428
492
|
}
|
|
429
493
|
if (params.view === "transcript") {
|
|
@@ -450,7 +514,7 @@ export function inspectSubagentStatus(params: RunStatusParams, deps: RunStatusDe
|
|
|
450
514
|
const capacity = inspectActiveAsyncCapacityOwner({ runId: status.runId, sessionId: status.sessionId, asyncDir }, { rootDir: deps.activeCapacityRoot, liveWorkflowRunIds: new Set(deps.state?.workflowControllers?.keys() ?? []), abandonedSlotReleaseAfterMs: deps.abandonedSlotReleaseAfterMs });
|
|
451
515
|
return {
|
|
452
516
|
content: [{ type: "text", text: formatRunLifecycleDebug({ status, asyncDir, sidecarProcessTerminal: sidecar, overlayProcessTerminal: overlay, capacity }) }],
|
|
453
|
-
details: { mode: "single", results: [], ...((sidecar ?? overlay) ? { lifecycleStatus: { processTerminal: sidecar ?? overlay } } : {}) },
|
|
517
|
+
details: { mode: "single", results: [], ...(status.workflowReceiptPath ? { workflowReceiptPath: status.workflowReceiptPath } : {}), ...((sidecar ?? overlay) ? { lifecycleStatus: { processTerminal: sidecar ?? overlay } } : {}) },
|
|
454
518
|
};
|
|
455
519
|
}
|
|
456
520
|
if (params.view === "transcript") {
|
|
@@ -626,6 +690,7 @@ export function inspectSubagentStatus(params: RunStatusParams, deps: RunStatusDe
|
|
|
626
690
|
}
|
|
627
691
|
}
|
|
628
692
|
if (nestedWarning) lines.push(`Warning: ${nestedWarning}`);
|
|
693
|
+
if (status.workflowReceiptPath) lines.push(`Workflow receipt: ${status.workflowReceiptPath}`);
|
|
629
694
|
if (status.sessionFile) lines.push(`Session: ${status.sessionFile}`);
|
|
630
695
|
const allExternal = (status.steps?.length ?? 0) > 0 && status.steps!.every((step) => step.runner?.type === "external-cli" || step.runner?.type === "external-job");
|
|
631
696
|
if (status.state === "running" && !allExternal && status.mode !== "workflow") lines.push(`Steer running child: subagent({ action: "steer", id: "${status.runId}", message: "..." })`);
|
|
@@ -639,7 +704,7 @@ export function inspectSubagentStatus(params: RunStatusParams, deps: RunStatusDe
|
|
|
639
704
|
|
|
640
705
|
const workflowChildren = parseWorkflowChildSummary(status.workflowChildren);
|
|
641
706
|
if (workflowChildren && workflowChildren.workflowRunId !== status.runId) throw new Error("workflowChildren.workflowRunId does not match async status runId.");
|
|
642
|
-
return { content: [{ type: "text", text: lines.join("\n") }], details: { mode: "single", results: [], ...(status.preflight ? { preflight: status.preflight } : {}), ...(status.workflow?.preflightWarnings?.length ? { preflightWarnings: status.workflow.preflightWarnings } : {}), ...(workflowChildren ? { workflowChildren } : {}), ...(runFanoutBudget ? { runFanoutBudget } : {}), ...(processTerminal ? { lifecycleStatus: { processTerminal } } : {}) } };
|
|
707
|
+
return { content: [{ type: "text", text: lines.join("\n") }], details: { mode: "single", results: [], ...(status.workflowReceiptPath ? { workflowReceiptPath: status.workflowReceiptPath } : {}), ...(status.preflight ? { preflight: status.preflight } : {}), ...(status.workflow?.preflightWarnings?.length ? { preflightWarnings: status.workflow.preflightWarnings } : {}), ...(workflowChildren ? { workflowChildren } : {}), ...(runFanoutBudget ? { runFanoutBudget } : {}), ...(processTerminal ? { lifecycleStatus: { processTerminal } } : {}) } };
|
|
643
708
|
}
|
|
644
709
|
}
|
|
645
710
|
|
|
@@ -680,6 +745,11 @@ export function inspectSubagentStatus(params: RunStatusParams, deps: RunStatusDe
|
|
|
680
745
|
const runId = data.runId ?? data.id ?? resolvedId;
|
|
681
746
|
const lines = [`Run: ${runId}`, data.toolCallId ? `Tool call: ${data.toolCallId}` : undefined, `State: ${status}`, `Result: ${resultPath}`].filter((line): line is string => Boolean(line));
|
|
682
747
|
if (data.parallelHandoff?.path) lines.push(`Parallel handoff: ${data.parallelHandoff.path}`);
|
|
748
|
+
const receipt = (data as Record<string, unknown>).workflowReceipt;
|
|
749
|
+
const receiptPath = receipt && typeof receipt === "object" && !Array.isArray(receipt)
|
|
750
|
+
? (receipt as Record<string, unknown>).path : undefined;
|
|
751
|
+
const workflowReceiptPath = typeof receiptPath === "string" && receiptPath ? receiptPath : undefined;
|
|
752
|
+
if (workflowReceiptPath) lines.push(`Workflow receipt: ${workflowReceiptPath}`);
|
|
683
753
|
const children = Array.isArray(data.results) ? data.results : data.agent ? [{ agent: data.agent, sessionFile: data.sessionFile }] : [];
|
|
684
754
|
lines.push(...formatTimeoutRecoveryLines(data.timeoutRecovery, " "));
|
|
685
755
|
for (const [index, child] of children.entries()) {
|
|
@@ -694,7 +764,7 @@ export function inspectSubagentStatus(params: RunStatusParams, deps: RunStatusDe
|
|
|
694
764
|
if (data.summary) lines.push("", data.summary);
|
|
695
765
|
const workflowChildren = parseWorkflowChildSummary((data as unknown as Record<string, unknown>).workflowChildren);
|
|
696
766
|
if (workflowChildren && workflowChildren.workflowRunId !== runId) throw new Error("workflowChildren.workflowRunId does not match the result run id.");
|
|
697
|
-
return { content: [{ type: "text", text: lines.join("\n") }], details: { mode: "single", results: [], ...(workflowChildren ? { workflowChildren } : {}) } };
|
|
767
|
+
return { content: [{ type: "text", text: lines.join("\n") }], details: { mode: "single", results: [], ...(workflowReceiptPath ? { workflowReceiptPath } : {}), ...(workflowChildren ? { workflowChildren } : {}) } };
|
|
698
768
|
} catch (error) {
|
|
699
769
|
const message = error instanceof Error ? error.message : String(error);
|
|
700
770
|
return {
|
|
@@ -23,9 +23,6 @@ export const HOST_PEER_ALIASES: ReadonlyArray<{ specifier: string; pkg: string;
|
|
|
23
23
|
{ specifier: "@earendil-works/pi-agent-core/node", pkg: "@earendil-works/pi-agent-core", subpath: "./node" },
|
|
24
24
|
{ specifier: "@earendil-works/chord", pkg: "@earendil-works/chord", subpath: "." },
|
|
25
25
|
{ specifier: "@earendil-works/chord/context", pkg: "@earendil-works/chord", subpath: "./context" },
|
|
26
|
-
{ specifier: "@earendil-works/pi-server", pkg: "@earendil-works/pi-server", subpath: "." },
|
|
27
|
-
{ specifier: "@earendil-works/pi-server/unix", pkg: "@earendil-works/pi-server", subpath: "./unix" },
|
|
28
|
-
{ specifier: "@earendil-works/pi-client/unix", pkg: "@earendil-works/pi-client", subpath: "./unix" },
|
|
29
26
|
{ specifier: "@earendil-works/pi-tui", pkg: "@earendil-works/pi-tui", subpath: "." },
|
|
30
27
|
{ specifier: "@earendil-works/pi-ai", pkg: "@earendil-works/pi-ai", subpath: "./compat" },
|
|
31
28
|
{ specifier: "@earendil-works/pi-ai/compat", pkg: "@earendil-works/pi-ai", subpath: "./compat" },
|
|
@@ -36,6 +33,13 @@ export const HOST_PEER_ALIASES: ReadonlyArray<{ specifier: string; pkg: string;
|
|
|
36
33
|
{ specifier: "typebox/value", pkg: "typebox", subpath: "./value" },
|
|
37
34
|
];
|
|
38
35
|
|
|
36
|
+
/** Experimental dependencies accidentally published in exactly Pi 0.85.0. */
|
|
37
|
+
const PI0850_PEER_ALIASES = [
|
|
38
|
+
{ specifier: "@earendil-works/pi-server", pkg: "@earendil-works/pi-server", subpath: "." },
|
|
39
|
+
{ specifier: "@earendil-works/pi-server/unix", pkg: "@earendil-works/pi-server", subpath: "./unix" },
|
|
40
|
+
{ specifier: "@earendil-works/pi-client/unix", pkg: "@earendil-works/pi-client", subpath: "./unix" },
|
|
41
|
+
];
|
|
42
|
+
|
|
39
43
|
interface PackageManifest {
|
|
40
44
|
name?: unknown;
|
|
41
45
|
version?: unknown;
|
|
@@ -103,7 +107,11 @@ export function resolvePackageSubpath(packageDir: string, subpath: string): stri
|
|
|
103
107
|
|
|
104
108
|
/** Find `pkg` as the pi package itself, one of its dependencies, or a sibling in a hoisted install. */
|
|
105
109
|
export function findHostPeerPackageDir(piPackageRoot: string, pkg: string): string | undefined {
|
|
106
|
-
|
|
110
|
+
return findPeerPackageDir(piPackageRoot, pkg, readManifest(piPackageRoot)?.name);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function findPeerPackageDir(piPackageRoot: string, pkg: string, hostName: unknown): string | undefined {
|
|
114
|
+
if (hostName === pkg) return piPackageRoot;
|
|
107
115
|
const candidates = [path.join(piPackageRoot, "node_modules", pkg)];
|
|
108
116
|
let dir = piPackageRoot;
|
|
109
117
|
for (;;) {
|
|
@@ -127,14 +135,17 @@ export function resolveHostPeerAliases(
|
|
|
127
135
|
const aliases: Record<string, string> = {};
|
|
128
136
|
const missing: string[] = [];
|
|
129
137
|
const supplemental: string[] = [];
|
|
130
|
-
|
|
131
|
-
|
|
138
|
+
const hostManifest = readManifest(piPackageRoot);
|
|
139
|
+
const isPi0850 = hostManifest?.version === "0.85.0";
|
|
140
|
+
const required = isPi0850 ? [...HOST_PEER_ALIASES, ...PI0850_PEER_ALIASES] : HOST_PEER_ALIASES;
|
|
141
|
+
for (const { specifier, pkg, subpath } of required) {
|
|
142
|
+
const packageDir = findPeerPackageDir(piPackageRoot, pkg, hostManifest?.name);
|
|
132
143
|
let target = packageDir ? resolvePackageSubpath(packageDir, subpath) : undefined;
|
|
133
144
|
// Pi 0.85.0 omitted this runtime dependency. Never replace working host
|
|
134
145
|
// exports or extend this exact version contract to other peers/hosts.
|
|
135
146
|
if ((!target || !fs.existsSync(target))
|
|
136
147
|
&& (specifier === "@earendil-works/pi-server" || specifier === "@earendil-works/pi-server/unix")
|
|
137
|
-
&&
|
|
148
|
+
&& isPi0850
|
|
138
149
|
&& (!packageDir || readManifest(packageDir)?.version === "0.85.0")) {
|
|
139
150
|
const localDir = findHostPeerPackageDir(extensionRoot, pkg);
|
|
140
151
|
if (localDir && readManifest(localDir)?.version === "0.85.0") {
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/** Native runner construction, separate from its executable entrypoint and attempt loop. */
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { buildInProcessChildLaunch, type BuildInProcessChildLaunchInput, type InheritedChildRuntime } from "../shared/child-launch.ts";
|
|
4
|
+
import { deriveForkPromptCacheKey } from "../shared/child-tool-plan.ts";
|
|
5
|
+
import { normalizeExtensionBindings } from "../shared/extension-bindings.ts";
|
|
6
|
+
import type { RunnerSubagentStep } from "../shared/parallel-utils.ts";
|
|
7
|
+
import { formatAcceptancePrompt } from "../shared/acceptance.ts";
|
|
8
|
+
import { isAgentContract } from "../shared/agent-contract.ts";
|
|
9
|
+
|
|
10
|
+
export interface RunnerChildLaunchContext {
|
|
11
|
+
cwd: string;
|
|
12
|
+
id: string;
|
|
13
|
+
flatIndex: number;
|
|
14
|
+
artifactsDir?: string;
|
|
15
|
+
childIntercomTarget?: string;
|
|
16
|
+
orchestratorIntercomTarget?: string;
|
|
17
|
+
nestedRoute?: BuildInProcessChildLaunchInput["nestedRoute"];
|
|
18
|
+
runFanoutBudget?: BuildInProcessChildLaunchInput["runFanoutBudget"];
|
|
19
|
+
capabilityCeiling?: BuildInProcessChildLaunchInput["capabilityCeiling"];
|
|
20
|
+
inheritedChildRuntime?: InheritedChildRuntime;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function buildRunnerChildLaunch(step: RunnerSubagentStep, ctx: RunnerChildLaunchContext, attempt: {
|
|
24
|
+
model?: string;
|
|
25
|
+
sessionEnabled: boolean;
|
|
26
|
+
sessionDir?: string;
|
|
27
|
+
sessionName?: string;
|
|
28
|
+
structuredOutput?: BuildInProcessChildLaunchInput["structuredOutput"];
|
|
29
|
+
childWatchdog?: BuildInProcessChildLaunchInput["childWatchdog"];
|
|
30
|
+
watchdogStatus: NonNullable<BuildInProcessChildLaunchInput["watchdogStatus"]>;
|
|
31
|
+
}) {
|
|
32
|
+
// Resource-backed system instructions survive SDK split-turn compaction verbatim.
|
|
33
|
+
const acceptancePrompt = step.effectiveAcceptance
|
|
34
|
+
? formatAcceptancePrompt(step.effectiveAcceptance, { reportOptional: isAgentContract(step.agentContract), structuredOutput: Boolean(step.structuredOutput?.acceptanceReportPath) })
|
|
35
|
+
: "";
|
|
36
|
+
return buildInProcessChildLaunch({
|
|
37
|
+
parentSessionId: step.parentSessionId,
|
|
38
|
+
forkCacheKey: step.context === "fork" ? deriveForkPromptCacheKey(step.parentSessionId) : undefined,
|
|
39
|
+
sessionEnabled: attempt.sessionEnabled,
|
|
40
|
+
sessionDir: attempt.sessionDir,
|
|
41
|
+
sessionFile: step.sessionFile,
|
|
42
|
+
model: attempt.model,
|
|
43
|
+
inheritProjectContext: step.inheritProjectContext,
|
|
44
|
+
inheritGlobalContext: step.inheritGlobalContext,
|
|
45
|
+
inheritSkills: step.inheritSkills,
|
|
46
|
+
requireReadTool: Boolean(step.skills?.length),
|
|
47
|
+
tools: step.tools,
|
|
48
|
+
excludeTools: step.excludeTools,
|
|
49
|
+
allowNestedSubagents: step.allowNestedSubagents,
|
|
50
|
+
extensions: step.extensions,
|
|
51
|
+
subagentOnlyExtensions: step.subagentOnlyExtensions,
|
|
52
|
+
fast: step.fast,
|
|
53
|
+
modelCandidates: step.modelCandidates,
|
|
54
|
+
systemPrompt: acceptancePrompt ? `${step.systemPrompt ?? ""}\n${acceptancePrompt}` : step.systemPrompt ?? "",
|
|
55
|
+
systemPromptMode: step.systemPromptMode,
|
|
56
|
+
mcpDirectTools: step.mcpDirectTools,
|
|
57
|
+
extensionBindings: normalizeExtensionBindings(step.extensionBindings)?.value,
|
|
58
|
+
capabilityCeiling: step.capabilityCeiling ?? ctx.capabilityCeiling,
|
|
59
|
+
cwd: step.cwd ?? ctx.cwd,
|
|
60
|
+
intercomSessionName: ctx.childIntercomTarget,
|
|
61
|
+
sessionName: attempt.sessionName,
|
|
62
|
+
orchestratorIntercomTarget: ctx.orchestratorIntercomTarget,
|
|
63
|
+
runId: ctx.id,
|
|
64
|
+
childAgentName: step.agent,
|
|
65
|
+
childIndex: ctx.flatIndex,
|
|
66
|
+
nestedRoute: ctx.nestedRoute,
|
|
67
|
+
runFanoutBudget: ctx.runFanoutBudget ? {
|
|
68
|
+
...ctx.runFanoutBudget,
|
|
69
|
+
...(step.runFanoutPath ? { parentPath: `${ctx.runFanoutBudget.parentPath ? `${ctx.runFanoutBudget.parentPath}/` : ""}${step.runFanoutPath}` } : {}),
|
|
70
|
+
} : undefined,
|
|
71
|
+
structuredOutput: attempt.structuredOutput,
|
|
72
|
+
toolBudget: step.toolBudget,
|
|
73
|
+
permissionRules: step.permissionRules,
|
|
74
|
+
permissionAuditPath: step.permissionRules && ctx.artifactsDir
|
|
75
|
+
? path.join(ctx.artifactsDir, "permission-audit", `${ctx.id}-${ctx.flatIndex}.jsonl`) : undefined,
|
|
76
|
+
childWatchdog: attempt.childWatchdog,
|
|
77
|
+
// registerChildWatchdog returns before reading the sink when unconfigured.
|
|
78
|
+
...(attempt.childWatchdog ? { watchdogStatus: attempt.watchdogStatus } : {}),
|
|
79
|
+
waitToolEnabled: step.waitToolEnabled,
|
|
80
|
+
waitToolDefaultTimeoutMs: step.waitToolDefaultTimeoutMs,
|
|
81
|
+
thinkingCeiling: step.thinkingCeiling,
|
|
82
|
+
maxSubagentDepth: step.maxSubagentDepth,
|
|
83
|
+
inherited: ctx.inheritedChildRuntime,
|
|
84
|
+
host: "runner",
|
|
85
|
+
});
|
|
86
|
+
}
|
|
@@ -9,6 +9,7 @@ import { resolveEffectiveThinking } from "../../shared/model-info.ts";
|
|
|
9
9
|
import { normalizeParallelGroups } from "./parallel-groups.ts";
|
|
10
10
|
import { nestedSummaryFromAsyncStatus, projectNestedEvents, resolveNestedAsyncDir, writeNestedEvent, type NestedRoute } from "../shared/nested-events.ts";
|
|
11
11
|
import { assertWorkflowGraphHostSteps } from "../shared/host-step-status.ts";
|
|
12
|
+
import type { RawDrainStatusObserver } from "../shared/readonly-drain-observation.ts";
|
|
12
13
|
|
|
13
14
|
export type PidLiveness = "alive" | "dead" | "unknown";
|
|
14
15
|
|
|
@@ -352,9 +353,10 @@ export function checkPidLiveness(pid: number, kill: KillFn = process.kill): PidL
|
|
|
352
353
|
}
|
|
353
354
|
}
|
|
354
355
|
|
|
355
|
-
export function reconcileAsyncRun(asyncDir: string, options: ReconcileAsyncRunOptions = {}): ReconcileAsyncRunResult {
|
|
356
|
+
export function reconcileAsyncRun(asyncDir: string, options: ReconcileAsyncRunOptions = {}, observeStatus?: RawDrainStatusObserver): ReconcileAsyncRunResult {
|
|
356
357
|
const now = options.now?.() ?? Date.now();
|
|
357
358
|
const status = readStatus(asyncDir);
|
|
359
|
+
observeStatus?.(status);
|
|
358
360
|
const startedStatus = !status && options.startedRun ? buildStartedStatus(asyncDir, options.startedRun, now) : undefined;
|
|
359
361
|
const effectiveStatus = status ?? startedStatus;
|
|
360
362
|
if (!effectiveStatus) return { status: null, repaired: false };
|