pi-subagents 0.83.0 → 0.83.1

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.
Files changed (63) hide show
  1. package/CHANGELOG.md +22 -2
  2. package/bundle/agents.js +4 -4
  3. package/bundle/capability-ceiling.js +1 -1
  4. package/bundle/child-tool-plan.js +3 -3
  5. package/bundle/{chunk-P2NCCTZP.js → chunk-2D52JFLF.js} +1 -1
  6. package/bundle/{chunk-WG2OTTAK.js → chunk-2DHZTCOL.js} +2 -2
  7. package/bundle/{chunk-MBDOYZ2V.js → chunk-2S4K53MV.js} +1 -1
  8. package/bundle/{chunk-HJJAVHFB.js → chunk-5YIITCPD.js} +23 -23
  9. package/bundle/{chunk-CNLDYEH4.js → chunk-AHIFYGR3.js} +11 -11
  10. package/bundle/{chunk-MHX22USZ.js → chunk-C5P5CHYK.js} +1 -1
  11. package/bundle/{chunk-XXKG3P2W.js → chunk-C7JMPU5F.js} +4 -4
  12. package/bundle/{chunk-VTMNBPCQ.js → chunk-D3JAFGMO.js} +1 -1
  13. package/bundle/{chunk-BKFWQ642.js → chunk-F4ASL6OV.js} +1 -1
  14. package/bundle/{chunk-CDI3GMNV.js → chunk-F5DBXJ2G.js} +15 -15
  15. package/bundle/{chunk-JHZ6DZ5M.js → chunk-G2Z6ZH26.js} +1 -1
  16. package/bundle/{chunk-TJQJ3MEA.js → chunk-IPGZMQXV.js} +10 -10
  17. package/bundle/{chunk-Y3I6H6DV.js → chunk-LFDBD4PW.js} +2 -2
  18. package/bundle/{chunk-7F5SK2U3.js → chunk-LNRJCP2Y.js} +17 -14
  19. package/bundle/{chunk-6B4ITO2W.js → chunk-MABL3FUM.js} +1 -1
  20. package/bundle/{chunk-CGXWSNPA.js → chunk-NPIYWVOD.js} +4 -4
  21. package/bundle/{chunk-RZJN63GU.js → chunk-QP5ZWNIS.js} +2 -2
  22. package/bundle/{chunk-Z7KVHQHL.js → chunk-QXASAHIA.js} +2 -2
  23. package/bundle/{chunk-5ESXK62X.js → chunk-RNIZLSEA.js} +416 -389
  24. package/bundle/{chunk-HLE22FSH.js → chunk-SSG7IEP5.js} +2 -2
  25. package/bundle/{chunk-CZTMUBUI.js → chunk-UFL7IYCW.js} +4 -4
  26. package/bundle/{chunk-LDMXJIA6.js → chunk-UUP372DR.js} +44 -67
  27. package/bundle/{chunk-OOFW75PV.js → chunk-UYK5MZWE.js} +15 -11
  28. package/bundle/{chunk-CSSZRTKY.js → chunk-V2BZYLEP.js} +2 -2
  29. package/bundle/{chunk-4BW55BTL.js → chunk-VLFZIN6D.js} +4 -4
  30. package/bundle/{chunk-527E3D2J.js → chunk-YXFJVCOL.js} +5 -5
  31. package/bundle/{chunk-HXVKGS33.js → chunk-ZVOAMTUQ.js} +4 -4
  32. package/bundle/control-channel.js +3 -3
  33. package/bundle/index.js +1 -1
  34. package/bundle/intercom-bridge.js +3 -3
  35. package/bundle/preflight.js +9 -9
  36. package/bundle/project-panes.js +4 -4
  37. package/docs/configuration.md +12 -1
  38. package/docs/extension-api.md +2 -0
  39. package/docs/missions.md +2 -0
  40. package/docs/tool-reference.md +1 -1
  41. package/package.json +1 -1
  42. package/src/agents/agent-management.d.ts +1 -1
  43. package/src/agents/agent-management.js +2 -2
  44. package/src/agents/agents.js +1 -2
  45. package/src/agents/frontmatter.js +22 -18
  46. package/src/extension/index.js +2 -2
  47. package/src/missions/actions.js +7 -2
  48. package/src/missions/lifecycle.d.ts +11 -0
  49. package/src/missions/lifecycle.js +37 -6
  50. package/src/missions/store.js +1 -0
  51. package/src/missions/types.d.ts +2 -0
  52. package/src/runs/background/async-execution.d.ts +1 -1
  53. package/src/runs/background/async-execution.js +3 -3
  54. package/src/runs/background/async-resume.js +3 -2
  55. package/src/runs/background/retained-children.js +1 -1
  56. package/src/runs/foreground/subagent-executor.js +24 -51
  57. package/src/runs/shared/capability-ceiling.d.ts +2 -1
  58. package/src/runs/shared/capability-ceiling.js +3 -2
  59. package/src/runs/shared/parallel-handoff.d.ts +1 -1
  60. package/src/runs/shared/parallel-handoff.js +10 -6
  61. package/src/shared/types.d.ts +1 -1
  62. package/src/workflows/scripted-workflow.js +4 -1
  63. package/workflow-script-worker.cjs +2 -1
