pi-subagents 0.81.0 → 0.82.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 +38 -0
- package/bundle/agents.js +3 -3
- package/bundle/child-tool-plan.js +2 -2
- package/bundle/{chunk-LSRUTHT7.js → chunk-43SCHIMD.js} +1 -1
- package/bundle/{chunk-G5DKFLLI.js → chunk-4SDU2BZX.js} +15 -10
- package/bundle/{chunk-V2JWDB5U.js → chunk-52UBALJL.js} +2 -2
- package/bundle/{chunk-3DC5KBVP.js → chunk-5TYMSZX3.js} +1 -1
- package/bundle/{chunk-CEKS3TFO.js → chunk-7IVSUMQQ.js} +6 -6
- package/bundle/{chunk-KSWQVGDW.js → chunk-7PCKZHR5.js} +225 -18
- package/bundle/{chunk-563BHAZ5.js → chunk-7YMXOYEU.js} +33 -25
- package/bundle/{chunk-XMFLCKMQ.js → chunk-B5EKXDHE.js} +336 -130
- package/bundle/{chunk-ZBC44JJ3.js → chunk-CIZQXJ5Q.js} +22 -9
- package/bundle/{chunk-L5ZHE4L4.js → chunk-GC4DSPMH.js} +4 -4
- package/bundle/{chunk-ARRKBB2B.js → chunk-GJECHSAR.js} +2 -2
- package/bundle/{chunk-2ZSNH26H.js → chunk-M2CTFYT3.js} +7 -7
- package/bundle/{chunk-TUWLXTAM.js → chunk-MHZ4G23A.js} +681 -320
- package/bundle/{chunk-WSBNENFS.js → chunk-NGGAFDL6.js} +14 -4
- package/bundle/{chunk-VJHOELQP.js → chunk-NRQ75MGH.js} +160 -78
- package/bundle/{chunk-BJN5LUQY.js → chunk-OYF6AFDY.js} +1 -1
- package/bundle/{chunk-EYNJ6VL6.js → chunk-PLCCBLPY.js} +2 -2
- package/bundle/{chunk-74RT5YQE.js → chunk-RMQFMMAT.js} +123 -33
- package/bundle/{chunk-V6MR5TOQ.js → chunk-SIJ4S3EN.js} +25 -19
- package/bundle/{chunk-NNLASTQE.js → chunk-VH5OAWVG.js} +19 -5
- package/bundle/{chunk-BPS2AGBZ.js → chunk-W2SZ22EM.js} +93 -52
- package/bundle/{chunk-V3BIUPLU.js → chunk-WVD4UEL4.js} +256 -52
- package/bundle/{chunk-ZP3HODFH.js → chunk-XSWOTIA3.js} +1 -1
- package/bundle/{chunk-3IF2PGW7.js → chunk-YKEWIB4M.js} +1 -1
- package/bundle/{chunk-26C3WLYQ.js → chunk-ZLINIYTP.js} +497 -272
- package/bundle/{chunk-2XYWN7QJ.js → chunk-ZQGSDHL6.js} +2 -2
- package/bundle/control-channel.js +2 -2
- package/bundle/index.js +1 -1
- package/bundle/intercom-bridge.js +2 -2
- package/bundle/preflight.js +8 -8
- package/bundle/project-panes.js +3 -3
- package/docs/agents.md +31 -0
- package/docs/configuration.md +9 -1
- package/docs/tool-reference.md +3 -1
- package/docs/workflows.md +16 -0
- package/package.json +2 -2
- package/skills/pi-subagents/references/execution-controls.md +5 -4
- package/src/agents/agent-management.js +3 -3
- package/src/agents/agent-memory.js +3 -1
- package/src/agents/agents.js +6 -6
- package/src/agents/frontmatter.d.ts +2 -0
- package/src/agents/frontmatter.js +15 -1
- package/src/agents/skills.js +2 -1
- package/src/extension/config.js +3 -0
- package/src/extension/herdr-pi-bridge.js +17 -10
- package/src/extension/index.js +82 -2
- package/src/extension/schemas.js +3 -3
- package/src/intercom/native-supervisor-channel.js +20 -0
- package/src/missions/lifecycle.js +7 -1
- package/src/runs/background/active-async-capacity.d.ts +1 -0
- package/src/runs/background/active-async-capacity.js +9 -3
- package/src/runs/background/async-execution.js +20 -10
- package/src/runs/background/async-job-tracker.d.ts +6 -1
- package/src/runs/background/async-job-tracker.js +9 -4
- package/src/runs/background/async-status.d.ts +2 -0
- package/src/runs/background/async-status.js +21 -2
- package/src/runs/background/await-async-run.js +3 -7
- package/src/runs/background/boot-id.d.ts +12 -0
- package/src/runs/background/boot-id.js +47 -0
- package/src/runs/background/interrupted-elsewhere.d.ts +22 -0
- package/src/runs/background/interrupted-elsewhere.js +68 -0
- package/src/runs/background/notify.d.ts +2 -0
- package/src/runs/background/notify.js +13 -0
- package/src/runs/background/result-delivery-ownership.d.ts +6 -0
- package/src/runs/background/result-delivery-ownership.js +19 -1
- package/src/runs/background/result-watcher.d.ts +3 -1
- package/src/runs/background/result-watcher.js +24 -8
- package/src/runs/background/run-child-session.d.ts +1 -6
- package/src/runs/background/run-child-session.js +1 -1
- package/src/runs/background/run-status.js +2 -1
- package/src/runs/background/stale-run-reconciler.d.ts +25 -0
- package/src/runs/background/stale-run-reconciler.js +52 -13
- package/src/runs/background/subagent-runner-bootstrap.js +3 -0
- package/src/runs/background/subagent-runner.d.ts +4 -0
- package/src/runs/background/subagent-runner.js +13 -5
- package/src/runs/background/subagent-wait.d.ts +1 -1
- package/src/runs/background/subagent-wait.js +6 -1
- package/src/runs/background/terminal-run-index.d.ts +1 -0
- package/src/runs/background/terminal-run-index.js +4 -2
- package/src/runs/foreground/async-stop-action.js +3 -1
- package/src/runs/foreground/execution.js +6 -3
- package/src/runs/foreground/foreground-history.d.ts +16 -2
- package/src/runs/foreground/foreground-history.js +133 -31
- package/src/runs/foreground/subagent-executor.d.ts +11 -0
- package/src/runs/foreground/subagent-executor.js +277 -74
- package/src/runs/shared/child-launch.d.ts +2 -2
- package/src/runs/shared/child-session.d.ts +3 -2
- package/src/runs/shared/child-session.js +51 -8
- package/src/runs/shared/child-tool-plan.d.ts +7 -0
- package/src/runs/shared/child-tool-plan.js +21 -6
- package/src/runs/shared/herdr-connection.d.ts +17 -8
- package/src/runs/shared/herdr-connection.js +19 -7
- package/src/runs/shared/herdr-external-adapters.d.ts +3 -2
- package/src/runs/shared/herdr-external-adapters.js +1 -1
- package/src/runs/shared/herdr-machine.d.ts +12 -2
- package/src/runs/shared/herdr-machine.js +55 -9
- package/src/runs/shared/herdr-pane-host.d.ts +88 -0
- package/src/runs/shared/herdr-pane-host.js +155 -0
- package/src/runs/shared/herdr-placed-run.d.ts +27 -150
- package/src/runs/shared/herdr-placed-run.js +48 -395
- package/src/runs/shared/local-machine-access.d.ts +5 -0
- package/src/runs/shared/local-machine-access.js +28 -0
- package/src/runs/shared/orca-progress-tabs.d.ts +0 -1
- package/src/runs/shared/orca-progress-tabs.js +1 -24
- package/src/runs/shared/parallel-utils.d.ts +1 -1
- package/src/runs/shared/placed-run-owner.d.ts +49 -0
- package/src/runs/shared/placed-run-owner.js +93 -0
- package/src/runs/shared/placement-host.d.ts +99 -0
- package/src/runs/shared/placement-host.js +157 -0
- package/src/runs/shared/rex-machine.d.ts +25 -0
- package/src/runs/shared/rex-machine.js +83 -0
- package/src/runs/shared/rex-pane-host.d.ts +62 -0
- package/src/runs/shared/rex-pane-host.js +327 -0
- package/src/shared/completion-owner.d.ts +5 -0
- package/src/shared/completion-owner.js +5 -0
- package/src/shared/session-identity.d.ts +2 -0
- package/src/shared/session-identity.js +26 -0
- package/src/shared/types.d.ts +45 -6
- package/src/tui/fleet.d.ts +1 -0
- package/src/tui/fleet.js +24 -15
- package/src/tui/render.d.ts +6 -0
- package/src/tui/render.js +3 -3
- package/src/tui/subagent-messages.js +1 -0
- package/src/workflows/scripted-workflow.d.ts +1 -1
- package/src/workflows/workflow-auto-resume.d.ts +32 -0
- package/src/workflows/workflow-auto-resume.js +102 -0
- package/src/workflows/workflow-launch-record.d.ts +27 -0
- package/src/workflows/workflow-launch-record.js +48 -0
- package/src/workflows/workflow-resume-claim.d.ts +82 -0
- package/src/workflows/workflow-resume-claim.js +210 -0
- package/src/workflows/workflow-resume.d.ts +56 -0
- package/src/workflows/workflow-resume.js +106 -0
- package/src/workflows/workflow-reuse.d.ts +15 -9
- package/src/workflows/workflow-reuse.js +77 -14
- package/src/workflows/workflow-revival.d.ts +2 -0
- package/src/workflows/workflow-revival.js +2 -1
- package/src/workflows/workflow-stop-cause.d.ts +6 -0
- package/src/workflows/workflow-stop-cause.js +8 -0
|
@@ -9,7 +9,7 @@ import { isStorageCapacityError } from "../../shared/file-system-retry.js";
|
|
|
9
9
|
import { resolveEffectiveThinking, toModelInfo } from "../../shared/model-info.js";
|
|
10
10
|
import { beginForegroundChild, finishForegroundChild, foregroundSchedulingSettled, settleForegroundSchedulingOwner, updateForegroundChild, } from "./foreground-control.js";
|
|
11
11
|
import { getLivePromptAudit, rewritePromptWithGuidance, updateLiveEffectivePrompt } from "./prompt-audit.js";
|
|
12
|
-
import {
|
|
12
|
+
import { discardRunningForegroundRun, persistForegroundRun, MAX_REMEMBERED_FOREGROUND_RUNS } from "./foreground-history.js";
|
|
13
13
|
import { projectScopeRequiresTrustMessage, resolveExecutionAgentScope } from "../../agents/agent-scope.js";
|
|
14
14
|
import { handleManagementAction } from "../../agents/agent-management.js";
|
|
15
15
|
import { handleRefinementAction } from "../../agents/agent-refinements.js";
|
|
@@ -71,6 +71,7 @@ import { dismissRecoveredWorkflow } from "./async-dismiss-action.js";
|
|
|
71
71
|
import { inheritedSessionIds } from "../../shared/session-succession.js";
|
|
72
72
|
import { promotePausedWorkflowIfSettled, reconcileDetachedWorkflowChildCompletion } from "./workflow-detach-reconcile.js";
|
|
73
73
|
import { reconcileAsyncRun } from "../background/stale-run-reconciler.js";
|
|
74
|
+
import { currentRecordedPid } from "../background/boot-id.js";
|
|
74
75
|
import { resolveAsyncRootResultPath, waitForImportedAsyncRoot } from "../background/chain-root-attachment.js";
|
|
75
76
|
import { awaitExistingAsyncRun, claimWorkflowAwaitedResult } from "../background/await-async-run.js";
|
|
76
77
|
import { fallbackResultPayloadPathForSessionRun, resultFilePath, retireResultSnapshot, writeAsyncResultFile } from "../background/result-files.js";
|
|
@@ -82,7 +83,7 @@ import { inspectSubagentStatus } from "../background/run-status.js";
|
|
|
82
83
|
import { getExternalJobProvider } from "../../api/external-job-provider.js";
|
|
83
84
|
import { externalJobFollowUpRequestDigest, externalJobFollowUpRequestId, externalJobFollowUpRunId, externalJobPromptDigest, externalJobStableJson } from "../shared/external-job-runner.js";
|
|
84
85
|
import { externalCliReceiptMetadata, normalizeExternalCliRunnerStatus } from "../shared/external-cli-contract.js";
|
|
85
|
-
import { formatHerdrMachineRunnerUnsupported,
|
|
86
|
+
import { formatHerdrMachineRunnerUnsupported, resolveMachinePlacement } from "../shared/herdr-machine.js";
|
|
86
87
|
import { lookupRunnerLauncher, resolveAgentRunnerLauncher } from "../shared/runner-launcher.js";
|
|
87
88
|
import { applyForceTopLevelAsyncOverride } from "../background/top-level-async.js";
|
|
88
89
|
import { handleMissionAction, MISSION_ACTIONS } from "../../missions/actions.js";
|
|
@@ -96,7 +97,9 @@ import { handleHerdrProjectPaneAction, HERDR_PROJECT_PANE_ACTIONS } from "../../
|
|
|
96
97
|
import { previewSimpleWorkflowRun, runWorkflowScript, validateWorkflowScript, WorkflowScriptError } from "../../workflows/scripted-workflow.js";
|
|
97
98
|
import { formatIncrementalChildCompletion, incrementalChildCompletionTriggersTurn } from "../background/notify.js";
|
|
98
99
|
import { COMPLETION_ACTION, STATUS_ACTION } from "../../shared/parent-wake.js";
|
|
99
|
-
import { appendWorkflowChildJournal, findWorkflowReuseSource, matchWorkflowReuse, readNamedWorkflowReuseSource,
|
|
100
|
+
import { appendWorkflowChildJournal, claimedWorkflowRunRefusal, findWorkflowReuseSource, matchWorkflowReuse, readNamedWorkflowReuseSource, WORKFLOW_RUNTIME_REPLACED_STOP_NOTICE, workflowChildFingerprint, workflowScriptDigest, workflowStopCause } from "../../workflows/workflow-reuse.js";
|
|
101
|
+
import { claimWorkflowResume, recordWorkflowResumeRefusal, releaseWorkflowResumeClaim, resumedRunStarted } from "../../workflows/workflow-resume-claim.js";
|
|
102
|
+
import { manualWorkflowResumeRefusalText, prepareWorkflowResume, workflowResumeRefusal, workflowResumeTargetId } from "../../workflows/workflow-resume.js";
|
|
100
103
|
import { executeWorkflowHostCommand, resolveWorkflowHostOutputClaimPath } from "../../workflows/host-command.js";
|
|
101
104
|
import { buildWorkflowReceipt, readWorkflowReceipt, workflowReceiptPath, resolveWorkflowReceiptResumeEntry, writeWorkflowReceipt } from "../../workflows/workflow-receipt.js";
|
|
102
105
|
import { formatWorkflowKeyRevival, recordWorkflowRevival, withWorkflowRevivals } from "../../workflows/workflow-revival.js";
|
|
@@ -107,6 +110,7 @@ import { resolveWorkflowChatProgress } from "../../workflows/chat-progress.js";
|
|
|
107
110
|
import { annotateWorkflowPreflightTrace, formatWorkflowPreflight, formatWorkflowPreflightWarnings, normalizeWorkflowPreflight, workflowPreflightWarnings } from "../../workflows/workflow-preflight.js";
|
|
108
111
|
import { authorizeWorkflowResourceHost, claimWorkflowChildPermit, consumeWorkflowResourcePermit, validateWorkflowChildPermitRoot, } from "../../shared/workflow-child-permit.js";
|
|
109
112
|
import { deepFreezeWorkflowArgs, normalizeWorkflowArgs, resolveStructuredWorkflowResource, resolveWorkflowResource } from "../../workflows/workflow-resources.js";
|
|
113
|
+
import { writeWorkflowLaunchRecord } from "../../workflows/workflow-launch-record.js";
|
|
110
114
|
import { stableJsonDigest } from "../../shared/launch-contract.js";
|
|
111
115
|
import { cleanupWorktrees, createWorktrees, preflightWorktreeSource, withWorktreeTransaction, diffWorktrees, formatWorktreeDiffSummary, } from "../shared/worktree.js";
|
|
112
116
|
import { applyReviewedCleanupPlan, formatCleanupReceipt, loadReviewedCleanupPlan } from "../shared/worktree-cleanup-apply.js";
|
|
@@ -400,9 +404,12 @@ function trimRememberedForegroundRuns(state) {
|
|
|
400
404
|
state.foregroundRuns.delete(oldestTerminal.runId);
|
|
401
405
|
}
|
|
402
406
|
}
|
|
403
|
-
function
|
|
407
|
+
function persistRememberedForegroundRun(state, runId) {
|
|
408
|
+
const run = state.foregroundRuns?.get(runId);
|
|
409
|
+
if (!run)
|
|
410
|
+
return;
|
|
404
411
|
try {
|
|
405
|
-
|
|
412
|
+
persistForegroundRun(run);
|
|
406
413
|
}
|
|
407
414
|
catch (error) {
|
|
408
415
|
console.error("Failed to persist foreground run history:", error);
|
|
@@ -422,6 +429,50 @@ function foregroundChildActivityFromProgress(progress) {
|
|
|
422
429
|
...(progress?.toolCount !== undefined ? { toolCount: progress.toolCount } : {}),
|
|
423
430
|
};
|
|
424
431
|
}
|
|
432
|
+
/** One remembered child: the result's outcome plus the launch contract that resume needs. */
|
|
433
|
+
function rememberedForegroundChild(input, result, index, status, updatedAt) {
|
|
434
|
+
const resumeContract = omitUndefinedProperties({
|
|
435
|
+
modelResponseAliases: input.modelResponseAliases,
|
|
436
|
+
outputSchema: input.params.outputSchema,
|
|
437
|
+
agentContract: input.params.agentContract,
|
|
438
|
+
acceptance: input.params.acceptance,
|
|
439
|
+
output: input.effectiveOutput,
|
|
440
|
+
outputMode: input.effectiveOutputMode,
|
|
441
|
+
});
|
|
442
|
+
return {
|
|
443
|
+
agent: result.agent,
|
|
444
|
+
index,
|
|
445
|
+
...(result.sessionName ? { sessionName: result.sessionName } : {}),
|
|
446
|
+
...(result.context ? { context: result.context } : {}),
|
|
447
|
+
status,
|
|
448
|
+
...foregroundChildActivityFromProgress(result.progress),
|
|
449
|
+
updatedAt,
|
|
450
|
+
...(result.exitCode !== undefined ? { exitCode: result.exitCode } : {}),
|
|
451
|
+
...(result.error ? { error: result.error } : {}),
|
|
452
|
+
...(result.finalOutput ? { finalOutput: result.finalOutput } : {}),
|
|
453
|
+
...(result.outputState ? { outputState: result.outputState } : {}),
|
|
454
|
+
...(result.outputPartial ? { outputPartial: true } : {}),
|
|
455
|
+
...(result.outputMode ? { outputMode: result.outputMode } : {}),
|
|
456
|
+
...(result.savedOutputPath ? { savedOutputPath: result.savedOutputPath } : {}),
|
|
457
|
+
...(result.outputSaveError ? { outputSaveError: result.outputSaveError } : {}),
|
|
458
|
+
...(result.sessionFile ? { sessionFile: result.sessionFile } : {}),
|
|
459
|
+
...(result.model ? { model: result.model } : {}),
|
|
460
|
+
...(result.thinking ? { thinking: result.thinking } : {}),
|
|
461
|
+
...(result.artifactPaths ? { artifactPaths: result.artifactPaths } : {}),
|
|
462
|
+
...(result.transcriptPath ? { transcriptPath: result.transcriptPath } : {}),
|
|
463
|
+
...(result.transcriptError ? { transcriptError: result.transcriptError } : {}),
|
|
464
|
+
...(result.detachedReason ? { detachedReason: result.detachedReason } : {}),
|
|
465
|
+
...(result.acceptance ? { acceptance: result.acceptance } : {}),
|
|
466
|
+
...(Object.keys(resumeContract).length ? { resumeContract } : {}),
|
|
467
|
+
...(result.launchContractDigest ? { launchContractDigest: result.launchContractDigest } : {}),
|
|
468
|
+
...(input.extensionBindings ? { extensionBindings: input.extensionBindings } : {}),
|
|
469
|
+
...(input.requiredExtensions?.length ? { requiredExtensions: input.requiredExtensions } : {}),
|
|
470
|
+
...(result.launchResolvedExtensions ? { launchResolvedExtensions: result.launchResolvedExtensions } : {}),
|
|
471
|
+
...(result.runtimeAcknowledgedExtensions ? { runtimeAcknowledgedExtensions: result.runtimeAcknowledgedExtensions } : {}),
|
|
472
|
+
...(result.capabilityCeiling ? { capabilityCeiling: result.capabilityCeiling } : {}),
|
|
473
|
+
...(result.capabilityAudit ? { capabilityAudit: result.capabilityAudit } : {}),
|
|
474
|
+
};
|
|
475
|
+
}
|
|
425
476
|
function rememberForegroundRun(state, input) {
|
|
426
477
|
state.foregroundRuns ??= new Map();
|
|
427
478
|
const previous = state.foregroundRuns.get(input.runId);
|
|
@@ -433,61 +484,43 @@ function rememberForegroundRun(state, input) {
|
|
|
433
484
|
...(input.sessionId ? { sessionId: input.sessionId } : {}),
|
|
434
485
|
updatedAt,
|
|
435
486
|
children: input.results.map((result, index) => {
|
|
436
|
-
const
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
agent: result.agent,
|
|
446
|
-
index,
|
|
447
|
-
...(result.sessionName ? { sessionName: result.sessionName } : {}),
|
|
448
|
-
...(result.context ? { context: result.context } : {}),
|
|
449
|
-
status: resolveSubagentResultStatus(omitUndefinedProperties({
|
|
450
|
-
exitCode: result.exitCode,
|
|
451
|
-
interrupted: result.interrupted,
|
|
452
|
-
detached: result.detached,
|
|
453
|
-
processSignal: result.processSignal,
|
|
454
|
-
timedOut: result.timedOut,
|
|
455
|
-
stopped: result.stopped,
|
|
456
|
-
turnBudgetExceeded: result.turnBudgetExceeded,
|
|
457
|
-
})),
|
|
458
|
-
...foregroundChildActivityFromProgress(result.progress),
|
|
459
|
-
updatedAt,
|
|
460
|
-
...(result.exitCode !== undefined ? { exitCode: result.exitCode } : {}),
|
|
461
|
-
...(result.error ? { error: result.error } : {}),
|
|
462
|
-
...(result.finalOutput ? { finalOutput: result.finalOutput } : {}),
|
|
463
|
-
...(result.outputState ? { outputState: result.outputState } : {}),
|
|
464
|
-
...(result.outputPartial ? { outputPartial: true } : {}),
|
|
465
|
-
...(result.outputMode ? { outputMode: result.outputMode } : {}),
|
|
466
|
-
...(result.savedOutputPath ? { savedOutputPath: result.savedOutputPath } : {}),
|
|
467
|
-
...(result.outputSaveError ? { outputSaveError: result.outputSaveError } : {}),
|
|
468
|
-
...(result.sessionFile ? { sessionFile: result.sessionFile } : {}),
|
|
469
|
-
...(result.model ? { model: result.model } : {}),
|
|
470
|
-
...(result.thinking ? { thinking: result.thinking } : {}),
|
|
471
|
-
...(result.artifactPaths ? { artifactPaths: result.artifactPaths } : {}),
|
|
472
|
-
...(result.transcriptPath ? { transcriptPath: result.transcriptPath } : {}),
|
|
473
|
-
...(result.transcriptError ? { transcriptError: result.transcriptError } : {}),
|
|
474
|
-
...(result.detachedReason ? { detachedReason: result.detachedReason } : {}),
|
|
475
|
-
...(result.acceptance ? { acceptance: result.acceptance } : {}),
|
|
476
|
-
...(Object.keys(resumeContract).length ? { resumeContract } : {}),
|
|
477
|
-
...(result.launchContractDigest ? { launchContractDigest: result.launchContractDigest } : {}),
|
|
478
|
-
...(input.extensionBindings ? { extensionBindings: input.extensionBindings } : {}),
|
|
479
|
-
...(input.requiredExtensions?.length ? { requiredExtensions: input.requiredExtensions } : {}),
|
|
480
|
-
...(result.launchResolvedExtensions ? { launchResolvedExtensions: result.launchResolvedExtensions } : {}),
|
|
481
|
-
...(result.runtimeAcknowledgedExtensions ? { runtimeAcknowledgedExtensions: result.runtimeAcknowledgedExtensions } : {}),
|
|
482
|
-
...(result.capabilityCeiling ? { capabilityCeiling: result.capabilityCeiling } : {}),
|
|
483
|
-
...(result.capabilityAudit ? { capabilityAudit: result.capabilityAudit } : {}),
|
|
484
|
-
};
|
|
487
|
+
const child = rememberedForegroundChild(input, result, index, resolveSubagentResultStatus(omitUndefinedProperties({
|
|
488
|
+
exitCode: result.exitCode,
|
|
489
|
+
interrupted: result.interrupted,
|
|
490
|
+
detached: result.detached,
|
|
491
|
+
processSignal: result.processSignal,
|
|
492
|
+
timedOut: result.timedOut,
|
|
493
|
+
stopped: result.stopped,
|
|
494
|
+
turnBudgetExceeded: result.turnBudgetExceeded,
|
|
495
|
+
})), updatedAt);
|
|
485
496
|
const recovered = previous?.children[index];
|
|
486
497
|
return child.status === "detached" && recovered && recovered.status !== "detached" ? recovered : child;
|
|
487
498
|
}),
|
|
488
499
|
});
|
|
489
500
|
trimRememberedForegroundRuns(state);
|
|
490
|
-
|
|
501
|
+
persistRememberedForegroundRun(state, input.runId);
|
|
502
|
+
}
|
|
503
|
+
/**
|
|
504
|
+
* Saves a starting single run to its history file only, so a Pi exit before the run ends leaves a
|
|
505
|
+
* record that loads as stopped and resumable. The final save replaces it by run id.
|
|
506
|
+
*/
|
|
507
|
+
function rememberRunningForegroundRun(input) {
|
|
508
|
+
const updatedAt = Date.now();
|
|
509
|
+
try {
|
|
510
|
+
persistForegroundRun({
|
|
511
|
+
runId: input.runId,
|
|
512
|
+
mode: "single",
|
|
513
|
+
cwd: input.cwd,
|
|
514
|
+
...(input.sessionId ? { sessionId: input.sessionId } : {}),
|
|
515
|
+
updatedAt,
|
|
516
|
+
children: [rememberedForegroundChild(input, input.child, 0, "running", updatedAt)],
|
|
517
|
+
owner: currentRecordedPid(),
|
|
518
|
+
...(input.parentWorkflowRunId ? { parentWorkflowRunId: input.parentWorkflowRunId } : {}),
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
catch (error) {
|
|
522
|
+
console.error("Failed to persist running foreground run:", error);
|
|
523
|
+
}
|
|
491
524
|
}
|
|
492
525
|
function applyControlEventToRememberedForegroundRun(state, event) {
|
|
493
526
|
const run = state.foregroundRuns?.get(event.runId);
|
|
@@ -565,7 +598,7 @@ function updateRememberedForegroundChild(state, input) {
|
|
|
565
598
|
...(input.result.capabilityAudit ? { capabilityAudit: input.result.capabilityAudit } : {}),
|
|
566
599
|
});
|
|
567
600
|
trimRememberedForegroundRuns(state);
|
|
568
|
-
|
|
601
|
+
persistRememberedForegroundRun(state, input.runId);
|
|
569
602
|
const output = getSingleResultOutput(input.result).trim();
|
|
570
603
|
const success = terminalStatus === "completed";
|
|
571
604
|
const summary = !success && input.result.error
|
|
@@ -3040,9 +3073,9 @@ function importWorkflowAwaitedChildResult(completed, options) {
|
|
|
3040
3073
|
};
|
|
3041
3074
|
}
|
|
3042
3075
|
// A replaced runtime leaves the child running so a relaunch of the workflow can re-attach to it.
|
|
3043
|
-
function stopAwaitedAsyncChildOnAbort(signal, state, runId, asyncDir, kill,
|
|
3076
|
+
function stopAwaitedAsyncChildOnAbort(signal, state, runId, asyncDir, kill, reattached = false) {
|
|
3044
3077
|
const stopOnAbort = () => { if (!workflowStopCause(signal?.reason))
|
|
3045
|
-
stopAsyncRun(state, runId, kill, { asyncDir, resolvedId: runId, ...(
|
|
3078
|
+
stopAsyncRun(state, runId, kill, { asyncDir, resolvedId: runId, ...(reattached ? { reattachedRunId: runId } : {}) }); };
|
|
3046
3079
|
if (signal?.aborted)
|
|
3047
3080
|
stopOnAbort();
|
|
3048
3081
|
else
|
|
@@ -3563,7 +3596,7 @@ async function runSinglePath(data, deps) {
|
|
|
3563
3596
|
if (requestedMachine) {
|
|
3564
3597
|
try {
|
|
3565
3598
|
assertRequiredChildExtensionsAdmitted([requiredExtensions], { agent: agentConfig.name, runnerType: agentConfig.runner?.type, machine: requestedMachine });
|
|
3566
|
-
foregroundMachine =
|
|
3599
|
+
foregroundMachine = resolveMachinePlacement({ machine: requestedMachine, cwd: ctx.cwd, stepCwd: params.machineCwd }).machine;
|
|
3567
3600
|
}
|
|
3568
3601
|
catch (error) {
|
|
3569
3602
|
return toExecutionErrorResult(params, error instanceof Error ? error : new Error(String(error)), data.contextPolicy.contextSummary);
|
|
@@ -3749,6 +3782,15 @@ async function runSinglePath(data, deps) {
|
|
|
3749
3782
|
}
|
|
3750
3783
|
: undefined;
|
|
3751
3784
|
const deadlineAt = data.deadlineAt ?? (data.timeoutMs !== undefined ? Date.now() + data.timeoutMs : undefined);
|
|
3785
|
+
// A launch that ends without its final save drops the launch-time record, or it would later load as interrupted.
|
|
3786
|
+
const discardRunningRecord = () => {
|
|
3787
|
+
try {
|
|
3788
|
+
discardRunningForegroundRun(runId);
|
|
3789
|
+
}
|
|
3790
|
+
catch (discardError) {
|
|
3791
|
+
console.error("Failed to discard running foreground run:", discardError);
|
|
3792
|
+
}
|
|
3793
|
+
};
|
|
3752
3794
|
let r;
|
|
3753
3795
|
let resolveDetachedWorkflowChild;
|
|
3754
3796
|
const detachedWorkflowChild = params.workflowAwaitDetached === true
|
|
@@ -3756,6 +3798,18 @@ async function runSinglePath(data, deps) {
|
|
|
3756
3798
|
: undefined;
|
|
3757
3799
|
try {
|
|
3758
3800
|
const childSessionFile = sessionFileForTask(params.agent, 0, modelOverride, modelOverrideFromParent, modelOrigin);
|
|
3801
|
+
rememberRunningForegroundRun({
|
|
3802
|
+
modelResponseAliases, runId, mode: "single", cwd: singleCwd, sessionId: data.parentSessionId, params, effectiveOutput, effectiveOutputMode, extensionBindings: params.extensionBindings, requiredExtensions,
|
|
3803
|
+
child: omitUndefinedProperties({
|
|
3804
|
+
agent: params.agent,
|
|
3805
|
+
context: shouldForkAgent(contextPolicy, params.agent) ? "fork" : "fresh",
|
|
3806
|
+
sessionFile: childSessionFile,
|
|
3807
|
+
model: modelOverride,
|
|
3808
|
+
thinking: resolveEffectiveThinking(modelOverride, thinkingOverrideForTask()),
|
|
3809
|
+
capabilityCeiling: data.capabilityCeiling,
|
|
3810
|
+
}),
|
|
3811
|
+
parentWorkflowRunId: params.workflowParentRunId ?? foregroundControl?.parentWorkflowRunId,
|
|
3812
|
+
});
|
|
3759
3813
|
const launched = await runSync(ctx.cwd, agents, params.agent, task, compactOptional({
|
|
3760
3814
|
machine: foregroundMachine,
|
|
3761
3815
|
parentProviderRegistry: ctx.modelRegistry,
|
|
@@ -3833,7 +3887,13 @@ async function runSinglePath(data, deps) {
|
|
|
3833
3887
|
}
|
|
3834
3888
|
try {
|
|
3835
3889
|
if (worktreeSetup) {
|
|
3836
|
-
|
|
3890
|
+
try {
|
|
3891
|
+
await finalizeSingleWorktreeHandoff({ worktreeSetup, artifactsDir, runId, cwd: sourceCwd, agent: params.agent, result, workflowKey: params.workflowKey, lane });
|
|
3892
|
+
}
|
|
3893
|
+
catch (error) {
|
|
3894
|
+
discardRunningRecord();
|
|
3895
|
+
throw error;
|
|
3896
|
+
}
|
|
3837
3897
|
}
|
|
3838
3898
|
try {
|
|
3839
3899
|
updateRememberedForegroundChild(deps.state, { runId, mode: "single", cwd: singleCwd, sessionId: data.parentSessionId, index: 0, result, events: deps.pi.events, notify: true });
|
|
@@ -3894,6 +3954,7 @@ async function runSinglePath(data, deps) {
|
|
|
3894
3954
|
r = launched.detached && detachedWorkflowChild ? await detachedWorkflowChild : launched;
|
|
3895
3955
|
}
|
|
3896
3956
|
catch (error) {
|
|
3957
|
+
discardRunningRecord();
|
|
3897
3958
|
await cleanupSingleWorktree();
|
|
3898
3959
|
throw error;
|
|
3899
3960
|
}
|
|
@@ -3909,9 +3970,15 @@ async function runSinglePath(data, deps) {
|
|
|
3909
3970
|
}
|
|
3910
3971
|
let worktreeHandoff;
|
|
3911
3972
|
if (worktreeSetup) {
|
|
3912
|
-
|
|
3913
|
-
|
|
3914
|
-
|
|
3973
|
+
try {
|
|
3974
|
+
worktreeHandoff = r.detached
|
|
3975
|
+
? { suffix: pendingHandoff ? formatParallelHandoffReference(pendingHandoff) : "", reference: pendingHandoff }
|
|
3976
|
+
: await finalizeSingleWorktreeHandoff({ worktreeSetup, artifactsDir, runId, cwd: sourceCwd, agent: params.agent, result: r, workflowKey: params.workflowKey, lane });
|
|
3977
|
+
}
|
|
3978
|
+
catch (error) {
|
|
3979
|
+
discardRunningRecord();
|
|
3980
|
+
throw error;
|
|
3981
|
+
}
|
|
3915
3982
|
}
|
|
3916
3983
|
if (r.progress)
|
|
3917
3984
|
allProgress.push(r.progress);
|
|
@@ -4731,6 +4798,17 @@ function createScheduledOwnerState(source, ownerSessionId, ctx) {
|
|
|
4731
4798
|
workflowChildStops: new Map(),
|
|
4732
4799
|
};
|
|
4733
4800
|
}
|
|
4801
|
+
/** Drops the fields that tie a launch to one run: a relaunch sets its own reuse and origin. */
|
|
4802
|
+
function savedWorkflowLaunchParams(params) {
|
|
4803
|
+
const { reuseFrom: _reuseFrom, scheduleOrigin: _scheduleOrigin, resumeOrigin: _resumeOrigin, ...saved } = params;
|
|
4804
|
+
return saved;
|
|
4805
|
+
}
|
|
4806
|
+
function workflowLaunchRecord(launch, cwd, scriptDigest) {
|
|
4807
|
+
if (launch.source === "structured")
|
|
4808
|
+
return { version: 1, source: "structured", scriptDigest };
|
|
4809
|
+
// The resolved cwd is saved: a relative one would resolve against a later session's cwd.
|
|
4810
|
+
return { version: 1, source: launch.source, params: { ...launch.params, cwd }, scriptDigest };
|
|
4811
|
+
}
|
|
4734
4812
|
export function createSubagentExecutor(deps) {
|
|
4735
4813
|
const executorDeps = deps;
|
|
4736
4814
|
const delegatedThinkingOverrides = new WeakMap();
|
|
@@ -4738,6 +4816,9 @@ export function createSubagentExecutor(deps) {
|
|
|
4738
4816
|
const delegatedExecutions = new WeakSet();
|
|
4739
4817
|
const publicExecutions = new WeakSet();
|
|
4740
4818
|
const workflowResourcePermits = new WeakMap();
|
|
4819
|
+
// Public launches captured by executePublic for launch.json; never built from the internal request.
|
|
4820
|
+
const publicWorkflowLaunches = new WeakMap();
|
|
4821
|
+
const workflowResumeLaunches = new WeakMap();
|
|
4741
4822
|
const workflowPermitContexts = new WeakMap();
|
|
4742
4823
|
const warnedArtifactPackageDirs = new Set();
|
|
4743
4824
|
const scheduledOwnerExecutors = new Map();
|
|
@@ -4755,7 +4836,14 @@ export function createSubagentExecutor(deps) {
|
|
|
4755
4836
|
throw new Error(error);
|
|
4756
4837
|
}
|
|
4757
4838
|
};
|
|
4758
|
-
|
|
4839
|
+
// A plain-reuse claim is given back when its request ends, by any return or throw, before the new run's status.json exists.
|
|
4840
|
+
const pendingReuseClaimReleases = new WeakMap();
|
|
4841
|
+
const execute = (...args) => executeRequest(...args).finally(() => {
|
|
4842
|
+
const releaseReuseClaim = pendingReuseClaimReleases.get(args[1]);
|
|
4843
|
+
pendingReuseClaimReleases.delete(args[1]);
|
|
4844
|
+
releaseReuseClaim?.();
|
|
4845
|
+
});
|
|
4846
|
+
const executeRequest = async (_id, params, signal, onUpdate, ctx, preserveActiveSession = false, parentModelOverride) => {
|
|
4759
4847
|
const projectScopeError = projectScopeTrustError(params, ctx);
|
|
4760
4848
|
if (projectScopeError)
|
|
4761
4849
|
return projectScopeError;
|
|
@@ -4768,6 +4856,8 @@ export function createSubagentExecutor(deps) {
|
|
|
4768
4856
|
const delegatedExecution = delegatedExecutions.has(params);
|
|
4769
4857
|
const publicExecution = publicExecutions.has(params);
|
|
4770
4858
|
const workflowResourcePermit = workflowResourcePermits.get(params);
|
|
4859
|
+
const workflowResumeLaunch = workflowResumeLaunches.get(params);
|
|
4860
|
+
const publicWorkflowLaunch = publicWorkflowLaunches.get(params);
|
|
4771
4861
|
const workflowPermitContext = workflowPermitContexts.get(params);
|
|
4772
4862
|
const delegatedWorkflowPermit = workflowPermitContext && "root" in workflowPermitContext ? workflowPermitContext.root : undefined;
|
|
4773
4863
|
const workflowChildPermitLaunch = workflowPermitContext && "child" in workflowPermitContext ? workflowPermitContext.child : undefined;
|
|
@@ -4914,14 +5004,18 @@ export function createSubagentExecutor(deps) {
|
|
|
4914
5004
|
&& workflowDepth === 0
|
|
4915
5005
|
&& !inheritedNestedRoute(deps)
|
|
4916
5006
|
&& !requestParams.workflowParentRunId;
|
|
4917
|
-
|
|
5007
|
+
// A resume claimed its run id before launching, so the claim already names this run.
|
|
5008
|
+
const workflowRunId = asyncWorkflow ? workflowResumeLaunch?.presetRunId ?? randomUUID() : undefined;
|
|
4918
5009
|
if (requestParams.reuseFrom !== undefined && !topLevelAsyncWorkflow) {
|
|
4919
5010
|
return { content: [{ type: "text", text: "options.reuseFrom requires an async top-level workflow." }], isError: true, details: { mode: "workflow", results: [] } };
|
|
4920
5011
|
}
|
|
4921
|
-
|
|
5012
|
+
// Both reuse readers run before capacity is taken: a run another relaunch continues is refused here.
|
|
5013
|
+
let workflowReuseSource;
|
|
4922
5014
|
try {
|
|
4923
5015
|
if (requestParams.reuseFrom !== undefined)
|
|
4924
|
-
|
|
5016
|
+
workflowReuseSource = readNamedWorkflowReuseSource(DIRS.async, requestParams.reuseFrom, workflowScriptDigest(requestParams.workflowScript), workflowCwd, workflowResumeLaunch?.presetRunId);
|
|
5017
|
+
else if (workflowRunId)
|
|
5018
|
+
workflowReuseSource = findWorkflowReuseSource(DIRS.async, resolveCurrentSessionId(ctx.sessionManager), workflowScriptDigest(requestParams.workflowScript), workflowArgsDigest);
|
|
4925
5019
|
}
|
|
4926
5020
|
catch (error) {
|
|
4927
5021
|
return { content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }], isError: true, details: { mode: "workflow", results: [] } };
|
|
@@ -5003,19 +5097,43 @@ export function createSubagentExecutor(deps) {
|
|
|
5003
5097
|
const completionOwnerId = deps.state.completionOwnerId ?? currentCompletionOwnerId();
|
|
5004
5098
|
deps.state.completionOwnerId = completionOwnerId;
|
|
5005
5099
|
const workflowScriptSourceDigest = workflowScriptDigest(requestParams.workflowScript);
|
|
5006
|
-
const workflowReuseSource = namedWorkflowReuseSource ?? findWorkflowReuseSource(DIRS.async, currentSessionId, workflowScriptSourceDigest, workflowArgsDigest);
|
|
5007
5100
|
const workflowRunEvidence = { ...workflowArgsEvidence, scriptDigest: workflowScriptSourceDigest };
|
|
5008
5101
|
if (workflowReuseSource)
|
|
5009
5102
|
workflowRunEvidence.reusedFrom = workflowReuseSource.runId;
|
|
5103
|
+
// In the first persisted status, before any child launches, so a later resume can count attempts.
|
|
5104
|
+
if (workflowResumeLaunch)
|
|
5105
|
+
workflowRunEvidence.resumeOrigin = workflowResumeLaunch.resumeOrigin;
|
|
5010
5106
|
const workflowJournalFingerprints = new Map();
|
|
5107
|
+
const storageFailure = (error) => {
|
|
5108
|
+
workflowCapacity?.rollback();
|
|
5109
|
+
return { content: [{ type: "text", text: `Failed to create async workflow storage: ${error instanceof Error ? error.message : String(error)}` }], isError: true, details: { mode: "workflow", results: [] } };
|
|
5110
|
+
};
|
|
5111
|
+
// The relaunch continuing a stopped run holds its claim, so any other relaunch of that run is refused.
|
|
5112
|
+
if (workflowReuseSource) {
|
|
5113
|
+
const reuseSourceDir = path.join(DIRS.async, workflowReuseSource.runId);
|
|
5114
|
+
let claim;
|
|
5115
|
+
try {
|
|
5116
|
+
claim = claimWorkflowResume(reuseSourceDir, { newRunId: workflowRunId, trigger: "reuse" });
|
|
5117
|
+
}
|
|
5118
|
+
catch (error) {
|
|
5119
|
+
return storageFailure(error);
|
|
5120
|
+
}
|
|
5121
|
+
if (claim.kind !== "owner") {
|
|
5122
|
+
workflowCapacity?.rollback();
|
|
5123
|
+
return { content: [{ type: "text", text: claimedWorkflowRunRefusal(workflowReuseSource.runId, claim, requestParams.reuseFrom !== undefined) }], isError: true, details: { mode: "workflow", results: [] } };
|
|
5124
|
+
}
|
|
5125
|
+
pendingReuseClaimReleases.set(params, () => { if (!fs.existsSync(statusPath))
|
|
5126
|
+
releaseWorkflowResumeClaim(reuseSourceDir, workflowRunId); });
|
|
5127
|
+
}
|
|
5011
5128
|
try {
|
|
5012
5129
|
fs.mkdirSync(asyncDir, { recursive: true });
|
|
5013
5130
|
writeRunFanoutBudgetDescriptor(asyncDir, workflowFanoutBudget);
|
|
5131
|
+
if (topLevelAsyncWorkflow && publicWorkflowLaunch)
|
|
5132
|
+
writeWorkflowLaunchRecord(asyncDir, workflowLaunchRecord(publicWorkflowLaunch, workflowCwd, workflowScriptSourceDigest));
|
|
5014
5133
|
fs.mkdirSync(DIRS.results, { recursive: true });
|
|
5015
5134
|
}
|
|
5016
5135
|
catch (error) {
|
|
5017
|
-
|
|
5018
|
-
return { content: [{ type: "text", text: `Failed to create async workflow storage: ${error instanceof Error ? error.message : String(error)}` }], isError: true, details: { mode: "workflow", results: [] } };
|
|
5136
|
+
return storageFailure(error);
|
|
5019
5137
|
}
|
|
5020
5138
|
const controller = new AbortController();
|
|
5021
5139
|
deps.state.workflowControllers ??= new Map();
|
|
@@ -5037,7 +5155,7 @@ export function createSubagentExecutor(deps) {
|
|
|
5037
5155
|
cwd: workflowCwd,
|
|
5038
5156
|
...(workflowSessionRoot ? { sessionRoot: workflowSessionRoot } : {}),
|
|
5039
5157
|
...(requestParams.scheduleOrigin ? { scheduleOrigin: requestParams.scheduleOrigin } : {}),
|
|
5040
|
-
|
|
5158
|
+
...currentRecordedPid(),
|
|
5041
5159
|
...(workflowCapabilityCeiling ? { admissionCapabilityCeiling: workflowCapabilityCeiling } : {}),
|
|
5042
5160
|
steps: [],
|
|
5043
5161
|
...(workflowPreflight ? { preflight: workflowPreflight } : {}),
|
|
@@ -5741,7 +5859,8 @@ export function createSubagentExecutor(deps) {
|
|
|
5741
5859
|
const priorAsyncDir = path.join(DIRS.async, priorRunId);
|
|
5742
5860
|
appendWorkflowChildJournal(asyncDir, { type: "start", key, fingerprint: journalFingerprint, runId: priorRunId });
|
|
5743
5861
|
recordReusedWorkflowChild(key, priorRunId);
|
|
5744
|
-
|
|
5862
|
+
// The journal names this exact run, which may belong to another session anywhere in the relaunch chain.
|
|
5863
|
+
const stopListener = stopAwaitedAsyncChildOnAbort(workflowSignal, deps.state, priorRunId, priorAsyncDir, deps.kill, true);
|
|
5745
5864
|
const reattached = await awaitExistingAsyncRun(priorAsyncDir, priorRunId, workflowSignal).finally(stopListener.remove);
|
|
5746
5865
|
const claimedPath = reattached.status === "settled" ? claimWorkflowAwaitedResult(priorAsyncDir, workflowRunId, { ...reattached.result.importedPublication, runId: priorRunId }) : undefined;
|
|
5747
5866
|
if (reattached.status === "settled" && claimedPath) {
|
|
@@ -5939,7 +6058,7 @@ export function createSubagentExecutor(deps) {
|
|
|
5939
6058
|
const errorPreview = status.error && status.error.length > 1_000 ? `${status.error.slice(0, 1_000)}… (truncated; full error: ${statusPath} (error))` : status.error;
|
|
5940
6059
|
const terminalSummary = `${status.state === "complete"
|
|
5941
6060
|
? "Workflow completed after detached child finished."
|
|
5942
|
-
: errorPreview ?? (pauseForDetached ? "Workflow paused." : "Workflow failed.")}${stopCause ? ` ${
|
|
6061
|
+
: errorPreview ?? (pauseForDetached ? "Workflow paused." : "Workflow failed.")}${stopCause ? ` ${WORKFLOW_RUNTIME_REPLACED_STOP_NOTICE}` : ""}${workflowOutputPathMappingSummary(partial.children)}${finalPreflightWarnings.length ? ` ${finalPreflightWarnings.join(" ")}` : ""}`;
|
|
5943
6062
|
const outputWarning = writeWorkflowAggregateOutput(workflowAggregateOutputPath, terminalSummary, producedChildOutputPaths);
|
|
5944
6063
|
const resultSummary = appendWorkflowOutputWarning(terminalSummary, outputWarning);
|
|
5945
6064
|
const receiptState = status.state === "complete" ? "complete" : status.state === "paused" ? "paused" : status.state === "stopped" ? "stopped" : "failed";
|
|
@@ -6013,6 +6132,7 @@ export function createSubagentExecutor(deps) {
|
|
|
6013
6132
|
let liveWorkflow = { trace: [], emits: [], console: [], ...(workflowResource ? { resource: workflowResource.provenance } : {}) };
|
|
6014
6133
|
const childProgress = new Map();
|
|
6015
6134
|
const childLaunches = new Map();
|
|
6135
|
+
const blockingWorkflowChildren = new Set();
|
|
6016
6136
|
let activityTimer;
|
|
6017
6137
|
let lastProgressAt = 0;
|
|
6018
6138
|
let workflowProgressClosed = false;
|
|
@@ -6114,6 +6234,10 @@ export function createSubagentExecutor(deps) {
|
|
|
6114
6234
|
workflowPermitContexts.set(childRequest, { child: { permit: delegatedWorkflowPermit, workflowRunId: foregroundWorkflowRunId, childKey: key } });
|
|
6115
6235
|
}
|
|
6116
6236
|
workflowLaunchObservers.set(childRequest, (launch) => {
|
|
6237
|
+
if (launch.async && launch.runId && (childRequest.workflowAwaitAsync === true || childRequest.action === "resume")) {
|
|
6238
|
+
(deps.state.blockingWorkflowChildren ??= new Set()).add(launch.runId);
|
|
6239
|
+
blockingWorkflowChildren.add(launch.runId);
|
|
6240
|
+
}
|
|
6117
6241
|
childLaunches.set(key, { agent: launch.agent, ...(launch.runId ? { runId: launch.runId } : {}) });
|
|
6118
6242
|
// Telemetry must not decide the child's outcome, as with onTrace in scripted-workflow.
|
|
6119
6243
|
try {
|
|
@@ -6242,6 +6366,8 @@ export function createSubagentExecutor(deps) {
|
|
|
6242
6366
|
}
|
|
6243
6367
|
finally {
|
|
6244
6368
|
workflowProgressClosed = true;
|
|
6369
|
+
for (const runId of blockingWorkflowChildren)
|
|
6370
|
+
deps.state.blockingWorkflowChildren?.delete(runId);
|
|
6245
6371
|
if (activityTimer)
|
|
6246
6372
|
clearTimeout(activityTimer);
|
|
6247
6373
|
childProgress.clear();
|
|
@@ -6705,6 +6831,9 @@ export function createSubagentExecutor(deps) {
|
|
|
6705
6831
|
return withBudget(inspectSubagentStatus(paramsWithResolvedCwd, omitUndefinedProperties({ state: deps.state, nested: nestedScope, sessionRoots, abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(deps.config.capacity?.abandonedSlotReleaseAfterMs) })));
|
|
6706
6832
|
}
|
|
6707
6833
|
if (action === "resume") {
|
|
6834
|
+
const workflowResumeSourceId = workflowResumeTargetId(DIRS.async, paramsWithResolvedCwd);
|
|
6835
|
+
if (workflowResumeSourceId)
|
|
6836
|
+
return resumeWorkflowById(_id, workflowResumeSourceId, signal, ctx);
|
|
6708
6837
|
return resumeAsyncRun(omitUndefinedProperties({
|
|
6709
6838
|
params: paramsWithResolvedCwd,
|
|
6710
6839
|
requestCwd,
|
|
@@ -7629,6 +7758,8 @@ export function createSubagentExecutor(deps) {
|
|
|
7629
7758
|
const errorResult = (text) => Promise.resolve({ content: [{ type: "text", text }], isError: true, details: { mode: publicParams.action ? "management" : "workflow", results: [] } });
|
|
7630
7759
|
// Models tend to put script text in the workflow string; say how to pass it instead.
|
|
7631
7760
|
const scriptTextHint = " A workflow string is a named workflow resource or a script file path. To run script text, write it in one ```js workflow block in the same reply and call subagent({ workflow: true }).";
|
|
7761
|
+
let workflowLaunch;
|
|
7762
|
+
const workflowResumeLaunch = workflowResumeLaunches.get(params);
|
|
7632
7763
|
if (publicParams.tasks !== undefined || publicParams.chain !== undefined) {
|
|
7633
7764
|
// Normalization admits chain/tasks only with workflow scripts disabled; the original input was checked above.
|
|
7634
7765
|
const kind = publicParams.tasks !== undefined ? "tasks" : "chain";
|
|
@@ -7638,12 +7769,24 @@ export function createSubagentExecutor(deps) {
|
|
|
7638
7769
|
const { tasks: _tasks, chain: _chain, task: _task, ...withoutStructuredInput } = publicParams;
|
|
7639
7770
|
publicParams = { ...withoutStructuredInput, workflowScript: resolved.resource.script };
|
|
7640
7771
|
workflowResourcePermits.set(publicParams, resolved.resource.permit);
|
|
7772
|
+
workflowLaunch = { source: "structured" };
|
|
7641
7773
|
}
|
|
7642
7774
|
else if (typeof workflow === "string" && !isWorkflowScriptPath(workflow)) {
|
|
7643
7775
|
const resolved = resolveWorkflowResource(workflow, publicParams.args, ctx.sessionManager.getSessionId() ?? undefined);
|
|
7776
|
+
// A resumed resource is resolved with current authority and must still be the script that ran.
|
|
7777
|
+
if (workflowResumeLaunch && !resolved.ok) {
|
|
7778
|
+
workflowResumeLaunch.refusal = { code: "authority", reason: `workflow resource '${workflow}' can no longer be resolved: ${resolved.error}` };
|
|
7779
|
+
return errorResult(workflowResumeLaunch.refusal.reason);
|
|
7780
|
+
}
|
|
7781
|
+
if (workflowResumeLaunch && resolved.ok && workflowScriptDigest(resolved.resource.script) !== workflowResumeLaunch.expectedScriptDigest) {
|
|
7782
|
+
workflowResumeLaunch.refusal = { code: "digest", reason: `workflow resource '${workflow}' changed since the run started` };
|
|
7783
|
+
return errorResult(workflowResumeLaunch.refusal.reason);
|
|
7784
|
+
}
|
|
7644
7785
|
if (!resolved.ok)
|
|
7645
7786
|
return Promise.resolve({ content: [{ type: "text", text: resolved.error + scriptTextHint }], isError: true, details: { mode: "workflow", results: [] } });
|
|
7646
7787
|
const { workflow: _workflow, args: _args, ...withoutResourceInput } = publicParams;
|
|
7788
|
+
// Saved by name: the runs.host permit cannot be saved, so a relaunch resolves the resource again.
|
|
7789
|
+
workflowLaunch = { source: "resource", params: savedWorkflowLaunchParams(publicParams) };
|
|
7647
7790
|
publicParams = { ...withoutResourceInput, workflowScript: resolved.resource.script };
|
|
7648
7791
|
workflowResourcePermits.set(publicParams, resolved.resource.permit);
|
|
7649
7792
|
}
|
|
@@ -7662,10 +7805,70 @@ export function createSubagentExecutor(deps) {
|
|
|
7662
7805
|
}
|
|
7663
7806
|
const { workflow: _workflow, ...withoutWorkflowSource } = publicParams;
|
|
7664
7807
|
publicParams = { ...withoutWorkflowSource, workflowScript, args: "error" in normalizedArgs ? publicParams.args : deepFreezeWorkflowArgs(normalizedArgs.args) };
|
|
7808
|
+
// The script is saved inline: a reply block or a script file cannot be read again later.
|
|
7809
|
+
workflowLaunch = { source: "script", params: savedWorkflowLaunchParams(publicParams) };
|
|
7665
7810
|
}
|
|
7811
|
+
if (workflowLaunch && publicParams.action === undefined)
|
|
7812
|
+
publicWorkflowLaunches.set(publicParams, workflowLaunch);
|
|
7813
|
+
if (workflowResumeLaunch)
|
|
7814
|
+
workflowResumeLaunches.set(publicParams, workflowResumeLaunch);
|
|
7666
7815
|
publicExecutions.add(publicParams);
|
|
7667
7816
|
return executeWithSingleDispatchGuard(id, publicParams, signal, onUpdate, ctx);
|
|
7668
7817
|
};
|
|
7818
|
+
const executeWorkflowResume = async (id, prepared, signal, ctx) => {
|
|
7819
|
+
const params = { ...prepared.params };
|
|
7820
|
+
const launch = { presetRunId: prepared.newRunId, resumeOrigin: prepared.resumeOrigin, expectedScriptDigest: prepared.expectedScriptDigest };
|
|
7821
|
+
workflowResumeLaunches.set(params, launch);
|
|
7822
|
+
let result;
|
|
7823
|
+
try {
|
|
7824
|
+
result = await executePublic(id, params, signal, undefined, ctx);
|
|
7825
|
+
}
|
|
7826
|
+
catch (error) {
|
|
7827
|
+
result = { content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }], isError: true, details: { mode: "workflow", results: [] } };
|
|
7828
|
+
}
|
|
7829
|
+
if (resumedRunStarted(prepared.sourceAsyncDir, prepared.newRunId))
|
|
7830
|
+
return { result, launched: true };
|
|
7831
|
+
const firstText = result.content.find((block) => block.type === "text")?.text ?? "no result";
|
|
7832
|
+
const refusal = launch.refusal
|
|
7833
|
+
? workflowResumeRefusal(launch.refusal.code, launch.refusal.reason, prepared.sourceRunId)
|
|
7834
|
+
: result.details?.activeAsyncCapacity
|
|
7835
|
+
? workflowResumeRefusal("capacity", `capacity refused: ${firstText}`, prepared.sourceRunId)
|
|
7836
|
+
: workflowResumeRefusal("launch-failed", `launch failed: ${firstText}`, prepared.sourceRunId);
|
|
7837
|
+
releaseWorkflowResumeClaim(prepared.sourceAsyncDir, prepared.newRunId);
|
|
7838
|
+
if (prepared.trigger === "auto") {
|
|
7839
|
+
try {
|
|
7840
|
+
recordWorkflowResumeRefusal(prepared.sourceAsyncDir, { reason: refusal.reason, byHand: refusal.byHand, newRunId: prepared.newRunId });
|
|
7841
|
+
}
|
|
7842
|
+
catch (error) {
|
|
7843
|
+
console.error(`Failed to record why workflow ${prepared.sourceRunId} was not resumed:`, error);
|
|
7844
|
+
}
|
|
7845
|
+
}
|
|
7846
|
+
return { result, launched: false, refusal };
|
|
7847
|
+
};
|
|
7848
|
+
const resumeWorkflowById = async (id, sourceRunId, signal, ctx) => {
|
|
7849
|
+
const refused = (text) => ({ content: [{ type: "text", text }], isError: true, details: { mode: "management", results: [] } });
|
|
7850
|
+
let preparation;
|
|
7851
|
+
try {
|
|
7852
|
+
preparation = prepareWorkflowResume({ asyncDirRoot: DIRS.async, sourceRunId, trigger: "manual", now: Date.now(), currentSessionId: resolveCurrentSessionId(ctx.sessionManager) });
|
|
7853
|
+
}
|
|
7854
|
+
catch (error) {
|
|
7855
|
+
return refused(`Cannot resume workflow '${sourceRunId}': ${error instanceof Error ? error.message : String(error)}`);
|
|
7856
|
+
}
|
|
7857
|
+
if (!preparation.ok)
|
|
7858
|
+
return refused(manualWorkflowResumeRefusalText(sourceRunId, preparation));
|
|
7859
|
+
const outcome = await executeWorkflowResume(id, preparation.prepared, signal, ctx);
|
|
7860
|
+
if (!outcome.launched)
|
|
7861
|
+
return refused(manualWorkflowResumeRefusalText(sourceRunId, outcome.refusal));
|
|
7862
|
+
const resumedLine = `Resumed workflow '${sourceRunId}' as ${preparation.prepared.newRunId}.`;
|
|
7863
|
+
const textIndex = outcome.result.content.findIndex((block) => block.type === "text");
|
|
7864
|
+
const content = [...outcome.result.content];
|
|
7865
|
+
const first = content[textIndex];
|
|
7866
|
+
if (first?.type === "text")
|
|
7867
|
+
content[textIndex] = { ...first, text: `${resumedLine}\n${first.text}` };
|
|
7868
|
+
else
|
|
7869
|
+
content.unshift({ type: "text", text: resumedLine });
|
|
7870
|
+
return { ...outcome.result, content };
|
|
7871
|
+
};
|
|
7669
7872
|
const executeDelegated = async (id, params, signal, onUpdate, ctx) => {
|
|
7670
7873
|
const disabledFeatureError = disabledFeatureResult(params);
|
|
7671
7874
|
if (disabledFeatureError)
|
|
@@ -7711,5 +7914,5 @@ export function createSubagentExecutor(deps) {
|
|
|
7711
7914
|
for (const owner of scheduledOwnerExecutors.get(ownerId)?.values() ?? [])
|
|
7712
7915
|
yield owner.state;
|
|
7713
7916
|
}
|
|
7714
|
-
return { execute: executeWithSingleDispatchGuard, executePublic, executeDelegated, executeScheduled, getCurrentSupervisorOwnerStates };
|
|
7917
|
+
return { execute: executeWithSingleDispatchGuard, executePublic, executeDelegated, executeScheduled, executeWorkflowResume, getCurrentSupervisorOwnerStates };
|
|
7715
7918
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ChildWatchdogConfig, ChildWatchdogStatusEvent } from "../../watchdog/child-status.ts";
|
|
2
2
|
import type { ThinkingLevel } from "../../shared/model-info.ts";
|
|
3
|
-
import { type LaunchResolvedChildExtensions, type ResolvedToolBudget, type RunFanoutBudgetDescriptor, type
|
|
3
|
+
import { type LaunchResolvedChildExtensions, type ResolvedToolBudget, type RunFanoutBudgetDescriptor, type PlacementMachineReference } from "../../shared/types.ts";
|
|
4
4
|
import { type McpRuntimeSnapshotHost, type ResolvedMcpDirectToolSelection } from "./mcp-direct-tool-allowlist.ts";
|
|
5
5
|
import type { PermissionRules } from "./permissions.ts";
|
|
6
6
|
import type { StructuredOutputRuntime } from "./structured-output.ts";
|
|
@@ -23,7 +23,7 @@ export declare const MCP_DIRECT_TOOLS_ENV = "MCP_DIRECT_TOOLS";
|
|
|
23
23
|
export type InheritedChildRuntime = Pick<ChildRuntimeConfig, "depth" | "maxDepth" | "nestedRoute" | "nestedParent" | "capabilityCeiling" | "thinkingCeiling" | "runFanoutBudget" | "requiredExtensions">;
|
|
24
24
|
export declare function inheritedChildRuntime(config: ChildRuntimeConfig | undefined): InheritedChildRuntime | undefined;
|
|
25
25
|
export interface BuildInProcessChildLaunchInput {
|
|
26
|
-
machine?:
|
|
26
|
+
machine?: PlacementMachineReference;
|
|
27
27
|
remoteSkillNames?: string[];
|
|
28
28
|
remoteReads?: string[] | false;
|
|
29
29
|
parentSessionId?: string;
|