pi-subagents 0.66.0 → 0.68.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 +138 -0
- package/README.md +5 -4
- package/agents/evidence-auditor.md +34 -0
- package/agents/reviewer.md +3 -2
- package/docs/agents.md +43 -15
- package/docs/configuration.md +67 -23
- package/docs/extension-api.md +38 -19
- package/docs/missions.md +10 -2
- package/docs/models.md +11 -79
- package/docs/observability.md +22 -12
- package/docs/standalone-background.md +59 -0
- package/docs/tool-reference.md +33 -20
- package/docs/watchdog.md +39 -10
- package/docs/workflows.md +37 -13
- package/index.ts +5 -2
- package/inspector-runner.mjs +2 -2
- package/package.json +4 -2
- package/prompts/parallel-review.md +1 -1
- package/runner-peer-loader.mjs +24 -0
- package/runner-peer-preload.mjs +32 -0
- package/skills/pi-subagents/SKILL.md +32 -21
- package/skills/pi-subagents/references/constraints-and-recipes.md +3 -2
- package/skills/pi-subagents/references/execution-controls.md +11 -9
- package/skills/pi-subagents/references/management-authoring-rpc.md +0 -1
- package/skills/pi-subagents/references/multi-lane-orchestration.md +1 -1
- package/skills/pi-subagents/references/prompting-and-roles.md +17 -13
- package/skills/pi-subagents/references/review-and-validation.md +3 -3
- package/src/agents/advertised-agent-prompt.ts +34 -3
- package/src/agents/agent-management.ts +57 -58
- package/src/agents/agent-serializer.ts +4 -3
- package/src/agents/agents.ts +190 -71
- package/src/agents/builtin-names.ts +1 -0
- package/src/agents/chain-serializer.ts +5 -0
- package/src/agents/runtime-agent-registry.ts +7 -6
- package/src/api/delegation.ts +4 -0
- package/src/api/preflight.ts +94 -59
- package/src/api/required-child-extensions.ts +6 -0
- package/src/api/shared-types.ts +2 -0
- package/src/extension/config.ts +10 -37
- package/src/extension/fanout-child.ts +66 -4
- package/src/extension/herdr-pi-bridge.ts +160 -0
- package/src/extension/index.ts +62 -39
- package/src/extension/public-execution.ts +7 -5
- package/src/extension/rpc.ts +4 -0
- package/src/extension/schemas.ts +81 -81
- package/src/extension/tool-description.ts +30 -82
- package/src/inspectors/actions.ts +148 -0
- package/src/inspectors/ghostty/actions.ts +74 -0
- package/src/inspectors/ghostty/plugin.ts +17 -0
- package/src/inspectors/herdr/actions.ts +99 -179
- package/src/inspectors/herdr/plugin.ts +20 -0
- package/src/inspectors/herdr/project-panes.ts +1 -1
- package/src/inspectors/{herdr/inspector-runner.ts → inspector-runner.ts} +12 -12
- package/src/inspectors/plugins.ts +8 -0
- package/src/inspectors/{herdr/session-roots-codec.ts → session-roots-codec.ts} +3 -14
- package/src/inspectors/types.ts +51 -0
- package/src/intercom/intercom-bridge.ts +50 -8
- package/src/intercom/native-supervisor-channel.ts +44 -31
- package/src/policy/authority.ts +4 -0
- package/src/profiles/profiles.ts +12 -6
- package/src/runs/background/active-async-capacity.ts +4 -0
- package/src/runs/background/active-run-index.ts +17 -1
- package/src/runs/background/async-execution.ts +348 -176
- package/src/runs/background/async-job-tracker.ts +8 -6
- package/src/runs/background/async-resume.ts +17 -12
- package/src/runs/background/async-status.ts +15 -4
- package/src/runs/background/auto-drain.ts +23 -10
- package/src/runs/background/binary-bootstrap.ts +38 -0
- package/src/runs/background/chain-append.ts +1 -1
- package/src/runs/background/chain-root-attachment.ts +14 -33
- package/src/runs/background/fleet-view.ts +30 -2
- package/src/runs/background/notify.ts +105 -7
- package/src/runs/background/owned-process-tree.ts +29 -2
- package/src/runs/background/result-files.ts +8 -4
- package/src/runs/background/result-watcher.ts +19 -2
- package/src/runs/background/run-child-session.ts +81 -33
- package/src/runs/background/run-status.ts +3 -0
- package/src/runs/background/runner-aliases.ts +12 -33
- package/src/runs/background/runner-child-launch.ts +6 -1
- package/src/runs/background/runner-child-sessions.ts +5 -4
- package/src/runs/background/runner-http-dispatcher.ts +119 -0
- package/src/runs/background/scheduled-runs.ts +51 -18
- package/src/runs/background/stale-run-reconciler.ts +35 -11
- package/src/runs/background/steering.ts +20 -2
- package/src/runs/background/subagent-runner.ts +441 -304
- package/src/runs/background/subagent-wait.ts +176 -28
- package/src/runs/background/wait-completions.ts +75 -27
- package/src/runs/background/wait-subscriptions.ts +9 -3
- package/src/runs/background/wait-tool.ts +5 -3
- package/src/runs/foreground/async-steering-action.ts +18 -7
- package/src/runs/foreground/async-stop-action.ts +93 -3
- package/src/runs/foreground/execution.ts +134 -248
- package/src/runs/foreground/foreground-history.ts +2 -1
- package/src/runs/foreground/prompt-audit.ts +3 -1
- package/src/runs/foreground/subagent-executor.ts +374 -178
- package/src/runs/foreground/workflow-detach-reconcile.ts +2 -0
- package/src/runs/foreground/workflow-foreground-steering.ts +2 -1
- package/src/runs/shared/acceptance.ts +38 -11
- package/src/runs/shared/async-status-projection.ts +127 -33
- package/src/runs/shared/capability-ceiling.ts +2 -0
- package/src/runs/shared/child-hooks.ts +25 -10
- package/src/runs/shared/child-launch-plan.ts +15 -3
- package/src/runs/shared/child-launch.ts +28 -5
- package/src/runs/shared/child-lifecycle.ts +6 -3
- package/src/runs/shared/child-runtime-config.ts +8 -1
- package/src/runs/shared/child-session.ts +127 -52
- package/src/runs/shared/child-tool-plan.ts +142 -11
- package/src/runs/shared/completion-guard.ts +5 -3
- package/src/runs/shared/dynamic-fanout.ts +2 -2
- package/src/runs/shared/effective-system-prompt.ts +33 -0
- package/src/runs/shared/external-cli-contract.ts +11 -1
- package/src/runs/shared/external-cli-preflight.ts +6 -2
- package/src/runs/shared/external-cli-runner.ts +9 -7
- package/src/runs/shared/herdr-connection.ts +134 -0
- package/src/runs/shared/herdr-external-adapters.ts +169 -0
- package/src/runs/shared/herdr-machine.ts +279 -0
- package/src/runs/shared/herdr-pi-protocol.ts +59 -0
- package/src/runs/shared/herdr-placed-run.ts +263 -0
- package/src/runs/shared/llm-intent-arbiter.ts +12 -3
- package/src/runs/shared/model-resolution-diagnostic.ts +76 -0
- package/src/runs/shared/{model-fallback.ts → model-resolution.ts} +22 -235
- package/src/runs/shared/model-scope.ts +1 -1
- package/src/runs/shared/nested-events.ts +11 -2
- package/src/runs/shared/orca-progress-tabs.ts +1 -1
- package/src/runs/shared/parallel-utils.ts +7 -2
- package/src/runs/shared/pi-spawn.ts +10 -0
- package/src/runs/shared/subagent-prompt-runtime.ts +12 -4
- package/src/runs/shared/task-intent.ts +46 -13
- package/src/runs/shared/workflow-async-child-guidance.ts +18 -0
- package/src/runs/shared/worktree-setup-command.ts +27 -4
- package/src/runs/shared/worktree.ts +45 -15
- package/src/shared/child-cache-retention.ts +43 -0
- package/src/shared/fork-context.ts +15 -72
- package/src/shared/launch-contract.ts +68 -8
- package/src/shared/opencode-session-headers.ts +30 -0
- package/src/shared/pruned-fork.ts +1 -1
- package/src/shared/required-child-extensions.ts +81 -0
- package/src/shared/settings.ts +5 -2
- package/src/shared/shortcuts.ts +0 -4
- package/src/shared/types.ts +74 -30
- package/src/slash/delegation-adapters.ts +3 -1
- package/src/slash/delegation-request.ts +14 -0
- package/src/slash/slash-commands.ts +2 -7
- package/src/slash/subagents-admin.ts +24 -13
- package/src/tui/fleet-status.ts +164 -19
- package/src/tui/fleet.ts +16 -14
- package/src/tui/render.ts +168 -37
- package/src/watchdog/child-status.ts +28 -28
- package/src/watchdog/lsp-diagnostics.ts +1 -1
- package/src/watchdog/model-selection.ts +21 -1
- package/src/watchdog/permission-arbiter.ts +3 -1
- package/src/watchdog/register-child.ts +10 -2
- package/src/watchdog/register-main.ts +39 -35
- package/src/watchdog/render.ts +1 -1
- package/src/watchdog/review.ts +123 -74
- package/src/watchdog/rules.ts +1 -1
- package/src/watchdog/runtime.ts +100 -27
- package/src/watchdog/scope.ts +1 -1
- package/src/watchdog/settings.ts +3 -0
- package/src/watchdog/tool-actions.ts +13 -12
- package/src/watchdog/turn-delta.ts +23 -0
- package/src/watchdog/types.ts +5 -3
- package/src/watchdog/warning-format.ts +1 -1
- package/src/workflows/scripted-workflow.ts +279 -10
- package/src/workflows/workflow-checklist.ts +2 -2
- package/src/workflows/workflow-receipt.ts +21 -3
- package/src/workflows/workflow-resources.ts +13 -2
- package/runner-server-preload.mjs +0 -13
- package/src/runs/shared/model-exclusions.ts +0 -374
- package/src/runs/shared/readonly-model-continuation.ts +0 -69
- package/src/runs/shared/readonly-session-evidence.ts +0 -307
- /package/src/inspectors/{herdr/shell-command.ts → shell-command.ts} +0 -0
|
@@ -243,6 +243,7 @@ export function reconcileDetachedWorkflowChildCompletion(input: {
|
|
|
243
243
|
cwd: next.cwd,
|
|
244
244
|
sessionId: next.sessionId ?? (typeof existing?.sessionId === "string" ? existing.sessionId : undefined),
|
|
245
245
|
completionOwnerId: next.completionOwnerId,
|
|
246
|
+
...(next.scheduleOrigin ? { scheduleOrigin: next.scheduleOrigin } : {}),
|
|
246
247
|
},
|
|
247
248
|
receipt,
|
|
248
249
|
receiptPath,
|
|
@@ -294,6 +295,7 @@ export function reconcileDetachedWorkflowChildCompletion(input: {
|
|
|
294
295
|
results,
|
|
295
296
|
sessionId: plan.status.sessionId,
|
|
296
297
|
completionOwnerId: plan.status.completionOwnerId,
|
|
298
|
+
...(plan.status.scheduleOrigin ? { scheduleOrigin: plan.status.scheduleOrigin } : {}),
|
|
297
299
|
timestamp: Date.now(),
|
|
298
300
|
triggerTurn: true,
|
|
299
301
|
});
|
|
@@ -6,6 +6,7 @@ import { readStatus } from "../../shared/utils.ts";
|
|
|
6
6
|
import { steeringReceipt, waitForSteeringAction } from "../background/steering.ts";
|
|
7
7
|
import { requestAsyncSteer, type SteerDeliveryMode } from "../background/control-channel.ts";
|
|
8
8
|
import { currentCompletionOwnerId } from "../../shared/completion-owner.ts";
|
|
9
|
+
import { workflowAsyncChildSteeringGuidance } from "../shared/workflow-async-child-guidance.ts";
|
|
9
10
|
|
|
10
11
|
export interface WorkflowForegroundSteeringTarget {
|
|
11
12
|
control: ForegroundRunControl;
|
|
@@ -85,7 +86,7 @@ export async function steerWorkflowRun(input: {
|
|
|
85
86
|
}
|
|
86
87
|
if (state.workflowControllers?.has(runId)) {
|
|
87
88
|
const route = resolveWorkflowForegroundSteeringTarget({ state, workflowRunId: runId, asyncDirRoot: path.dirname(asyncDir) });
|
|
88
|
-
if (!route.ok) return managementError(route.message);
|
|
89
|
+
if (!route.ok) return managementError([route.message, ...workflowAsyncChildSteeringGuidance(status, state)].join("\n"));
|
|
89
90
|
return steerWorkflowForegroundTarget({ target: route.target, message: input.message, mode: input.mode, index: input.index });
|
|
90
91
|
}
|
|
91
92
|
if (!state.currentSessionId) return managementError("Workflow steering requires an active parent session.");
|
|
@@ -11,6 +11,7 @@ import type {
|
|
|
11
11
|
AcceptanceLedger,
|
|
12
12
|
AcceptanceLevel,
|
|
13
13
|
AcceptanceReport,
|
|
14
|
+
AcceptanceReviewGate,
|
|
14
15
|
AcceptanceRole,
|
|
15
16
|
AcceptanceRuntimeCheck,
|
|
16
17
|
AcceptanceRuntimeCheckStatus,
|
|
@@ -105,11 +106,15 @@ function inferLevel(input: {
|
|
|
105
106
|
const inferredReadOnly = readOnlyTask || ((readOnlyAgent || input.acceptanceRole === "read-only") && !taskMayWrite);
|
|
106
107
|
const roleResolvesReadOnly = input.acceptanceRole !== undefined && inferredReadOnly;
|
|
107
108
|
const dynamicResolvesReadOnly = inferredReadOnly && !writeTask;
|
|
108
|
-
const
|
|
109
|
+
const riskyKeywordPattern = /\b(?:release|migration|migrate|security|data[- ]loss|destructive|post-review|fix pass)\b/;
|
|
110
|
+
// Topic keywords cannot override classified read-only intent; unknown tasks keep their risk gate.
|
|
111
|
+
const keywordRiskReadOnly = input.acceptanceRole === undefined
|
|
112
|
+
? intent.kind === "read-only"
|
|
113
|
+
: inferredReadOnly;
|
|
109
114
|
const risky = Boolean(input.async && writeTask)
|
|
110
115
|
|| (Boolean(input.dynamic) && !roleResolvesReadOnly && !dynamicResolvesReadOnly)
|
|
111
116
|
|| (Boolean(input.dynamicGroup) && !roleResolvesReadOnly && !dynamicResolvesReadOnly)
|
|
112
|
-
|| (!keywordRiskReadOnly &&
|
|
117
|
+
|| (!keywordRiskReadOnly && riskyKeywordPattern.test(task));
|
|
113
118
|
|
|
114
119
|
if (risky) {
|
|
115
120
|
reasons.push(input.async ? "async write-capable or risky run" : "risky write-capable run");
|
|
@@ -367,7 +372,7 @@ export function validateAcceptanceInput(input: unknown, pathLabel = "acceptance"
|
|
|
367
372
|
return errors;
|
|
368
373
|
}
|
|
369
374
|
|
|
370
|
-
|
|
375
|
+
type ExecutionAcceptanceInput = {
|
|
371
376
|
acceptance?: unknown;
|
|
372
377
|
outputSchema?: unknown;
|
|
373
378
|
tasks?: Array<{ acceptance?: unknown; outputSchema?: unknown }>;
|
|
@@ -376,23 +381,39 @@ export function validateExecutionAcceptance(input: {
|
|
|
376
381
|
outputSchema?: unknown;
|
|
377
382
|
parallel?: Array<{ acceptance?: unknown; outputSchema?: unknown }> | { acceptance?: unknown; outputSchema?: unknown };
|
|
378
383
|
}>;
|
|
379
|
-
}
|
|
384
|
+
};
|
|
385
|
+
|
|
386
|
+
export function validateExecutionAcceptancePolicy(input: ExecutionAcceptanceInput): string[] {
|
|
380
387
|
const errors = validateAcceptanceInput(input.acceptance, "acceptance");
|
|
381
|
-
errors.push(...validateAcceptanceReportMode(input.acceptance, input.outputSchema, "acceptance"));
|
|
382
388
|
for (const [index, task] of (input.tasks ?? []).entries()) {
|
|
383
389
|
errors.push(...validateAcceptanceInput(task.acceptance, `tasks[${index}].acceptance`));
|
|
384
|
-
errors.push(...validateAcceptanceReportMode(task.acceptance, task.outputSchema, `tasks[${index}].acceptance`));
|
|
385
390
|
}
|
|
386
391
|
for (const [stepIndex, step] of (input.chain ?? []).entries()) {
|
|
387
392
|
errors.push(...validateAcceptanceInput(step.acceptance, `chain[${stepIndex}].acceptance`));
|
|
388
|
-
errors.push(...validateAcceptanceReportMode(step.acceptance, step.outputSchema, `chain[${stepIndex}].acceptance`));
|
|
389
393
|
if (Array.isArray(step.parallel)) {
|
|
390
394
|
for (const [taskIndex, task] of step.parallel.entries()) {
|
|
391
395
|
errors.push(...validateAcceptanceInput(task.acceptance, `chain[${stepIndex}].parallel[${taskIndex}].acceptance`));
|
|
392
|
-
errors.push(...validateAcceptanceReportMode(task.acceptance, task.outputSchema, `chain[${stepIndex}].parallel[${taskIndex}].acceptance`));
|
|
393
396
|
}
|
|
394
397
|
} else if (step.parallel) {
|
|
395
398
|
errors.push(...validateAcceptanceInput(step.parallel.acceptance, `chain[${stepIndex}].parallel.acceptance`));
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
return errors;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
export function validateExecutionAcceptance(input: ExecutionAcceptanceInput): string[] {
|
|
405
|
+
const errors = validateExecutionAcceptancePolicy(input);
|
|
406
|
+
errors.push(...validateAcceptanceReportMode(input.acceptance, input.outputSchema, "acceptance"));
|
|
407
|
+
for (const [index, task] of (input.tasks ?? []).entries()) {
|
|
408
|
+
errors.push(...validateAcceptanceReportMode(task.acceptance, task.outputSchema, `tasks[${index}].acceptance`));
|
|
409
|
+
}
|
|
410
|
+
for (const [stepIndex, step] of (input.chain ?? []).entries()) {
|
|
411
|
+
errors.push(...validateAcceptanceReportMode(step.acceptance, step.outputSchema, `chain[${stepIndex}].acceptance`));
|
|
412
|
+
if (Array.isArray(step.parallel)) {
|
|
413
|
+
for (const [taskIndex, task] of step.parallel.entries()) {
|
|
414
|
+
errors.push(...validateAcceptanceReportMode(task.acceptance, task.outputSchema, `chain[${stepIndex}].parallel[${taskIndex}].acceptance`));
|
|
415
|
+
}
|
|
416
|
+
} else if (step.parallel) {
|
|
396
417
|
errors.push(...validateAcceptanceReportMode(step.parallel.acceptance, step.parallel.outputSchema, `chain[${stepIndex}].parallel.acceptance`));
|
|
397
418
|
}
|
|
398
419
|
}
|
|
@@ -405,7 +426,7 @@ function validateAcceptanceReportMode(acceptance: unknown, outputSchema: unknown
|
|
|
405
426
|
acceptance = normalized.value;
|
|
406
427
|
if (!acceptance || typeof acceptance !== "object" || Array.isArray(acceptance)) return [];
|
|
407
428
|
if (!("report" in acceptance)) return [];
|
|
408
|
-
return outputSchema === undefined ? [`${pathLabel}.report requires outputSchema.`] : [];
|
|
429
|
+
return outputSchema === undefined || outputSchema === false ? [`${pathLabel}.report requires outputSchema.`] : [];
|
|
409
430
|
}
|
|
410
431
|
|
|
411
432
|
function normalizeCriteria(criteria: Array<string | { id?: string; must?: string; evidence?: AcceptanceEvidenceKind[]; severity?: "required" | "recommended" }> | undefined, evidence: AcceptanceEvidenceKind[]): ResolvedAcceptanceGate[] {
|
|
@@ -486,6 +507,12 @@ function acceptanceRequiresChildReport(acceptance: ResolvedAcceptanceConfig): bo
|
|
|
486
507
|
return acceptance.criteria.length > 0 || acceptance.evidence.length > 0;
|
|
487
508
|
}
|
|
488
509
|
|
|
510
|
+
/** Label a declared review gate the same way in prompts and capability summaries. */
|
|
511
|
+
export function formatReviewGateLabel(review: AcceptanceReviewGate): string {
|
|
512
|
+
const status = review.required === false ? "optional" : "required";
|
|
513
|
+
return review.agent ? `${status} by ${review.agent}` : status;
|
|
514
|
+
}
|
|
515
|
+
|
|
489
516
|
export function formatAcceptancePrompt(acceptance: ResolvedAcceptanceConfig, options: { reportOptional?: boolean; structuredOutput?: boolean } = {}): string {
|
|
490
517
|
if (acceptance.level === "none") return "";
|
|
491
518
|
if (options.reportOptional && !acceptanceRequiresChildReport(acceptance)) return "";
|
|
@@ -505,7 +532,7 @@ export function formatAcceptancePrompt(acceptance: ResolvedAcceptanceConfig, opt
|
|
|
505
532
|
for (const command of acceptance.verify) lines.push(`- ${command.id}: ${command.command}`);
|
|
506
533
|
}
|
|
507
534
|
if (acceptance.review) {
|
|
508
|
-
lines.push("", `Review gate: ${acceptance.review
|
|
535
|
+
lines.push("", `Review gate: ${formatReviewGateLabel(acceptance.review)}.`);
|
|
509
536
|
if (acceptance.review.focus) lines.push(`Review focus: ${acceptance.review.focus}`);
|
|
510
537
|
}
|
|
511
538
|
if (acceptance.stopRules.length > 0) {
|
|
@@ -1394,7 +1421,7 @@ export async function evaluateAcceptance(input: {
|
|
|
1394
1421
|
if (input.watchdog) {
|
|
1395
1422
|
const unresolved = unresolvedChildWatchdogBlockers(input.watchdog);
|
|
1396
1423
|
ledger.runtimeChecks.push(unresolved.length
|
|
1397
|
-
? { id: "watchdog-blocker", status: "failed", message:
|
|
1424
|
+
? { id: "watchdog-blocker", status: "failed", message: "Unresolved watchdog blocker (details are available in child watchdog status)." }
|
|
1398
1425
|
: { id: "watchdog-blocker", status: "passed", message: "No unresolved watchdog blockers." });
|
|
1399
1426
|
}
|
|
1400
1427
|
|
|
@@ -108,6 +108,8 @@ export interface AsyncStatusWorkflowRow {
|
|
|
108
108
|
modelThinking?: string;
|
|
109
109
|
activity?: string;
|
|
110
110
|
startedAt?: number;
|
|
111
|
+
endedAt?: number;
|
|
112
|
+
durationMs?: number;
|
|
111
113
|
tokens?: number;
|
|
112
114
|
window?: number;
|
|
113
115
|
overflow?: number;
|
|
@@ -222,16 +224,25 @@ function appendBoundedChildren(children: AsyncStatusSnapshotNode[], source: read
|
|
|
222
224
|
ctx.omitted.children += Math.max(0, source.length - remaining);
|
|
223
225
|
}
|
|
224
226
|
|
|
227
|
+
function stepIdentity(step: AsyncJobStep | NestedStepSummary, index: number, ctx: ProjectionContext) {
|
|
228
|
+
return {
|
|
229
|
+
id: publicText("workflowKey" in step && step.workflowKey ? step.workflowKey : "runId" in step && step.runId ? step.runId : `step:${index}`, `step:${index}`, ctx.caps.maxStringLength),
|
|
230
|
+
label: publicText("label" in step && step.label ? step.label : step.agent, "step", ctx.caps.maxStringLength),
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
225
234
|
function projectStep(step: AsyncJobStep | NestedStepSummary, index: number, depth: number, ctx: ProjectionContext): AsyncStatusSnapshotNode {
|
|
226
235
|
const state = normalizeState(step.status);
|
|
227
236
|
const startedAt = publicTime(step.startedAt);
|
|
228
|
-
const
|
|
237
|
+
const durationMs = "durationMs" in step ? publicTime(step.durationMs) : undefined;
|
|
238
|
+
const endedAt = publicTime(step.endedAt) ?? (terminalState(state) && startedAt !== undefined && durationMs !== undefined ? publicTime(startedAt + durationMs) : undefined);
|
|
229
239
|
const updatedAt = endedAt ?? publicTime(step.lastActivityAt) ?? startedAt;
|
|
230
240
|
const activity = activityFor(step, ctx);
|
|
241
|
+
const { id, label } = stepIdentity(step, index, ctx);
|
|
231
242
|
const node: AsyncStatusSnapshotNode = {
|
|
232
|
-
id
|
|
243
|
+
id,
|
|
233
244
|
kind: "step",
|
|
234
|
-
label
|
|
245
|
+
label,
|
|
235
246
|
state,
|
|
236
247
|
...(startedAt !== undefined ? { startedAt } : {}),
|
|
237
248
|
...(updatedAt !== undefined ? { updatedAt } : {}),
|
|
@@ -249,16 +260,6 @@ function projectStep(step: AsyncJobStep | NestedStepSummary, index: number, dept
|
|
|
249
260
|
return node;
|
|
250
261
|
}
|
|
251
262
|
|
|
252
|
-
function projectWorkflowGraphNode(node: WorkflowGraphSnapshot["nodes"][number], index: number, depth: number, ctx: ProjectionContext): AsyncStatusSnapshotNode {
|
|
253
|
-
const step: AsyncJobStep = {
|
|
254
|
-
agent: node.agent ?? node.label,
|
|
255
|
-
status: workflowGraphStepStatus(node.status),
|
|
256
|
-
workflowKey: node.id,
|
|
257
|
-
label: node.label,
|
|
258
|
-
};
|
|
259
|
-
return projectStep(step, node.flatIndex ?? index, depth, ctx);
|
|
260
|
-
}
|
|
261
|
-
|
|
262
263
|
function projectNestedRun(child: NestedRunSummary, index: number, depth: number, ctx: ProjectionContext): AsyncStatusSnapshotNode {
|
|
263
264
|
const state = normalizeState(child.state);
|
|
264
265
|
const startedAt = publicTime(child.startedAt);
|
|
@@ -322,7 +323,86 @@ function projectHostStep(hostStep: HostStepNode, ctx: ProjectionContext): AsyncS
|
|
|
322
323
|
};
|
|
323
324
|
}
|
|
324
325
|
|
|
325
|
-
|
|
326
|
+
type MaterializedChildren = Map<string, AsyncJobState[]>;
|
|
327
|
+
|
|
328
|
+
function asyncJobOrder(left: AsyncJobState, right: AsyncJobState): number {
|
|
329
|
+
const leftRank = left.status === "running" ? 0 : left.status === "queued" ? 1 : 2;
|
|
330
|
+
const rightRank = right.status === "running" ? 0 : right.status === "queued" ? 1 : 2;
|
|
331
|
+
const leftTime = left.updatedAt ?? left.startedAt ?? 0;
|
|
332
|
+
const rightTime = right.updatedAt ?? right.startedAt ?? 0;
|
|
333
|
+
return leftRank - rightRank || rightTime - leftTime || left.asyncId.localeCompare(right.asyncId);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function hasCyclicWorkflowParent(job: AsyncJobState, parents: ReadonlyMap<string, AsyncJobState>): boolean {
|
|
337
|
+
const seen = new Set([job.asyncId]);
|
|
338
|
+
let parentId = job.parentWorkflowRunId;
|
|
339
|
+
while (parentId) {
|
|
340
|
+
if (seen.has(parentId)) return true;
|
|
341
|
+
seen.add(parentId);
|
|
342
|
+
parentId = parents.get(parentId)?.parentWorkflowRunId;
|
|
343
|
+
}
|
|
344
|
+
return false;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function groupMaterializedChildren(jobs: readonly AsyncJobState[]) {
|
|
348
|
+
const parents = new Map(jobs.filter((job) => job.mode === "workflow").map((job) => [job.asyncId, job]));
|
|
349
|
+
const childrenByParent: MaterializedChildren = new Map();
|
|
350
|
+
const liveRoots = new Set<AsyncJobState>();
|
|
351
|
+
const roots: AsyncJobState[] = [];
|
|
352
|
+
for (const job of jobs) {
|
|
353
|
+
const parent = job.parentWorkflowRunId ? parents.get(job.parentWorkflowRunId) : undefined;
|
|
354
|
+
const keepLiveRoot = (job.status === "running" || job.status === "queued") && parent?.status !== "running";
|
|
355
|
+
if (parent && !hasCyclicWorkflowParent(job, parents)) {
|
|
356
|
+
const siblings = childrenByParent.get(parent.asyncId) ?? [];
|
|
357
|
+
siblings.push(job);
|
|
358
|
+
childrenByParent.set(parent.asyncId, siblings);
|
|
359
|
+
if (keepLiveRoot) {
|
|
360
|
+
liveRoots.add(job);
|
|
361
|
+
roots.push(job);
|
|
362
|
+
}
|
|
363
|
+
} else {
|
|
364
|
+
roots.push(job);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
for (const children of childrenByParent.values()) children.sort(asyncJobOrder);
|
|
368
|
+
return { roots, childrenByParent, liveRoots };
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function assignMaterializedChildren(steps: readonly AsyncJobStep[], children: readonly AsyncJobState[]): Map<AsyncJobStep, AsyncJobState> {
|
|
372
|
+
const assigned = new Map<AsyncJobStep, AsyncJobState>();
|
|
373
|
+
const claimed = new Set<AsyncJobState>();
|
|
374
|
+
for (const step of steps) {
|
|
375
|
+
if (!step.runId) continue;
|
|
376
|
+
const child = children.find((candidate) => !claimed.has(candidate) && candidate.asyncId === step.runId);
|
|
377
|
+
if (child) {
|
|
378
|
+
assigned.set(step, child);
|
|
379
|
+
claimed.add(child);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
for (const step of steps) {
|
|
383
|
+
if (assigned.has(step) || !step.workflowKey) continue;
|
|
384
|
+
const child = children.find((candidate) => !claimed.has(candidate) && candidate.workflowKey === step.workflowKey);
|
|
385
|
+
if (child) {
|
|
386
|
+
assigned.set(step, child);
|
|
387
|
+
claimed.add(child);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
return assigned;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
function projectMaterializedStep(step: AsyncJobStep, index: number, child: AsyncJobState, depth: number, ctx: ProjectionContext, childrenByParent: MaterializedChildren, liveRoots: ReadonlySet<AsyncJobState>): AsyncStatusSnapshotNode {
|
|
394
|
+
const { id, label } = stepIdentity(step, index, ctx);
|
|
395
|
+
const selfStep = child.mode === undefined || child.mode === "single" ? child.steps?.[0] : undefined;
|
|
396
|
+
const live = projectRun(child, ctx, depth, childrenByParent, liveRoots, selfStep !== undefined);
|
|
397
|
+
const laneActivity = activityFor(step, ctx);
|
|
398
|
+
const selfActivity = selfStep ? activityFor(selfStep, ctx) : undefined;
|
|
399
|
+
const activity = laneActivity || selfActivity || live.activity ? { ...laneActivity, ...selfActivity, ...live.activity } : undefined;
|
|
400
|
+
const node: AsyncStatusSnapshotNode = { ...live, id, kind: "step", label };
|
|
401
|
+
if (activity) node.activity = activity;
|
|
402
|
+
return node;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
function projectRun(job: AsyncJobState, ctx: ProjectionContext, depth: number, childrenByParent: MaterializedChildren, liveRoots: ReadonlySet<AsyncJobState>, omitSteps = false): AsyncStatusSnapshotNode {
|
|
326
406
|
const state = normalizeState(job.status);
|
|
327
407
|
const startedAt = publicTime(job.startedAt);
|
|
328
408
|
const updatedAt = publicTime(job.updatedAt) ?? startedAt;
|
|
@@ -337,16 +417,32 @@ function projectRun(job: AsyncJobState, ctx: ProjectionContext): AsyncStatusSnap
|
|
|
337
417
|
...(terminalState(state) && updatedAt !== undefined ? { endedAt: updatedAt } : {}),
|
|
338
418
|
...(activity ? { activity } : {}),
|
|
339
419
|
};
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
420
|
+
const steps = omitSteps ? [] : job.steps ?? [];
|
|
421
|
+
const materialized = childrenByParent.get(job.asyncId) ?? [];
|
|
422
|
+
const nestedSummaries = (job.nestedChildren ?? []).filter((child) => !materialized.some((live) => live.asyncId === child.id));
|
|
423
|
+
const loadedKeys = new Set(steps.flatMap((step) => step.workflowKey ? [step.workflowKey] : []));
|
|
424
|
+
const graphStages = job.mode === "workflow" ? workflowGraphStageNodes(job.workflowGraph).filter((graphNode) => !loadedKeys.has(graphNode.id)) : [];
|
|
425
|
+
const graphSteps = graphStages.map((node, index) => ({
|
|
426
|
+
step: { agent: node.agent ?? node.label, status: workflowGraphStepStatus(node.status), workflowKey: node.id, label: node.label },
|
|
427
|
+
index: node.flatIndex ?? index,
|
|
428
|
+
}));
|
|
429
|
+
const lanes = [...steps, ...graphSteps.map(({ step }) => step)];
|
|
430
|
+
const assigned = assignMaterializedChildren(lanes, materialized);
|
|
431
|
+
const claimed = new Set(assigned.values());
|
|
432
|
+
if (depth < ctx.caps.maxDepth) {
|
|
433
|
+
const childDepth = depth + 1;
|
|
434
|
+
const projectLane = (step: AsyncJobStep, index: number): AsyncStatusSnapshotNode | undefined => {
|
|
435
|
+
const child = assigned.get(step);
|
|
436
|
+
if (!child) return projectStep(step, index, childDepth, ctx);
|
|
437
|
+
if (liveRoots.has(child)) return undefined;
|
|
438
|
+
return projectMaterializedStep(step, index, child, childDepth, ctx, childrenByParent, liveRoots);
|
|
439
|
+
};
|
|
440
|
+
const stepChildren = steps.map((step, index) => projectLane(step, step.index ?? index)).filter((child): child is AsyncStatusSnapshotNode => child !== undefined);
|
|
441
|
+
const graphChildren = graphSteps.map(({ step, index }) => projectLane(step, index)).filter((child): child is AsyncStatusSnapshotNode => child !== undefined);
|
|
442
|
+
const nestedChildren = nestedSummaries.map((child, index) => projectNestedRun(child, index, childDepth, ctx));
|
|
443
|
+
const unclaimedChildren = materialized.filter((child) => !claimed.has(child) && !liveRoots.has(child)).map((child) => projectRun(child, ctx, childDepth, childrenByParent, liveRoots));
|
|
348
444
|
const hostStepChildren = validHostStepList(job.hostSteps).map((hostStep) => projectHostStep(hostStep, ctx));
|
|
349
|
-
const ordinaryChildren = [...stepChildren, ...graphChildren, ...nestedChildren];
|
|
445
|
+
const ordinaryChildren = [...stepChildren, ...graphChildren, ...nestedChildren, ...unclaimedChildren];
|
|
350
446
|
const retainedHostSteps = hostStepChildren.slice(0, ctx.caps.maxChildrenPerNode);
|
|
351
447
|
const retainedOrdinaryChildren = ordinaryChildren.slice(0, ctx.caps.maxChildrenPerNode - retainedHostSteps.length);
|
|
352
448
|
const bounded: AsyncStatusSnapshotNode[] = [];
|
|
@@ -354,10 +450,9 @@ function projectRun(job: AsyncJobState, ctx: ProjectionContext): AsyncStatusSnap
|
|
|
354
450
|
ctx.omitted.children += ordinaryChildren.length - retainedOrdinaryChildren.length + hostStepChildren.length - retainedHostSteps.length;
|
|
355
451
|
if (bounded.length) node.children = bounded;
|
|
356
452
|
} else {
|
|
357
|
-
const
|
|
358
|
-
const
|
|
359
|
-
|
|
360
|
-
ctx.omitted.children += (job.steps?.length ?? 0) + graphCount + (job.nestedChildren?.length ?? 0) + validHostStepList(job.hostSteps).length;
|
|
453
|
+
const rootAssignments = [...assigned.values()].filter((child) => liveRoots.has(child)).length;
|
|
454
|
+
const unclaimedChildren = materialized.filter((child) => !claimed.has(child) && !liveRoots.has(child)).length;
|
|
455
|
+
ctx.omitted.children += lanes.length - rootAssignments + nestedSummaries.length + unclaimedChildren + validHostStepList(job.hostSteps).length;
|
|
361
456
|
}
|
|
362
457
|
return node;
|
|
363
458
|
}
|
|
@@ -536,6 +631,8 @@ function projectLoadedWorkflowRow(step: AsyncJobStep, index: number, preflight?:
|
|
|
536
631
|
...(modelThinking ? { modelThinking } : {}),
|
|
537
632
|
...(activity ? { activity } : {}),
|
|
538
633
|
...(step.startedAt !== undefined ? { startedAt: step.startedAt } : {}),
|
|
634
|
+
...(step.endedAt !== undefined ? { endedAt: step.endedAt } : {}),
|
|
635
|
+
...(step.durationMs !== undefined ? { durationMs: step.durationMs } : {}),
|
|
539
636
|
...(step.tokens?.total !== undefined ? { tokens: step.tokens.total } : {}),
|
|
540
637
|
...(step.tokens?.window !== undefined ? { window: step.tokens.window } : {}),
|
|
541
638
|
...(preflight ? { preflight } : {}),
|
|
@@ -546,11 +643,8 @@ function projectLoadedWorkflowRow(step: AsyncJobStep, index: number, preflight?:
|
|
|
546
643
|
export function projectAsyncStatusSnapshot(jobs: Iterable<AsyncJobState>, options: AsyncStatusSnapshotOptions = {}): AsyncStatusSnapshot {
|
|
547
644
|
const caps = resolveCaps(options);
|
|
548
645
|
const ctx: ProjectionContext = { caps, omitted: { runs: 0, children: 0, byteLimitExceeded: false } };
|
|
549
|
-
const
|
|
550
|
-
|
|
551
|
-
const rightUpdated = right.updatedAt ?? right.startedAt ?? 0;
|
|
552
|
-
return rightUpdated - leftUpdated || left.asyncId.localeCompare(right.asyncId);
|
|
553
|
-
});
|
|
646
|
+
const { roots, childrenByParent, liveRoots } = groupMaterializedChildren([...jobs]);
|
|
647
|
+
const sorted = roots.sort(asyncJobOrder);
|
|
554
648
|
ctx.omitted.runs += Math.max(0, sorted.length - caps.maxRuns);
|
|
555
649
|
const snapshot: AsyncStatusSnapshot = {
|
|
556
650
|
kind: ASYNC_STATUS_SNAPSHOT_KIND,
|
|
@@ -558,7 +652,7 @@ export function projectAsyncStatusSnapshot(jobs: Iterable<AsyncJobState>, option
|
|
|
558
652
|
generatedAt: options.generatedAt ?? Date.now(),
|
|
559
653
|
caps,
|
|
560
654
|
omitted: ctx.omitted,
|
|
561
|
-
runs: sorted.slice(0, caps.maxRuns).map((job) => projectRun(job, ctx)),
|
|
655
|
+
runs: sorted.slice(0, caps.maxRuns).map((job) => projectRun(job, ctx, 0, childrenByParent, liveRoots)),
|
|
562
656
|
};
|
|
563
657
|
enforceByteLimit(snapshot);
|
|
564
658
|
return snapshot;
|
|
@@ -29,6 +29,8 @@ export interface SubagentCapabilityAudit {
|
|
|
29
29
|
effectiveMcpTools: string[];
|
|
30
30
|
agentAllowed: boolean;
|
|
31
31
|
agentRestrictionSources?: string[];
|
|
32
|
+
/** Builtin tools declared but unavailable on the host runtime. */
|
|
33
|
+
unavailableHostBuiltins?: string[];
|
|
32
34
|
}
|
|
33
35
|
|
|
34
36
|
export interface RegisterSubagentCapabilityCeilingOptions {
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import type { ExtensionAPI
|
|
1
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { listBackgroundWorkProviders } from "../../api/background-work.ts";
|
|
3
3
|
import { ReadonlyDrainObservation } from "./readonly-drain-observation.ts";
|
|
4
|
-
import registerFanoutChildSubagentExtension from "../../extension/fanout-child.ts";
|
|
4
|
+
import registerFanoutChildSubagentExtension, { createChildSafeState } from "../../extension/fanout-child.ts";
|
|
5
5
|
import registerSubagentFastModeExtension from "./fast-mode-extension.ts";
|
|
6
6
|
import registerSubagentPromptRuntime from "./subagent-prompt-runtime.ts";
|
|
7
7
|
import type { ChildRuntimeConfig } from "./child-runtime-config.ts";
|
|
8
8
|
import type { ChildToolDiagnostic } from "./tool-availability.ts";
|
|
9
9
|
import type { ChildSessionLaunch } from "./child-session.ts";
|
|
10
|
+
import type { ArbiterModelContext } from "./llm-intent-arbiter.ts";
|
|
10
11
|
import type { ChildTranscriptWriter } from "../../shared/child-transcript.ts";
|
|
11
12
|
import { projectRuntimeAcknowledgedExtensions } from "./runtime-acknowledged-extensions.ts";
|
|
12
13
|
|
|
@@ -130,17 +131,22 @@ export function createChildHooks(config: ChildRuntimeConfig): ChildHookExtension
|
|
|
130
131
|
export function createCapturedChildHooks(config: ChildRuntimeConfig, runner = false) {
|
|
131
132
|
let diagnostic: ChildToolDiagnostic | undefined;
|
|
132
133
|
let acknowledgedIds: string[] | undefined;
|
|
133
|
-
let completionIntentContext:
|
|
134
|
+
let completionIntentContext: ArbiterModelContext | undefined;
|
|
135
|
+
let finalDrainHeld = false;
|
|
134
136
|
const capture: OwnedCapture = {
|
|
135
137
|
toolDiagnostic: (value) => { diagnostic = value; },
|
|
136
138
|
runtimeAcknowledgements: (ids) => { acknowledgedIds = ids; },
|
|
137
139
|
};
|
|
138
140
|
Object.assign(config, capture);
|
|
139
|
-
const hooks = childHooks(config, capture);
|
|
141
|
+
const hooks = childHooks(config, capture, (held) => { finalDrainHeld = held; });
|
|
140
142
|
if (runner) {
|
|
141
143
|
hooks.push({ name: "pi-subagents:completion-intent", factory: (pi) => pi.on("session_start", (_event, childCtx) => {
|
|
142
|
-
// Retain only attempt model services, not the live child session.
|
|
143
|
-
completionIntentContext = {
|
|
144
|
+
// Retain only attempt model services and the session id string, not the live child session.
|
|
145
|
+
completionIntentContext = {
|
|
146
|
+
model: childCtx.model,
|
|
147
|
+
modelRegistry: childCtx.modelRegistry,
|
|
148
|
+
sessionId: childCtx.sessionManager.getSessionId(),
|
|
149
|
+
};
|
|
144
150
|
}) });
|
|
145
151
|
const proof = promptProofs.get(hooks[0]!.factory);
|
|
146
152
|
if (proof) proof.factories = hooks.map((hook) => hook.factory);
|
|
@@ -150,18 +156,27 @@ export function createCapturedChildHooks(config: ChildRuntimeConfig, runner = fa
|
|
|
150
156
|
completionIntentContext: () => completionIntentContext,
|
|
151
157
|
toolDiagnostic: () => diagnostic,
|
|
152
158
|
runtimeAcknowledgedExtensions: () => acknowledgedIds ? projectRuntimeAcknowledgedExtensions(acknowledgedIds) : undefined,
|
|
159
|
+
finalDrainHeld: () => finalDrainHeld,
|
|
153
160
|
};
|
|
154
161
|
}
|
|
155
162
|
|
|
156
|
-
function childHooks(config: ChildRuntimeConfig, capture?: OwnedCapture): ChildHookExtension[] {
|
|
163
|
+
function childHooks(config: ChildRuntimeConfig, capture?: OwnedCapture, holdFinalDrain?: (held: boolean) => void): ChildHookExtension[] {
|
|
157
164
|
const snapshot = readonlyConfig(config, capture);
|
|
158
165
|
const proof: PromptProof | undefined = snapshot === undefined ? undefined : { config, snapshot, capture };
|
|
166
|
+
const runtime = Object.create(config) as ChildRuntimeConfig;
|
|
167
|
+
const ownedState = Object.getOwnPropertyDescriptor(config, "runtimeState");
|
|
168
|
+
if (!ownedState || !("value" in ownedState) || ownedState.value == null) {
|
|
169
|
+
Object.defineProperty(runtime, "runtimeState", { configurable: true, enumerable: true, writable: true, value: createChildSafeState() });
|
|
170
|
+
}
|
|
171
|
+
if (holdFinalDrain) {
|
|
172
|
+
Object.defineProperty(runtime, "holdFinalDrain", { configurable: true, enumerable: true, writable: true, value: holdFinalDrain });
|
|
173
|
+
}
|
|
159
174
|
const hooks: ChildHookExtension[] = [
|
|
160
175
|
{ name: "pi-subagents:prompt-runtime", factory: function promptRuntime(pi) {
|
|
161
|
-
if (!proof?.observeNext) return registerSubagentPromptRuntime(pi,
|
|
176
|
+
if (!proof?.observeNext) return registerSubagentPromptRuntime(pi, runtime);
|
|
162
177
|
proof.observation = proof.observeNext;
|
|
163
178
|
proof.observeNext = undefined;
|
|
164
|
-
registerSubagentPromptRuntime(pi,
|
|
179
|
+
registerSubagentPromptRuntime(pi, runtime, proof.observation);
|
|
165
180
|
} },
|
|
166
181
|
];
|
|
167
182
|
if (proof) {
|
|
@@ -169,6 +184,6 @@ function childHooks(config: ChildRuntimeConfig, capture?: OwnedCapture): ChildHo
|
|
|
169
184
|
promptProofs.set(hooks[0]!.factory, proof);
|
|
170
185
|
}
|
|
171
186
|
if (config.fast) hooks.push({ name: "pi-subagents:fast-mode", factory: (pi) => registerSubagentFastModeExtension(pi) });
|
|
172
|
-
if (config.fanoutChild) hooks.push({ name: "pi-subagents:fanout-child", factory: (pi) => registerFanoutChildSubagentExtension(pi,
|
|
187
|
+
if (config.fanoutChild) hooks.push({ name: "pi-subagents:fanout-child", factory: (pi) => registerFanoutChildSubagentExtension(pi, runtime) });
|
|
173
188
|
return hooks;
|
|
174
189
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as path from "node:path";
|
|
2
2
|
import type { AgentConfig } from "../../agents/agents.ts";
|
|
3
3
|
import { resolveChildCwd } from "../../shared/utils.ts";
|
|
4
|
-
import type { OutputMode } from "../../shared/types.ts";
|
|
4
|
+
import type { JsonSchemaObject, OutputMode } from "../../shared/types.ts";
|
|
5
5
|
import { resolveSingleOutputPath } from "./single-output.ts";
|
|
6
6
|
|
|
7
7
|
export interface ResolvedStepBehavior {
|
|
@@ -12,6 +12,7 @@ export interface ResolvedStepBehavior {
|
|
|
12
12
|
skills: string[] | false;
|
|
13
13
|
model?: string;
|
|
14
14
|
fast?: boolean;
|
|
15
|
+
outputSchema?: JsonSchemaObject;
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
export type OutputOverrideInput = string | boolean;
|
|
@@ -24,6 +25,7 @@ export interface StepOverrides {
|
|
|
24
25
|
skills?: string[] | false;
|
|
25
26
|
model?: string;
|
|
26
27
|
fast?: boolean;
|
|
28
|
+
outputSchema?: JsonSchemaObject | false;
|
|
27
29
|
}
|
|
28
30
|
|
|
29
31
|
export interface ChildLaunchPlanInput {
|
|
@@ -35,6 +37,7 @@ export interface ChildLaunchPlanInput {
|
|
|
35
37
|
runtimeCwd: string;
|
|
36
38
|
stepCwdInput?: string;
|
|
37
39
|
behaviorCwd?: string;
|
|
40
|
+
machineCwd?: string;
|
|
38
41
|
chainSkills?: string[];
|
|
39
42
|
outputBaseDir?: string;
|
|
40
43
|
parallelOutputNamespace?: { stepIndex: number; taskIndex?: number };
|
|
@@ -58,6 +61,14 @@ export function normalizeOutputOverride(output: unknown): string | false | undef
|
|
|
58
61
|
return typeof output === "string" && output.length > 0 ? output : undefined;
|
|
59
62
|
}
|
|
60
63
|
|
|
64
|
+
export const resolveEffectiveOutputSchema = (agentConfig: AgentConfig, override?: JsonSchemaObject | false): JsonSchemaObject | undefined => override === false ? undefined : override !== undefined ? override : agentConfig.outputSchema;
|
|
65
|
+
|
|
66
|
+
type OutputSchemaStep = { agent: string; outputSchema?: JsonSchemaObject | false };
|
|
67
|
+
export function projectChainOutputSchemas<S extends OutputSchemaStep, R = S, G = { parallel: R[] | R }>(chain: readonly (S | { parallel: S[] | S })[], agents: AgentConfig[], projectStep?: (step: S, outputSchema: JsonSchemaObject | undefined) => R, projectGroup?: (step: { parallel: S[] | S }, parallel: R[] | R) => G): Array<R | G> {
|
|
68
|
+
const project = (step: S): R => { const agent = agents.find((candidate) => candidate.name === step.agent); if (!agent && !projectStep) return step as unknown as R; const outputSchema = agent && resolveEffectiveOutputSchema(agent, step.outputSchema); return projectStep ? projectStep(step, outputSchema) : { ...step, outputSchema } as unknown as R; };
|
|
69
|
+
return chain.map((step) => { if (!("parallel" in step)) return project(step as S); const parallel = Array.isArray(step.parallel) ? step.parallel.map(project) : project(step.parallel as S); return projectGroup ? projectGroup(step, parallel) : { ...step, parallel } as unknown as G; });
|
|
70
|
+
}
|
|
71
|
+
|
|
61
72
|
export function resolveStepBehavior(
|
|
62
73
|
agentConfig: AgentConfig,
|
|
63
74
|
stepOverrides: StepOverrides,
|
|
@@ -94,7 +105,8 @@ export function resolveStepBehavior(
|
|
|
94
105
|
const outputMode = stepOverrides.outputMode ?? agentConfig.outputMode ?? "inline";
|
|
95
106
|
const model = stepOverrides.model ?? agentConfig.model;
|
|
96
107
|
const fast = stepOverrides.fast ?? agentConfig.fast;
|
|
97
|
-
|
|
108
|
+
const outputSchema = resolveEffectiveOutputSchema(agentConfig, stepOverrides.outputSchema);
|
|
109
|
+
return { output, outputMode, reads, progress, skills, model, fast, ...(outputSchema !== undefined ? { outputSchema } : {}) };
|
|
98
110
|
}
|
|
99
111
|
|
|
100
112
|
export function resolveTaskTextForFileUpdatePolicy(task: string | undefined, originalTask?: string): string | undefined {
|
|
@@ -117,7 +129,7 @@ export function suppressProgressForReadOnlyTask(behavior: ResolvedStepBehavior,
|
|
|
117
129
|
}
|
|
118
130
|
|
|
119
131
|
export function planChildLaunch(input: ChildLaunchPlanInput): ChildLaunchPlan {
|
|
120
|
-
const stepCwd = resolveChildCwd(input.runnerCwd, input.stepCwdInput);
|
|
132
|
+
const stepCwd = input.machineCwd ?? resolveChildCwd(input.runnerCwd, input.stepCwdInput);
|
|
121
133
|
const instructionCwd = input.behaviorCwd ?? stepCwd;
|
|
122
134
|
const readExistenceCwd = input.behaviorCwd ? stepCwd : instructionCwd;
|
|
123
135
|
let behavior = suppressProgressForReadOnlyTask(
|