pi-subagents 0.65.1 → 0.67.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 +123 -0
- package/README.md +5 -4
- package/agents/evidence-auditor.md +34 -0
- package/agents/researcher.md +23 -13
- package/agents/reviewer.md +3 -2
- package/docs/agents.md +20 -3
- package/docs/configuration.md +25 -5
- package/docs/extension-api.md +124 -18
- package/docs/missions.md +8 -0
- package/docs/models.md +59 -2
- package/docs/observability.md +46 -6
- package/docs/standalone-background.md +49 -0
- package/docs/tool-reference.md +20 -10
- package/docs/watchdog.md +35 -4
- package/docs/workflows.md +40 -19
- package/inspector-runner.mjs +2 -2
- package/package.json +2 -1
- package/prompts/parallel-review.md +1 -1
- package/{runner-server-preload.mjs → runner-peer-preload.mjs} +8 -3
- package/skills/pi-subagents/SKILL.md +14 -0
- package/skills/pi-subagents/references/execution-controls.md +20 -5
- package/skills/pi-subagents/references/management-authoring-rpc.md +2 -1
- package/skills/pi-subagents/references/prompting-and-roles.md +2 -2
- package/src/agents/advertised-agent-prompt.ts +94 -0
- package/src/agents/agent-management.ts +14 -1
- package/src/agents/agent-serializer.ts +2 -0
- package/src/agents/agents.ts +14 -0
- package/src/agents/builtin-names.ts +1 -0
- package/src/api/delegation.ts +4 -0
- package/src/api/preflight.ts +76 -45
- package/src/api/shared-types.ts +3 -1
- package/src/api/workflow-resources.ts +6 -0
- package/src/extension/fanout-child.ts +63 -4
- package/src/extension/index.ts +58 -8
- package/src/extension/public-execution.ts +4 -3
- package/src/extension/rpc.ts +8 -21
- package/src/extension/schemas.ts +71 -80
- package/src/extension/tool-description.ts +29 -81
- package/src/inspectors/actions.ts +148 -0
- package/src/inspectors/ghostty/actions.ts +74 -0
- package/src/inspectors/ghostty/plugin.ts +17 -0
- package/src/inspectors/herdr/actions.ts +99 -179
- package/src/inspectors/herdr/plugin.ts +20 -0
- package/src/inspectors/herdr/project-panes.ts +1 -1
- package/src/inspectors/{herdr/inspector-runner.ts → inspector-runner.ts} +12 -12
- package/src/inspectors/plugins.ts +8 -0
- package/src/inspectors/{herdr/session-roots-codec.ts → session-roots-codec.ts} +3 -14
- package/src/inspectors/types.ts +51 -0
- package/src/intercom/intercom-bridge.ts +50 -8
- package/src/intercom/native-supervisor-channel.ts +104 -67
- package/src/runs/background/active-async-capacity.ts +22 -18
- package/src/runs/background/async-execution.ts +45 -56
- package/src/runs/background/async-job-tracker.ts +35 -3
- package/src/runs/background/async-resume.ts +5 -9
- 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 +44 -30
- package/src/runs/background/binary-bootstrap.ts +33 -0
- package/src/runs/background/chain-root-attachment.ts +8 -0
- package/src/runs/background/control-channel.ts +78 -44
- package/src/runs/background/fleet-view.ts +30 -2
- package/src/runs/background/notify.ts +117 -13
- package/src/runs/background/owned-process-tree.ts +35 -8
- package/src/runs/background/process-terminal.ts +23 -23
- package/src/runs/background/run-child-session.ts +121 -36
- package/src/runs/background/run-status.ts +78 -5
- package/src/runs/background/runner-aliases.ts +28 -9
- package/src/runs/background/runner-child-launch.ts +88 -0
- package/src/runs/background/runner-child-sessions.ts +5 -4
- package/src/runs/background/scheduled-runs.ts +40 -13
- package/src/runs/background/stale-run-reconciler.ts +3 -1
- package/src/runs/background/steering.ts +20 -2
- package/src/runs/background/subagent-runner.ts +458 -239
- package/src/runs/background/subagent-wait.ts +54 -8
- package/src/runs/background/wait-completions.ts +4 -0
- package/src/runs/background/wait-tool.ts +1 -1
- package/src/runs/foreground/async-steering-action.ts +37 -7
- package/src/runs/foreground/execution.ts +145 -56
- package/src/runs/foreground/prompt-audit.ts +3 -1
- package/src/runs/foreground/subagent-executor.ts +584 -297
- package/src/runs/foreground/workflow-detach-reconcile.ts +10 -5
- package/src/runs/foreground/workflow-foreground-steering.ts +57 -2
- package/src/runs/shared/acceptance.ts +7 -4
- package/src/runs/shared/agent-contract.ts +1 -1
- package/src/runs/shared/async-status-projection.ts +51 -47
- package/src/runs/shared/capability-ceiling.ts +2 -0
- package/src/runs/shared/child-hooks.ts +167 -3
- package/src/runs/shared/child-launch.ts +28 -13
- package/src/runs/shared/child-lifecycle.ts +6 -3
- package/src/runs/shared/child-runtime-config.ts +3 -1
- package/src/runs/shared/child-session.ts +75 -8
- package/src/runs/shared/child-tool-plan.ts +124 -5
- package/src/runs/shared/completion-evidence.ts +2 -2
- package/src/runs/shared/completion-guard.ts +6 -3
- package/src/runs/shared/effective-system-prompt.ts +33 -0
- package/src/runs/shared/external-cli-runner.ts +9 -7
- package/src/runs/shared/host-step-status.ts +11 -11
- package/src/runs/shared/llm-intent-arbiter.ts +21 -11
- package/src/runs/shared/model-fallback.ts +12 -6
- package/src/runs/shared/nested-events.ts +5 -5
- package/src/runs/shared/orca-progress-tabs.ts +7 -1
- package/src/runs/shared/parallel-handoff.ts +57 -12
- package/src/runs/shared/parallel-utils.ts +2 -2
- package/src/runs/shared/pi-spawn.ts +10 -0
- 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 +20 -4
- package/src/runs/shared/task-intent.ts +46 -13
- package/src/runs/shared/workflow-async-child-guidance.ts +18 -0
- package/src/runs/shared/worktree-setup-command.ts +190 -0
- package/src/runs/shared/worktree.ts +366 -208
- package/src/shared/fork-context.ts +15 -72
- package/src/shared/launch-contract.ts +65 -2
- package/src/shared/opencode-session-headers.ts +30 -0
- package/src/shared/types.ts +85 -61
- package/src/shared/utils.ts +7 -2
- package/src/shared/workflow-child-permit.ts +18 -13
- package/src/slash/delegation-adapters.ts +3 -1
- package/src/slash/delegation-request.ts +14 -0
- package/src/slash/slash-commands.ts +2 -1
- package/src/slash/subagents-admin.ts +11 -4
- package/src/tui/fleet-status.ts +164 -19
- package/src/tui/fleet.ts +27 -19
- package/src/tui/render.ts +172 -33
- package/src/watchdog/child-status.ts +8 -0
- package/src/watchdog/model-selection.ts +20 -0
- package/src/watchdog/permission-arbiter.ts +3 -1
- package/src/watchdog/register-child.ts +1 -0
- package/src/watchdog/register-main.ts +31 -27
- package/src/watchdog/review.ts +132 -67
- package/src/watchdog/runtime.ts +82 -20
- package/src/watchdog/scope.ts +1 -1
- package/src/watchdog/settings.ts +9 -3
- package/src/watchdog/tool-actions.ts +13 -12
- package/src/watchdog/turn-delta.ts +23 -0
- package/src/watchdog/types.ts +4 -0
- package/src/workflows/chat-progress.ts +3 -3
- package/src/workflows/scripted-workflow.ts +275 -17
- package/src/workflows/workflow-checklist.ts +13 -17
- 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
- /package/src/inspectors/{herdr/shell-command.ts → shell-command.ts} +0 -0
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
import type { Message } from "@earendil-works/pi-ai";
|
|
9
9
|
import type { ChildTranscriptWriter } from "../../shared/child-transcript.ts";
|
|
10
10
|
import { extractTextFromContent, extractToolArgsPreview, getFinalOutput, hasEmptyTerminalAssistantResponse } from "../../shared/utils.ts";
|
|
11
|
-
import type { EffectsProjection,
|
|
11
|
+
import type { EffectsProjection, RuntimeAcknowledgedChildExtensions, SubagentOutputState, ToolBudgetState, Usage } from "../../shared/types.ts";
|
|
12
12
|
import {
|
|
13
13
|
acceptChildWatchdogEvent,
|
|
14
14
|
applyChildWatchdogMessage,
|
|
@@ -22,10 +22,12 @@ import { projectChildLifecycle, type ChildLifecycleAction, type ChildLifecycleSt
|
|
|
22
22
|
import { formatSubagentModelVerificationError } from "../shared/model-fallback.ts";
|
|
23
23
|
import { isMutatingTool, resolveCurrentPath } from "../shared/long-running-guard.ts";
|
|
24
24
|
import { effectiveToolTimeoutMs, formatToolTimeoutMessage, toolTimeoutCallKey } from "../shared/tool-timeout.ts";
|
|
25
|
-
import type
|
|
26
|
-
import { projectChildSessionEventForJson, type ChildSession, type ChildSessionEvent, type ChildSessionFactory } from "../shared/child-session.ts";
|
|
25
|
+
import { createReportedChildSessionInput, type InProcessChildLaunch } from "../shared/child-launch.ts";
|
|
26
|
+
import { childSessionHasQueuedMessages, projectChildSessionEventForJson, type ChildSession, type ChildSessionEvent, type ChildSessionFactory } from "../shared/child-session.ts";
|
|
27
27
|
import { formatSteerMessage } from "../shared/subagent-prompt-runtime.ts";
|
|
28
|
+
import { getReadonlySessionEvidence, requestReadonlySessionEvidence, type SettledReadonlyEvidence } from "../shared/readonly-session-evidence.ts";
|
|
28
29
|
import type { SteerDeliveryStatus, SteerRequest } from "./control-channel.ts";
|
|
30
|
+
import { takeMatchingAcceptedSteer, unconsumedSteerReason } from "./steering.ts";
|
|
29
31
|
|
|
30
32
|
export interface ChildEventContext {
|
|
31
33
|
runId: string;
|
|
@@ -83,6 +85,8 @@ export interface RunChildSessionInput {
|
|
|
83
85
|
registerTimeout?: (interrupt: (() => void) | undefined) => void;
|
|
84
86
|
registerStop?: (stop: (() => void) | undefined) => void;
|
|
85
87
|
registerSteer?: (steer: StepSteerHandler | undefined) => void;
|
|
88
|
+
/** Consumption (or unconsumed settlement) after the child accepted a steer or follow-up. */
|
|
89
|
+
onSteerOutcome?: (request: SteerRequest, delivery: SteerDelivery) => void;
|
|
86
90
|
/** Receives the sink the child's watchdog hook reports status through; the launch's `watchdogStatus` must forward to it. */
|
|
87
91
|
registerWatchdogStatus?: (sink: ((event: ChildWatchdogStatusEvent) => void) | undefined) => void;
|
|
88
92
|
timeoutMessage?: string;
|
|
@@ -95,6 +99,16 @@ export interface RunChildSessionInput {
|
|
|
95
99
|
modelVerificationRegistry?: Array<{ provider: string; id: string; fullId: string }>;
|
|
96
100
|
modelResponseAliases?: Record<string, string[]>;
|
|
97
101
|
mutationTools?: readonly string[];
|
|
102
|
+
/** Internal guarded continuation handoff; never part of persisted results. */
|
|
103
|
+
readonlyContinuation?: { source: ChildSession; expected: SettledReadonlyEvidence; modelId: string };
|
|
104
|
+
collectReadonlyEvidence?: boolean;
|
|
105
|
+
canContinue?: () => boolean;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const settledChildren = new WeakMap<RunChildSessionResult, ChildSession>();
|
|
109
|
+
export function getSettledReadonlyChild(result: RunChildSessionResult): ChildSession | undefined {
|
|
110
|
+
const child = settledChildren.get(result);
|
|
111
|
+
return child && getReadonlySessionEvidence(child) ? child : undefined;
|
|
98
112
|
}
|
|
99
113
|
|
|
100
114
|
export interface RunChildSessionResult {
|
|
@@ -123,7 +137,7 @@ export interface RunChildSessionResult {
|
|
|
123
137
|
toolBudget?: ToolBudgetState;
|
|
124
138
|
toolBudgetBlocked?: boolean;
|
|
125
139
|
structuredOutput?: unknown;
|
|
126
|
-
runtimeAcknowledgedExtensions?:
|
|
140
|
+
runtimeAcknowledgedExtensions?: RuntimeAcknowledgedChildExtensions;
|
|
127
141
|
abortRecoveryDiagnostic?: string;
|
|
128
142
|
effects?: EffectsProjection;
|
|
129
143
|
}
|
|
@@ -176,12 +190,14 @@ export function runChildSession(input: RunChildSessionInput): Promise<RunChildSe
|
|
|
176
190
|
let currentPath: string | undefined;
|
|
177
191
|
let toolCount = 0;
|
|
178
192
|
let session: ChildSession | undefined;
|
|
193
|
+
const acceptedSteers: Array<{ request: SteerRequest; text: string }> = [];
|
|
179
194
|
let unsubscribe: (() => void) | undefined;
|
|
180
195
|
let settled = false;
|
|
181
196
|
let promptSettled = false;
|
|
182
197
|
let forcedTermination = false;
|
|
183
198
|
let cleanTerminalAssistantStopReceived = false;
|
|
184
199
|
let agentSettledReceived = false;
|
|
200
|
+
let queuedDrainHold = false;
|
|
185
201
|
let compactionStartedReceived = false;
|
|
186
202
|
let afterCompactionSettlement = false;
|
|
187
203
|
let finalDrainTimer: NodeJS.Timeout | undefined;
|
|
@@ -300,14 +316,28 @@ export function runChildSession(input: RunChildSessionInput): Promise<RunChildSe
|
|
|
300
316
|
}
|
|
301
317
|
// If the child emits its terminal event but its run never settles (a hook
|
|
302
318
|
// is stuck), abort it after a short grace period and then finish without it.
|
|
319
|
+
const observeQueuedDrainHold = (): boolean => {
|
|
320
|
+
if (childSessionHasQueuedMessages(session)) queuedDrainHold = true;
|
|
321
|
+
return queuedDrainHold;
|
|
322
|
+
};
|
|
303
323
|
function startFinalDrain(): void {
|
|
304
324
|
if (childWatchdogIsActive(childWatchdogState)) {
|
|
305
325
|
armWatchdogTail();
|
|
306
326
|
return;
|
|
307
327
|
}
|
|
328
|
+
if (promptSettled || finalDrainTimer || settled) return;
|
|
329
|
+
if (observeQueuedDrainHold()) return;
|
|
330
|
+
armFinalDrainTimer();
|
|
331
|
+
}
|
|
332
|
+
function armFinalDrainTimer(): void {
|
|
308
333
|
if (promptSettled || finalDrainTimer || settled) return;
|
|
309
334
|
finalDrainTimer = setTimeout(() => {
|
|
310
335
|
if (settled || promptSettled) return;
|
|
336
|
+
if (input.launch.capture.finalDrainHeld() || observeQueuedDrainHold()) {
|
|
337
|
+
finalDrainTimer = undefined;
|
|
338
|
+
armFinalDrainTimer();
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
311
341
|
forcedTermination = true;
|
|
312
342
|
if (!cleanTerminalAssistantStopReceived && !agentSettledReceived && !error && !assistantError) {
|
|
313
343
|
error = `Subagent session did not settle within ${FINAL_STOP_GRACE_MS}ms after its terminal event. Aborting it.`;
|
|
@@ -323,6 +353,8 @@ export function runChildSession(input: RunChildSessionInput): Promise<RunChildSe
|
|
|
323
353
|
}
|
|
324
354
|
const applyChildLifecycle = (action: ChildLifecycleAction): void => {
|
|
325
355
|
if (action === "cancel-drain") {
|
|
356
|
+
cleanTerminalAssistantStopReceived = false;
|
|
357
|
+
agentSettledReceived = false;
|
|
326
358
|
clearFinalDrainTimers();
|
|
327
359
|
clearWatchdogTailTimer();
|
|
328
360
|
return;
|
|
@@ -390,6 +422,9 @@ export function runChildSession(input: RunChildSessionInput): Promise<RunChildSe
|
|
|
390
422
|
compactionStartedReceived = false;
|
|
391
423
|
afterCompactionSettlement = false;
|
|
392
424
|
}
|
|
425
|
+
if (event.type === "turn_start" || event.type === "agent_start" || event.type === "auto_retry_start") {
|
|
426
|
+
queuedDrainHold = false;
|
|
427
|
+
}
|
|
393
428
|
if (event.type === "agent_start" || event.type === "auto_retry_start") {
|
|
394
429
|
compactionStartedReceived = false;
|
|
395
430
|
afterCompactionSettlement = false;
|
|
@@ -459,6 +494,16 @@ export function runChildSession(input: RunChildSessionInput): Promise<RunChildSe
|
|
|
459
494
|
messages.push(event.message);
|
|
460
495
|
const text = extractTextFromContent(event.message.content);
|
|
461
496
|
if (text) writeOutputText(text);
|
|
497
|
+
if (event.type === "message_end" && event.message.role === "user" && text) {
|
|
498
|
+
const matched = takeMatchingAcceptedSteer(acceptedSteers, text);
|
|
499
|
+
if (matched) {
|
|
500
|
+
input.onSteerOutcome?.(matched.request, {
|
|
501
|
+
state: "delivered",
|
|
502
|
+
deliveryStatus: "delivered",
|
|
503
|
+
message: "Child consumed the steering input.",
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
}
|
|
462
507
|
|
|
463
508
|
if (input.childWatchdog && event.type === "message_end") {
|
|
464
509
|
const next = applyChildWatchdogMessage(childWatchdogState, event.message);
|
|
@@ -474,6 +519,10 @@ export function runChildSession(input: RunChildSessionInput): Promise<RunChildSe
|
|
|
474
519
|
}
|
|
475
520
|
}
|
|
476
521
|
if (event.message.errorMessage) assistantError = event.message.errorMessage;
|
|
522
|
+
else if (hasToolCall && event.message.stopReason === "toolUse") {
|
|
523
|
+
// A recovered request can finish via a terminating tool, without a text stop.
|
|
524
|
+
assistantError = undefined;
|
|
525
|
+
}
|
|
477
526
|
const eventUsage = event.message.usage;
|
|
478
527
|
if (eventUsage) {
|
|
479
528
|
usage.turns++;
|
|
@@ -514,9 +563,15 @@ export function runChildSession(input: RunChildSessionInput): Promise<RunChildSe
|
|
|
514
563
|
};
|
|
515
564
|
|
|
516
565
|
/** The child run ended (or was forced to end); fold in the outcome once the child's shutdown work is done. */
|
|
566
|
+
const failUnconsumedSteers = (): void => {
|
|
567
|
+
for (const entry of acceptedSteers.splice(0)) {
|
|
568
|
+
input.onSteerOutcome?.(entry.request, { state: "failed", message: unconsumedSteerReason(entry.request.mode) });
|
|
569
|
+
}
|
|
570
|
+
};
|
|
517
571
|
const settle = (promptError: unknown, forced = false): void => {
|
|
518
572
|
if (settled) return;
|
|
519
573
|
settled = true;
|
|
574
|
+
failUnconsumedSteers();
|
|
520
575
|
const closed = finish();
|
|
521
576
|
const finalOutput = getFinalOutput(messages);
|
|
522
577
|
let finalError = error ?? assistantError;
|
|
@@ -533,29 +588,33 @@ export function runChildSession(input: RunChildSessionInput): Promise<RunChildSe
|
|
|
533
588
|
: interrupted || (forcedDrainAfterFinalSuccess && !forcedDrainAfterEmptyTerminal)
|
|
534
589
|
? 0
|
|
535
590
|
: finalError || promptError !== undefined ? 1 : 0;
|
|
536
|
-
void closed.then(() =>
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
591
|
+
void closed.then(() => {
|
|
592
|
+
const result: RunChildSessionResult = omitUndefined({
|
|
593
|
+
exitCode,
|
|
594
|
+
messages,
|
|
595
|
+
usage,
|
|
596
|
+
toolCount,
|
|
597
|
+
durationMs: Date.now() - startedAt,
|
|
598
|
+
model,
|
|
599
|
+
error: stopped ? stopMessage() : timedOut ? (error ?? timeoutMessage()) : interrupted || (forcedDrainAfterFinalSuccess && !forcedDrainAfterEmptyTerminal) ? undefined : finalError,
|
|
600
|
+
finalOutput: (timedOut || stopped) && !finalOutput.trim() ? (stopped ? stopMessage() : error ?? timeoutMessage()) : finalOutput,
|
|
601
|
+
outputState: finalOutput.trim() ? "present" : "absent",
|
|
602
|
+
interrupted: interrupted || undefined,
|
|
603
|
+
timedOut: timedOut || undefined,
|
|
604
|
+
stopped: stopped || undefined,
|
|
605
|
+
observedMutationAttempt,
|
|
606
|
+
structuredOutputToolInvoked,
|
|
607
|
+
structuredOutputMessageStartIndex,
|
|
608
|
+
watchdog: childWatchdogState,
|
|
609
|
+
sessionFile: session?.sessionFile,
|
|
610
|
+
currentTool,
|
|
611
|
+
currentToolArgs,
|
|
612
|
+
currentPath,
|
|
613
|
+
afterCompactionSettlement: afterCompactionSettlement || undefined,
|
|
614
|
+
});
|
|
615
|
+
if (session && !forced && !forcedTermination && !interrupted && !timedOut && !stopped && getReadonlySessionEvidence(session)) settledChildren.set(result, session);
|
|
616
|
+
resolve(result);
|
|
617
|
+
});
|
|
559
618
|
};
|
|
560
619
|
|
|
561
620
|
input.registerInterrupt?.(() => {
|
|
@@ -575,33 +634,59 @@ export function runChildSession(input: RunChildSessionInput): Promise<RunChildSe
|
|
|
575
634
|
|
|
576
635
|
void (async () => {
|
|
577
636
|
try {
|
|
578
|
-
const
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
637
|
+
const continuation = input.readonlyContinuation;
|
|
638
|
+
const checkContinuation = () => {
|
|
639
|
+
if (!continuation) return;
|
|
640
|
+
if (interrupted || timedOut || stopped || input.canContinue?.() !== true
|
|
641
|
+
|| (input.runDeadlineAt !== undefined && Date.now() >= input.runDeadlineAt)
|
|
642
|
+
|| getReadonlySessionEvidence(continuation.source) !== continuation.expected
|
|
643
|
+
|| continuation.source.detached || continuation.source.shutDown) throw new Error("Read-only continuation handoff vetoed");
|
|
644
|
+
};
|
|
645
|
+
checkContinuation();
|
|
646
|
+
const createInput = createReportedChildSessionInput(input.launch, input.transcriptWriter);
|
|
647
|
+
if (input.collectReadonlyEvidence || continuation) requestReadonlySessionEvidence(createInput, continuation?.expected);
|
|
648
|
+
const created = await input.factory.create(createInput);
|
|
584
649
|
if (settled) {
|
|
585
650
|
void created.dispose();
|
|
586
651
|
return;
|
|
587
652
|
}
|
|
588
653
|
session = created;
|
|
654
|
+
const steer = created.steer.bind(created);
|
|
655
|
+
const followUp = created.followUp.bind(created);
|
|
656
|
+
created.steer = async (text) => {
|
|
657
|
+
if (cleanTerminalAssistantStopReceived || agentSettledReceived) queuedDrainHold = true;
|
|
658
|
+
return steer(text);
|
|
659
|
+
};
|
|
660
|
+
created.followUp = async (text) => {
|
|
661
|
+
if (cleanTerminalAssistantStopReceived || agentSettledReceived) queuedDrainHold = true;
|
|
662
|
+
return followUp(text);
|
|
663
|
+
};
|
|
664
|
+
checkContinuation();
|
|
665
|
+
if (continuation && (created.modelId !== continuation.modelId || input.launch.capture.completionIntentContext?.()?.model?.api !== continuation.expected.api)) throw new Error("Read-only continuation model changed");
|
|
589
666
|
unsubscribe = created.subscribe(processEvent);
|
|
590
667
|
input.registerWatchdogStatus?.((event) => processEvent(event as unknown as ChildSessionEvent));
|
|
591
668
|
input.registerSteer?.(async (request) => {
|
|
592
669
|
const text = formatSteerMessage(request);
|
|
593
670
|
const followUp = request.mode === "follow_up";
|
|
671
|
+
const accepted = { request, text };
|
|
672
|
+
acceptedSteers.push(accepted);
|
|
673
|
+
const queued: SteerDelivery = {
|
|
674
|
+
state: "queued",
|
|
675
|
+
deliveryStatus: "queued",
|
|
676
|
+
message: followUp ? "Pi queued the follow-up input." : "Pi accepted the steering input.",
|
|
677
|
+
};
|
|
594
678
|
try {
|
|
595
679
|
if (followUp) await created.followUp(text);
|
|
596
680
|
else await created.steer(text);
|
|
597
681
|
} catch (steerError) {
|
|
682
|
+
const index = acceptedSteers.indexOf(accepted);
|
|
683
|
+
if (index >= 0) acceptedSteers.splice(index, 1);
|
|
598
684
|
return { state: "failed", message: steerError instanceof Error ? steerError.message : String(steerError) };
|
|
599
685
|
}
|
|
600
|
-
return
|
|
601
|
-
? { state: "queued", deliveryStatus: "queued", message: "Pi queued the follow-up input." }
|
|
602
|
-
: { state: "delivered", deliveryStatus: "delivered", message: "Pi accepted the steering input." };
|
|
686
|
+
return queued;
|
|
603
687
|
});
|
|
604
688
|
if (interrupted || timedOut || stopped) abortChild();
|
|
689
|
+
checkContinuation();
|
|
605
690
|
await created.prompt(input.prompt);
|
|
606
691
|
promptSettled = true;
|
|
607
692
|
settle(undefined);
|
|
@@ -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";
|
|
@@ -30,6 +32,7 @@ import { getExternalJobProvider } from "../../api/external-job-provider.ts";
|
|
|
30
32
|
import { formatTimeoutRecoveryLines } from "../shared/mutation-evidence.ts";
|
|
31
33
|
import { formatWorkflowChecklistText, projectWorkflowChecklist } from "../../workflows/workflow-checklist.ts";
|
|
32
34
|
import { validHostStepNodes } from "../shared/host-step-status.ts";
|
|
35
|
+
import { workflowAsyncChildSteeringGuidance } from "../shared/workflow-async-child-guidance.ts";
|
|
33
36
|
|
|
34
37
|
interface RunStatusParams {
|
|
35
38
|
action?: string;
|
|
@@ -88,6 +91,7 @@ function formatRunLifecycleDebug(input: { status: AsyncStatus; asyncDir: string;
|
|
|
88
91
|
`Run: ${status.runId}`,
|
|
89
92
|
`Dir: ${asyncDir}`,
|
|
90
93
|
`Status file: ${path.join(asyncDir, "status.json")}`,
|
|
94
|
+
status.workflowReceiptPath ? `Workflow receipt: ${status.workflowReceiptPath}` : undefined,
|
|
91
95
|
`Process terminal file: ${path.join(asyncDir, "process-terminal.json")}`,
|
|
92
96
|
`Session: ${status.sessionId ?? "unknown"}`,
|
|
93
97
|
`State: ${status.state}`,
|
|
@@ -241,6 +245,54 @@ function formatRememberedForegroundStatus(run: ForegroundResumeRun): string {
|
|
|
241
245
|
return lines.join("\n");
|
|
242
246
|
}
|
|
243
247
|
|
|
248
|
+
/** Request-only snapshot of the same artifact used by Fleet; no session or output fallback. */
|
|
249
|
+
function formatLiveForegroundTranscript(control: ForegroundRunControl, state: SubagentState, options: { index?: number; lines?: number }): string {
|
|
250
|
+
if (!state.currentSessionId || control.sessionId !== state.currentSessionId) {
|
|
251
|
+
throw new Error(`Foreground run '${control.runId}' is not owned by the current session.`);
|
|
252
|
+
}
|
|
253
|
+
if (options.index !== undefined && (!Number.isInteger(options.index) || options.index < 0)) {
|
|
254
|
+
throw new Error("Transcript index must be a non-negative integer.");
|
|
255
|
+
}
|
|
256
|
+
const children = control.activeChildren
|
|
257
|
+
? [...control.activeChildren.values()]
|
|
258
|
+
: control.currentAgent ? [{ index: control.currentIndex ?? 0, agent: control.currentAgent, sessionName: control.sessionName }] : [];
|
|
259
|
+
const header = [`Run: ${control.runId}`, "State: live foreground"];
|
|
260
|
+
if (children.length === 0) return `${header.map((line) => safeTerminalText(line)).join("\n")}\nTranscript unavailable: no active foreground child.`;
|
|
261
|
+
if (options.index === undefined && children.length > 1) {
|
|
262
|
+
throw new Error(`Transcript view requires index for foreground run '${control.runId}'. Active child indexes: ${children.map((child) => child.index).join(", ")}.`);
|
|
263
|
+
}
|
|
264
|
+
const child = options.index === undefined ? children[0]! : children.find((child) => child.index === options.index);
|
|
265
|
+
if (!child) throw new Error(`Transcript index ${options.index} is not an active foreground child of '${control.runId}'.`);
|
|
266
|
+
const root = getArtifactsDir(state.parentSessionFile ?? null, control.cwd ?? state.baseCwd, state.artifactDirPreference);
|
|
267
|
+
const transcriptPath = getArtifactPaths(root, control.runId, child.agent, child.index).transcriptPath;
|
|
268
|
+
const transcript = readFleetTranscript(transcriptPath, { trustedRoots: [root] });
|
|
269
|
+
const lineLimit = Number.isFinite(options.lines) ? Math.max(1, Math.min(500, Math.trunc(options.lines!))) : 80;
|
|
270
|
+
const allLines = transcript.events.flatMap((event) => {
|
|
271
|
+
const text = event.kind === "tool"
|
|
272
|
+
? [`Tool: ${event.name} (${event.status})`, event.argsPayload ?? event.args, event.output ?? event.error].filter(Boolean).join("\n")
|
|
273
|
+
: event.kind === "notice" ? event.text : `${event.kind === "assistant" ? "Assistant" : "Supervisor"}: ${event.text}`;
|
|
274
|
+
return text.split(/\r?\n/);
|
|
275
|
+
});
|
|
276
|
+
let body = allLines.slice(-lineLimit).join("\n");
|
|
277
|
+
let truncated = transcript.truncated || allLines.length > lineLimit
|
|
278
|
+
|| transcript.events.some((event) => event.kind === "tool" && event.outputTruncated)
|
|
279
|
+
|| body.includes("… message truncated");
|
|
280
|
+
const maxOutputBytes = 32 * 1024;
|
|
281
|
+
const bytes = Buffer.from(body);
|
|
282
|
+
if (bytes.length > maxOutputBytes) {
|
|
283
|
+
// Skip UTF-8 continuation bytes at the beginning of the bounded tail.
|
|
284
|
+
let start = bytes.length - maxOutputBytes;
|
|
285
|
+
while ((bytes[start]! & 0xc0) === 0x80) start++;
|
|
286
|
+
body = bytes.subarray(start).toString("utf-8");
|
|
287
|
+
truncated = true;
|
|
288
|
+
}
|
|
289
|
+
header.push(`Child: ${child.index} (${child.sessionName?.trim() || child.agent})`, `Transcript: ${transcriptPath}`);
|
|
290
|
+
if (transcript.warning) header.push(`Transcript warning: ${transcript.warning}`);
|
|
291
|
+
header.push(`Live transcript tail${truncated ? " (tail truncated)" : ""}:`);
|
|
292
|
+
if (!body) header.push("Transcript unavailable: no readable activity in the bounded artifact tail yet.");
|
|
293
|
+
return [...header.map((line) => safeTerminalText(line)), body].filter(Boolean).join("\n");
|
|
294
|
+
}
|
|
295
|
+
|
|
244
296
|
function formatRememberedForegroundTranscript(run: ForegroundResumeRun, options: { index?: number; lines?: number }): string {
|
|
245
297
|
let index = options.index;
|
|
246
298
|
if (index !== undefined && !Number.isInteger(index)) throw new Error("Transcript index must be an integer.");
|
|
@@ -313,6 +365,12 @@ export function inspectSubagentStatus(params: RunStatusParams, deps: RunStatusDe
|
|
|
313
365
|
return inspectSubagentFleet(params, { asyncDirRoot, resultsDir, kill: deps.kill, now: deps.now, state: deps.state, childSafe: Boolean(deps.nested) });
|
|
314
366
|
}
|
|
315
367
|
if (!params.id && !params.runId && !params.dir) {
|
|
368
|
+
if (params.view === "transcript" && deps.state?.currentSessionId) {
|
|
369
|
+
const controls = [...deps.state.foregroundControls.values()].filter((control) => control.sessionId === currentSessionId);
|
|
370
|
+
const foreground = controls.find((control) => control.runId === deps.state?.lastForegroundControlId)
|
|
371
|
+
?? controls.sort((left, right) => right.updatedAt - left.updatedAt)[0];
|
|
372
|
+
if (foreground) return inspectSubagentStatus({ ...params, id: foreground.runId }, deps);
|
|
373
|
+
}
|
|
316
374
|
if (deps.nested) {
|
|
317
375
|
return {
|
|
318
376
|
content: [{ type: "text", text: "Child-safe subagent status requires an id when no foreground run is active." }],
|
|
@@ -353,6 +411,13 @@ export function inspectSubagentStatus(params: RunStatusParams, deps: RunStatusDe
|
|
|
353
411
|
} else if (!params.dir && requestedId) {
|
|
354
412
|
const resolved = resolveSubagentRunId(requestedId, { asyncDirRoot, resultsDir, state: deps.state, nested: deps.nested });
|
|
355
413
|
if (resolved?.kind === "foreground") {
|
|
414
|
+
const control = deps.state?.foregroundControls.get(resolved.id);
|
|
415
|
+
if (control && deps.state && params.view === "transcript") {
|
|
416
|
+
return {
|
|
417
|
+
content: [{ type: "text", text: formatLiveForegroundTranscript(control, deps.state, params) }],
|
|
418
|
+
details: { mode: "management", results: [] },
|
|
419
|
+
};
|
|
420
|
+
}
|
|
356
421
|
const run = deps.state?.foregroundRuns?.get(resolved.id);
|
|
357
422
|
if (run) {
|
|
358
423
|
try {
|
|
@@ -423,7 +488,7 @@ export function inspectSubagentStatus(params: RunStatusParams, deps: RunStatusDe
|
|
|
423
488
|
const capacity = inspectActiveAsyncCapacityOwner({ runId: diskStatus.runId, sessionId: diskStatus.sessionId, asyncDir }, { rootDir: deps.activeCapacityRoot, liveWorkflowRunIds: new Set(deps.state?.workflowControllers?.keys() ?? []), abandonedSlotReleaseAfterMs: deps.abandonedSlotReleaseAfterMs });
|
|
424
489
|
return {
|
|
425
490
|
content: [{ type: "text", text: formatRunLifecycleDebug({ status: diskStatus, asyncDir, sidecarProcessTerminal: sidecar, overlayProcessTerminal: overlay, capacity }) }],
|
|
426
|
-
details: { mode: "single", results: [], ...((sidecar ?? overlay) ? { lifecycleStatus: { processTerminal: sidecar ?? overlay } } : {}) },
|
|
491
|
+
details: { mode: "single", results: [], ...(diskStatus.workflowReceiptPath ? { workflowReceiptPath: diskStatus.workflowReceiptPath } : {}), ...((sidecar ?? overlay) ? { lifecycleStatus: { processTerminal: sidecar ?? overlay } } : {}) },
|
|
427
492
|
};
|
|
428
493
|
}
|
|
429
494
|
if (params.view === "transcript") {
|
|
@@ -450,7 +515,7 @@ export function inspectSubagentStatus(params: RunStatusParams, deps: RunStatusDe
|
|
|
450
515
|
const capacity = inspectActiveAsyncCapacityOwner({ runId: status.runId, sessionId: status.sessionId, asyncDir }, { rootDir: deps.activeCapacityRoot, liveWorkflowRunIds: new Set(deps.state?.workflowControllers?.keys() ?? []), abandonedSlotReleaseAfterMs: deps.abandonedSlotReleaseAfterMs });
|
|
451
516
|
return {
|
|
452
517
|
content: [{ type: "text", text: formatRunLifecycleDebug({ status, asyncDir, sidecarProcessTerminal: sidecar, overlayProcessTerminal: overlay, capacity }) }],
|
|
453
|
-
details: { mode: "single", results: [], ...((sidecar ?? overlay) ? { lifecycleStatus: { processTerminal: sidecar ?? overlay } } : {}) },
|
|
518
|
+
details: { mode: "single", results: [], ...(status.workflowReceiptPath ? { workflowReceiptPath: status.workflowReceiptPath } : {}), ...((sidecar ?? overlay) ? { lifecycleStatus: { processTerminal: sidecar ?? overlay } } : {}) },
|
|
454
519
|
};
|
|
455
520
|
}
|
|
456
521
|
if (params.view === "transcript") {
|
|
@@ -560,6 +625,7 @@ export function inspectSubagentStatus(params: RunStatusParams, deps: RunStatusDe
|
|
|
560
625
|
const display = runStatusStepDisplayName(step);
|
|
561
626
|
const phase = step.phase ? `[${step.phase}] ` : "";
|
|
562
627
|
lines.push(`${stepLineLabel(status, index)}: ${phase}${display} ${step.status}${modelText}${stepActivityText ? `, ${stepActivityText}` : ""}${steeringSuffix}${acceptanceText}${budgetText}${errorText}`);
|
|
628
|
+
if (status.mode === "workflow" && step.runId) lines.push(` Child run: ${step.runId}`);
|
|
563
629
|
const structuredOutputPreview = step.structuredOutput === undefined ? undefined : formatWorkflowJsonPreview(step.structuredOutput, 4_000);
|
|
564
630
|
if (structuredOutputPreview !== undefined) lines.push(` Structured output: ${structuredOutputPreview}`);
|
|
565
631
|
if (step.structuredOutputPath) lines.push(` Structured output path: ${step.structuredOutputPath}`);
|
|
@@ -624,8 +690,10 @@ export function inspectSubagentStatus(params: RunStatusParams, deps: RunStatusDe
|
|
|
624
690
|
lines.push(`Steer live foreground child: subagent({ action: "steer", id: "${control.runId}", index: ${index}, message: "..." })`);
|
|
625
691
|
}
|
|
626
692
|
}
|
|
693
|
+
lines.push(...workflowAsyncChildSteeringGuidance(status, deps.state));
|
|
627
694
|
}
|
|
628
695
|
if (nestedWarning) lines.push(`Warning: ${nestedWarning}`);
|
|
696
|
+
if (status.workflowReceiptPath) lines.push(`Workflow receipt: ${status.workflowReceiptPath}`);
|
|
629
697
|
if (status.sessionFile) lines.push(`Session: ${status.sessionFile}`);
|
|
630
698
|
const allExternal = (status.steps?.length ?? 0) > 0 && status.steps!.every((step) => step.runner?.type === "external-cli" || step.runner?.type === "external-job");
|
|
631
699
|
if (status.state === "running" && !allExternal && status.mode !== "workflow") lines.push(`Steer running child: subagent({ action: "steer", id: "${status.runId}", message: "..." })`);
|
|
@@ -639,7 +707,7 @@ export function inspectSubagentStatus(params: RunStatusParams, deps: RunStatusDe
|
|
|
639
707
|
|
|
640
708
|
const workflowChildren = parseWorkflowChildSummary(status.workflowChildren);
|
|
641
709
|
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 } } : {}) } };
|
|
710
|
+
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
711
|
}
|
|
644
712
|
}
|
|
645
713
|
|
|
@@ -680,6 +748,11 @@ export function inspectSubagentStatus(params: RunStatusParams, deps: RunStatusDe
|
|
|
680
748
|
const runId = data.runId ?? data.id ?? resolvedId;
|
|
681
749
|
const lines = [`Run: ${runId}`, data.toolCallId ? `Tool call: ${data.toolCallId}` : undefined, `State: ${status}`, `Result: ${resultPath}`].filter((line): line is string => Boolean(line));
|
|
682
750
|
if (data.parallelHandoff?.path) lines.push(`Parallel handoff: ${data.parallelHandoff.path}`);
|
|
751
|
+
const receipt = (data as Record<string, unknown>).workflowReceipt;
|
|
752
|
+
const receiptPath = receipt && typeof receipt === "object" && !Array.isArray(receipt)
|
|
753
|
+
? (receipt as Record<string, unknown>).path : undefined;
|
|
754
|
+
const workflowReceiptPath = typeof receiptPath === "string" && receiptPath ? receiptPath : undefined;
|
|
755
|
+
if (workflowReceiptPath) lines.push(`Workflow receipt: ${workflowReceiptPath}`);
|
|
683
756
|
const children = Array.isArray(data.results) ? data.results : data.agent ? [{ agent: data.agent, sessionFile: data.sessionFile }] : [];
|
|
684
757
|
lines.push(...formatTimeoutRecoveryLines(data.timeoutRecovery, " "));
|
|
685
758
|
for (const [index, child] of children.entries()) {
|
|
@@ -694,7 +767,7 @@ export function inspectSubagentStatus(params: RunStatusParams, deps: RunStatusDe
|
|
|
694
767
|
if (data.summary) lines.push("", data.summary);
|
|
695
768
|
const workflowChildren = parseWorkflowChildSummary((data as unknown as Record<string, unknown>).workflowChildren);
|
|
696
769
|
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 } : {}) } };
|
|
770
|
+
return { content: [{ type: "text", text: lines.join("\n") }], details: { mode: "single", results: [], ...(workflowReceiptPath ? { workflowReceiptPath } : {}), ...(workflowChildren ? { workflowChildren } : {}) } };
|
|
698
771
|
} catch (error) {
|
|
699
772
|
const message = error instanceof Error ? error.message : String(error);
|
|
700
773
|
return {
|
|
@@ -21,11 +21,6 @@ export const HOST_PEER_ALIASES: ReadonlyArray<{ specifier: string; pkg: string;
|
|
|
21
21
|
{ specifier: "@earendil-works/pi-coding-agent", pkg: "@earendil-works/pi-coding-agent", subpath: "." },
|
|
22
22
|
{ specifier: "@earendil-works/pi-agent-core", pkg: "@earendil-works/pi-agent-core", subpath: "." },
|
|
23
23
|
{ specifier: "@earendil-works/pi-agent-core/node", pkg: "@earendil-works/pi-agent-core", subpath: "./node" },
|
|
24
|
-
{ specifier: "@earendil-works/chord", pkg: "@earendil-works/chord", subpath: "." },
|
|
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
24
|
{ specifier: "@earendil-works/pi-tui", pkg: "@earendil-works/pi-tui", subpath: "." },
|
|
30
25
|
{ specifier: "@earendil-works/pi-ai", pkg: "@earendil-works/pi-ai", subpath: "./compat" },
|
|
31
26
|
{ specifier: "@earendil-works/pi-ai/compat", pkg: "@earendil-works/pi-ai", subpath: "./compat" },
|
|
@@ -36,6 +31,19 @@ export const HOST_PEER_ALIASES: ReadonlyArray<{ specifier: string; pkg: string;
|
|
|
36
31
|
{ specifier: "typebox/value", pkg: "typebox", subpath: "./value" },
|
|
37
32
|
];
|
|
38
33
|
|
|
34
|
+
/** Public Pi manifests introduce chord in 0.85.0 (absent through 0.84.4). */
|
|
35
|
+
const CHORD_PEER_ALIASES = [
|
|
36
|
+
{ specifier: "@earendil-works/chord", pkg: "@earendil-works/chord", subpath: "." },
|
|
37
|
+
{ specifier: "@earendil-works/chord/context", pkg: "@earendil-works/chord", subpath: "./context" },
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
/** Experimental dependencies accidentally published in exactly Pi 0.85.0. */
|
|
41
|
+
const PI0850_PEER_ALIASES = [
|
|
42
|
+
{ specifier: "@earendil-works/pi-server", pkg: "@earendil-works/pi-server", subpath: "." },
|
|
43
|
+
{ specifier: "@earendil-works/pi-server/unix", pkg: "@earendil-works/pi-server", subpath: "./unix" },
|
|
44
|
+
{ specifier: "@earendil-works/pi-client/unix", pkg: "@earendil-works/pi-client", subpath: "./unix" },
|
|
45
|
+
];
|
|
46
|
+
|
|
39
47
|
interface PackageManifest {
|
|
40
48
|
name?: unknown;
|
|
41
49
|
version?: unknown;
|
|
@@ -103,7 +111,11 @@ export function resolvePackageSubpath(packageDir: string, subpath: string): stri
|
|
|
103
111
|
|
|
104
112
|
/** Find `pkg` as the pi package itself, one of its dependencies, or a sibling in a hoisted install. */
|
|
105
113
|
export function findHostPeerPackageDir(piPackageRoot: string, pkg: string): string | undefined {
|
|
106
|
-
|
|
114
|
+
return findPeerPackageDir(piPackageRoot, pkg, readManifest(piPackageRoot)?.name);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function findPeerPackageDir(piPackageRoot: string, pkg: string, hostName: unknown): string | undefined {
|
|
118
|
+
if (hostName === pkg) return piPackageRoot;
|
|
107
119
|
const candidates = [path.join(piPackageRoot, "node_modules", pkg)];
|
|
108
120
|
let dir = piPackageRoot;
|
|
109
121
|
for (;;) {
|
|
@@ -127,14 +139,21 @@ export function resolveHostPeerAliases(
|
|
|
127
139
|
const aliases: Record<string, string> = {};
|
|
128
140
|
const missing: string[] = [];
|
|
129
141
|
const supplemental: string[] = [];
|
|
130
|
-
|
|
131
|
-
|
|
142
|
+
const hostManifest = readManifest(piPackageRoot);
|
|
143
|
+
const isPi0850 = hostManifest?.version === "0.85.0";
|
|
144
|
+
// Only known stable pre-chord versions may omit it. Unknown/prerelease
|
|
145
|
+
// hosts retain the required aliases, rather than hiding a broken install.
|
|
146
|
+
const stableVersion = typeof hostManifest?.version === "string" ? /^0\.(\d+)\.\d+$/.exec(hostManifest.version) : null;
|
|
147
|
+
const isPreChord = stableVersion !== null && Number(stableVersion[1]) < 85;
|
|
148
|
+
const required = [...HOST_PEER_ALIASES, ...(isPreChord ? [] : CHORD_PEER_ALIASES), ...(isPi0850 ? PI0850_PEER_ALIASES : [])];
|
|
149
|
+
for (const { specifier, pkg, subpath } of required) {
|
|
150
|
+
const packageDir = findPeerPackageDir(piPackageRoot, pkg, hostManifest?.name);
|
|
132
151
|
let target = packageDir ? resolvePackageSubpath(packageDir, subpath) : undefined;
|
|
133
152
|
// Pi 0.85.0 omitted this runtime dependency. Never replace working host
|
|
134
153
|
// exports or extend this exact version contract to other peers/hosts.
|
|
135
154
|
if ((!target || !fs.existsSync(target))
|
|
136
155
|
&& (specifier === "@earendil-works/pi-server" || specifier === "@earendil-works/pi-server/unix")
|
|
137
|
-
&&
|
|
156
|
+
&& isPi0850
|
|
138
157
|
&& (!packageDir || readManifest(packageDir)?.version === "0.85.0")) {
|
|
139
158
|
const localDir = findHostPeerPackageDir(extensionRoot, pkg);
|
|
140
159
|
if (localDir && readManifest(localDir)?.version === "0.85.0") {
|
|
@@ -0,0 +1,88 @@
|
|
|
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
|
+
hostAvailableBuiltins?: readonly string[];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function buildRunnerChildLaunch(step: RunnerSubagentStep, ctx: RunnerChildLaunchContext, attempt: {
|
|
25
|
+
model?: string;
|
|
26
|
+
sessionEnabled: boolean;
|
|
27
|
+
sessionDir?: string;
|
|
28
|
+
sessionName?: string;
|
|
29
|
+
structuredOutput?: BuildInProcessChildLaunchInput["structuredOutput"];
|
|
30
|
+
childWatchdog?: BuildInProcessChildLaunchInput["childWatchdog"];
|
|
31
|
+
watchdogStatus: NonNullable<BuildInProcessChildLaunchInput["watchdogStatus"]>;
|
|
32
|
+
}) {
|
|
33
|
+
// Resource-backed system instructions survive SDK split-turn compaction verbatim.
|
|
34
|
+
const acceptancePrompt = step.effectiveAcceptance
|
|
35
|
+
? formatAcceptancePrompt(step.effectiveAcceptance, { reportOptional: isAgentContract(step.agentContract), structuredOutput: Boolean(step.structuredOutput?.acceptanceReportPath) })
|
|
36
|
+
: "";
|
|
37
|
+
return buildInProcessChildLaunch({
|
|
38
|
+
parentSessionId: step.parentSessionId,
|
|
39
|
+
forkCacheKey: step.context === "fork" ? deriveForkPromptCacheKey(step.parentSessionId) : undefined,
|
|
40
|
+
sessionEnabled: attempt.sessionEnabled,
|
|
41
|
+
sessionDir: attempt.sessionDir,
|
|
42
|
+
sessionFile: step.sessionFile,
|
|
43
|
+
model: attempt.model,
|
|
44
|
+
inheritProjectContext: step.inheritProjectContext,
|
|
45
|
+
inheritGlobalContext: step.inheritGlobalContext,
|
|
46
|
+
inheritSkills: step.inheritSkills,
|
|
47
|
+
requireReadTool: Boolean(step.skills?.length),
|
|
48
|
+
tools: step.tools,
|
|
49
|
+
excludeTools: step.excludeTools,
|
|
50
|
+
allowNestedSubagents: step.allowNestedSubagents,
|
|
51
|
+
extensions: step.extensions,
|
|
52
|
+
subagentOnlyExtensions: step.subagentOnlyExtensions,
|
|
53
|
+
fast: step.fast,
|
|
54
|
+
modelCandidates: step.modelCandidates,
|
|
55
|
+
systemPrompt: acceptancePrompt ? `${step.systemPrompt ?? ""}\n${acceptancePrompt}` : step.systemPrompt ?? "",
|
|
56
|
+
systemPromptMode: step.systemPromptMode,
|
|
57
|
+
mcpDirectTools: step.mcpDirectTools,
|
|
58
|
+
extensionBindings: normalizeExtensionBindings(step.extensionBindings)?.value,
|
|
59
|
+
capabilityCeiling: step.capabilityCeiling ?? ctx.capabilityCeiling,
|
|
60
|
+
cwd: step.cwd ?? ctx.cwd,
|
|
61
|
+
intercomSessionName: ctx.childIntercomTarget,
|
|
62
|
+
sessionName: attempt.sessionName,
|
|
63
|
+
orchestratorIntercomTarget: ctx.orchestratorIntercomTarget,
|
|
64
|
+
runId: ctx.id,
|
|
65
|
+
childAgentName: step.agent,
|
|
66
|
+
childIndex: ctx.flatIndex,
|
|
67
|
+
nestedRoute: ctx.nestedRoute,
|
|
68
|
+
runFanoutBudget: ctx.runFanoutBudget ? {
|
|
69
|
+
...ctx.runFanoutBudget,
|
|
70
|
+
...(step.runFanoutPath ? { parentPath: `${ctx.runFanoutBudget.parentPath ? `${ctx.runFanoutBudget.parentPath}/` : ""}${step.runFanoutPath}` } : {}),
|
|
71
|
+
} : undefined,
|
|
72
|
+
structuredOutput: attempt.structuredOutput,
|
|
73
|
+
toolBudget: step.toolBudget,
|
|
74
|
+
permissionRules: step.permissionRules,
|
|
75
|
+
permissionAuditPath: step.permissionRules && ctx.artifactsDir
|
|
76
|
+
? path.join(ctx.artifactsDir, "permission-audit", `${ctx.id}-${ctx.flatIndex}.jsonl`) : undefined,
|
|
77
|
+
childWatchdog: attempt.childWatchdog,
|
|
78
|
+
// registerChildWatchdog returns before reading the sink when unconfigured.
|
|
79
|
+
...(attempt.childWatchdog ? { watchdogStatus: attempt.watchdogStatus } : {}),
|
|
80
|
+
waitToolEnabled: step.waitToolEnabled,
|
|
81
|
+
waitToolDefaultTimeoutMs: step.waitToolDefaultTimeoutMs,
|
|
82
|
+
thinkingCeiling: step.thinkingCeiling,
|
|
83
|
+
maxSubagentDepth: step.maxSubagentDepth,
|
|
84
|
+
inherited: ctx.inheritedChildRuntime,
|
|
85
|
+
hostAvailableBuiltins: ctx.hostAvailableBuiltins,
|
|
86
|
+
host: "runner",
|
|
87
|
+
});
|
|
88
|
+
}
|