pi-subagents 0.57.0 → 0.59.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 +104 -0
- package/docs/agents.md +18 -9
- package/docs/configuration.md +4 -4
- package/docs/extension-api.md +40 -1
- package/docs/models.md +27 -4
- package/docs/observability.md +1 -1
- package/docs/tool-reference.md +92 -6
- package/docs/workflows.md +115 -0
- package/package.json +3 -1
- package/prompts/review-loop.md +2 -2
- package/skills/council-mode/SKILL.md +1 -1
- package/skills/pi-subagents/SKILL.md +3 -1
- package/skills/pi-subagents/references/execution-controls.md +6 -2
- package/skills/pi-subagents/references/management-authoring-rpc.md +2 -2
- package/skills/pi-subagents/references/prompting-and-roles.md +26 -5
- package/src/agents/agent-management.ts +28 -17
- package/src/agents/agent-serializer.ts +9 -2
- package/src/agents/agents.ts +103 -27
- package/src/agents/runtime-agent-events.ts +70 -0
- package/src/agents/runtime-agent-registry.ts +19 -18
- package/src/api/agents.ts +10 -5
- package/src/api/background-work.ts +5 -1
- package/src/api/delegation.ts +0 -7
- package/src/api/preflight.ts +32 -10
- package/src/extension/fanout-child.ts +5 -3
- package/src/extension/index.ts +66 -27
- package/src/extension/public-execution.ts +15 -2
- package/src/extension/rpc.ts +35 -14
- package/src/extension/schemas.ts +37 -12
- package/src/extension/tool-description.ts +25 -6
- package/src/integrations/herdr-status.ts +5 -0
- package/src/intercom/result-intercom.ts +2 -0
- package/src/profiles/profiles.ts +5 -6
- package/src/runs/background/active-async-capacity.ts +2 -2
- package/src/runs/background/async-execution.ts +78 -46
- package/src/runs/background/async-job-tracker.ts +14 -12
- package/src/runs/background/async-resume.ts +29 -27
- package/src/runs/background/async-status-snapshot.ts +23 -261
- package/src/runs/background/async-status.ts +68 -7
- package/src/runs/background/chain-append.ts +8 -3
- package/src/runs/background/chain-root-attachment.ts +60 -8
- package/src/runs/background/fleet-view.ts +21 -11
- package/src/runs/background/notify.ts +184 -9
- package/src/runs/background/result-delivery-ownership.ts +45 -0
- package/src/runs/background/result-files.ts +2 -1
- package/src/runs/background/result-watcher.ts +29 -10
- package/src/runs/background/resume-guidance.ts +1 -1
- package/src/runs/background/retained-children.ts +1 -1
- package/src/runs/background/run-status.ts +18 -8
- package/src/runs/background/scheduled-runs.ts +86 -7
- package/src/runs/background/stale-run-reconciler.ts +10 -4
- package/src/runs/background/steering.ts +4 -14
- package/src/runs/background/subagent-runner.ts +389 -350
- package/src/runs/background/subagent-wait.ts +58 -10
- package/src/runs/background/terminal-run-index.ts +1 -1
- package/src/runs/background/wait-completions.ts +22 -1
- package/src/runs/background/wait-config.ts +23 -9
- package/src/runs/background/wait-tool.ts +9 -2
- package/src/runs/foreground/async-steering-action.ts +2 -2
- package/src/runs/foreground/execution.ts +134 -121
- package/src/runs/foreground/foreground-control.ts +3 -0
- package/src/runs/foreground/foreground-history.ts +1 -0
- package/src/runs/foreground/subagent-executor.ts +527 -257
- package/src/runs/foreground/workflow-detach-reconcile.ts +130 -120
- package/src/runs/shared/abort-recovery.ts +119 -0
- package/src/runs/shared/async-status-projection.ts +463 -0
- package/src/runs/shared/child-identity.ts +19 -4
- package/src/runs/shared/child-launch-plan.ts +151 -0
- package/src/runs/shared/completion-evidence.ts +89 -0
- package/src/runs/shared/completion-guard.ts +5 -4
- package/src/runs/shared/dynamic-fanout.ts +3 -3
- package/src/runs/shared/fast-mode-extension.ts +5 -5
- package/src/runs/shared/host-step-status.ts +230 -0
- package/src/runs/shared/lane-metadata.ts +105 -0
- package/src/runs/shared/launch-cwd.ts +16 -0
- package/src/runs/shared/long-running-guard.ts +2 -1
- package/src/runs/shared/mcp-config-sources.ts +422 -0
- package/src/runs/shared/mcp-direct-tool-allowlist.ts +221 -158
- package/src/runs/shared/mcp-direct-tool-grant.ts +197 -0
- package/src/runs/shared/model-exclusions.ts +12 -0
- package/src/runs/shared/model-fallback.ts +25 -6
- package/src/runs/shared/nested-events.ts +6 -2
- package/src/runs/shared/nested-render.ts +7 -3
- package/src/runs/shared/parallel-handoff.ts +419 -7
- package/src/runs/shared/parallel-utils.ts +15 -1
- package/src/runs/shared/pi-args.ts +81 -8
- package/src/runs/shared/single-output.ts +44 -4
- package/src/runs/shared/subagent-prompt-runtime.ts +98 -13
- package/src/runs/shared/worktree-cleanup-plan.ts +847 -0
- package/src/runs/shared/worktree.ts +18 -0
- package/src/shared/child-session-name.ts +46 -0
- package/src/shared/extension-context.ts +24 -0
- package/src/shared/formatters.ts +11 -2
- package/src/shared/launch-contract.ts +5 -1
- package/src/shared/settings.ts +9 -103
- package/src/shared/types.ts +240 -30
- package/src/shared/utils.ts +41 -84
- package/src/slash/delegation-adapters.ts +1 -8
- package/src/slash/delegation-request.ts +0 -4
- package/src/slash/slash-bridge.ts +1 -2
- package/src/slash/slash-commands.ts +369 -90
- package/src/slash/slash-live-state.ts +22 -11
- package/src/slash/subagents-admin.ts +3 -0
- package/src/tui/fleet-status.ts +125 -74
- package/src/tui/fleet.ts +11 -5
- package/src/tui/render.ts +330 -33
- package/src/watchdog/turn-delta.ts +1 -1
- package/src/workflows/chat-progress.ts +6 -3
- package/src/workflows/host-command.ts +230 -0
- package/src/workflows/scripted-workflow.ts +496 -35
- package/src/workflows/workflow-child-summary.ts +9 -5
- package/src/workflows/workflow-preflight.ts +270 -0
- package/src/workflows/workflow-receipt.ts +79 -5
- package/src/workflows/workflow-settlement.ts +246 -0
- package/src/runs/shared/turn-budget.ts +0 -98
package/src/extension/index.ts
CHANGED
|
@@ -21,10 +21,12 @@ import { keyText, type ExtensionAPI, type ExtensionContext, type ToolDefinition
|
|
|
21
21
|
import { Box, Container, Spacer, Text, truncateToWidth, visibleWidth, wrapTextWithAnsi, type Component } from "@earendil-works/pi-tui";
|
|
22
22
|
import { discoverAgents, discoverAgentsAll, type AgentConfig, type AgentScope } from "../agents/agents.ts";
|
|
23
23
|
import { clearRuntimeAgentsForPi, listRuntimeAgentConfigs, mergeRuntimeAgents } from "../agents/runtime-agent-registry.ts";
|
|
24
|
+
import { registerRuntimeAgentEventListener } from "../agents/runtime-agent-events.ts";
|
|
24
25
|
import { ensureAccessibleDir } from "../shared/accessible-dir.ts";
|
|
25
26
|
import { cleanupAllArtifactDirs, cleanupOldArtifacts, getArtifactsDir } from "../shared/artifacts.ts";
|
|
26
27
|
import { resolveCurrentSessionId } from "../shared/session-identity.ts";
|
|
27
28
|
import { getAgentDir } from "../shared/utils.ts";
|
|
29
|
+
import { isStaleExtensionContextError, withCachedUiContext } from "../shared/extension-context.ts";
|
|
28
30
|
import { currentCompletionOwnerId } from "../shared/completion-owner.ts";
|
|
29
31
|
import { cleanupOldChainDirs } from "../shared/settings.ts";
|
|
30
32
|
import { clearLegacyResultAnimationTimer, renderSubagentResult, renderSubagentSummary } from "../tui/render.ts";
|
|
@@ -37,6 +39,7 @@ import { getActiveAsyncCapacitySnapshot, resolveAbandonedSlotReleaseAfterMs, res
|
|
|
37
39
|
import { cleanupResultIndexes, missionObserverResultCandidateFiles } from "../runs/background/result-files.ts";
|
|
38
40
|
import { ASYNC_RETENTION_DELAY_MS, cleanupAsyncRetention } from "../runs/background/async-retention.ts";
|
|
39
41
|
import { createResultWatcher } from "../runs/background/result-watcher.ts";
|
|
42
|
+
import { createResultDeliveryOwnership } from "../runs/background/result-delivery-ownership.ts";
|
|
40
43
|
import { createScheduledRunManager } from "../runs/background/scheduled-runs.ts";
|
|
41
44
|
import { registerSlashCommands } from "../slash/slash-commands.ts";
|
|
42
45
|
import { registerPromptTemplateDelegationBridge } from "../slash/prompt-template-bridge.ts";
|
|
@@ -47,7 +50,6 @@ import { registerHerdrStatusBridge, type HerdrStatusRun } from "../integrations/
|
|
|
47
50
|
import { listHerdrProjectPaneRoots, restoreHerdrProjectPaneSnapshots } from "../inspectors/herdr/project-panes.ts";
|
|
48
51
|
import { registerSubagentRpcBridge } from "./rpc.ts";
|
|
49
52
|
import { clearSlashSnapshots, getSlashRenderableSnapshot, resolveSlashMessageDetails, restoreSlashFinalSnapshots, type SlashMessageDetails } from "../slash/slash-live-state.ts";
|
|
50
|
-
import { inspectSubagentStatus } from "../runs/background/run-status.ts";
|
|
51
53
|
import { resolveWaitToolConfig } from "../runs/background/subagent-wait.ts";
|
|
52
54
|
import { registerWaitTool } from "../runs/background/wait-tool.ts";
|
|
53
55
|
import { createWaitSubscriptionManager } from "../runs/background/wait-subscriptions.ts";
|
|
@@ -59,6 +61,7 @@ import { resolveCurrentSubagentCapabilityCeiling } from "../runs/shared/capabili
|
|
|
59
61
|
import { formatDuration, shortenPath } from "../shared/formatters.ts";
|
|
60
62
|
import { applyModelExclusionsConfig, loadConfig, resolveAsyncByDefault, resolveScheduledStoreRoot } from "./config.ts";
|
|
61
63
|
import { buildSubagentToolDescription, buildSubagentToolPromptMetadata } from "./tool-description.ts";
|
|
64
|
+
import { formatWorkflowPreflightSummary, normalizeWorkflowPreflight } from "../workflows/workflow-preflight.ts";
|
|
62
65
|
import { finalizeToolResult } from "./tool-result.ts";
|
|
63
66
|
import { collectGoalContinuationNotices } from "../missions/goal-driver.ts";
|
|
64
67
|
import { restoreForegroundRunHistory } from "../runs/foreground/foreground-history.ts";
|
|
@@ -249,15 +252,25 @@ function workflowLaneKeys(script: string): string[] {
|
|
|
249
252
|
return keys;
|
|
250
253
|
}
|
|
251
254
|
|
|
252
|
-
function
|
|
255
|
+
function formatWorkflowPreflightCall(input: unknown): string {
|
|
256
|
+
if (input === undefined) return "";
|
|
257
|
+
try {
|
|
258
|
+
return formatWorkflowPreflightSummary(normalizeWorkflowPreflight(input));
|
|
259
|
+
} catch (error) {
|
|
260
|
+
return `preflight · rejected: ${error instanceof Error ? error.message : String(error)}`;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function formatWorkflowManifest(script: string, async: unknown, clarify: unknown, preflightInput?: unknown): string {
|
|
253
265
|
if (clarify === true) return "workflow script · rejected: clarify UI unsupported";
|
|
254
266
|
const keys = workflowLaneKeys(script);
|
|
255
267
|
// The workflow executor starts background work unless callers pass async:false.
|
|
256
268
|
const mode = async === false ? "foreground" : "background";
|
|
257
|
-
|
|
269
|
+
const preflight = formatWorkflowPreflightCall(preflightInput);
|
|
270
|
+
if (keys.length === 0) return `workflow script · ${mode}${preflight ? ` · ${preflight}` : ""}`;
|
|
258
271
|
const visibleKeys = keys.slice(0, 4).join(", ");
|
|
259
272
|
const remainder = keys.length > 4 ? `, +${keys.length - 4}` : "";
|
|
260
|
-
return `workflow · ${mode} · ${keys.length} lane${keys.length === 1 ? "" : "s"}: ${visibleKeys}${remainder}`;
|
|
273
|
+
return `workflow · ${mode} · ${keys.length} lane${keys.length === 1 ? "" : "s"}: ${visibleKeys}${remainder}${preflight ? ` · ${preflight}` : ""}`;
|
|
261
274
|
}
|
|
262
275
|
|
|
263
276
|
/**
|
|
@@ -290,10 +303,6 @@ function isSlashResultError(result: { details?: Details }): boolean {
|
|
|
290
303
|
return result.details?.results.some((entry) => entry.exitCode !== 0 && entry.progress?.status !== "running") || false;
|
|
291
304
|
}
|
|
292
305
|
|
|
293
|
-
function isStaleExtensionContextError(error: unknown): boolean {
|
|
294
|
-
return error instanceof Error && error.message.includes("Extension context no longer active");
|
|
295
|
-
}
|
|
296
|
-
|
|
297
306
|
function rebuildSlashResultContainer(
|
|
298
307
|
container: Container,
|
|
299
308
|
result: AgentToolResult<Details>,
|
|
@@ -462,16 +471,31 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
462
471
|
clear: () => {},
|
|
463
472
|
},
|
|
464
473
|
};
|
|
474
|
+
const withLastUiContext = <T>(run: (ctx: ExtensionContext) => T): T | undefined => {
|
|
475
|
+
const cached = state.lastUiContext;
|
|
476
|
+
return withCachedUiContext(cached, () => {
|
|
477
|
+
if (state.lastUiContext === cached) state.lastUiContext = null;
|
|
478
|
+
}, run);
|
|
479
|
+
};
|
|
465
480
|
|
|
466
481
|
const supervisorChannel = createNativeSupervisorChannel(pi, state);
|
|
467
482
|
const waitSubscriptionManager = createWaitSubscriptionManager(pi, state);
|
|
468
483
|
const mainWatchdog = registerMainWatchdog(pi);
|
|
469
|
-
const
|
|
484
|
+
const resultDeliveryOwnership = createResultDeliveryOwnership(state);
|
|
485
|
+
const completionNotifier = registerSubagentNotify(pi, state, { batchConfig: config.completionBatch, ownership: resultDeliveryOwnership });
|
|
470
486
|
const fleetStatus = fleetViewEnabled
|
|
471
487
|
? new SubagentFleetStatus(state, async (itemKey) => {
|
|
472
|
-
const ctx =
|
|
473
|
-
if (!ctx
|
|
474
|
-
|
|
488
|
+
const ctx = withLastUiContext((current) => current);
|
|
489
|
+
if (!ctx) return;
|
|
490
|
+
try {
|
|
491
|
+
await openSubagentFleet(ctx, state, { initialKey: itemKey, asyncDirRoot: DIRS.async, resultsDir: DIRS.results, fleetKeybindings: config.fleetKeybindings });
|
|
492
|
+
} catch (error) {
|
|
493
|
+
if (isStaleExtensionContextError(error)) {
|
|
494
|
+
if (state.lastUiContext === ctx) state.lastUiContext = null;
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
throw error;
|
|
498
|
+
}
|
|
475
499
|
}, { placement: fleetViewPlacement })
|
|
476
500
|
: undefined;
|
|
477
501
|
let executorScheduled: ((id: string, params: SubagentParamsLike, signal: AbortSignal, ctx: ExtensionContext) => Promise<AgentToolResult<Details>>) | undefined;
|
|
@@ -500,10 +524,10 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
500
524
|
if (scheduledRunManager.observedCompletionRunIds().size > 0) return true;
|
|
501
525
|
return missionObserverResultCandidateFiles(DIRS.results).length > 0;
|
|
502
526
|
};
|
|
503
|
-
const discoverAgentsForRuntime = (cwd: string, scope: AgentScope) => {
|
|
504
|
-
const discovered = discoverAgents(cwd, scope);
|
|
527
|
+
const discoverAgentsForRuntime = (cwd: string, scope: AgentScope, preferredModelProvider?: string) => {
|
|
528
|
+
const discovered = discoverAgents(cwd, scope, preferredModelProvider);
|
|
505
529
|
if (listRuntimeAgentConfigs(pi).length === 0) return discovered;
|
|
506
|
-
const all = discoverAgentsAll(cwd);
|
|
530
|
+
const all = discoverAgentsAll(cwd, preferredModelProvider);
|
|
507
531
|
const configuredAgents: AgentConfig[] = [
|
|
508
532
|
...all.builtin,
|
|
509
533
|
...all.package,
|
|
@@ -528,6 +552,7 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
528
552
|
10 * 60 * 1000,
|
|
529
553
|
{
|
|
530
554
|
notifier: completionNotifier,
|
|
555
|
+
ownership: resultDeliveryOwnership,
|
|
531
556
|
observeCompletion: (result) => scheduledRunManager.handleAsyncCompletion(result),
|
|
532
557
|
observedCompletionRunIds: () => scheduledRunManager.observedCompletionRunIds(),
|
|
533
558
|
hasDeliveryDemand: hasResultDeliveryDemand,
|
|
@@ -535,7 +560,7 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
535
560
|
resultScanLogging: config.resultScanLogging,
|
|
536
561
|
},
|
|
537
562
|
);
|
|
538
|
-
const { startResultWatcher, primeExistingResults, stopResultWatcher } = resultWatcher;
|
|
563
|
+
const { startResultWatcher, transitionResultDelivery, primeExistingResults, stopResultWatcher } = resultWatcher;
|
|
539
564
|
refreshResultDelivery = resultWatcher.refreshResultDelivery;
|
|
540
565
|
const asyncRetentionAbort = new AbortController();
|
|
541
566
|
const asyncRetentionTimer = setTimeout(async () => {
|
|
@@ -562,6 +587,7 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
562
587
|
config,
|
|
563
588
|
asyncByDefault,
|
|
564
589
|
waitToolEnabled: waitToolConfig.enabled,
|
|
590
|
+
waitToolDefaultTimeoutMs: waitToolConfig.defaultTimeoutMs,
|
|
565
591
|
handleScheduledRunAction: (params, ctx) => scheduledRunManager.handleToolCall(params, ctx),
|
|
566
592
|
watchdog: mainWatchdog,
|
|
567
593
|
tempArtifactsDir,
|
|
@@ -649,7 +675,6 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
649
675
|
|
|
650
676
|
const slashBridge = registerSlashSubagentBridge({
|
|
651
677
|
events: pi.events,
|
|
652
|
-
asyncByDefault,
|
|
653
678
|
getContext: () => state.lastUiContext,
|
|
654
679
|
execute: (id, params, signal, onUpdate, ctx) =>
|
|
655
680
|
executeSubagentCollapsed(id, params, signal, onUpdate, ctx),
|
|
@@ -698,13 +723,13 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
698
723
|
}
|
|
699
724
|
if (args.workflowScript)
|
|
700
725
|
return new Text(
|
|
701
|
-
`${title}${gap}${formatWorkflowManifest(args.workflowScript, args.async, false)}`,
|
|
726
|
+
`${title}${gap}${formatWorkflowManifest(args.workflowScript, args.async, false, args.preflight)}`,
|
|
702
727
|
0,
|
|
703
728
|
0,
|
|
704
729
|
);
|
|
705
730
|
if (args.workflowScriptPath)
|
|
706
731
|
return new Text(
|
|
707
|
-
`${title}${gap}${theme.fg("accent", args.workflowScriptPath)}${args.async === true ? `${gap}${theme.fg("warning", "[async]")}` : ""}`,
|
|
732
|
+
`${title}${gap}${theme.fg("accent", args.workflowScriptPath)}${args.async === true ? `${gap}${theme.fg("warning", "[async]")}` : ""}${args.preflight !== undefined ? `${gap}${theme.fg("dim", formatWorkflowPreflightCall(args.preflight))}` : ""}`,
|
|
708
733
|
0,
|
|
709
734
|
0,
|
|
710
735
|
);
|
|
@@ -728,7 +753,7 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
728
753
|
|
|
729
754
|
pi.registerTool(tool);
|
|
730
755
|
|
|
731
|
-
registerWaitTool(pi, state, waitToolConfig.enabled, waitSubscriptionManager);
|
|
756
|
+
registerWaitTool(pi, state, waitToolConfig.enabled, waitSubscriptionManager, waitToolConfig.defaultTimeoutMs);
|
|
732
757
|
|
|
733
758
|
pi.on("agent_end", async (_event, ctx) => {
|
|
734
759
|
if (!ctx.hasUI) await drainOutstandingWork({ state, events: pi.events });
|
|
@@ -751,7 +776,7 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
751
776
|
}
|
|
752
777
|
});
|
|
753
778
|
|
|
754
|
-
registerSlashCommands(pi, state, {
|
|
779
|
+
const disposeSlashCommands = registerSlashCommands(pi, state, {
|
|
755
780
|
fleetKeybindings: config.fleetKeybindings,
|
|
756
781
|
foregroundDetachShortcut: config.foregroundDetachShortcut,
|
|
757
782
|
});
|
|
@@ -791,6 +816,7 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
791
816
|
fleetStatus?.refresh();
|
|
792
817
|
};
|
|
793
818
|
const eventUnsubscribes = [
|
|
819
|
+
registerRuntimeAgentEventListener(pi),
|
|
794
820
|
pi.events.on(SUBAGENT_ASYNC_STARTED_EVENT, asyncStartedHandler),
|
|
795
821
|
pi.events.on(SUBAGENT_ASYNC_COMPLETE_EVENT, asyncCompleteHandler),
|
|
796
822
|
pi.events.on(SUBAGENT_PROCESS_TERMINAL_EVENT, () => {
|
|
@@ -807,7 +833,9 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
807
833
|
if (event.toolName !== "subagent") return;
|
|
808
834
|
if (!ctx.hasUI) return;
|
|
809
835
|
state.lastUiContext = ctx;
|
|
836
|
+
const activeJobCount = state.asyncJobs.size;
|
|
810
837
|
restoreActiveJobs(ctx);
|
|
838
|
+
if (state.asyncJobs.size > activeJobCount) herdrStatusBridge.syncRuns();
|
|
811
839
|
fleetStatus?.setContext(ctx);
|
|
812
840
|
fleetStatus?.refresh();
|
|
813
841
|
if (state.asyncJobs.size > 0) {
|
|
@@ -830,14 +858,13 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
830
858
|
const suspendWidgetsForCompaction = () => {
|
|
831
859
|
if (state.widgetsSuspended) return;
|
|
832
860
|
state.widgetsSuspended = true;
|
|
833
|
-
|
|
861
|
+
withLastUiContext((ctx) => ctx.ui.setWidget(WIDGET_KEY, undefined));
|
|
834
862
|
fleetStatus?.refresh();
|
|
835
863
|
};
|
|
836
864
|
const resumeWidgetsAfterCompaction = () => {
|
|
837
865
|
if (!state.widgetsSuspended) return;
|
|
838
866
|
state.widgetsSuspended = false;
|
|
839
|
-
|
|
840
|
-
if (ctx?.hasUI) refreshWidget(ctx);
|
|
867
|
+
withLastUiContext((ctx) => refreshWidget(ctx));
|
|
841
868
|
fleetStatus?.refresh();
|
|
842
869
|
};
|
|
843
870
|
|
|
@@ -853,11 +880,14 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
853
880
|
);
|
|
854
881
|
};
|
|
855
882
|
|
|
856
|
-
const resetSessionState = (ctx: ExtensionContext, recovering: boolean) => {
|
|
883
|
+
const resetSessionState = (ctx: ExtensionContext, recovering: boolean, previousSessionFile?: string) => {
|
|
857
884
|
state.widgetsSuspended = false;
|
|
858
885
|
state.baseCwd = ctx.cwd;
|
|
859
886
|
goalTurnId = 0;
|
|
887
|
+
const previousRuntimeSessionId = state.currentSessionId;
|
|
888
|
+
resultDeliveryOwnership.claimPredecessor(previousSessionFile, previousRuntimeSessionId);
|
|
860
889
|
state.currentSessionId = resolveCurrentSessionId(ctx.sessionManager);
|
|
890
|
+
transitionResultDelivery();
|
|
861
891
|
state.parentSessionFile = ctx.sessionManager.getSessionFile();
|
|
862
892
|
state.trustedSessionFileRoot = state.parentSessionFile ? path.join(getAgentDir(), "sessions") : undefined;
|
|
863
893
|
state.trustedSessionRoots = [...new Set([
|
|
@@ -929,11 +959,19 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
929
959
|
if (runtimeCleaned) return;
|
|
930
960
|
runtimeCleaned = true;
|
|
931
961
|
const shuttingDownParentSession = parentSessionEnvValue;
|
|
962
|
+
// Workflow continuations retain their launch context; abort them before
|
|
963
|
+
// teardown so a reload cannot launch through a stale context.
|
|
964
|
+
for (const controller of state.workflowControllers?.values() ?? []) {
|
|
965
|
+
if (!controller.signal.aborted) controller.abort(new Error("Workflow stopped because the extension session was replaced or reloaded."));
|
|
966
|
+
}
|
|
967
|
+
state.workflowControllers?.clear();
|
|
968
|
+
state.workflowChildStops?.clear();
|
|
932
969
|
clearRuntimeAgentsForPi(pi);
|
|
933
970
|
clearTimeout(resultIndexCleanupTimer);
|
|
934
971
|
clearTimeout(asyncRetentionTimer);
|
|
935
972
|
asyncRetentionAbort.abort();
|
|
936
973
|
stopResultWatcher();
|
|
974
|
+
resultDeliveryOwnership.clear();
|
|
937
975
|
completionNotifier.dispose();
|
|
938
976
|
mainWatchdog.dispose();
|
|
939
977
|
scheduledRunManager.stop();
|
|
@@ -951,6 +989,7 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
951
989
|
// Best effort cleanup during shutdown or reload.
|
|
952
990
|
}
|
|
953
991
|
}
|
|
992
|
+
disposeSlashCommands.dispose();
|
|
954
993
|
slashBridge.cancelAll();
|
|
955
994
|
slashBridge.dispose();
|
|
956
995
|
promptTemplateBridge.cancelAll();
|
|
@@ -1020,7 +1059,7 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
1020
1059
|
|
|
1021
1060
|
pi.on("session_compact", () => {
|
|
1022
1061
|
const hasActiveAsyncWork = [...state.asyncJobs.values()].some((job) => job.status === "queued" || job.status === "running");
|
|
1023
|
-
if (!hasActiveAsyncWork ||
|
|
1062
|
+
if (!hasActiveAsyncWork || !withLastUiContext(() => true)) return;
|
|
1024
1063
|
pi.sendMessage(
|
|
1025
1064
|
{
|
|
1026
1065
|
customType: "subagent-compaction-resume",
|
|
@@ -1034,7 +1073,7 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
1034
1073
|
pi.on("session_start", (event, ctx) => {
|
|
1035
1074
|
installRuntime(ctx);
|
|
1036
1075
|
const recovering = event.reason === "startup" || event.reason === "reload" || event.reason === "resume";
|
|
1037
|
-
resetSessionState(ctx, recovering);
|
|
1076
|
+
resetSessionState(ctx, recovering, event.previousSessionFile);
|
|
1038
1077
|
herdrStatusBridge.sessionStarted({
|
|
1039
1078
|
hasUI: ctx.hasUI === true,
|
|
1040
1079
|
runs: activeHerdrRuns(),
|
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
export interface PublicSubagentExecutionParams {
|
|
2
2
|
action?: unknown;
|
|
3
|
+
mode?: unknown;
|
|
4
|
+
repo?: unknown;
|
|
5
|
+
planId?: unknown;
|
|
3
6
|
agent?: unknown;
|
|
4
7
|
task?: unknown;
|
|
8
|
+
handoffPath?: unknown;
|
|
9
|
+
laneId?: unknown;
|
|
10
|
+
merge?: unknown;
|
|
11
|
+
supersession?: unknown;
|
|
5
12
|
step?: unknown;
|
|
6
13
|
tasks?: unknown;
|
|
7
14
|
chain?: unknown;
|
|
@@ -12,8 +19,10 @@ export interface PublicSubagentExecutionParams {
|
|
|
12
19
|
config?: unknown;
|
|
13
20
|
workflowScript?: unknown;
|
|
14
21
|
workflowScriptPath?: unknown;
|
|
22
|
+
preflight?: unknown;
|
|
15
23
|
isolation?: unknown;
|
|
16
24
|
worktree?: unknown;
|
|
25
|
+
lane?: unknown;
|
|
17
26
|
async?: unknown;
|
|
18
27
|
output?: unknown;
|
|
19
28
|
resume?: unknown;
|
|
@@ -22,6 +31,7 @@ export interface PublicSubagentExecutionParams {
|
|
|
22
31
|
workflowKey?: unknown;
|
|
23
32
|
workflowChildAsyncId?: unknown;
|
|
24
33
|
workflowAwaitAsync?: unknown;
|
|
34
|
+
workflowAwaitDetached?: unknown;
|
|
25
35
|
workflowParentDeadlineAt?: unknown;
|
|
26
36
|
suppressRoutineResultIntercom?: unknown;
|
|
27
37
|
runFanoutBudget?: unknown;
|
|
@@ -38,11 +48,14 @@ export type PublicSubagentExecutionNormalization<T> =
|
|
|
38
48
|
* Enforce the public execution cutover before requests reach the executor.
|
|
39
49
|
* Internal runs.run children and structured owned delegation bypass this boundary.
|
|
40
50
|
*/
|
|
41
|
-
export function normalizePublicSubagentExecution<T extends PublicSubagentExecutionParams>(params: T
|
|
51
|
+
export function normalizePublicSubagentExecution<T extends PublicSubagentExecutionParams>(params: T): PublicSubagentExecutionNormalization<T> {
|
|
42
52
|
if (params.workflowScript !== undefined && params.workflowScriptPath !== undefined) {
|
|
43
53
|
return { ok: false, error: "workflowScript and workflowScriptPath are mutually exclusive.", mode: "workflow" };
|
|
44
54
|
}
|
|
45
55
|
const hasWorkflowInput = params.workflowScript !== undefined || params.workflowScriptPath !== undefined;
|
|
56
|
+
if (params.preflight !== undefined && !hasWorkflowInput) {
|
|
57
|
+
return { ok: false, error: "preflight requires workflowScript or workflowScriptPath.", mode: params.action === undefined ? "workflow" : "management" };
|
|
58
|
+
}
|
|
46
59
|
const hasValidWorkflowInput = (typeof params.workflowScript === "string" && Boolean(params.workflowScript.trim()))
|
|
47
60
|
|| (typeof params.workflowScriptPath === "string" && Boolean(params.workflowScriptPath.trim()));
|
|
48
61
|
if (params.isolation !== undefined) {
|
|
@@ -59,7 +72,7 @@ export function normalizePublicSubagentExecution<T extends PublicSubagentExecuti
|
|
|
59
72
|
if (params.runFanoutBudget !== undefined || params.runFanoutAdmitted !== undefined) {
|
|
60
73
|
return { ok: false, error: "Public execution does not accept internal run fan-out fields.", mode: hasWorkflowInput ? "workflow" : "management" };
|
|
61
74
|
}
|
|
62
|
-
if (params.workflowParentRunId !== undefined || params.workflowKey !== undefined || params.workflowChildAsyncId !== undefined || params.workflowAwaitAsync !== undefined || params.workflowParentDeadlineAt !== undefined || params.suppressRoutineResultIntercom !== undefined) {
|
|
75
|
+
if (params.workflowParentRunId !== undefined || params.workflowKey !== undefined || params.workflowChildAsyncId !== undefined || params.workflowAwaitAsync !== undefined || params.workflowAwaitDetached !== undefined || params.workflowParentDeadlineAt !== undefined || params.suppressRoutineResultIntercom !== undefined) {
|
|
63
76
|
return { ok: false, error: "Public execution does not accept internal workflow child fields.", mode: hasWorkflowInput ? "workflow" : "management" };
|
|
64
77
|
}
|
|
65
78
|
const action = params.action;
|
package/src/extension/rpc.ts
CHANGED
|
@@ -557,7 +557,7 @@ function stopAsyncRun(
|
|
|
557
557
|
childId: stoppedChild.id,
|
|
558
558
|
status: "stopping",
|
|
559
559
|
ts,
|
|
560
|
-
reason: "
|
|
560
|
+
reason: "rpc",
|
|
561
561
|
source: "rpc",
|
|
562
562
|
asyncDir,
|
|
563
563
|
stepIndex: stoppedChild.index,
|
|
@@ -579,20 +579,21 @@ function stopAsyncRun(
|
|
|
579
579
|
if (initialStatus.mode === "workflow" && initialStatus.state === "running") {
|
|
580
580
|
if (child) {
|
|
581
581
|
const stopChild = options.state?.workflowChildStops?.get(initialRunId);
|
|
582
|
-
if (
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
582
|
+
if (stopChild) {
|
|
583
|
+
if (!stopChild(child.id, `Workflow child '${child.id}' stopped by RPC.`)) throw new SubagentRpcError("invalid_state", `Child '${childId}' in workflow ${initialRunId} is not available to stop.`);
|
|
584
|
+
emitChildStopping(initialRunId, location.asyncDir, child);
|
|
585
|
+
return {
|
|
586
|
+
runId: initialRunId,
|
|
587
|
+
asyncDir: location.asyncDir,
|
|
588
|
+
previousState: initialStatus.state,
|
|
589
|
+
state: "stopping",
|
|
590
|
+
childId: child.id,
|
|
591
|
+
message: `Stop requested for child ${child.id} in async run ${initialRunId}.`,
|
|
592
|
+
};
|
|
593
|
+
}
|
|
593
594
|
}
|
|
594
595
|
const workflowController = options.state?.workflowControllers?.get(initialRunId);
|
|
595
|
-
if (workflowController) {
|
|
596
|
+
if (workflowController && !child) {
|
|
596
597
|
workflowController.abort(new Error("Workflow stopped by RPC."));
|
|
597
598
|
return {
|
|
598
599
|
runId: initialRunId,
|
|
@@ -602,7 +603,27 @@ function stopAsyncRun(
|
|
|
602
603
|
message: `Stop requested for async run ${initialRunId}.`,
|
|
603
604
|
};
|
|
604
605
|
}
|
|
605
|
-
|
|
606
|
+
try {
|
|
607
|
+
deliverStopRequest({
|
|
608
|
+
asyncDir: location.asyncDir,
|
|
609
|
+
pid: initialStatus.pid,
|
|
610
|
+
kill: options.kill,
|
|
611
|
+
now: options.now,
|
|
612
|
+
source: "rpc-stop",
|
|
613
|
+
...(child ? { targetIndex: child.index, childId: child.id } : {}),
|
|
614
|
+
});
|
|
615
|
+
} catch (error) {
|
|
616
|
+
throw new SubagentRpcError("execution_failed", error instanceof Error ? error.message : String(error));
|
|
617
|
+
}
|
|
618
|
+
if (child) emitChildStopping(initialRunId, location.asyncDir, child);
|
|
619
|
+
return {
|
|
620
|
+
runId: initialRunId,
|
|
621
|
+
asyncDir: location.asyncDir,
|
|
622
|
+
previousState: initialStatus.state,
|
|
623
|
+
state: "stopping",
|
|
624
|
+
...(child ? { childId: child.id } : {}),
|
|
625
|
+
message: child ? `Stop requested for child ${child.id} in async run ${initialRunId}.` : `Stop requested for async run ${initialRunId}.`,
|
|
626
|
+
};
|
|
606
627
|
}
|
|
607
628
|
|
|
608
629
|
let status;
|
package/src/extension/schemas.ts
CHANGED
|
@@ -101,10 +101,14 @@ const ChainGateOverride = Type.String({
|
|
|
101
101
|
description: "For chain steps with agentContract, choose whether the chain advances on execution success or acceptance success. Defaults to execution.",
|
|
102
102
|
});
|
|
103
103
|
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
104
|
+
const WorkflowLaneMetadata = Type.Object({
|
|
105
|
+
version: Type.Integer({ minimum: 1, maximum: 1 }),
|
|
106
|
+
key: Type.String({ minLength: 1, maxLength: 128, pattern: "^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$" }),
|
|
107
|
+
mode: Type.Optional(Type.String({ enum: ["mutation", "review", "scout", "gate"] })),
|
|
108
|
+
sourceRef: Type.Optional(Type.String({ minLength: 1, maxLength: 128 })),
|
|
109
|
+
claims: Type.Optional(Type.Array(Type.String({ minLength: 1, maxLength: 160 }), { maxItems: 20 })),
|
|
110
|
+
outputPaths: Type.Optional(Type.Array(Type.String({ minLength: 1, maxLength: 256 }), { maxItems: 10 })),
|
|
111
|
+
}, { additionalProperties: false, description: "Optional bounded child lane metadata. Display/triage only; sourceRef is opaque and never resolved during status rendering." });
|
|
108
112
|
|
|
109
113
|
const ToolBudgetBlock = Type.Unsafe({
|
|
110
114
|
anyOf: [
|
|
@@ -129,6 +133,21 @@ const UsageBudgetOverride = Type.Object({
|
|
|
129
133
|
costUsd: Type.Optional(UsageBudgetLimitOverride),
|
|
130
134
|
}, { additionalProperties: false, description: "Optional root-only reported-usage budget. Hard limits prevent future child launches; running children are not stopped." });
|
|
131
135
|
|
|
136
|
+
const WorkflowPreflightLane = Type.Object({
|
|
137
|
+
key: Type.String({ minLength: 1, maxLength: 128 }),
|
|
138
|
+
mode: Type.Optional(Type.String({ enum: ["mutation", "review", "scout", "gate"] })),
|
|
139
|
+
decision: Type.Optional(Type.String({ maxLength: 256 })),
|
|
140
|
+
claims: Type.Optional(Type.Array(Type.String({ maxLength: 256 }), { maxItems: 16 })),
|
|
141
|
+
expectedOutput: Type.Optional(Type.String({ maxLength: 256 })),
|
|
142
|
+
independence: Type.Optional(Type.String({ maxLength: 256 })),
|
|
143
|
+
}, { additionalProperties: false });
|
|
144
|
+
|
|
145
|
+
const WorkflowPreflightOverride = Type.Object({
|
|
146
|
+
version: Type.Integer({ minimum: 1, maximum: 1 }),
|
|
147
|
+
coverage: Type.Optional(Type.String({ enum: ["complete", "partial"] })),
|
|
148
|
+
lanes: Type.Array(WorkflowPreflightLane, { maxItems: 64 }),
|
|
149
|
+
}, { additionalProperties: false, description: "Bounded display-only lane hints for workflow launch/status. V1 coverage mismatches warn but never change launch authority or execution." });
|
|
150
|
+
|
|
132
151
|
// Parallel task item (within a parallel step)
|
|
133
152
|
export const ParallelTaskSchema = Type.Object({
|
|
134
153
|
agent: Type.String(),
|
|
@@ -273,7 +292,12 @@ const SubagentParamProperties = {
|
|
|
273
292
|
dir: Type.Optional(Type.String({
|
|
274
293
|
description: "Async run directory for status/debug.run, stop, resume, or steer."
|
|
275
294
|
})),
|
|
276
|
-
handoffPath: Type.Optional(Type.String({ description: "worktree.discard
|
|
295
|
+
handoffPath: Type.Optional(Type.String({ description: "Existing parallel handoff manifest for worktree.discard, worktree.cleanup metadata, or lane evidence actions." })),
|
|
296
|
+
repo: Type.Optional(Type.String({ description: "Repository path for action='worktree.cleanup'; defaults to cwd." })),
|
|
297
|
+
planId: Type.Optional(Type.String({ description: "Cleanup plan id reserved for a future worktree.cleanup apply action." })),
|
|
298
|
+
laneId: Type.Optional(Type.String({ minLength: 1, maxLength: 128, description: "Exact manifest run id for lane.status, lane.recordMerge, or lane.recordSupersession." })),
|
|
299
|
+
merge: Type.Optional(Type.Unsafe({ type: "object", additionalProperties: true, description: "Attested merge evidence for lane.recordMerge: prNumber, reviewedHead, mergeCommit, treeEquivalent, postMergeChecks, attestedBy, and attestedAt." })),
|
|
300
|
+
supersession: Type.Optional(Type.Unsafe({ type: "object", additionalProperties: true, description: "Attested replacement-lane evidence for lane.recordSupersession: supersededBy, attestedBy, and attestedAt." })),
|
|
277
301
|
index: Type.Optional(Type.Integer({ minimum: 0, description: "Zero-based child index for actions that target a specific child or transcript." })),
|
|
278
302
|
childId: Type.Optional(Type.String({ minLength: 1, maxLength: 256, description: "Stable child identity for child-scoped stop requests." })),
|
|
279
303
|
view: Type.Optional(Type.String({
|
|
@@ -283,13 +307,13 @@ const SubagentParamProperties = {
|
|
|
283
307
|
lines: Type.Optional(Type.Integer({ minimum: 1, maximum: 500, description: "Maximum transcript lines for action='status', view='transcript'. Defaults to 80." })),
|
|
284
308
|
topic: Type.Optional(Type.String()),
|
|
285
309
|
message: Type.Optional(Type.String({ description: "Follow-up message for resume, live guidance for steer, or optional startup prompt for project.open." })),
|
|
286
|
-
mode: Type.Optional(Type.String({ enum: ["steer", "follow_up", "auto"], description: "Delivery mode for action='steer'
|
|
310
|
+
mode: Type.Optional(Type.String({ enum: ["steer", "follow_up", "auto", "plan", "apply"], description: "Delivery mode for action='steer', or plan/apply mode for worktree.cleanup. worktree.cleanup currently supports plan only; apply/removal is not available yet." })),
|
|
287
311
|
steeringRecovery: Type.Optional(Type.Boolean({ description: "For action='steer', allow pause-and-revive recovery after a missed acknowledgment. Defaults true for direct tool calls in steer mode; extension RPC steering forces false so callers retain exact child ownership." })),
|
|
288
312
|
additional: Type.Optional(Type.Integer({ minimum: 1, description: "Positive launches to add with action='grant-spawn-budget'. Root interactive parent with native user confirmation only; total grants cannot exceed the original configured cap." })),
|
|
289
313
|
scope: Type.Optional(Type.String({ enum: ["session", "user", "project"], description: "Scope for action='watchdog.configure'. Defaults to session to avoid persistent settings writes unless user/project is explicit." })),
|
|
290
314
|
target: Type.Optional(Type.String({ enum: ["main", "children", "child"], description: "Target for watchdog actions." })),
|
|
291
315
|
focus: Type.Optional(Type.Boolean({ description: "Focus the new Herdr pane for inspector.open or project.open." })),
|
|
292
|
-
thinking: Type.Optional(Type.Unsafe({ anyOf: [{ type: "string" }, { type: "boolean", enum: [false] }], description: "Thinking level for action='watchdog.configure' (off/minimal/low/medium/high/xhigh/max, inherit, or false for off)." })),
|
|
316
|
+
thinking: Type.Optional(Type.Unsafe({ anyOf: [{ type: "string" }, { type: "boolean", enum: [false] }], description: "Thinking level for action='watchdog.configure' only (off/minimal/low/medium/high/xhigh/max, inherit, or false for off). Ignored on dispatch; set per-run child thinking with a suffix on the model string, e.g. model: 'provider/id:high'." })),
|
|
293
317
|
at: Type.Optional(Type.String({ description: "One-shot trigger for action='schedule.create': a relative delay such as '+10m' or an ISO timestamp with timezone." })),
|
|
294
318
|
every: Type.Optional(Type.String({ description: "Fixed recurring interval for action='schedule.create', such as '30m', '6h', '2d', or '2w'." })),
|
|
295
319
|
on: Type.Optional(Type.Unsafe({ anyOf: [{ type: "string" }, { type: "integer" }], description: "Calendar selector reserved for a later schedule slice." })),
|
|
@@ -312,11 +336,13 @@ const SubagentParamProperties = {
|
|
|
312
336
|
],
|
|
313
337
|
description: "Agent config for create/update. Object or JSON string."
|
|
314
338
|
})),
|
|
315
|
-
workflowScript: Type.Optional(Type.String({ minLength: 1, description: "Trusted inline JavaScript statement body. Normally async unless asyncByDefault:false; set async:true when async matters. Use async:false only when the parent must block until completion, never for reviews or gates. Use explicit return for output. Use top-level await, plain helper functions, or explicit Promise chains; nested async function, arrow, and method helpers are rejected. Use await runs.run(key, {agent, task, worktree?, gate?}) or runs.run(key, {resume, task}), where resume is a retained run id or {workflowRunId,key,latest:true} from a durable async workflow receipt. Use runs.all([...]), await runs.steer(key, message, {mode?, index?, ackTimeoutMs?}), runs.status(id), runs.ref(s), emit(value), console, and return. For ordinary parallel fanout, use await runs.all([{key, agent, task}, ...]); it resolves to an ordered array, not a key map, so use results[0], destructuring, or results.map(...), not results.<key>. Do not read .output from unawaited runs.run launches. Stored runs.run promises are only for advanced rolling fanout, and each must later be observed with direct await, Promise.race, or Promise.all. runs.steer targets a prior stable child key, never a raw run id, and must be awaited or returned. Mission workflows also have async state.get(key) and state.set(key, JSONValue). Compose sequential and parallel phases dynamically. Set worktree:true at workflow or child level for a separate managed worktree; child fields override workflow defaults. gate is one host-run command and cannot be combined with acceptance. runs.run accepts one child only. No filesystem, shell, Pi tools, or host globals." })),
|
|
339
|
+
workflowScript: Type.Optional(Type.String({ minLength: 1, description: "Trusted inline JavaScript statement body. Normally async unless asyncByDefault:false; set async:true when async matters. Use async:false only when the parent must block until completion, never for reviews or gates. Use explicit return for output. Use top-level await, plain helper functions, or explicit Promise chains; nested async function, arrow, and method helpers are rejected. Use await runs.run(key, {agent, task, worktree?, gate?}) or runs.run(key, {resume, task}), where resume is a retained run id or {workflowRunId,key,latest:true} from a durable async workflow receipt. Each workflow key identifies one result lane: use a new stable workflow key for every distinct retained resume pass; same-key calls are reused only when launch parameters are identical, and incompatible parameters are rejected. Use runs.all([...]), runs.host(key,{kind:'command',command,timeoutMs,output?,role?,provider?}), await runs.steer(key, message, {mode?, index?, ackTimeoutMs?}), runs.status(id), runs.ref(s), emit(value), console, and return. For bounded parallel sequential chains, use runs.lanes([{key,stages:[{key,agent,task},{key,resume:'previous',task},...]}]); first stages run together, later stages sequence per lane, and the bounded board reports lane-local failures. Only an explicit structuredOutput.verdict === 'blocked' blocks a successful stage; reviewer prose is not parsed. For ordinary parallel fanout, use await runs.all([{key, agent, task}, ...]); it resolves to an ordered array, not a key map, so use results[0], destructuring, or results.map(...), not results.<key>. Do not read .output from unawaited runs.run launches. Stored runs.run promises are only for advanced rolling fanout, and each must later be observed with direct await, Promise.race, or Promise.all. runs.steer targets a prior stable child key, never a raw run id, and must be awaited or returned. Mission workflows also have async state.get(key) and state.set(key, JSONValue). Compose sequential and parallel phases dynamically. Set worktree:true at workflow or child level for a separate managed worktree; child fields override workflow defaults. gate is one host-run command and cannot be combined with acceptance. runs.run accepts one child only. No filesystem, shell, Pi tools, or host globals except through runs.host." })),
|
|
316
340
|
workflowScriptPath: Type.Optional(Type.String({ minLength: 1, description: "Path to a trusted JavaScript workflow file. Mutually exclusive with workflowScript. Relative paths resolve against the request cwd. The host reads the file before the filesystem-free workflow sandbox starts." })),
|
|
341
|
+
preflight: Type.Optional(WorkflowPreflightOverride),
|
|
317
342
|
chatProgress: Type.Optional(Type.String({ enum: ["auto", "off", "live-card"], description: "WorkflowScript chat progress projection. auto shows a live in-chat card only for watched foreground workflows in the same Git repository; it is off otherwise. Explicit live-card requires same-repository async:false; async workflows should omit chatProgress or use auto/off." })),
|
|
318
343
|
isolation: Type.Optional(Type.String({ enum: ["none", "worktree"], description: "Workflow child isolation. none runs in the shared cwd; worktree requires managed git worktree isolation." })),
|
|
319
344
|
worktree: Type.Optional(Type.Boolean({ description: "Managed child isolation. true gives each workflow child a separate git worktree; an individual runs.run/runs.all item can override a workflow default with worktree:false." })),
|
|
345
|
+
lane: Type.Optional(WorkflowLaneMetadata),
|
|
320
346
|
context: Type.Optional(Type.String({
|
|
321
347
|
enum: ["fresh", "fork", "profile"],
|
|
322
348
|
description: "'fresh' or 'fork' to branch from parent session, or 'profile' to require the selected agent's declared defaultContext. Explicit fresh/fork overrides every child; profile ignores config defaultSubagentContext and fails when an agent has no defaultContext. If omitted, config defaultSubagentContext wins over each agent defaultContext; implicit fork needs a persisted parent session and leaf, else fresh. Config forkContext may prune resolved forks before spawn without adding another context value.",
|
|
@@ -325,7 +351,6 @@ const SubagentParamProperties = {
|
|
|
325
351
|
timeoutMs: Type.Optional(Type.Integer({ minimum: 1, description: "Timeout. Foreground and single async runs use config timeoutMs, else 30m; async composites have no default parent deadline. Alias maxRuntimeMs." })),
|
|
326
352
|
maxRuntimeMs: Type.Optional(Type.Integer({ minimum: 1, description: "Alias timeoutMs. Foreground and single async runs use config timeoutMs, else 30m; async composites have no default parent deadline." })),
|
|
327
353
|
toolTimeoutMs: Type.Optional(Type.Integer({ minimum: 1, description: "Optional hard per-tool-call timeout in milliseconds; known-fast built-in tools have a five-minute default." })),
|
|
328
|
-
turnBudget: Type.Optional(TurnBudgetOverride),
|
|
329
354
|
toolBudget: Type.Optional(ToolBudgetOverride),
|
|
330
355
|
usageBudget: Type.Optional(UsageBudgetOverride),
|
|
331
356
|
agentScope: Type.Optional(Type.String({ description: "Agent discovery scope: 'user', 'project', or 'both' (default: 'both'; project wins on name collisions)" })),
|
|
@@ -343,11 +368,11 @@ const SubagentParamProperties = {
|
|
|
343
368
|
{ type: "string" },
|
|
344
369
|
{ type: "boolean" },
|
|
345
370
|
],
|
|
346
|
-
description: "Default child output file (string), or false to disable. Relative paths
|
|
371
|
+
description: "Default child output file (string), or false to disable. Relative workflow child paths use managed artifact routing. Task filename prose is not an output declaration; for durable workflow handoff, return the child's outputReference, outputPathMapping, or artifactPaths.",
|
|
347
372
|
})),
|
|
348
373
|
outputMode: Type.Optional(OutputModeOverride),
|
|
349
374
|
skill: Type.Optional(SkillOverride),
|
|
350
|
-
model: Type.Optional(Type.String({ description: "Default child model override. Full provider/id values are accepted; bare ids resolve from the active registry." })),
|
|
375
|
+
model: Type.Optional(Type.String({ description: "Default child model override. Full provider/id values are accepted; bare ids resolve from the active registry. Append a thinking suffix (off/minimal/low/medium/high/xhigh/max, e.g. 'provider/id:low') to set the child's thinking level for the run; the suffix wins over the agent's thinking default." })),
|
|
351
376
|
fast: Type.Optional(Type.Boolean({ description: "Opt into priority service tier for supported native OpenAI-Codex child models. Default false. This can increase quota or cost." })),
|
|
352
377
|
outputSchema: Type.Optional(JsonSchemaObject),
|
|
353
378
|
agentContract: Type.Optional(AgentContractOverride),
|
|
@@ -375,7 +400,7 @@ const SubagentWaitParamsSchema = Type.Object({
|
|
|
375
400
|
})),
|
|
376
401
|
timeoutMs: Type.Optional(Type.Integer({
|
|
377
402
|
minimum: 1,
|
|
378
|
-
description: "Give up waiting after this many milliseconds (the runs keep going regardless). Defaults to 1800000 (30 minutes).",
|
|
403
|
+
description: "Give up waiting after this many milliseconds (the runs keep going regardless). Defaults to config waitTool.defaultTimeoutMs, then 1800000 (30 minutes). Window expiry is a non-error active-work result.",
|
|
379
404
|
})),
|
|
380
405
|
stopOnAttention: Type.Optional(Type.Boolean({
|
|
381
406
|
description: "Blocking waits stop when a run needs attention by default. Set false to keep waiting through idle or long-thinking attention; supervisor/contact requests still stop the wait.",
|