@@ -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 = getAsyncInterruptTarget(state, runId, location, { fallbackToNewest: runId === undefined });
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 ?? "current"}'.` }],
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
  };
@@ -1086,7 +1057,7 @@ function appendStepToAsyncChain(input) {
1086
1057
  worktreeBranchPrefix: input.deps.config.worktreeBranchPrefix,
1087
1058
  asyncDir: resolved.location.asyncDir,
1088
1059
  validateOutputBindings: false,
1089
- capabilityCeiling: intersectSubagentCapabilityCeilings(status.capabilityCeiling, resolveCurrentSubagentCapabilityCeiling(asyncCtx.currentSessionId)),
1060
+ capabilityCeiling: intersectSubagentCapabilityCeilings(status.capabilityCeiling, resolveCurrentSubagentCapabilityCeiling(asyncCtx.parentSessionId)),
1090
1061
  requiredExtensions: retainedRequiredExtensions,
1091
1062
  }));
1092
1063
  if ("error" in built) {
@@ -1562,7 +1533,7 @@ async function resumeExternalJobFollowUp(input) {
1562
1533
  availableModels: input.ctx.modelRegistry.getAvailable().map(toModelInfo),
1563
1534
  outputBaseDir: resolveSingleRunOutputBaseDir(input.deps, artifactsDir, runId),
1564
1535
  ...(input.absoluteDeadlineAt !== undefined ? { absoluteDeadlineAt: input.absoluteDeadlineAt } : {}),
1565
- capabilityCeiling: resolveCurrentSubagentCapabilityCeiling(currentSessionId),
1536
+ capabilityCeiling: resolveCurrentSubagentCapabilityCeiling(input.ctx.sessionManager.getSessionId() ?? undefined),
1566
1537
  runFanoutBudget: createRunFanoutBudget(runId, resolveMaxSubagentSpawnsPerRun(input.deps.config.maxSubagentSpawnsPerRun)),
1567
1538
  activeAsyncCapacity,
1568
1539
  externalJobFollowUp: { sourceRunId: input.target.runId, sourceStepIndex: input.target.index, parentProviderJobId: externalJob.providerJobId, requestId, requestDigest },
@@ -1858,7 +1829,7 @@ async function resumeAsyncRun(input) {
1858
1829
  childIntercomTarget: intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(runId, agent, index) : undefined,
1859
1830
  globalConcurrencyLimit: input.deps.config.globalConcurrencyLimit,
1860
1831
  runFanoutBudget: createRunFanoutBudget(runId, resolveMaxSubagentSpawnsPerRun(input.deps.config.maxSubagentSpawnsPerRun)),
1861
- capabilityCeiling: intersectSubagentCapabilityCeilings("capabilityCeiling" in target ? target.capabilityCeiling : undefined, resolveCurrentSubagentCapabilityCeiling(input.deps.state.currentSessionId)),
1832
+ capabilityCeiling: intersectSubagentCapabilityCeilings("capabilityCeiling" in target ? target.capabilityCeiling : undefined, resolveCurrentSubagentCapabilityCeiling(input.ctx.sessionManager.getSessionId() ?? undefined)),
1862
1833
  thinkingCeiling: target.thinkingCeiling,
1863
1834
  activeAsyncCapacity,
1864
1835
  }));
