pi-subagents 0.83.0 → 0.84.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 +39 -2
- package/bundle/agents.js +4 -4
- package/bundle/capability-ceiling.js +1 -1
- package/bundle/child-tool-plan.js +3 -3
- package/bundle/{chunk-HJJAVHFB.js → chunk-235AWJUQ.js} +35 -32
- package/bundle/{chunk-P2NCCTZP.js → chunk-2D52JFLF.js} +1 -1
- package/bundle/{chunk-WG2OTTAK.js → chunk-2DHZTCOL.js} +2 -2
- package/bundle/{chunk-MBDOYZ2V.js → chunk-2S4K53MV.js} +1 -1
- package/bundle/{chunk-527E3D2J.js → chunk-4E7BKDTX.js} +5 -5
- package/bundle/{chunk-CZTMUBUI.js → chunk-7BWGNDNK.js} +4 -4
- package/bundle/{chunk-HXVKGS33.js → chunk-AKOIVLA5.js} +4 -4
- package/bundle/{chunk-MHX22USZ.js → chunk-C5P5CHYK.js} +1 -1
- package/bundle/{chunk-5ESXK62X.js → chunk-CGN2TVE7.js} +417 -389
- package/bundle/{chunk-VTMNBPCQ.js → chunk-D3JAFGMO.js} +1 -1
- package/bundle/{chunk-CDI3GMNV.js → chunk-DZDETGRJ.js} +18 -15
- package/bundle/{chunk-BKFWQ642.js → chunk-F4ASL6OV.js} +1 -1
- package/bundle/{chunk-JHZ6DZ5M.js → chunk-G2Z6ZH26.js} +1 -1
- package/bundle/{chunk-XXKG3P2W.js → chunk-J5YL7LYR.js} +4 -4
- package/bundle/{chunk-LDMXJIA6.js → chunk-JG4QKI5M.js} +58 -67
- package/bundle/{chunk-OOFW75PV.js → chunk-L3WFKBEI.js} +15 -11
- package/bundle/{chunk-Y3I6H6DV.js → chunk-LFDBD4PW.js} +2 -2
- package/bundle/{chunk-7F5SK2U3.js → chunk-LNRJCP2Y.js} +17 -14
- package/bundle/{chunk-4BW55BTL.js → chunk-NENDSQUY.js} +4 -4
- package/bundle/{chunk-6B4ITO2W.js → chunk-NKQ6JNAS.js} +1 -1
- package/bundle/{chunk-RZJN63GU.js → chunk-PALJGGYC.js} +2 -2
- package/bundle/{chunk-TJQJ3MEA.js → chunk-PV63UFUR.js} +10 -10
- package/bundle/{chunk-Z7KVHQHL.js → chunk-QXASAHIA.js} +2 -2
- package/bundle/{chunk-HLE22FSH.js → chunk-SSG7IEP5.js} +2 -2
- package/bundle/{chunk-CSSZRTKY.js → chunk-V2BZYLEP.js} +2 -2
- package/bundle/{chunk-CNLDYEH4.js → chunk-VQVVHSY6.js} +11 -11
- package/bundle/{chunk-CGXWSNPA.js → chunk-XDQORLZ5.js} +4 -4
- package/bundle/control-channel.js +3 -3
- package/bundle/index.js +1 -1
- package/bundle/intercom-bridge.js +3 -3
- package/bundle/preflight.js +9 -9
- package/bundle/project-panes.js +4 -4
- package/docs/configuration.md +12 -1
- package/docs/extension-api.md +2 -0
- package/docs/missions.md +2 -0
- package/docs/tool-reference.md +1 -1
- package/package.json +1 -1
- package/src/agents/agent-management.d.ts +1 -1
- package/src/agents/agent-management.js +2 -2
- package/src/agents/agents.js +1 -2
- package/src/agents/frontmatter.js +22 -18
- package/src/extension/index.js +2 -2
- package/src/missions/actions.js +7 -2
- package/src/missions/lifecycle.d.ts +11 -0
- package/src/missions/lifecycle.js +37 -6
- package/src/missions/store.js +1 -0
- package/src/missions/types.d.ts +2 -0
- package/src/runs/background/async-execution.d.ts +4 -1
- package/src/runs/background/async-execution.js +18 -3
- package/src/runs/background/async-resume.js +3 -2
- package/src/runs/background/async-retention.d.ts +1 -0
- package/src/runs/background/async-retention.js +16 -10
- package/src/runs/background/retained-children.js +1 -1
- package/src/runs/background/run-child-session.d.ts +2 -0
- package/src/runs/background/run-child-session.js +9 -1
- package/src/runs/background/subagent-runner.js +1 -0
- package/src/runs/foreground/subagent-executor.js +29 -51
- package/src/runs/shared/capability-ceiling.d.ts +2 -1
- package/src/runs/shared/capability-ceiling.js +3 -2
- package/src/runs/shared/child-session.d.ts +2 -0
- package/src/runs/shared/child-session.js +1 -0
- package/src/runs/shared/model-resolution-diagnostic.d.ts +11 -0
- package/src/runs/shared/model-resolution-diagnostic.js +25 -0
- package/src/runs/shared/parallel-handoff.d.ts +1 -1
- package/src/runs/shared/parallel-handoff.js +10 -6
- package/src/runs/shared/parallel-utils.d.ts +2 -0
- package/src/shared/types.d.ts +1 -1
- package/src/workflows/scripted-workflow.js +6 -1
- package/workflow-script-worker.cjs +450 -175
|
@@ -87,7 +87,7 @@ import { formatHerdrMachineRunnerUnsupported, resolveMachinePlacement } from "..
|
|
|
87
87
|
import { lookupRunnerLauncher, resolveAgentRunnerLauncher } from "../shared/runner-launcher.js";
|
|
88
88
|
import { applyForceTopLevelAsyncOverride } from "../background/top-level-async.js";
|
|
89
89
|
import { handleMissionAction, MISSION_ACTIONS } from "../../missions/actions.js";
|
|
90
|
-
import { attachMissionToLaunchResult, prepareMissionLaunch, writeMissionAsyncBinding } from "../../missions/lifecycle.js";
|
|
90
|
+
import { attachMissionToLaunchResult, missionAcceptanceStatus, prepareMissionLaunch, writeMissionAsyncBinding } from "../../missions/lifecycle.js";
|
|
91
91
|
import { MissionNotFoundError, updateMission } from "../../missions/store.js";
|
|
92
92
|
import { createMissionWorkflowState } from "../../missions/workflow-state.js";
|
|
93
93
|
import { resolveAuthorityDecision } from "../../policy/authority.js";
|
|
@@ -678,9 +678,6 @@ function resolveForegroundResumeTarget(params, state, options = {}) {
|
|
|
678
678
|
...(child.capabilityCeiling ? { capabilityCeiling: child.capabilityCeiling } : {}),
|
|
679
679
|
};
|
|
680
680
|
}
|
|
681
|
-
function isAsyncRunNotFound(error) {
|
|
682
|
-
return error instanceof Error && error.message.startsWith("Async run not found.");
|
|
683
|
-
}
|
|
684
681
|
function isMissingExactAsyncStatusFile(error, requested) {
|
|
685
682
|
return error instanceof Error && error.message === `Status file not found for async run '${requested}'.`;
|
|
686
683
|
}
|
|
@@ -750,38 +747,12 @@ function resolveResumeTarget(params, state, options = {}) {
|
|
|
750
747
|
throw foregroundError;
|
|
751
748
|
return asyncTarget;
|
|
752
749
|
}
|
|
753
|
-
if (foregroundError && !isAsyncRunNotFound(asyncError))
|
|
754
|
-
throw foregroundError;
|
|
755
750
|
if (foregroundError)
|
|
756
751
|
throw foregroundError;
|
|
757
752
|
if (asyncError)
|
|
758
753
|
throw asyncError;
|
|
759
754
|
throw new Error("Run not found. Provide id or runId.");
|
|
760
755
|
}
|
|
761
|
-
function getAsyncInterruptTarget(state, runId, location, options = {}) {
|
|
762
|
-
if (location?.asyncDir) {
|
|
763
|
-
return {
|
|
764
|
-
asyncId: location.resolvedId ?? runId ?? path.basename(location.asyncDir),
|
|
765
|
-
asyncDir: location.asyncDir,
|
|
766
|
-
};
|
|
767
|
-
}
|
|
768
|
-
if (runId) {
|
|
769
|
-
const direct = state.asyncJobs.get(runId);
|
|
770
|
-
if (direct)
|
|
771
|
-
return { asyncId: direct.asyncId, asyncDir: direct.asyncDir };
|
|
772
|
-
if (options.fallbackToNewest === false)
|
|
773
|
-
return undefined;
|
|
774
|
-
}
|
|
775
|
-
let newest;
|
|
776
|
-
for (const job of state.asyncJobs.values()) {
|
|
777
|
-
if (job.status !== "running")
|
|
778
|
-
continue;
|
|
779
|
-
if (!newest || (job.updatedAt ?? 0) > newest.updatedAt) {
|
|
780
|
-
newest = { asyncId: job.asyncId, asyncDir: job.asyncDir, updatedAt: job.updatedAt ?? 0 };
|
|
781
|
-
}
|
|
782
|
-
}
|
|
783
|
-
return newest ? { asyncId: newest.asyncId, asyncDir: newest.asyncDir } : undefined;
|
|
784
|
-
}
|
|
785
756
|
function isStaleExtensionContextError(error) {
|
|
786
757
|
if (!(error instanceof Error))
|
|
787
758
|
return false;
|
|
@@ -853,13 +824,13 @@ function emitControlNotification(input) {
|
|
|
853
824
|
}
|
|
854
825
|
}
|
|
855
826
|
function interruptAsyncRun(state, runId, kill, location) {
|
|
856
|
-
const target =
|
|
827
|
+
const target = location?.asyncDir ? { asyncId: location.resolvedId ?? runId, asyncDir: location.asyncDir } : state.asyncJobs.get(runId);
|
|
857
828
|
if (!target)
|
|
858
829
|
return null;
|
|
859
830
|
const status = reconcileAsyncRun(target.asyncDir, omitUndefinedProperties({ kill })).status;
|
|
860
831
|
if (!status || status.state !== "running" || typeof status.pid !== "number") {
|
|
861
832
|
return {
|
|
862
|
-
content: [{ type: "text", text: `No running async run with an interrupt-capable pid was found for '${runId
|
|
833
|
+
content: [{ type: "text", text: `No running async run with an interrupt-capable pid was found for '${runId}'.` }],
|
|
863
834
|
isError: true,
|
|
864
835
|
details: { mode: "management", results: [] },
|
|
865
836
|
};
|
|
@@ -1047,6 +1018,7 @@ function appendStepToAsyncChain(input) {
|
|
|
1047
1018
|
parentSessionId: input.ctx.sessionManager.getSessionId() ?? undefined,
|
|
1048
1019
|
parentSessionFile: input.ctx.sessionManager.getSessionFile() ?? undefined,
|
|
1049
1020
|
currentModelProvider: parentModel?.provider,
|
|
1021
|
+
parentProviderRegistry: input.ctx.modelRegistry,
|
|
1050
1022
|
currentModel: parentModel,
|
|
1051
1023
|
scopedModelIds: scopedModelIdsFromContext(input.ctx),
|
|
1052
1024
|
modelScope: discoveredForAppend.modelScope,
|
|
@@ -1086,7 +1058,7 @@ function appendStepToAsyncChain(input) {
|
|
|
1086
1058
|
worktreeBranchPrefix: input.deps.config.worktreeBranchPrefix,
|
|
1087
1059
|
asyncDir: resolved.location.asyncDir,
|
|
1088
1060
|
validateOutputBindings: false,
|
|
1089
|
-
capabilityCeiling: intersectSubagentCapabilityCeilings(status.capabilityCeiling, resolveCurrentSubagentCapabilityCeiling(asyncCtx.
|
|
1061
|
+
capabilityCeiling: intersectSubagentCapabilityCeilings(status.capabilityCeiling, resolveCurrentSubagentCapabilityCeiling(asyncCtx.parentSessionId)),
|
|
1090
1062
|
requiredExtensions: retainedRequiredExtensions,
|
|
1091
1063
|
}));
|
|
1092
1064
|
if ("error" in built) {
|
|
@@ -1534,6 +1506,7 @@ async function resumeExternalJobFollowUp(input) {
|
|
|
1534
1506
|
parentSessionId: input.ctx.sessionManager.getSessionId() ?? undefined,
|
|
1535
1507
|
parentSessionFile: input.ctx.sessionManager.getSessionFile() ?? undefined,
|
|
1536
1508
|
currentModelProvider: parentModel?.provider,
|
|
1509
|
+
parentProviderRegistry: input.ctx.modelRegistry,
|
|
1537
1510
|
currentModel: parentModel,
|
|
1538
1511
|
scopedModelIds: scopedModelIdsFromContext(input.ctx),
|
|
1539
1512
|
modelScope: input.modelScope,
|
|
@@ -1562,7 +1535,7 @@ async function resumeExternalJobFollowUp(input) {
|
|
|
1562
1535
|
availableModels: input.ctx.modelRegistry.getAvailable().map(toModelInfo),
|
|
1563
1536
|
outputBaseDir: resolveSingleRunOutputBaseDir(input.deps, artifactsDir, runId),
|
|
1564
1537
|
...(input.absoluteDeadlineAt !== undefined ? { absoluteDeadlineAt: input.absoluteDeadlineAt } : {}),
|
|
1565
|
-
capabilityCeiling: resolveCurrentSubagentCapabilityCeiling(
|
|
1538
|
+
capabilityCeiling: resolveCurrentSubagentCapabilityCeiling(input.ctx.sessionManager.getSessionId() ?? undefined),
|
|
1566
1539
|
runFanoutBudget: createRunFanoutBudget(runId, resolveMaxSubagentSpawnsPerRun(input.deps.config.maxSubagentSpawnsPerRun)),
|
|
1567
1540
|
activeAsyncCapacity,
|
|
1568
1541
|
externalJobFollowUp: { sourceRunId: input.target.runId, sourceStepIndex: input.target.index, parentProviderJobId: externalJob.providerJobId, requestId, requestDigest },
|
|
@@ -1822,6 +1795,7 @@ async function resumeAsyncRun(input) {
|
|
|
1822
1795
|
parentSessionId: input.ctx.sessionManager.getSessionId() ?? undefined,
|
|
1823
1796
|
parentSessionFile: input.ctx.sessionManager.getSessionFile() ?? undefined,
|
|
1824
1797
|
currentModelProvider: parentModel?.provider,
|
|
1798
|
+
parentProviderRegistry: input.ctx.modelRegistry,
|
|
1825
1799
|
currentModel: parentModel,
|
|
1826
1800
|
scopedModelIds: scopedModelIdsFromContext(input.ctx),
|
|
1827
1801
|
modelScope,
|
|
@@ -1858,7 +1832,7 @@ async function resumeAsyncRun(input) {
|
|
|
1858
1832
|
childIntercomTarget: intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(runId, agent, index) : undefined,
|
|
1859
1833
|
globalConcurrencyLimit: input.deps.config.globalConcurrencyLimit,
|
|
1860
1834
|
runFanoutBudget: createRunFanoutBudget(runId, resolveMaxSubagentSpawnsPerRun(input.deps.config.maxSubagentSpawnsPerRun)),
|
|
1861
|
-
capabilityCeiling: intersectSubagentCapabilityCeilings("capabilityCeiling" in target ? target.capabilityCeiling : undefined, resolveCurrentSubagentCapabilityCeiling(input.
|
|
1835
|
+
capabilityCeiling: intersectSubagentCapabilityCeilings("capabilityCeiling" in target ? target.capabilityCeiling : undefined, resolveCurrentSubagentCapabilityCeiling(input.ctx.sessionManager.getSessionId() ?? undefined)),
|
|
1862
1836
|
thinkingCeiling: target.thinkingCeiling,
|
|
1863
1837
|
activeAsyncCapacity,
|
|
1864
1838
|
}));
|
|
@@ -1963,6 +1937,7 @@ async function resumeAsyncRun(input) {
|
|
|
1963
1937
|
parentSessionId: input.ctx.sessionManager.getSessionId() ?? undefined,
|
|
1964
1938
|
parentSessionFile: input.ctx.sessionManager.getSessionFile() ?? undefined,
|
|
1965
1939
|
currentModelProvider: parentModel?.provider,
|
|
1940
|
+
parentProviderRegistry: input.ctx.modelRegistry,
|
|
1966
1941
|
currentModel: parentModel,
|
|
1967
1942
|
scopedModelIds: scopedModelIdsFromContext(input.ctx),
|
|
1968
1943
|
modelScope,
|
|
@@ -2028,7 +2003,7 @@ async function resumeAsyncRun(input) {
|
|
|
2028
2003
|
...(input.params.toolBudget !== undefined ? { toolBudget: input.params.toolBudget } : {}),
|
|
2029
2004
|
// Recovery descriptors, remembered foreground runs, and current workflow roots
|
|
2030
2005
|
// preserve parent authority before the selected agent's descendant ceiling.
|
|
2031
|
-
capabilityCeiling: intersectSubagentCapabilityCeilings(recoveryDescriptor?.capabilityCeiling ?? (target.source === "foreground" || (target.source === "async" && target.mode === "workflow") ? target.capabilityCeiling : undefined), resolveCurrentSubagentCapabilityCeiling(input.
|
|
2006
|
+
capabilityCeiling: intersectSubagentCapabilityCeilings(recoveryDescriptor?.capabilityCeiling ?? (target.source === "foreground" || (target.source === "async" && target.mode === "workflow") ? target.capabilityCeiling : undefined), resolveCurrentSubagentCapabilityCeiling(input.ctx.sessionManager.getSessionId() ?? undefined)),
|
|
2032
2007
|
runFanoutBudget: input.params.runFanoutBudget ?? recoveryDescriptor?.runFanoutBudget ?? createRunFanoutBudget(runId, resolveMaxSubagentSpawnsPerRun(input.deps.config.maxSubagentSpawnsPerRun)),
|
|
2033
2008
|
parentWorkflowRunId: input.params.workflowParentRunId,
|
|
2034
2009
|
workflowKey: input.params.workflowKey,
|
|
@@ -3134,6 +3109,7 @@ async function runAsyncPath(data, deps) {
|
|
|
3134
3109
|
parentSessionId: data.parentPiSessionId,
|
|
3135
3110
|
parentSessionFile: ctx.sessionManager.getSessionFile() ?? undefined,
|
|
3136
3111
|
currentModelProvider: parentModel?.provider,
|
|
3112
|
+
parentProviderRegistry: ctx.modelRegistry,
|
|
3137
3113
|
currentModel: parentModel,
|
|
3138
3114
|
scopedModelIds: data.scopedModelIds,
|
|
3139
3115
|
modelScope: data.modelScope,
|
|
@@ -5093,7 +5069,7 @@ export function createSubagentExecutor(deps) {
|
|
|
5093
5069
|
const eventsPath = path.join(asyncDir, "events.jsonl");
|
|
5094
5070
|
const startedAt = Date.now();
|
|
5095
5071
|
const currentSessionId = resolveCurrentSessionId(ctx.sessionManager);
|
|
5096
|
-
const workflowCapabilityCeiling = intersectSubagentCapabilityCeilings(requestParams.capabilityCeiling, resolveCurrentSubagentCapabilityCeiling(
|
|
5072
|
+
const workflowCapabilityCeiling = intersectSubagentCapabilityCeilings(requestParams.capabilityCeiling, resolveCurrentSubagentCapabilityCeiling(ctx.sessionManager.getSessionId() ?? undefined));
|
|
5097
5073
|
const completionOwnerId = deps.state.completionOwnerId ?? currentCompletionOwnerId();
|
|
5098
5074
|
deps.state.completionOwnerId = completionOwnerId;
|
|
5099
5075
|
const workflowScriptSourceDigest = workflowScriptDigest(requestParams.workflowScript);
|
|
@@ -5971,6 +5947,7 @@ export function createSubagentExecutor(deps) {
|
|
|
5971
5947
|
...(result.details.results[0]?.sessionFile ? { sessionPath: result.details.results[0].sessionFile } : {}),
|
|
5972
5948
|
artifactPaths: child.artifactPaths,
|
|
5973
5949
|
...(["completed", "failed"].includes(childStatus) ? { completedAt: new Date().toISOString() } : {}),
|
|
5950
|
+
acceptanceStatus: missionAcceptanceStatus(result.details.results),
|
|
5974
5951
|
heartbeat: { status: childStatus, ...(childPhase ? { phase: childPhase } : {}) },
|
|
5975
5952
|
});
|
|
5976
5953
|
if (result.details.asyncId) {
|
|
@@ -6137,7 +6114,7 @@ export function createSubagentExecutor(deps) {
|
|
|
6137
6114
|
let lastProgressAt = 0;
|
|
6138
6115
|
let workflowProgressClosed = false;
|
|
6139
6116
|
const workflowDeadlineAt = timeout === undefined ? undefined : Date.now() + timeout;
|
|
6140
|
-
const workflowCapabilityCeiling = intersectSubagentCapabilityCeilings(requestParams.capabilityCeiling, resolveCurrentSubagentCapabilityCeiling(
|
|
6117
|
+
const workflowCapabilityCeiling = intersectSubagentCapabilityCeilings(requestParams.capabilityCeiling, resolveCurrentSubagentCapabilityCeiling(ctx.sessionManager.getSessionId() ?? undefined));
|
|
6141
6118
|
const sendWorkflowProgress = () => {
|
|
6142
6119
|
if (workflowProgressClosed)
|
|
6143
6120
|
return;
|
|
@@ -6294,6 +6271,7 @@ export function createSubagentExecutor(deps) {
|
|
|
6294
6271
|
...(result.details.results[0]?.sessionFile ? { sessionPath: result.details.results[0].sessionFile } : {}),
|
|
6295
6272
|
artifactPaths: child.artifactPaths,
|
|
6296
6273
|
...(["completed", "failed"].includes(childStatus) ? { completedAt: new Date().toISOString() } : {}),
|
|
6274
|
+
acceptanceStatus: missionAcceptanceStatus(result.details.results),
|
|
6297
6275
|
heartbeat: { status: childStatus, ...(childPhase ? { phase: childPhase } : {}) },
|
|
6298
6276
|
});
|
|
6299
6277
|
return child;
|
|
@@ -7061,22 +7039,22 @@ export function createSubagentExecutor(deps) {
|
|
|
7061
7039
|
if (stopResult)
|
|
7062
7040
|
return stopResult;
|
|
7063
7041
|
return {
|
|
7064
|
-
content: [{ type: "text", text:
|
|
7042
|
+
content: [{ type: "text", text: `No stoppable async run found for '${targetRunId}'.` }],
|
|
7065
7043
|
isError: true,
|
|
7066
7044
|
details: { mode: "management", results: [] },
|
|
7067
7045
|
};
|
|
7068
7046
|
}
|
|
7069
7047
|
if (action === "interrupt") {
|
|
7070
7048
|
const targetRunId = paramsWithResolvedCwd.runId ?? paramsWithResolvedCwd.id;
|
|
7049
|
+
if (!targetRunId)
|
|
7050
|
+
return { content: [{ type: "text", text: "action='interrupt' requires id." }], isError: true, details: { mode: "management", results: [] } };
|
|
7071
7051
|
let resolved;
|
|
7072
|
-
|
|
7073
|
-
|
|
7074
|
-
|
|
7075
|
-
|
|
7076
|
-
|
|
7077
|
-
|
|
7078
|
-
return { content: [{ type: "text", text: message }], isError: true, details: { mode: "management", results: [] } };
|
|
7079
|
-
}
|
|
7052
|
+
try {
|
|
7053
|
+
resolved = resolveSubagentRunId(targetRunId, omitUndefinedProperties({ state: deps.state, nested: nestedResolutionScopeForExecutor(deps) }));
|
|
7054
|
+
}
|
|
7055
|
+
catch (error) {
|
|
7056
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
7057
|
+
return { content: [{ type: "text", text: message }], isError: true, details: { mode: "management", results: [] } };
|
|
7080
7058
|
}
|
|
7081
7059
|
if (resolved?.kind === "nested")
|
|
7082
7060
|
return interruptNestedRun(resolved);
|
|
@@ -7101,7 +7079,7 @@ export function createSubagentExecutor(deps) {
|
|
|
7101
7079
|
if (asyncInterruptResult)
|
|
7102
7080
|
return asyncInterruptResult;
|
|
7103
7081
|
return {
|
|
7104
|
-
content: [{ type: "text", text:
|
|
7082
|
+
content: [{ type: "text", text: `No interrupt-capable run found for '${targetRunId}'.` }],
|
|
7105
7083
|
isError: true,
|
|
7106
7084
|
details: { mode: "management", results: [] },
|
|
7107
7085
|
};
|
|
@@ -7117,7 +7095,7 @@ export function createSubagentExecutor(deps) {
|
|
|
7117
7095
|
...ctx,
|
|
7118
7096
|
cwd: requestCwd,
|
|
7119
7097
|
config: deps.config,
|
|
7120
|
-
|
|
7098
|
+
piSessionId: ctx.sessionManager.getSessionId() ?? undefined,
|
|
7121
7099
|
discoverAgentsAll: deps.discoverAgentsAll,
|
|
7122
7100
|
runtimeAgentOwner: deps.pi,
|
|
7123
7101
|
onAgentsChanged: deps.onAgentsChanged,
|
|
@@ -7218,7 +7196,7 @@ export function createSubagentExecutor(deps) {
|
|
|
7218
7196
|
let prepareForkSessionForIndex = async () => { };
|
|
7219
7197
|
// Check before pruner/session preparation, then recheck immediately before each fork
|
|
7220
7198
|
// because the session capability ceiling can tighten while preparation awaits.
|
|
7221
|
-
const launchCapabilityCeiling = () => intersectSubagentCapabilityCeilings(intersectSubagentCapabilityCeilings(effectiveParams.capabilityCeiling, resolveCurrentSubagentCapabilityCeiling(
|
|
7199
|
+
const launchCapabilityCeiling = () => intersectSubagentCapabilityCeilings(intersectSubagentCapabilityCeilings(effectiveParams.capabilityCeiling, resolveCurrentSubagentCapabilityCeiling(requestPiSessionId)), deps.childRuntime?.capabilityCeiling);
|
|
7222
7200
|
const assertLaunchableAgentsAllowedBeforeFork = () => {
|
|
7223
7201
|
if (!contextPolicy.usesFork)
|
|
7224
7202
|
return;
|
|
@@ -7499,7 +7477,7 @@ export function createSubagentExecutor(deps) {
|
|
|
7499
7477
|
scopedModelIds: scopedModelIdsFromContext(ctx),
|
|
7500
7478
|
parentSessionId: requestSessionId,
|
|
7501
7479
|
parentPiSessionId: requestPiSessionId,
|
|
7502
|
-
capabilityCeiling: intersectSubagentCapabilityCeilings(effectiveParams.capabilityCeiling, resolveCurrentSubagentCapabilityCeiling(
|
|
7480
|
+
capabilityCeiling: intersectSubagentCapabilityCeilings(effectiveParams.capabilityCeiling, resolveCurrentSubagentCapabilityCeiling(requestPiSessionId)),
|
|
7503
7481
|
runFanoutBudget,
|
|
7504
7482
|
topLevelAsyncCapacityEligible,
|
|
7505
7483
|
activeAsyncCapacity,
|
|
@@ -48,7 +48,8 @@ export declare function parseSubagentCapabilityCeiling(value: unknown, field?: s
|
|
|
48
48
|
export declare function registerSubagentCapabilityCeiling(options: RegisterSubagentCapabilityCeilingOptions): SubagentCapabilityCeilingHandle;
|
|
49
49
|
export declare function intersectSubagentCapabilityCeilings(...ceilings: Array<ResolvedSubagentCapabilityCeiling | undefined>): ResolvedSubagentCapabilityCeiling | undefined;
|
|
50
50
|
export declare function resolveSubagentCapabilityCeiling(sessionId: string | undefined, inherited?: ResolvedSubagentCapabilityCeiling): ResolvedSubagentCapabilityCeiling | undefined;
|
|
51
|
-
|
|
51
|
+
/** Ceilings are registered under `ctx.sessionManager.getSessionId()`; pass that id, never the session file path. */
|
|
52
|
+
export declare function resolveCurrentSubagentCapabilityCeiling(piSessionId: string | undefined): ResolvedSubagentCapabilityCeiling | undefined;
|
|
52
53
|
export declare function isAgentAllowedByCapabilityCeiling(agentName: string, ceiling: ResolvedSubagentCapabilityCeiling | undefined): boolean;
|
|
53
54
|
export declare function capabilityCeilingAgentRestrictionMessage(agentName: string, ceiling: ResolvedSubagentCapabilityCeiling | undefined): string | undefined;
|
|
54
55
|
export declare function assertAgentAllowedByCapabilityCeiling(agentName: string, ceiling: ResolvedSubagentCapabilityCeiling | undefined): void;
|
|
@@ -135,8 +135,9 @@ export function resolveSubagentCapabilityCeiling(sessionId, inherited) {
|
|
|
135
135
|
}
|
|
136
136
|
return intersectSubagentCapabilityCeilings(inherited, ...active);
|
|
137
137
|
}
|
|
138
|
-
|
|
139
|
-
|
|
138
|
+
/** Ceilings are registered under `ctx.sessionManager.getSessionId()`; pass that id, never the session file path. */
|
|
139
|
+
export function resolveCurrentSubagentCapabilityCeiling(piSessionId) {
|
|
140
|
+
return resolveSubagentCapabilityCeiling(piSessionId, undefined);
|
|
140
141
|
}
|
|
141
142
|
export function isAgentAllowedByCapabilityCeiling(agentName, ceiling) {
|
|
142
143
|
return ceiling?.allowedAgents === undefined || ceiling.allowedAgents.includes(agentName);
|
|
@@ -109,6 +109,8 @@ export interface ChildSession {
|
|
|
109
109
|
readonly modelId: string | undefined;
|
|
110
110
|
/** Provider id of the selected model when this child inherited that provider from the parent's registry instead of loading its extension. */
|
|
111
111
|
readonly inheritedProvider?: string;
|
|
112
|
+
/** Provider id of the selected model when an extension in this child's model runtime registered that provider, at load or later (e.g. on `session_start`). */
|
|
113
|
+
readonly claimedProvider?: string;
|
|
112
114
|
/** Live provider/id of the selected model when Pi marks it virtual (`api === "pi-virtual"`); assistant messages then name the dispatched physical model. */
|
|
113
115
|
readonly virtualModelId?: string;
|
|
114
116
|
readonly contextWindow?: number;
|
|
@@ -633,6 +633,7 @@ export function createDefaultChildSessionFactory(options = {}) {
|
|
|
633
633
|
get sessionId() { return session.sessionId; },
|
|
634
634
|
get modelId() { return session.model ? `${session.model.provider}/${session.model.id}` : undefined; },
|
|
635
635
|
get inheritedProvider() { return session.model && inheritedProviders.has(session.model.provider) ? session.model.provider : undefined; },
|
|
636
|
+
get claimedProvider() { return session.model && !inheritedProviders.has(session.model.provider) && modelRuntime.getRegisteredProviderIds().includes(session.model.provider) ? session.model.provider : undefined; },
|
|
636
637
|
get virtualModelId() { return session.model?.api === "pi-virtual" ? `${session.model.provider}/${session.model.id}` : undefined; },
|
|
637
638
|
get contextWindow() { return session.model?.contextWindow; },
|
|
638
639
|
};
|
|
@@ -31,3 +31,14 @@ export declare function formatChildModelResolutionDiagnostic(diagnostic: ChildMo
|
|
|
31
31
|
export declare function formatInheritedProviderDiagnostic(diagnostic: ChildModelResolutionDiagnostic & {
|
|
32
32
|
provider: string;
|
|
33
33
|
}): string;
|
|
34
|
+
/**
|
|
35
|
+
* Explain a background child that failed before any successful response on a
|
|
36
|
+
* provider a parent extension registered, when that extension did not load in
|
|
37
|
+
* the child. A parent extension can register a provider under a built-in id,
|
|
38
|
+
* so the model still resolves in the child, through the built-in provider
|
|
39
|
+
* rather than the parent's. Whether that caused this failure is unknown here,
|
|
40
|
+
* hence the conditional wording.
|
|
41
|
+
*/
|
|
42
|
+
export declare function formatMissingParentProviderDiagnostic(diagnostic: ChildModelResolutionDiagnostic & {
|
|
43
|
+
provider: string;
|
|
44
|
+
}): string;
|
|
@@ -89,3 +89,28 @@ export function formatInheritedProviderDiagnostic(diagnostic) {
|
|
|
89
89
|
`If ${provider} depends on those hooks, run this agent with \`async: true\` (a background child loads the ambient extensions unless the agent sets \`extensions\`), or load the extension for this agent with \`subagentOnlyExtensions\` or \`extensions\` in the agent frontmatter.`,
|
|
90
90
|
].join("\n");
|
|
91
91
|
}
|
|
92
|
+
/**
|
|
93
|
+
* Explain a background child that failed before any successful response on a
|
|
94
|
+
* provider a parent extension registered, when that extension did not load in
|
|
95
|
+
* the child. A parent extension can register a provider under a built-in id,
|
|
96
|
+
* so the model still resolves in the child, through the built-in provider
|
|
97
|
+
* rather than the parent's. Whether that caused this failure is unknown here,
|
|
98
|
+
* hence the conditional wording.
|
|
99
|
+
*/
|
|
100
|
+
export function formatMissingParentProviderDiagnostic(diagnostic) {
|
|
101
|
+
const subject = diagnostic.agent ? `Agent '${diagnostic.agent}'` : "Subagent";
|
|
102
|
+
const model = diagnostic.model ? `'${diagnostic.model}'` : "this model";
|
|
103
|
+
const provider = `'${diagnostic.provider}'`;
|
|
104
|
+
const facts = `${subject} ran as a background child without the ambient extensions. In the parent, provider ${provider} for ${model} is registered by an extension that did not load in this child, so the child sent its requests without that extension's provider, through the built-in provider of the same id if there is one.`;
|
|
105
|
+
if (capabilityCeilingDeniesExtensions(diagnostic)) {
|
|
106
|
+
return [
|
|
107
|
+
facts,
|
|
108
|
+
`${denyExtensionsClause(diagnostic)}, so that extension cannot load for this child: listed \`subagentOnlyExtensions\` or \`extensions\` entries are suppressed, and leaving \`extensions\` unset does not enable ambient loading.`,
|
|
109
|
+
`If ${provider} depends on that extension, relax the capability ceiling to allow extensions, or use a model whose provider does not.`,
|
|
110
|
+
].join("\n");
|
|
111
|
+
}
|
|
112
|
+
return [
|
|
113
|
+
facts,
|
|
114
|
+
`If ${provider} depends on that extension, list it in \`subagentOnlyExtensions\` or \`extensions\` in the agent frontmatter, leave \`extensions\` unset so the child loads the ambient extensions, or register it from the host with \`registerRequiredChildExtensions\` so it loads for every launch.`,
|
|
115
|
+
].join("\n");
|
|
116
|
+
}
|
|
@@ -23,7 +23,7 @@ export declare function resolveParallelHandoffChild(input: {
|
|
|
23
23
|
child: ParallelHandoffGroup["children"][number];
|
|
24
24
|
} | undefined;
|
|
25
25
|
export declare function protectRetainedWorktreeForResume(manifestPath: string, runId: string, childIndex: number, signal?: AbortSignal): Promise<string>;
|
|
26
|
-
export declare function resolveRetainedWorktreeCwd(manifestPath: string, runId: string, childIndex: number): string | undefined;
|
|
26
|
+
export declare function resolveRetainedWorktreeCwd(manifestPath: string, runId: string, childIndex: number, recordedWorktreePath?: string): string | undefined;
|
|
27
27
|
export declare function isTerminalParallelHandoffChildStatus(status: unknown): boolean;
|
|
28
28
|
export declare function formatStoredParallelHandoffCleanup(manifestPath: string, manifest?: ParallelHandoffManifest): string;
|
|
29
29
|
export interface ParallelHandoffEvidenceResult {
|
|
@@ -165,17 +165,18 @@ export async function protectRetainedWorktreeForResume(manifestPath, runId, chil
|
|
|
165
165
|
return cwd;
|
|
166
166
|
}), { signal }));
|
|
167
167
|
}
|
|
168
|
-
export function resolveRetainedWorktreeCwd(manifestPath, runId, childIndex) {
|
|
168
|
+
export function resolveRetainedWorktreeCwd(manifestPath, runId, childIndex, recordedWorktreePath) {
|
|
169
169
|
const manifest = readParallelHandoffManifest(manifestPath);
|
|
170
|
-
if (
|
|
171
|
-
return undefined;
|
|
172
|
-
if (manifest.runId !== runId)
|
|
170
|
+
if (manifest && manifest.runId !== runId)
|
|
173
171
|
throw new Error(`Managed worktree handoff belongs to run '${manifest.runId}', not '${runId}'.`);
|
|
174
|
-
const match = manifest
|
|
172
|
+
const match = manifest?.groups
|
|
175
173
|
.flatMap((group) => group.children.map((child) => ({ group, child })))
|
|
176
174
|
.find(({ child }) => child.index === childIndex);
|
|
177
|
-
if (!match)
|
|
175
|
+
if (!manifest || !match) {
|
|
176
|
+
if (recordedWorktreePath)
|
|
177
|
+
throw new Error(`Async run '${runId}' child ${childIndex} ran in managed worktree ${recordedWorktreePath}, but its handoff has no resumable record yet. Retry after the run settles; if it already ended, inspect ${manifestPath}.`);
|
|
178
178
|
return undefined;
|
|
179
|
+
}
|
|
179
180
|
const cleanup = match.group.cleanup.tasks.find((task) => task.index === match.child.taskIndex);
|
|
180
181
|
if (!cleanup)
|
|
181
182
|
throw new Error(`Async run '${runId}' child ${childIndex} has no managed worktree cleanup record.`);
|
|
@@ -185,6 +186,9 @@ export function resolveRetainedWorktreeCwd(manifestPath, runId, childIndex) {
|
|
|
185
186
|
const requiredCwd = path.join(cleanup.path, relativeCwd);
|
|
186
187
|
if (cleanup.worktreeRemoved || cleanup.branchRemoved)
|
|
187
188
|
throw new Error(`Async run '${runId}' required managed worktree was removed: ${requiredCwd}`);
|
|
189
|
+
// The runner removes the worktree right after this marker, without the locks that guard resume admission.
|
|
190
|
+
if (cleanup.reason?.includes("pending durable handoff capture"))
|
|
191
|
+
throw new Error(`Async run '${runId}' child ${childIndex} is still finalizing its managed worktree: ${requiredCwd}. Retry after the run settles; if it already ended, inspect ${manifestPath}.`);
|
|
188
192
|
let worktreeRoot = "";
|
|
189
193
|
let resolvedRequiredCwd = "";
|
|
190
194
|
try {
|
|
@@ -55,6 +55,8 @@ export interface RunnerSubagentStep {
|
|
|
55
55
|
}>;
|
|
56
56
|
modelResponseAliases?: Record<string, string[]>;
|
|
57
57
|
tools?: string[];
|
|
58
|
+
/** Provider of `model` that a parent extension registers; set only when this child loads no ambient extensions. */
|
|
59
|
+
parentExtensionProvider?: string;
|
|
58
60
|
excludeTools?: string[];
|
|
59
61
|
allowNestedSubagents?: boolean;
|
|
60
62
|
/** Resolved selected-agent policy for launches made by this child. */
|
package/src/shared/types.d.ts
CHANGED
|
@@ -1936,7 +1936,7 @@ export interface AsyncStatus {
|
|
|
1936
1936
|
label?: string;
|
|
1937
1937
|
workflowKey?: string;
|
|
1938
1938
|
lane?: WorkflowLaneMetadata;
|
|
1939
|
-
/**
|
|
1939
|
+
/** Worktree path copied at setup; the handoff stays authoritative for the cwd. */
|
|
1940
1940
|
worktreePath?: string;
|
|
1941
1941
|
/** Display-only branch copied at launch; handoff remains authoritative. */
|
|
1942
1942
|
branch?: string;
|
|
@@ -774,17 +774,22 @@ export function validateWorkflowScript(script, options = {}) {
|
|
|
774
774
|
: undefined;
|
|
775
775
|
const workflowBody = wrapper && astNode(wrapper.body) ? wrapper.body : root;
|
|
776
776
|
// A rebound `runs` may not be the workflow API, so its calls prove nothing about child launches.
|
|
777
|
-
const
|
|
777
|
+
const runsRebound = invalidatesRunsBinding(workflowBody);
|
|
778
|
+
const agentNameError = options.agentNameError && !runsRebound ? options.agentNameError : undefined;
|
|
778
779
|
walkAst(workflowBody, (node) => {
|
|
779
780
|
if (node !== wrapper && node.async === true && (node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression")) {
|
|
780
781
|
errors.push({ message: "workflowScript does not support nested async functions. Use top-level await, plain helper functions that return runs.run(...), or explicit Promise chains.", ...nodeLocation(node) });
|
|
781
782
|
}
|
|
783
|
+
if (node.type === "ImportExpression")
|
|
784
|
+
errors.push({ message: "workflowScript cannot load modules with import().", ...nodeLocation(node) });
|
|
782
785
|
if (directRunsCall(node, "run")) {
|
|
783
786
|
const args = Array.isArray(node.arguments) ? node.arguments : [];
|
|
784
787
|
const keyNode = astNode(args[0]) ? args[0] : undefined;
|
|
785
788
|
const key = literalString(keyNode);
|
|
786
789
|
if (keyNode && key !== undefined && !KEY_PATTERN.test(key))
|
|
787
790
|
errors.push({ message: "runs.run key must be 1-128 characters using letters, numbers, '.', '_' or '-', and start with a letter or number.", ...nodeLocation(keyNode) });
|
|
791
|
+
if (keyNode?.type === "ObjectExpression" && !runsRebound)
|
|
792
|
+
errors.push({ message: `runs.run expects (key, params); got an object as the first argument. Use runs.run("plan", { agent, task }) or runs.all([{ key, agent, task }]).`, ...nodeLocation(keyNode) });
|
|
788
793
|
if (astNode(args[1])) {
|
|
789
794
|
errors.push(...validateStaticBaseRef(args[1], "runs.run"));
|
|
790
795
|
const message = definitelyNonJson(args[1]);
|