pi-subagents 0.66.0 → 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 +68 -0
- package/README.md +4 -3
- package/agents/evidence-auditor.md +34 -0
- package/agents/reviewer.md +3 -2
- package/docs/agents.md +6 -3
- package/docs/configuration.md +7 -5
- package/docs/extension-api.md +33 -18
- package/docs/missions.md +8 -0
- package/docs/models.md +1 -1
- package/docs/observability.md +4 -4
- package/docs/standalone-background.md +49 -0
- package/docs/tool-reference.md +18 -8
- package/docs/watchdog.md +35 -4
- package/docs/workflows.md +26 -12
- package/inspector-runner.mjs +2 -2
- package/package.json +1 -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 +7 -5
- package/skills/pi-subagents/references/prompting-and-roles.md +2 -2
- package/src/agents/advertised-agent-prompt.ts +34 -3
- package/src/agents/agents.ts +6 -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 +2 -0
- package/src/extension/fanout-child.ts +63 -4
- package/src/extension/index.ts +20 -8
- package/src/extension/public-execution.ts +4 -2
- package/src/extension/rpc.ts +4 -0
- package/src/extension/schemas.ts +67 -78
- package/src/extension/tool-description.ts +29 -82
- 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 +22 -13
- package/src/runs/background/active-async-capacity.ts +4 -0
- package/src/runs/background/async-execution.ts +45 -56
- package/src/runs/background/async-resume.ts +5 -9
- package/src/runs/background/auto-drain.ts +6 -3
- package/src/runs/background/binary-bootstrap.ts +33 -0
- package/src/runs/background/fleet-view.ts +30 -2
- package/src/runs/background/notify.ts +31 -1
- package/src/runs/background/owned-process-tree.ts +29 -2
- package/src/runs/background/run-child-session.ts +61 -4
- package/src/runs/background/run-status.ts +3 -0
- package/src/runs/background/runner-aliases.ts +11 -3
- package/src/runs/background/runner-child-launch.ts +2 -0
- package/src/runs/background/runner-child-sessions.ts +5 -4
- package/src/runs/background/scheduled-runs.ts +40 -13
- package/src/runs/background/steering.ts +20 -2
- package/src/runs/background/subagent-runner.ts +47 -31
- package/src/runs/background/subagent-wait.ts +51 -8
- package/src/runs/background/wait-tool.ts +1 -1
- package/src/runs/foreground/async-steering-action.ts +18 -7
- package/src/runs/foreground/execution.ts +44 -31
- package/src/runs/foreground/prompt-audit.ts +3 -1
- package/src/runs/foreground/subagent-executor.ts +110 -100
- package/src/runs/foreground/workflow-detach-reconcile.ts +2 -0
- package/src/runs/foreground/workflow-foreground-steering.ts +2 -1
- package/src/runs/shared/acceptance.ts +5 -2
- package/src/runs/shared/async-status-projection.ts +4 -0
- package/src/runs/shared/capability-ceiling.ts +2 -0
- package/src/runs/shared/child-hooks.ts +25 -10
- package/src/runs/shared/child-launch.ts +12 -2
- 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 +33 -2
- package/src/runs/shared/child-tool-plan.ts +122 -3
- package/src/runs/shared/completion-guard.ts +5 -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/llm-intent-arbiter.ts +12 -3
- package/src/runs/shared/model-fallback.ts +2 -0
- package/src/runs/shared/orca-progress-tabs.ts +1 -1
- package/src/runs/shared/pi-spawn.ts +10 -0
- package/src/runs/shared/subagent-prompt-runtime.ts +9 -3
- 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.ts +42 -12
- 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 +4 -1
- 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 +16 -14
- package/src/tui/render.ts +149 -28
- 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/scripted-workflow.ts +237 -7
- package/src/workflows/workflow-checklist.ts +2 -2
- /package/src/inspectors/{herdr/shell-command.ts → shell-command.ts} +0 -0
|
@@ -96,8 +96,8 @@ import { applyThinkingSuffix, projectLaunchResolvedChildExtensions, resolvePiLau
|
|
|
96
96
|
import type { InheritedChildRuntime } from "../shared/child-launch.ts";
|
|
97
97
|
import { buildRunnerChildLaunch } from "./runner-child-launch.ts";
|
|
98
98
|
import { normalizeExtensionBindings } from "../shared/extension-bindings.ts";
|
|
99
|
-
import type { ChildSessionFactory } from "../shared/child-session.ts";
|
|
100
|
-
import { getSettledReadonlyChild, runChildSession, type ChildEvent, type RunChildSessionInput, type RunChildSessionResult, type StepSteerHandler } from "./run-child-session.ts";
|
|
99
|
+
import type { ChildSessionFactory, DefaultChildSessionFactoryOptions } from "../shared/child-session.ts";
|
|
100
|
+
import { getSettledReadonlyChild, runChildSession, type ChildEvent, type RunChildSessionInput, type RunChildSessionResult, type SteerDelivery, type StepSteerHandler } from "./run-child-session.ts";
|
|
101
101
|
import { planReadonlyModelContinuation, READONLY_CONTINUATION_PROMPT, type LogicalRecoveryState } from "../shared/readonly-model-continuation.ts";
|
|
102
102
|
import { getReadonlySessionEvidence } from "../shared/readonly-session-evidence.ts";
|
|
103
103
|
import { loadRunnerChildSessionFactory } from "./runner-child-sessions.ts";
|
|
@@ -114,7 +114,7 @@ import { claimRunFanoutBatch, getRunFanoutBudgetSnapshot } from "../shared/run-f
|
|
|
114
114
|
import { nestedSummaryFromAsyncStatus, projectNestedEvents, resolveNestedAsyncDir, writeNestedEvent } from "../shared/nested-events.ts";
|
|
115
115
|
import { formatModelAttemptNote, formatSubagentModelVerificationError, isContextOverflow, isRetryableModelFailureAttempt, recordRetryableModelFailure } from "../shared/model-fallback.ts";
|
|
116
116
|
import { markProcessTerminalCandidateLeaseRelease, processTerminalPath, writeProcessTerminalCandidate, type ProcessTerminalCandidate } from "./process-terminal.ts";
|
|
117
|
-
import { createSteeringStatus, recordSteeringRequest, steeringStatus, terminalSteeringNoticeState, updateSteeringTarget } from "./steering.ts";
|
|
117
|
+
import { createSteeringStatus, recordSteeringRequest, steeringStatus, terminalSteeringNoticeState, unconsumedSteerReason, updateSteeringTarget } from "./steering.ts";
|
|
118
118
|
import { PROMPT_REDACTED, detectSubagentError, extractTextFromContent, extractToolArgsPreview, formatEmptyTerminalAssistantResponseError, getFinalOutput, hasEmptyTerminalAssistantResponse, readStatus } from "../../shared/utils.ts";
|
|
119
119
|
import { evaluateCompletionMutationGuard, expectsImplementationMutation, hasMutationToolCapability, validateImplementationToolContract } from "../shared/completion-guard.ts";
|
|
120
120
|
import { planCompletionEvidence, projectSettlementDiagnostic } from "../shared/completion-evidence.ts";
|
|
@@ -147,7 +147,7 @@ import {
|
|
|
147
147
|
} from "../shared/worktree.ts";
|
|
148
148
|
import { findModelInfo, resolveEffectiveThinking, splitKnownThinkingSuffix } from "../../shared/model-info.ts";
|
|
149
149
|
import { assertThinkingWithinCeiling } from "../../shared/thinking-ceiling.ts";
|
|
150
|
-
import {
|
|
150
|
+
import { resolveLaunchBinding } from "../../shared/launch-contract.ts";
|
|
151
151
|
import { writeInitialProgressFile } from "../../shared/settings.ts";
|
|
152
152
|
import { resolveSubagentIntercomTarget } from "../../intercom/intercom-bridge.ts";
|
|
153
153
|
import { acceptanceFailureMessage, aggregateAcceptanceReport, buildSkippedAcceptanceLedger, evaluateAcceptance, formatAcceptancePrompt, resolveAcceptanceReportMode, resolveEffectiveAcceptance, stripAcceptanceReport } from "../shared/acceptance.ts";
|
|
@@ -184,7 +184,7 @@ const INTERCOM_DETACH_RECEIPT = "Detached for intercom coordination before task
|
|
|
184
184
|
// child host.
|
|
185
185
|
process.env[SUBAGENT_CHILD_ENV] = "1";
|
|
186
186
|
|
|
187
|
-
interface SubagentRunConfig {
|
|
187
|
+
export interface SubagentRunConfig {
|
|
188
188
|
id: string;
|
|
189
189
|
steps: RunnerStep[];
|
|
190
190
|
resultPath: string;
|
|
@@ -232,6 +232,8 @@ interface SubagentRunConfig {
|
|
|
232
232
|
globalConcurrencyLimit?: number;
|
|
233
233
|
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
234
234
|
runFanoutBudget?: RunFanoutBudgetDescriptor;
|
|
235
|
+
/** Builtin tool names the host runtime provides; used to intersect agent-declared tools. */
|
|
236
|
+
hostAvailableBuiltins?: readonly string[];
|
|
235
237
|
launchContractDigest?: string;
|
|
236
238
|
launchResolvedExtensions?: LaunchResolvedChildExtensions;
|
|
237
239
|
runtimeAcknowledgedExtensions?: RuntimeAcknowledgedChildExtensions;
|
|
@@ -682,6 +684,8 @@ interface SingleStepContext {
|
|
|
682
684
|
registerStop?: (stop: (() => void) | undefined) => void;
|
|
683
685
|
/** Receives the live child's steer handler while its session runs. */
|
|
684
686
|
registerSteer?: (steer: StepSteerHandler | undefined) => void;
|
|
687
|
+
/** Reports a live child's later steer consumption or unconsumed settlement. */
|
|
688
|
+
onSteerOutcome?: (request: SteerRequest, delivery: SteerDelivery) => void;
|
|
685
689
|
timeoutSignal?: AbortSignal;
|
|
686
690
|
stopSignal?: AbortSignal;
|
|
687
691
|
timeoutMessage?: string;
|
|
@@ -695,6 +699,7 @@ interface SingleStepContext {
|
|
|
695
699
|
nestedRoute?: NestedRouteInfo;
|
|
696
700
|
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
697
701
|
runFanoutBudget?: RunFanoutBudgetDescriptor;
|
|
702
|
+
hostAvailableBuiltins?: readonly string[];
|
|
698
703
|
onAttemptStart?: (attempt: { model?: string; thinking?: string; contextLimit?: number }) => void;
|
|
699
704
|
onChildEvent?: (event: ChildEvent) => void;
|
|
700
705
|
onExternalProcess?: (process: ExternalProcessStatus) => void;
|
|
@@ -811,6 +816,7 @@ export async function runSingleStepInner(
|
|
|
811
816
|
capabilityCeiling: step.capabilityCeiling ?? ctx.capabilityCeiling,
|
|
812
817
|
inheritedCapabilityCeiling: ctx.inheritedChildRuntime?.capabilityCeiling,
|
|
813
818
|
permissionRules: step.permissionRules,
|
|
819
|
+
hostAvailableBuiltins: ctx.hostAvailableBuiltins,
|
|
814
820
|
}));
|
|
815
821
|
const contractTools = resolvedTaskToolPlan.explicitToolAllowlist ? resolvedTaskToolPlan.effectiveToolAllowlist : undefined;
|
|
816
822
|
const contractError = validateImplementationToolContract({
|
|
@@ -1149,31 +1155,27 @@ export async function runSingleStepInner(
|
|
|
1149
1155
|
capabilityCeiling: step.capabilityCeiling ?? ctx.capabilityCeiling,
|
|
1150
1156
|
inheritedCapabilityCeiling: ctx.inheritedChildRuntime?.capabilityCeiling,
|
|
1151
1157
|
permissionRules: step.permissionRules,
|
|
1158
|
+
hostAvailableBuiltins: ctx.hostAvailableBuiltins,
|
|
1152
1159
|
}));
|
|
1153
1160
|
launchResolvedExtensions = projectLaunchResolvedChildExtensions(toolPlan);
|
|
1154
|
-
actualLaunchContractDigest =
|
|
1161
|
+
actualLaunchContractDigest = resolveLaunchBinding({
|
|
1155
1162
|
definitionDigest: step.definitionDigest,
|
|
1156
|
-
task: step.launchBindingTask ?? task,
|
|
1157
|
-
...(candidate ? { model: candidate } : {}),
|
|
1158
|
-
modelCandidates: candidates as string[],
|
|
1159
|
-
...(step.fast !== undefined ? { fast: step.fast } : {}),
|
|
1160
|
-
...(resolveEffectiveThinking(candidate, step.thinking) ? { thinking: resolveEffectiveThinking(candidate, step.thinking) } : {}),
|
|
1161
|
-
...(step.thinkingCeiling ? { thinkingCeiling: step.thinkingCeiling } : {}),
|
|
1162
|
-
systemPrompt: step.systemPrompt ?? "",
|
|
1163
1163
|
systemPromptMode: step.systemPromptMode,
|
|
1164
1164
|
inheritProjectContext: step.inheritProjectContext,
|
|
1165
1165
|
inheritGlobalContext: step.inheritGlobalContext,
|
|
1166
1166
|
inheritSkills: step.inheritSkills,
|
|
1167
|
+
task: step.launchBindingTask ?? task,
|
|
1168
|
+
modelCandidates: candidates as string[],
|
|
1169
|
+
fast: step.fast,
|
|
1170
|
+
thinking: resolveEffectiveThinking(candidate, step.thinking),
|
|
1171
|
+
systemPrompt: step.systemPrompt ?? "",
|
|
1167
1172
|
skills: step.skills,
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
...(step.structuredOutputSchema ? { structuredOutputSchema: step.structuredOutputSchema } : {}),
|
|
1175
|
-
...(extensionBindings ? { extensionBindings } : {}),
|
|
1176
|
-
}));
|
|
1173
|
+
toolPlan,
|
|
1174
|
+
outputPath: step.outputPath,
|
|
1175
|
+
outputMode: step.outputMode,
|
|
1176
|
+
structuredOutputSchema: step.structuredOutputSchema,
|
|
1177
|
+
extensionBindings,
|
|
1178
|
+
}).launchContractDigest;
|
|
1177
1179
|
}
|
|
1178
1180
|
capabilityAudit = attemptCapabilityAudit;
|
|
1179
1181
|
// Each attempt rewrites the step output log; synchronous appends keep a
|
|
@@ -1201,6 +1203,7 @@ export async function runSingleStepInner(
|
|
|
1201
1203
|
registerTimeout: ctx.registerTimeout,
|
|
1202
1204
|
registerStop: ctx.registerStop,
|
|
1203
1205
|
registerSteer: ctx.registerSteer,
|
|
1206
|
+
onSteerOutcome: ctx.onSteerOutcome,
|
|
1204
1207
|
registerWatchdogStatus: (sink) => { watchdogSink = sink; },
|
|
1205
1208
|
timeoutMessage: ctx.timeoutMessage,
|
|
1206
1209
|
stopMessage: ctx.stopMessage,
|
|
@@ -1213,6 +1216,9 @@ export async function runSingleStepInner(
|
|
|
1213
1216
|
modelResponseAliases: step.modelResponseAliases,
|
|
1214
1217
|
mutationTools: step.mutationTools,
|
|
1215
1218
|
}));
|
|
1219
|
+
// A parked run still owes completion evidence when it actually finishes.
|
|
1220
|
+
// Stopped/timedOut runs already have terminal failures; checking completion evidence there is meaningless.
|
|
1221
|
+
const completionDiagnosticsEligible = !run.interrupted && !run.stopped && !run.timedOut;
|
|
1216
1222
|
const toolDiagnostic = run.exitCode === 0 && !run.error ? launch.capture.toolDiagnostic() : undefined;
|
|
1217
1223
|
const toolAvailabilityError = toolDiagnostic ? formatChildToolDiagnostic(toolDiagnostic) : undefined;
|
|
1218
1224
|
const runtimeAcknowledgedExtensions = launch.capture.runtimeAcknowledgedExtensions();
|
|
@@ -1228,7 +1234,7 @@ export async function runSingleStepInner(
|
|
|
1228
1234
|
let structuredOutput: unknown;
|
|
1229
1235
|
let structuredError: string | undefined;
|
|
1230
1236
|
let validatedStructuredOutput = false;
|
|
1231
|
-
if (effectiveStructuredOutput && run.exitCode === 0 && !run.error && !toolAvailabilityError && !midToolExitError) {
|
|
1237
|
+
if (completionDiagnosticsEligible && effectiveStructuredOutput && run.exitCode === 0 && !run.error && !toolAvailabilityError && !midToolExitError) {
|
|
1232
1238
|
if (!run.structuredOutputToolInvoked) {
|
|
1233
1239
|
structuredError = MISSING_STRUCTURED_OUTPUT_CALL_ERROR;
|
|
1234
1240
|
} else {
|
|
@@ -1250,13 +1256,13 @@ export async function runSingleStepInner(
|
|
|
1250
1256
|
const errorMessages = validatedStructuredOutput
|
|
1251
1257
|
? run.messages.slice(run.structuredOutputMessageStartIndex ?? run.messages.length)
|
|
1252
1258
|
: run.messages;
|
|
1253
|
-
const hiddenError = run.exitCode === 0 && !run.error && !toolAvailabilityError && !structuredError && !midToolExitError
|
|
1259
|
+
const hiddenError = completionDiagnosticsEligible && run.exitCode === 0 && !run.error && !toolAvailabilityError && !structuredError && !midToolExitError
|
|
1254
1260
|
? detectSubagentError(errorMessages)
|
|
1255
1261
|
: null;
|
|
1256
1262
|
const terminalEmptyAfterUsefulWork = !validatedStructuredOutput
|
|
1257
1263
|
&& hasEmptyTerminalAssistantResponse(run.messages)
|
|
1258
1264
|
&& (run.toolCount > 0 || Boolean(run.finalOutput.trim()));
|
|
1259
|
-
const emptyOutputError = run.exitCode === 0
|
|
1265
|
+
const emptyOutputError = completionDiagnosticsEligible && run.exitCode === 0
|
|
1260
1266
|
&& !run.error
|
|
1261
1267
|
&& !toolAvailabilityError
|
|
1262
1268
|
&& !structuredError
|
|
@@ -1271,7 +1277,7 @@ export async function runSingleStepInner(
|
|
|
1271
1277
|
const mutationEvidence = collectTrackedMutationEvidence(mutationSnapshot, step.cwd ?? ctx.cwd);
|
|
1272
1278
|
finalMutationEvidence = mutationEvidence;
|
|
1273
1279
|
const completionMutationEvidence = ctx.trackedMutationEvidenceForCompletionGuard === false ? undefined : mutationEvidence;
|
|
1274
|
-
const completionGuard = run.exitCode === 0 && !run.error && !structuredError && !hiddenError?.hasError && !midToolExitError && !emptyOutputError && completionGuardEnabled
|
|
1280
|
+
const completionGuard = completionDiagnosticsEligible && run.exitCode === 0 && !run.error && !structuredError && !hiddenError?.hasError && !midToolExitError && !emptyOutputError && completionGuardEnabled
|
|
1275
1281
|
? evaluateCompletionMutationGuard(omitUndefinedProperties({
|
|
1276
1282
|
agent: step.agent,
|
|
1277
1283
|
task: taskForCompletionGuard,
|
|
@@ -1313,7 +1319,7 @@ export async function runSingleStepInner(
|
|
|
1313
1319
|
? { kind: "structured" as const, path: effectiveStructuredOutput.outputPath, missing: !fs.existsSync(effectiveStructuredOutput.outputPath) }
|
|
1314
1320
|
: undefined;
|
|
1315
1321
|
finalRequiredOutputMissing = requiredOutput?.missing;
|
|
1316
|
-
const missingRequiredOutputError = formatRequiredOutputError(requiredOutput);
|
|
1322
|
+
const missingRequiredOutputError = completionDiagnosticsEligible ? formatRequiredOutputError(requiredOutput) : undefined;
|
|
1317
1323
|
const missingRequiredOutputAfterMutation = Boolean(missingRequiredOutputError) && (mutationAttemptObserved || Boolean(mutationEvidence.changedFiles.length));
|
|
1318
1324
|
const effectiveExitCode = toolAvailabilityError || completionEvidence.legacyFailureError || midToolExitError || structuredError || emptyOutputError || missingRequiredOutputError
|
|
1319
1325
|
? 1
|
|
@@ -2911,7 +2917,10 @@ export async function runSubagent(
|
|
|
2911
2917
|
const applySteerDelivery = (requestId: string, index: number, delivery: { state: "delivered" | "queued" | "failed"; message: string }): void => {
|
|
2912
2918
|
const lifecycle = steeringStatus(statusPayload);
|
|
2913
2919
|
const request = lifecycle.recent.find((candidate) => candidate.id === requestId);
|
|
2914
|
-
|
|
2920
|
+
const target = request?.targets.find((candidate) => candidate.index === index);
|
|
2921
|
+
if (!request || !target) return;
|
|
2922
|
+
if (target.state === "delivered" || target.state === "late" || target.state === "failed") return;
|
|
2923
|
+
if (target.state === delivery.state) return;
|
|
2915
2924
|
const late = fs.existsSync(steeringMarkerPath(requestId));
|
|
2916
2925
|
const now = Date.now();
|
|
2917
2926
|
if (delivery.state === "delivered") {
|
|
@@ -3686,10 +3695,12 @@ export async function runSubagent(
|
|
|
3686
3695
|
nestedRoute: config.nestedRoute,
|
|
3687
3696
|
capabilityCeiling: config.capabilityCeiling,
|
|
3688
3697
|
runFanoutBudget: config.runFanoutBudget,
|
|
3698
|
+
hostAvailableBuiltins: config.hostAvailableBuiltins,
|
|
3689
3699
|
registerInterrupt: (interrupt) => registerStepInterrupt(fi, interrupt),
|
|
3690
3700
|
registerTimeout: (interrupt) => registerStepTimeout(fi, interrupt),
|
|
3691
3701
|
registerStop: (stop) => registerStepStop(fi, stop),
|
|
3692
3702
|
registerSteer: (steer) => registerStepSteer(fi, steer),
|
|
3703
|
+
onSteerOutcome: (request, delivery) => applySteerDelivery(request.id, fi, delivery),
|
|
3693
3704
|
timeoutSignal: timeoutAbortController.signal,
|
|
3694
3705
|
stopSignal: stopAbortController.signal,
|
|
3695
3706
|
trackedMutationEvidenceForCompletionGuard: false,
|
|
@@ -4096,10 +4107,12 @@ export async function runSubagent(
|
|
|
4096
4107
|
nestedRoute: config.nestedRoute,
|
|
4097
4108
|
capabilityCeiling: config.capabilityCeiling,
|
|
4098
4109
|
runFanoutBudget: config.runFanoutBudget,
|
|
4110
|
+
hostAvailableBuiltins: config.hostAvailableBuiltins,
|
|
4099
4111
|
registerInterrupt: (interrupt) => registerStepInterrupt(fi, interrupt),
|
|
4100
4112
|
registerTimeout: (interrupt) => registerStepTimeout(fi, interrupt),
|
|
4101
4113
|
registerStop: (stop) => registerStepStop(fi, stop),
|
|
4102
4114
|
registerSteer: (steer) => registerStepSteer(fi, steer),
|
|
4115
|
+
onSteerOutcome: (request, delivery) => applySteerDelivery(request.id, fi, delivery),
|
|
4103
4116
|
timeoutSignal: timeoutAbortController.signal,
|
|
4104
4117
|
stopSignal: stopAbortController.signal,
|
|
4105
4118
|
trackedMutationEvidenceForCompletionGuard: Boolean(worktreeSetup),
|
|
@@ -4496,10 +4509,12 @@ export async function runSubagent(
|
|
|
4496
4509
|
nestedRoute: config.nestedRoute,
|
|
4497
4510
|
capabilityCeiling: config.capabilityCeiling,
|
|
4498
4511
|
runFanoutBudget: config.runFanoutBudget,
|
|
4512
|
+
hostAvailableBuiltins: config.hostAvailableBuiltins,
|
|
4499
4513
|
registerInterrupt: (interrupt) => registerStepInterrupt(flatIndex, interrupt),
|
|
4500
4514
|
registerTimeout: (interrupt) => registerStepTimeout(flatIndex, interrupt),
|
|
4501
4515
|
registerStop: (stop) => registerStepStop(flatIndex, stop),
|
|
4502
4516
|
registerSteer: (steer) => registerStepSteer(flatIndex, steer),
|
|
4517
|
+
onSteerOutcome: (request, delivery) => applySteerDelivery(request.id, flatIndex, delivery),
|
|
4503
4518
|
timeoutSignal: timeoutAbortController.signal,
|
|
4504
4519
|
stopSignal: stopAbortController.signal,
|
|
4505
4520
|
timeoutMessage,
|
|
@@ -4832,7 +4847,7 @@ export async function runSubagent(
|
|
|
4832
4847
|
for (const target of request.targets) {
|
|
4833
4848
|
if (target.state !== "scheduled" && target.state !== "routed" && target.state !== "queued") continue;
|
|
4834
4849
|
changed = true;
|
|
4835
|
-
const reason = target.state === "queued" ?
|
|
4850
|
+
const reason = target.state === "queued" ? unconsumedSteerReason() : "child terminated before steering delivery";
|
|
4836
4851
|
updateSteeringLifecycleTarget(request.id, target.index, "failed", Date.now(), { reason });
|
|
4837
4852
|
markSteeringAttention(target.index);
|
|
4838
4853
|
emitSteeringEvent("subagent.steer.failed", { type: "steer", id: request.id, ts: request.requestedAt, message: reason }, target.index, { reason });
|
|
@@ -5080,7 +5095,8 @@ async function waitForStartupControl(
|
|
|
5080
5095
|
throw new Error(`Timed out after ${timeoutMs}ms waiting for runner startup control '${action}'.`);
|
|
5081
5096
|
}
|
|
5082
5097
|
|
|
5083
|
-
|
|
5098
|
+
// Both hosts enter here: startup authorization, revival leases and disposal stay shared.
|
|
5099
|
+
export async function runConfiguredSubagent(config: SubagentRunConfig, options?: DefaultChildSessionFactoryOptions): Promise<void> {
|
|
5084
5100
|
let lease: ReturnType<typeof acquireSessionLease> | undefined;
|
|
5085
5101
|
let startupCommitted = config.revivalLease === undefined && config.launchBarrierToken === undefined;
|
|
5086
5102
|
const startupPath = path.join(config.asyncDir, "runner-startup.json");
|
|
@@ -5119,7 +5135,7 @@ async function runConfiguredSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
5119
5135
|
}
|
|
5120
5136
|
}
|
|
5121
5137
|
}
|
|
5122
|
-
const childSessions = await loadRunnerChildSessionFactory(config);
|
|
5138
|
+
const childSessions = await loadRunnerChildSessionFactory(config, options);
|
|
5123
5139
|
try {
|
|
5124
5140
|
await runSubagent(config, childSessions);
|
|
5125
5141
|
} finally {
|
|
@@ -214,14 +214,13 @@ function matchesId(run: AsyncRunSummary, id: string): boolean {
|
|
|
214
214
|
}
|
|
215
215
|
|
|
216
216
|
function activeDetachedForegroundRuns(params: SubagentWaitParams, deps: SubagentWaitDeps): ForegroundResumeRun[] {
|
|
217
|
-
if (!params.id || !deps.state.foregroundRuns) return [];
|
|
218
217
|
const sessionId = deps.state.currentSessionId;
|
|
219
|
-
if (!sessionId) return [];
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
&& run.sessionId === sessionId
|
|
223
|
-
&& run.children.some((child) => child.status === "detached")
|
|
218
|
+
if (!sessionId || !deps.state.foregroundRuns) return [];
|
|
219
|
+
const runs = [...deps.state.foregroundRuns.values()].filter((run) =>
|
|
220
|
+
run.sessionId === sessionId && run.children.some((child) => child.status === "detached")
|
|
224
221
|
);
|
|
222
|
+
if (params.id) return runs.filter((run) => run.runId === params.id || run.runId.startsWith(params.id!));
|
|
223
|
+
return params.all === true ? runs : [];
|
|
225
224
|
}
|
|
226
225
|
|
|
227
226
|
function summarizeForegroundChildren(run: ForegroundResumeRun, indices: Set<number>): string {
|
|
@@ -534,6 +533,36 @@ async function waitForDetachedForegroundRun(
|
|
|
534
533
|
}
|
|
535
534
|
}
|
|
536
535
|
|
|
536
|
+
async function waitForSessionDetachedForegroundRuns(
|
|
537
|
+
runs: ForegroundResumeRun[],
|
|
538
|
+
signal: AbortSignal | undefined,
|
|
539
|
+
deps: SubagentWaitDeps,
|
|
540
|
+
startedAt: number,
|
|
541
|
+
now: () => number,
|
|
542
|
+
pollIntervalMs: number,
|
|
543
|
+
timeoutMs: number,
|
|
544
|
+
): Promise<AgentToolResult<Details>> {
|
|
545
|
+
const texts: string[] = [];
|
|
546
|
+
for (const run of runs) {
|
|
547
|
+
const one = await waitForDetachedForegroundRun(run, signal, deps, startedAt, now, pollIntervalMs, timeoutMs);
|
|
548
|
+
if (one.isError) return one;
|
|
549
|
+
if (one.details.wait?.reason === "window_elapsed") {
|
|
550
|
+
const activeRunIds = runs.filter((initial) => {
|
|
551
|
+
const current = deps.state.foregroundRuns?.get(initial.runId);
|
|
552
|
+
if (!current || current.sessionId !== initial.sessionId) return false;
|
|
553
|
+
return initial.children.some((child) => child.status === "detached"
|
|
554
|
+
&& current.children.some((candidate) => candidate.index === child.index && candidate.status === "detached"));
|
|
555
|
+
}).map((activeRun) => activeRun.runId);
|
|
556
|
+
return windowElapsedResult(
|
|
557
|
+
one.content.map((part) => part.type === "text" ? part.text : "").join("\n"),
|
|
558
|
+
activeRunIds,
|
|
559
|
+
);
|
|
560
|
+
}
|
|
561
|
+
texts.push(one.content.map((part) => part.type === "text" ? part.text : "").join("\n").trim());
|
|
562
|
+
}
|
|
563
|
+
return result(texts.filter(Boolean).join("\n") || `Waited ${formatDuration(now() - startedAt)} for remembered detached foreground run(s); done.`);
|
|
564
|
+
}
|
|
565
|
+
|
|
537
566
|
/**
|
|
538
567
|
* Block until the targeted async or remembered detached foreground run finishes,
|
|
539
568
|
* the timeout elapses, or the turn is aborted. Resolves with a short
|
|
@@ -570,7 +599,10 @@ export async function waitForSubagents(
|
|
|
570
599
|
let providerSnapshot: BackgroundWorkSnapshot;
|
|
571
600
|
try {
|
|
572
601
|
active = activeRunsForSession(params, deps);
|
|
573
|
-
foreground = activeDetachedForegroundRuns(params, deps)
|
|
602
|
+
foreground = activeDetachedForegroundRuns(params, deps).map((run) => ({
|
|
603
|
+
...run,
|
|
604
|
+
children: run.children.map((child) => ({ ...child })),
|
|
605
|
+
}));
|
|
574
606
|
providerSnapshot = params.id ? { providers: [], items: [] } : backgroundWorkForSession(deps, startedAt);
|
|
575
607
|
} catch (error) {
|
|
576
608
|
return result(error instanceof Error ? error.message : String(error), true);
|
|
@@ -606,6 +638,9 @@ export async function waitForSubagents(
|
|
|
606
638
|
|
|
607
639
|
let providerActive = providerSnapshot.items;
|
|
608
640
|
if (active.length === 0 && providerActive.length === 0) {
|
|
641
|
+
if (waitForAll && !params.id && foreground.length > 0) {
|
|
642
|
+
return waitForSessionDetachedForegroundRuns(foreground, signal, deps, startedAt, now, pollIntervalMs, timeoutMs);
|
|
643
|
+
}
|
|
609
644
|
return result(params.id
|
|
610
645
|
? `No active run matched "${params.id}". Nothing to wait for.`
|
|
611
646
|
: "No active async runs or registered provider work in this session. Nothing to wait for.");
|
|
@@ -697,6 +732,14 @@ export async function waitForSubagents(
|
|
|
697
732
|
const recoveryNote = formatCompletionRecovery(completions);
|
|
698
733
|
|
|
699
734
|
if (waitForAll) {
|
|
735
|
+
const foregroundResult = !params.id && foreground.length > 0 && relevantAttention.length === 0
|
|
736
|
+
? await waitForSessionDetachedForegroundRuns(foreground, signal, deps, startedAt, now, pollIntervalMs, timeoutMs)
|
|
737
|
+
: undefined;
|
|
738
|
+
if (foregroundResult?.isError) return foregroundResult;
|
|
739
|
+
if (foregroundResult?.details.wait?.reason === "window_elapsed") return foregroundResult;
|
|
740
|
+
const foregroundNote = foregroundResult
|
|
741
|
+
? `\n${foregroundResult.content.map((part) => part.type === "text" ? part.text : "").join("\n")}`
|
|
742
|
+
: "";
|
|
700
743
|
const scope = params.id
|
|
701
744
|
? `run "${params.id}"`
|
|
702
745
|
: initialProviderIds.size === 0
|
|
@@ -704,7 +747,7 @@ export async function waitForSubagents(
|
|
|
704
747
|
: `${initialAsyncIds.size} async run(s) and ${initialProviderIds.size} provider item(s)`;
|
|
705
748
|
const status = relevantAttention.length > 0 ? "attention required" : "done";
|
|
706
749
|
return result(
|
|
707
|
-
`Waited ${elapsed} for ${scope}; ${status}.${outcome}${recoveryNote}${resumeGuidance}${attentionNote} Completion/control events have been observed; inspect status if a notification is not visible yet.`,
|
|
750
|
+
`Waited ${elapsed} for ${scope}; ${status}.${outcome}${recoveryNote}${resumeGuidance}${attentionNote}${foregroundNote} Completion/control events have been observed; inspect status if a notification is not visible yet.`,
|
|
708
751
|
(deps.failOnFailedRuns === true && failedAsyncCount > 0) || (deps.failOnAttention === true && relevantAttention.length > 0),
|
|
709
752
|
completions,
|
|
710
753
|
);
|
|
@@ -17,7 +17,7 @@ export function registerWaitTool(
|
|
|
17
17
|
Ordinary async subagent runs already notify this session natively when they complete or need attention. In an interactive chat, return control instead of calling this merely to wait. Use this tool for provider jobs, remembered detached foreground runs, or other background work without a native notification path. Headless runs auto-drain current-session subagent work at agent_end; use this tool only when the current turn must receive non-notifying background work results.
|
|
18
18
|
|
|
19
19
|
• { } — return when the first initially active async run or registered provider item finishes, or when a subagent needs attention.
|
|
20
|
-
• { all: true } — wait for every async run
|
|
20
|
+
• { all: true } — wait for every async run, provider item, and remembered detached foreground descendant that was active when the call began.
|
|
21
21
|
• { id: "..." } — wait for one async or remembered detached foreground subagent run (id or prefix).
|
|
22
22
|
• { id: "...", nonBlocking: true } — resolve the prefix once, persist an exact-run wake subscription, and return immediately. Use this for detached work without native completion delivery; the originating interactive session wakes on completion, failure, attention, reconciliation failure, or timeout.
|
|
23
23
|
• { stopOnAttention: false } — for blocking waits only, keep waiting through idle or long-thinking attention; supervisor/contact requests still stop the wait.
|
|
@@ -3,13 +3,18 @@ import * as fs from "node:fs";
|
|
|
3
3
|
import * as path from "node:path";
|
|
4
4
|
import type { AgentToolResult } from "@earendil-works/pi-agent-core";
|
|
5
5
|
import { writeAtomicJson } from "../../shared/atomic-json.ts";
|
|
6
|
-
import type { AsyncStatus, Details, SubagentState, ToolBudgetConfig } from "../../shared/types.ts";
|
|
6
|
+
import type { AsyncStatus, Details, SteerActionResult, SubagentState, ToolBudgetConfig } from "../../shared/types.ts";
|
|
7
7
|
import { readStatus } from "../../shared/utils.ts";
|
|
8
8
|
import { deliverInterruptRequest, queueRevivalBrief, requestAsyncSteer, type SteerDeliveryMode, type SteerRequest } from "../background/control-channel.ts";
|
|
9
9
|
import { resolveAsyncResumeTarget } from "../background/async-resume.ts";
|
|
10
10
|
import { reconcileAsyncRun } from "../background/stale-run-reconciler.ts";
|
|
11
11
|
import { actionResultFromSteeringStatus, claimSteeringRecovery, createSteeringStatus, recordSteeringRequest, remainingSteeringRecoveryLimits, steeringReceipt, updateSteeringTarget, waitForSteeringAction } from "../background/steering.ts";
|
|
12
12
|
|
|
13
|
+
function steerTargetsAccepted(result: Pick<SteerActionResult, "targets"> | undefined): boolean {
|
|
14
|
+
const targets = result?.targets ?? [];
|
|
15
|
+
return targets.length > 0 && targets.every((target) => target.state === "queued" || target.state === "delivered");
|
|
16
|
+
}
|
|
17
|
+
|
|
13
18
|
export function canQueueRetainedAsyncFollowUp(status: AsyncStatus, index?: number): boolean {
|
|
14
19
|
const steps = status.steps ?? [];
|
|
15
20
|
return status.state === "complete"
|
|
@@ -170,9 +175,11 @@ export async function steerAsyncRun(input: {
|
|
|
170
175
|
}
|
|
171
176
|
const finalStatus = readStatus(asyncDir);
|
|
172
177
|
const finalResult = finalStatus?.steering ? actionResultFromSteeringStatus(finalStatus.steering, status.runId, requestId) : undefined;
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
178
|
+
const acceptedReply = (accepted: SteerActionResult): AgentToolResult<Details> => {
|
|
179
|
+
const acceptedState = accepted.state === "delivered" ? "delivered" : "queued";
|
|
180
|
+
return { content: [{ type: "text", text: steeringReceipt(input.message, `Steering ${acceptedState} for async run ${status.runId} (request ${requestId}).`) }], details: { mode: "management", results: [], steering: accepted } };
|
|
181
|
+
};
|
|
182
|
+
if (finalResult && steerTargetsAccepted(finalResult)) return acceptedReply(finalResult);
|
|
176
183
|
const running = (finalStatus?.steps ?? status.steps ?? []).filter((step) => step.status === "running");
|
|
177
184
|
const recoveryAllowed = (input.mode ?? "steer") === "steer" && status.mode === "single" && status.isNested !== true && running.length === 1 && Boolean(finalStatus?.steering) && (input.index === undefined || input.index === 0);
|
|
178
185
|
if (recoveryAllowed && finalResult?.state !== "scheduled" && input.recover) {
|
|
@@ -186,17 +193,21 @@ export async function steerAsyncRun(input: {
|
|
|
186
193
|
try {
|
|
187
194
|
const latest = readStatus(asyncDir);
|
|
188
195
|
const latestResult = latest?.steering ? actionResultFromSteeringStatus(latest.steering, status.runId, requestId) : undefined;
|
|
189
|
-
if (latestResult
|
|
196
|
+
if (latestResult && steerTargetsAccepted(latestResult)) return acceptedReply(latestResult);
|
|
190
197
|
const committedAt = Date.now();
|
|
191
198
|
input.onBeforeRecoveryClaim?.(requestId, committedAt);
|
|
192
199
|
const { claimPath, markerPath } = claimSteeringRecovery(asyncDir, { requestId, sourceRunId: status.runId, committedAt });
|
|
193
200
|
input.onRecoveryCommitted?.(requestId, committedAt);
|
|
194
201
|
const preCommitStatus = readStatus(asyncDir);
|
|
195
202
|
const preCommitResult = preCommitStatus?.steering ? actionResultFromSteeringStatus(preCommitStatus.steering, status.runId, requestId) : undefined;
|
|
196
|
-
if (
|
|
203
|
+
if (
|
|
204
|
+
preCommitResult
|
|
205
|
+
&& preCommitResult.targets.length > 0
|
|
206
|
+
&& preCommitResult.targets.every((target) => target.state === "queued" || (target.deliveredAt !== undefined && target.deliveredAt <= committedAt))
|
|
207
|
+
) {
|
|
197
208
|
fs.rmSync(markerPath, { force: true });
|
|
198
209
|
fs.rmSync(claimPath, { force: true });
|
|
199
|
-
return
|
|
210
|
+
return acceptedReply(preCommitResult);
|
|
200
211
|
}
|
|
201
212
|
try {
|
|
202
213
|
deliverInterruptRequest({ asyncDir, source: "steering-recovery" });
|
|
@@ -6,8 +6,8 @@ import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
|
6
6
|
import * as path from "node:path";
|
|
7
7
|
import type { Message } from "@earendil-works/pi-ai";
|
|
8
8
|
import { discoverAgents, formatUnknownAgentError, unknownAgentDiagnosticContext, type AgentConfig } from "../../agents/agents.ts";
|
|
9
|
-
import { appendAgentRefinementOverlay } from "../../agents/agent-refinements.ts";
|
|
10
9
|
import { alignForkedSessionCwd } from "../../shared/fork-session-cwd.ts";
|
|
10
|
+
import { buildEffectiveSystemPrompt } from "../shared/effective-system-prompt.ts";
|
|
11
11
|
import {
|
|
12
12
|
ensureArtifactsDir,
|
|
13
13
|
formatOutputArtifactContent,
|
|
@@ -52,8 +52,7 @@ import {
|
|
|
52
52
|
boundStreamedRecentOutput,
|
|
53
53
|
boundStreamedToolCalls,
|
|
54
54
|
} from "../../shared/utils.ts";
|
|
55
|
-
import {
|
|
56
|
-
import { buildAgentMemoryInjection } from "../../agents/agent-memory.ts";
|
|
55
|
+
import { resolveSkillsWithFallback } from "../../agents/skills.ts";
|
|
57
56
|
import { effectiveToolTimeoutMs, formatToolTimeoutMessage, resolveToolTimeoutMs, toolTimeoutCallKey, toolTimeoutFromEnv } from "../shared/tool-timeout.ts";
|
|
58
57
|
import { evaluateCompletionMutationGuard, expectsImplementationMutation, hasMutationToolCapability, validateImplementationToolContract } from "../shared/completion-guard.ts";
|
|
59
58
|
import { planCompletionEvidence } from "../shared/completion-evidence.ts";
|
|
@@ -75,7 +74,7 @@ import { MISSING_STRUCTURED_ACCEPTANCE_REPORT_ERROR, MISSING_STRUCTURED_OUTPUT_C
|
|
|
75
74
|
import { formatMidToolExitError, isOrdinaryToolForMidToolExit } from "../shared/process-signal.ts";
|
|
76
75
|
import { formatChildToolDiagnostic } from "../shared/tool-availability.ts";
|
|
77
76
|
import { buildTimeoutRecoverySummary, collectTrackedMutationEvidence, snapshotTrackedMutations } from "../shared/mutation-evidence.ts";
|
|
78
|
-
import { captureSingleOutputSnapshot, extractChildWrittenOutput, finalizeSingleOutput, formatSavedOutputReference, hasSingleOutputChangedSinceSnapshot,
|
|
77
|
+
import { captureSingleOutputSnapshot, extractChildWrittenOutput, finalizeSingleOutput, formatSavedOutputReference, hasSingleOutputChangedSinceSnapshot, resolveSingleOutput, validateFileOnlyOutputMode, type SingleOutputSnapshot } from "../shared/single-output.ts";
|
|
79
78
|
import {
|
|
80
79
|
buildModelCandidates,
|
|
81
80
|
formatSubagentModelVerificationError,
|
|
@@ -100,7 +99,7 @@ import { PROMPT_REDACTED } from "../../shared/utils.ts";
|
|
|
100
99
|
import { attachContractProjections, isAgentContract } from "../shared/agent-contract.ts";
|
|
101
100
|
import { initialToolBudgetState, toolBudgetState } from "../shared/tool-budget.ts";
|
|
102
101
|
import { resolveWatchdogConfig } from "../../watchdog/settings.ts";
|
|
103
|
-
import {
|
|
102
|
+
import { resolveLaunchBinding } from "../../shared/launch-contract.ts";
|
|
104
103
|
import { consumeWorkflowChildPermit } from "../../shared/workflow-child-permit.ts";
|
|
105
104
|
import { projectChildLifecycle, type ChildLifecycleAction, type ChildLifecycleState } from "../shared/child-lifecycle.ts";
|
|
106
105
|
import {
|
|
@@ -113,7 +112,7 @@ import {
|
|
|
113
112
|
type ChildWatchdogStatusEvent,
|
|
114
113
|
} from "../../watchdog/child-status.ts";
|
|
115
114
|
import { buildInProcessChildLaunch, createReportedChildSessionInput } from "../shared/child-launch.ts";
|
|
116
|
-
import { childSessionFactory, projectChildSessionEventForJson, type ChildSession, type ChildSessionEvent } from "../shared/child-session.ts";
|
|
115
|
+
import { childSessionFactory, childSessionHasQueuedMessages, projectChildSessionEventForJson, type ChildSession, type ChildSessionEvent } from "../shared/child-session.ts";
|
|
117
116
|
|
|
118
117
|
const artifactOutputByResult = new WeakMap<SingleResult, string>();
|
|
119
118
|
const acceptanceOutputByResult = new WeakMap<SingleResult, string>();
|
|
@@ -399,6 +398,7 @@ async function runSingleAttempt(
|
|
|
399
398
|
: undefined;
|
|
400
399
|
let onWatchdogStatus: ((event: ChildWatchdogStatusEvent) => void) | undefined;
|
|
401
400
|
const launch = buildInProcessChildLaunch({
|
|
401
|
+
extensionBindings: options.extensionBindings,
|
|
402
402
|
sessionEnabled: shared.sessionEnabled,
|
|
403
403
|
sessionDir: options.sessionDir,
|
|
404
404
|
sessionFile: options.sessionFile,
|
|
@@ -442,6 +442,7 @@ async function runSingleAttempt(
|
|
|
442
442
|
thinkingCeiling: options.thinkingCeiling,
|
|
443
443
|
maxSubagentDepth: options.maxSubagentDepth,
|
|
444
444
|
runtimeSnapshotHost: options.runtimeSnapshotHost,
|
|
445
|
+
hostAvailableBuiltins: options.hostAvailableBuiltins,
|
|
445
446
|
inherited: options.childRuntime,
|
|
446
447
|
host: "parent",
|
|
447
448
|
});
|
|
@@ -482,24 +483,16 @@ async function runSingleAttempt(
|
|
|
482
483
|
...(toolPlan.capabilityAudit ? { capabilityAudit: toolPlan.capabilityAudit } : {}),
|
|
483
484
|
};
|
|
484
485
|
}
|
|
485
|
-
const
|
|
486
|
-
|
|
486
|
+
const fast = options.fast ?? agent.fast;
|
|
487
|
+
const { launchContractDigest } = resolveLaunchBinding({
|
|
488
|
+
agent,
|
|
487
489
|
task: shared.originalTask ?? task,
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
...((options.fast ?? agent.fast) !== undefined ? { fast: options.fast ?? agent.fast } : {}),
|
|
490
|
+
modelCandidates: shared.modelCandidates ?? [],
|
|
491
|
+
...(fast !== undefined ? { fast } : {}),
|
|
491
492
|
...(resolvedThinking ? { thinking: resolvedThinking } : {}),
|
|
492
|
-
...(options.thinkingCeiling ? { thinkingCeiling: options.thinkingCeiling } : {}),
|
|
493
493
|
systemPrompt: effectiveSystemPrompt,
|
|
494
|
-
systemPromptMode: agent.systemPromptMode,
|
|
495
|
-
inheritProjectContext: agent.inheritProjectContext,
|
|
496
|
-
inheritGlobalContext: agent.inheritGlobalContext,
|
|
497
|
-
inheritSkills: agent.inheritSkills,
|
|
498
494
|
skills: shared.resolvedSkillNames ?? [],
|
|
499
|
-
|
|
500
|
-
...(toolPlan.excludeTools.length > 0 ? { excludeTools: toolPlan.excludeTools } : {}),
|
|
501
|
-
extensions: toolPlan.extensionArgs,
|
|
502
|
-
mcpDirectTools: toolPlan.effectiveMcpTools,
|
|
495
|
+
toolPlan,
|
|
503
496
|
...(options.outputPath ? { outputPath: options.outputPath } : {}),
|
|
504
497
|
outputMode: options.outputMode ?? "inline",
|
|
505
498
|
...(options.structuredOutput ? { structuredOutputSchema: options.structuredOutput.schema } : {}),
|
|
@@ -681,6 +674,7 @@ async function runSingleAttempt(
|
|
|
681
674
|
let forcedTermination = false;
|
|
682
675
|
let cleanTerminalAssistantStopReceived = false;
|
|
683
676
|
let agentSettledReceived = false;
|
|
677
|
+
let queuedDrainHold = false;
|
|
684
678
|
let compactionStartedReceived = false;
|
|
685
679
|
let finalDrainTimer: NodeJS.Timeout | undefined;
|
|
686
680
|
let finalHardFinishTimer: NodeJS.Timeout | undefined;
|
|
@@ -707,14 +701,28 @@ async function runSingleAttempt(
|
|
|
707
701
|
finalHardFinishTimer = undefined;
|
|
708
702
|
}
|
|
709
703
|
};
|
|
704
|
+
const observeQueuedDrainHold = (): boolean => {
|
|
705
|
+
if (childSessionHasQueuedMessages(session)) queuedDrainHold = true;
|
|
706
|
+
return queuedDrainHold;
|
|
707
|
+
};
|
|
710
708
|
const startFinalDrain = () => {
|
|
711
709
|
if (childWatchdogIsActive(childWatchdogState)) {
|
|
712
710
|
armWatchdogTail();
|
|
713
711
|
return;
|
|
714
712
|
}
|
|
713
|
+
if (sessionSettled || finalDrainTimer || lifecycleFinished) return;
|
|
714
|
+
if (observeQueuedDrainHold()) return;
|
|
715
|
+
armFinalDrainTimer();
|
|
716
|
+
};
|
|
717
|
+
const armFinalDrainTimer = () => {
|
|
715
718
|
if (sessionSettled || finalDrainTimer || lifecycleFinished) return;
|
|
716
719
|
finalDrainTimer = setTimeout(() => {
|
|
717
720
|
if (lifecycleFinished || sessionSettled) return;
|
|
721
|
+
if (capture.finalDrainHeld() || observeQueuedDrainHold()) {
|
|
722
|
+
finalDrainTimer = undefined;
|
|
723
|
+
armFinalDrainTimer();
|
|
724
|
+
return;
|
|
725
|
+
}
|
|
718
726
|
forcedTermination = true;
|
|
719
727
|
if (!cleanTerminalAssistantStopReceived && !agentSettledReceived && !assistantError) {
|
|
720
728
|
result.error = result.error ?? `Subagent session did not settle within ${FINAL_STOP_GRACE_MS}ms after its terminal event. Aborting it.`;
|
|
@@ -746,6 +754,8 @@ async function runSingleAttempt(
|
|
|
746
754
|
}
|
|
747
755
|
const applyChildLifecycle = (action: ChildLifecycleAction): void => {
|
|
748
756
|
if (action === "cancel-drain") {
|
|
757
|
+
cleanTerminalAssistantStopReceived = false;
|
|
758
|
+
agentSettledReceived = false;
|
|
749
759
|
clearFinalDrainTimers();
|
|
750
760
|
clearWatchdogTailTimer();
|
|
751
761
|
return;
|
|
@@ -992,6 +1002,9 @@ async function runSingleAttempt(
|
|
|
992
1002
|
compactionStartedReceived = false;
|
|
993
1003
|
afterCompactionSettlement = false;
|
|
994
1004
|
}
|
|
1005
|
+
if (evt.type === "turn_start" || evt.type === "agent_start" || evt.type === "auto_retry_start") {
|
|
1006
|
+
queuedDrainHold = false;
|
|
1007
|
+
}
|
|
995
1008
|
if (evt.type === "agent_start" || evt.type === "auto_retry_start") {
|
|
996
1009
|
compactionStartedReceived = false;
|
|
997
1010
|
afterCompactionSettlement = false;
|
|
@@ -1378,6 +1391,16 @@ async function runSingleAttempt(
|
|
|
1378
1391
|
return;
|
|
1379
1392
|
}
|
|
1380
1393
|
session = created;
|
|
1394
|
+
const steer = created.steer.bind(created);
|
|
1395
|
+
const followUp = created.followUp.bind(created);
|
|
1396
|
+
created.steer = async (text) => {
|
|
1397
|
+
if (cleanTerminalAssistantStopReceived || agentSettledReceived) queuedDrainHold = true;
|
|
1398
|
+
return steer(text);
|
|
1399
|
+
};
|
|
1400
|
+
created.followUp = async (text) => {
|
|
1401
|
+
if (cleanTerminalAssistantStopReceived || agentSettledReceived) queuedDrainHold = true;
|
|
1402
|
+
return followUp(text);
|
|
1403
|
+
};
|
|
1381
1404
|
created.detached = detached;
|
|
1382
1405
|
unsubscribe = created.subscribe((event) => processEvent(event as Parameters<typeof processEvent>[0]));
|
|
1383
1406
|
if (abortedBySignal || interruptedByControl || result.timedOut) {
|
|
@@ -1779,17 +1802,7 @@ async function runSyncCompletionInner(
|
|
|
1779
1802
|
error: "Skills not found: pi-subagents",
|
|
1780
1803
|
}, options.context));
|
|
1781
1804
|
}
|
|
1782
|
-
|
|
1783
|
-
if (resolvedSkills.length > 0) {
|
|
1784
|
-
const skillInjection = buildSkillInjection(resolvedSkills);
|
|
1785
|
-
systemPrompt = systemPrompt ? `${systemPrompt}\n\n${skillInjection}` : skillInjection;
|
|
1786
|
-
}
|
|
1787
|
-
const memoryInjection = buildAgentMemoryInjection(agent, skillCwd);
|
|
1788
|
-
if (memoryInjection) {
|
|
1789
|
-
systemPrompt = systemPrompt ? `${systemPrompt}\n\n${memoryInjection}` : memoryInjection;
|
|
1790
|
-
}
|
|
1791
|
-
systemPrompt = appendAgentRefinementOverlay(systemPrompt, { cwd: skillCwd, agentName });
|
|
1792
|
-
systemPrompt = injectOutputPathSystemPrompt(systemPrompt, options.outputPath, agent);
|
|
1805
|
+
const systemPrompt = buildEffectiveSystemPrompt({ agent, resolvedSkills, cwd: skillCwd, ...(options.outputPath ? { outputPath: options.outputPath } : {}) });
|
|
1793
1806
|
|
|
1794
1807
|
const candidates = buildModelCandidates(
|
|
1795
1808
|
options.modelOverride ?? agent.model,
|
|
@@ -2,6 +2,7 @@ import type { Agent, StreamFn, ThinkingLevel } from "@earendil-works/pi-agent-co
|
|
|
2
2
|
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
3
3
|
import type { ProviderHeaders } from "@earendil-works/pi-ai";
|
|
4
4
|
import { agentStreamOptions } from "../../shared/agent-stream-options.ts";
|
|
5
|
+
import { opencodeSessionHeaders } from "../../shared/opencode-session-headers.ts";
|
|
5
6
|
import type { ForegroundRunControl } from "../../shared/types.ts";
|
|
6
7
|
export type PromptAuditView = "authored" | "runtime" | "effective";
|
|
7
8
|
|
|
@@ -75,11 +76,12 @@ export async function rewritePromptWithGuidance(input: {
|
|
|
75
76
|
const baseStreamFn = input.streamFn ?? (registeredProvider?.streamSimple && registeredProvider.api === model.api
|
|
76
77
|
? registeredProvider.streamSimple
|
|
77
78
|
: streamSimple);
|
|
79
|
+
const sessionId = input.ctx.sessionManager.getSessionId();
|
|
78
80
|
const streamFn: StreamFn = (nextModel, context, streamOptions) => baseStreamFn(nextModel, context, {
|
|
79
81
|
...streamOptions,
|
|
80
82
|
...(auth.apiKey ? { apiKey: auth.apiKey } : {}),
|
|
81
83
|
env: auth.env || streamOptions?.env ? { ...(auth.env ?? {}), ...(streamOptions?.env ?? {}) } : undefined,
|
|
82
|
-
headers: { ...(streamOptions?.headers ?? {}), ...(auth.headers ?? {}) },
|
|
84
|
+
headers: { ...opencodeSessionHeaders(nextModel, sessionId), ...(streamOptions?.headers ?? {}), ...(auth.headers ?? {}) },
|
|
83
85
|
});
|
|
84
86
|
const ctxThinking = (input.ctx as { getThinkingLevel?: () => ThinkingLevel }).getThinkingLevel?.();
|
|
85
87
|
const agent = new Agent({
|