@@ -2028,7 +1999,7 @@ async function resumeAsyncRun(input) {
2028
1999
  ...(input.params.toolBudget !== undefined ? { toolBudget: input.params.toolBudget } : {}),
2029
2000
  // Recovery descriptors, remembered foreground runs, and current workflow roots
2030
2001
  // 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.deps.state.currentSessionId)),
2002
+ capabilityCeiling: intersectSubagentCapabilityCeilings(recoveryDescriptor?.capabilityCeiling ?? (target.source === "foreground" || (target.source === "async" && target.mode === "workflow") ? target.capabilityCeiling : undefined), resolveCurrentSubagentCapabilityCeiling(input.ctx.sessionManager.getSessionId() ?? undefined)),
2032
2003
  runFanoutBudget: input.params.runFanoutBudget ?? recoveryDescriptor?.runFanoutBudget ?? createRunFanoutBudget(runId, resolveMaxSubagentSpawnsPerRun(input.deps.config.maxSubagentSpawnsPerRun)),
2033
2004
  parentWorkflowRunId: input.params.workflowParentRunId,
2034
2005
  workflowKey: input.params.workflowKey,
@@ -5093,7 +5064,7 @@ export function createSubagentExecutor(deps) {
5093
5064
  const eventsPath = path.join(asyncDir, "events.jsonl");
5094
5065
  const startedAt = Date.now();
5095
5066
  const currentSessionId = resolveCurrentSessionId(ctx.sessionManager);
5096
- const workflowCapabilityCeiling = intersectSubagentCapabilityCeilings(requestParams.capabilityCeiling, resolveCurrentSubagentCapabilityCeiling(currentSessionId));
5067
+ const workflowCapabilityCeiling = intersectSubagentCapabilityCeilings(requestParams.capabilityCeiling, resolveCurrentSubagentCapabilityCeiling(ctx.sessionManager.getSessionId() ?? undefined));
5097
5068
  const completionOwnerId = deps.state.completionOwnerId ?? currentCompletionOwnerId();
5098
5069
  deps.state.completionOwnerId = completionOwnerId;
5099
5070
  const workflowScriptSourceDigest = workflowScriptDigest(requestParams.workflowScript);
@@ -5971,6 +5942,7 @@ export function createSubagentExecutor(deps) {
5971
5942
  ...(result.details.results[0]?.sessionFile ? { sessionPath: result.details.results[0].sessionFile } : {}),
5972
5943
  artifactPaths: child.artifactPaths,
5973
5944
  ...(["completed", "failed"].includes(childStatus) ? { completedAt: new Date().toISOString() } : {}),
5945
+ acceptanceStatus: missionAcceptanceStatus(result.details.results),
5974
5946
  heartbeat: { status: childStatus, ...(childPhase ? { phase: childPhase } : {}) },
5975
5947
  });
5976
5948
  if (result.details.asyncId) {
@@ -6137,7 +6109,7 @@ export function createSubagentExecutor(deps) {
6137
6109
  let lastProgressAt = 0;
6138
6110
  let workflowProgressClosed = false;
6139
6111
  const workflowDeadlineAt = timeout === undefined ? undefined : Date.now() + timeout;
6140
- const workflowCapabilityCeiling = intersectSubagentCapabilityCeilings(requestParams.capabilityCeiling, resolveCurrentSubagentCapabilityCeiling(resolveCurrentSessionId(ctx.sessionManager)));
6112
+ const workflowCapabilityCeiling = intersectSubagentCapabilityCeilings(requestParams.capabilityCeiling, resolveCurrentSubagentCapabilityCeiling(ctx.sessionManager.getSessionId() ?? undefined));
6141
6113
  const sendWorkflowProgress = () => {
6142
6114
  if (workflowProgressClosed)
6143
6115
  return;
@@ -6294,6 +6266,7 @@ export function createSubagentExecutor(deps) {
6294
6266
  ...(result.details.results[0]?.sessionFile ? { sessionPath: result.details.results[0].sessionFile } : {}),
6295
6267
  artifactPaths: child.artifactPaths,
6296
6268
  ...(["completed", "failed"].includes(childStatus) ? { completedAt: new Date().toISOString() } : {}),
6269
+ acceptanceStatus: missionAcceptanceStatus(result.details.results),
6297
6270
  heartbeat: { status: childStatus, ...(childPhase ? { phase: childPhase } : {}) },
6298
6271
  });
6299
6272
  return child;
@@ -7061,22 +7034,22 @@ export function createSubagentExecutor(deps) {
7061
7034
  if (stopResult)
7062
7035
  return stopResult;
7063
7036
  return {
7064
- content: [{ type: "text", text: "No stoppable async run found in this session." }],
7037
+ content: [{ type: "text", text: `No stoppable async run found for '${targetRunId}'.` }],
7065
7038
  isError: true,
7066
7039
  details: { mode: "management", results: [] },
7067
7040
  };
7068
7041
  }
7069
7042
  if (action === "interrupt") {
7070
7043
  const targetRunId = paramsWithResolvedCwd.runId ?? paramsWithResolvedCwd.id;
7044
+ if (!targetRunId)
7045
+ return { content: [{ type: "text", text: "action='interrupt' requires id." }], isError: true, details: { mode: "management", results: [] } };
7071
7046
  let resolved;
7072
- if (targetRunId) {
7073
- try {
7074
- resolved = resolveSubagentRunId(targetRunId, omitUndefinedProperties({ state: deps.state, nested: nestedResolutionScopeForExecutor(deps) }));
7075
- }
7076
- catch (error) {
7077
- const message = error instanceof Error ? error.message : String(error);
7078
- return { content: [{ type: "text", text: message }], isError: true, details: { mode: "management", results: [] } };
7079
- }
7047
+ try {
7048
+ resolved = resolveSubagentRunId(targetRunId, omitUndefinedProperties({ state: deps.state, nested: nestedResolutionScopeForExecutor(deps) }));
7049
+ }
7050
+ catch (error) {
7051
+ const message = error instanceof Error ? error.message : String(error);
7052
+ return { content: [{ type: "text", text: message }], isError: true, details: { mode: "management", results: [] } };
7080
7053
  }
7081
7054
  if (resolved?.kind === "nested")
7082
7055
  return interruptNestedRun(resolved);
@@ -7101,7 +7074,7 @@ export function createSubagentExecutor(deps) {
7101
7074
  if (asyncInterruptResult)
7102
7075
  return asyncInterruptResult;
7103
7076
  return {
7104
- content: [{ type: "text", text: "No interrupt-capable run found in this session." }],
7077
+ content: [{ type: "text", text: `No interrupt-capable run found for '${targetRunId}'.` }],
7105
7078
  isError: true,
7106
7079
  details: { mode: "management", results: [] },
7107
7080
  };
@@ -7117,7 +7090,7 @@ export function createSubagentExecutor(deps) {
7117
7090
  ...ctx,
7118
7091
  cwd: requestCwd,
7119
7092
  config: deps.config,
7120
- currentSessionId: deps.state.currentSessionId ?? ctx.sessionManager.getSessionId() ?? undefined,
7093
+ piSessionId: ctx.sessionManager.getSessionId() ?? undefined,
7121
7094
  discoverAgentsAll: deps.discoverAgentsAll,
7122
7095
  runtimeAgentOwner: deps.pi,
7123
7096
  onAgentsChanged: deps.onAgentsChanged,
@@ -7218,7 +7191,7 @@ export function createSubagentExecutor(deps) {
7218
7191
  let prepareForkSessionForIndex = async () => { };
7219
7192
  // Check before pruner/session preparation, then recheck immediately before each fork
7220
7193
  // because the session capability ceiling can tighten while preparation awaits.
7221
- const launchCapabilityCeiling = () => intersectSubagentCapabilityCeilings(intersectSubagentCapabilityCeilings(effectiveParams.capabilityCeiling, resolveCurrentSubagentCapabilityCeiling(requestSessionId)), deps.childRuntime?.capabilityCeiling);
7194
+ const launchCapabilityCeiling = () => intersectSubagentCapabilityCeilings(intersectSubagentCapabilityCeilings(effectiveParams.capabilityCeiling, resolveCurrentSubagentCapabilityCeiling(requestPiSessionId)), deps.childRuntime?.capabilityCeiling);
7222
7195
  const assertLaunchableAgentsAllowedBeforeFork = () => {
7223
7196
  if (!contextPolicy.usesFork)
7224
7197
  return;
@@ -7499,7 +7472,7 @@ export function createSubagentExecutor(deps) {
7499
7472
  scopedModelIds: scopedModelIdsFromContext(ctx),
7500
7473
  parentSessionId: requestSessionId,
7501
7474
  parentPiSessionId: requestPiSessionId,
7502
- capabilityCeiling: intersectSubagentCapabilityCeilings(effectiveParams.capabilityCeiling, resolveCurrentSubagentCapabilityCeiling(requestSessionId)),
7475
+ capabilityCeiling: intersectSubagentCapabilityCeilings(effectiveParams.capabilityCeiling, resolveCurrentSubagentCapabilityCeiling(requestPiSessionId)),
7503
7476
  runFanoutBudget,
7504
7477
  topLevelAsyncCapacityEligible,
7505
7478
  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
- export declare function resolveCurrentSubagentCapabilityCeiling(sessionId: string | undefined): ResolvedSubagentCapabilityCeiling | undefined;
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
- export function resolveCurrentSubagentCapabilityCeiling(sessionId) {
139
- return resolveSubagentCapabilityCeiling(sessionId, undefined);
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);
@@ -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 (!manifest)
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.groups
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 {
@@ -1936,7 +1936,7 @@ export interface AsyncStatus {
1936
1936
  label?: string;
1937
1937
  workflowKey?: string;
1938
1938
  lane?: WorkflowLaneMetadata;
1939
- /** Display-only worktree path copied at launch; handoff remains authoritative. */
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,7 +774,8 @@ 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 agentNameError = options.agentNameError && !invalidatesRunsBinding(workflowBody) ? options.agentNameError : undefined;
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) });
@@ -785,6 +786,8 @@ export function validateWorkflowScript(script, options = {}) {
785
786
  const key = literalString(keyNode);
786
787
  if (keyNode && key !== undefined && !KEY_PATTERN.test(key))
787
788
  errors.push({ message: "runs.run key must be 1-128 characters using letters, numbers, '.', '_' or '-', and start with a letter or number.", ...nodeLocation(keyNode) });
789
+ if (keyNode?.type === "ObjectExpression" && !runsRebound)
790
+ 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
791
  if (astNode(args[1])) {
789
792
  errors.push(...validateStaticBaseRef(args[1], "runs.run"));
790
793
  const message = definitelyNonJson(args[1]);
@@ -307,7 +307,7 @@ function laneBoardText(value) {
307
307
  if (typeof value !== "string" || !value) return undefined;
308
308
  if (laneByteLength(value) <= MAX_LANE_BOARD_TEXT_BYTES) return value;
309
309
  const bytes = new TextEncoder().encode(value).subarray(0, MAX_LANE_BOARD_TEXT_BYTES - 3);
310
- return new TextDecoder().decode(bytes) + "...";
310
+ return new TextDecoder().decode(bytes, { stream: true }) + "...";
311
311
  }
312
312
 
313
313
  function laneBoardPath(value) {
@@ -758,6 +758,7 @@ function launchRunsAllPermissive(items) {
758
758
 
759
759
  const runs = Object.freeze({
760
760
  run(key, params) {
761
+ if (key && typeof key === "object" && !Array.isArray(key)) throw new Error('runs.run expects (key, params); got an object as the first argument. Use runs.run("plan", { agent, task }) or runs.all([{ key, agent, task }]).');
761
762
  validateRunCall(key, params, "runs.run", runFingerprints);
762
763
  const launched = runHostCall(key, params, false);
763
764
  return trackRunObservation([{ key, operation: "run", callId: launched.callId }], launched.promise.then(decorateWorkflowChildResult));