pi-subagents 0.65.1 → 0.67.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 +123 -0
- package/README.md +5 -4
- package/agents/evidence-auditor.md +34 -0
- package/agents/researcher.md +23 -13
- package/agents/reviewer.md +3 -2
- package/docs/agents.md +20 -3
- package/docs/configuration.md +25 -5
- package/docs/extension-api.md +124 -18
- package/docs/missions.md +8 -0
- package/docs/models.md +59 -2
- package/docs/observability.md +46 -6
- package/docs/standalone-background.md +49 -0
- package/docs/tool-reference.md +20 -10
- package/docs/watchdog.md +35 -4
- package/docs/workflows.md +40 -19
- package/inspector-runner.mjs +2 -2
- package/package.json +2 -1
- package/prompts/parallel-review.md +1 -1
- package/{runner-server-preload.mjs → runner-peer-preload.mjs} +8 -3
- package/skills/pi-subagents/SKILL.md +14 -0
- package/skills/pi-subagents/references/execution-controls.md +20 -5
- package/skills/pi-subagents/references/management-authoring-rpc.md +2 -1
- package/skills/pi-subagents/references/prompting-and-roles.md +2 -2
- package/src/agents/advertised-agent-prompt.ts +94 -0
- package/src/agents/agent-management.ts +14 -1
- package/src/agents/agent-serializer.ts +2 -0
- package/src/agents/agents.ts +14 -0
- package/src/agents/builtin-names.ts +1 -0
- package/src/api/delegation.ts +4 -0
- package/src/api/preflight.ts +76 -45
- package/src/api/shared-types.ts +3 -1
- package/src/api/workflow-resources.ts +6 -0
- package/src/extension/fanout-child.ts +63 -4
- package/src/extension/index.ts +58 -8
- package/src/extension/public-execution.ts +4 -3
- package/src/extension/rpc.ts +8 -21
- package/src/extension/schemas.ts +71 -80
- package/src/extension/tool-description.ts +29 -81
- 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 +104 -67
- package/src/runs/background/active-async-capacity.ts +22 -18
- package/src/runs/background/async-execution.ts +45 -56
- package/src/runs/background/async-job-tracker.ts +35 -3
- package/src/runs/background/async-resume.ts +5 -9
- package/src/runs/background/async-status-snapshot.ts +10 -12
- package/src/runs/background/async-status.ts +17 -9
- package/src/runs/background/auto-drain.ts +44 -30
- package/src/runs/background/binary-bootstrap.ts +33 -0
- package/src/runs/background/chain-root-attachment.ts +8 -0
- package/src/runs/background/control-channel.ts +78 -44
- package/src/runs/background/fleet-view.ts +30 -2
- package/src/runs/background/notify.ts +117 -13
- package/src/runs/background/owned-process-tree.ts +35 -8
- package/src/runs/background/process-terminal.ts +23 -23
- package/src/runs/background/run-child-session.ts +121 -36
- package/src/runs/background/run-status.ts +78 -5
- package/src/runs/background/runner-aliases.ts +28 -9
- package/src/runs/background/runner-child-launch.ts +88 -0
- package/src/runs/background/runner-child-sessions.ts +5 -4
- package/src/runs/background/scheduled-runs.ts +40 -13
- package/src/runs/background/stale-run-reconciler.ts +3 -1
- package/src/runs/background/steering.ts +20 -2
- package/src/runs/background/subagent-runner.ts +458 -239
- package/src/runs/background/subagent-wait.ts +54 -8
- package/src/runs/background/wait-completions.ts +4 -0
- package/src/runs/background/wait-tool.ts +1 -1
- package/src/runs/foreground/async-steering-action.ts +37 -7
- package/src/runs/foreground/execution.ts +145 -56
- package/src/runs/foreground/prompt-audit.ts +3 -1
- package/src/runs/foreground/subagent-executor.ts +584 -297
- package/src/runs/foreground/workflow-detach-reconcile.ts +10 -5
- package/src/runs/foreground/workflow-foreground-steering.ts +57 -2
- package/src/runs/shared/acceptance.ts +7 -4
- package/src/runs/shared/agent-contract.ts +1 -1
- package/src/runs/shared/async-status-projection.ts +51 -47
- package/src/runs/shared/capability-ceiling.ts +2 -0
- package/src/runs/shared/child-hooks.ts +167 -3
- package/src/runs/shared/child-launch.ts +28 -13
- package/src/runs/shared/child-lifecycle.ts +6 -3
- package/src/runs/shared/child-runtime-config.ts +3 -1
- package/src/runs/shared/child-session.ts +75 -8
- package/src/runs/shared/child-tool-plan.ts +124 -5
- package/src/runs/shared/completion-evidence.ts +2 -2
- package/src/runs/shared/completion-guard.ts +6 -3
- package/src/runs/shared/effective-system-prompt.ts +33 -0
- package/src/runs/shared/external-cli-runner.ts +9 -7
- package/src/runs/shared/host-step-status.ts +11 -11
- package/src/runs/shared/llm-intent-arbiter.ts +21 -11
- package/src/runs/shared/model-fallback.ts +12 -6
- package/src/runs/shared/nested-events.ts +5 -5
- package/src/runs/shared/orca-progress-tabs.ts +7 -1
- package/src/runs/shared/parallel-handoff.ts +57 -12
- package/src/runs/shared/parallel-utils.ts +2 -2
- package/src/runs/shared/pi-spawn.ts +10 -0
- package/src/runs/shared/readonly-drain-observation.ts +42 -0
- package/src/runs/shared/readonly-model-continuation.ts +69 -0
- package/src/runs/shared/readonly-session-evidence.ts +307 -0
- package/src/runs/shared/run-fanout-budget.ts +8 -8
- package/src/runs/shared/runtime-acknowledged-extensions.ts +3 -3
- package/src/runs/shared/subagent-prompt-runtime.ts +20 -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 +190 -0
- package/src/runs/shared/worktree.ts +366 -208
- package/src/shared/fork-context.ts +15 -72
- package/src/shared/launch-contract.ts +65 -2
- package/src/shared/opencode-session-headers.ts +30 -0
- package/src/shared/types.ts +85 -61
- package/src/shared/utils.ts +7 -2
- package/src/shared/workflow-child-permit.ts +18 -13
- package/src/slash/delegation-adapters.ts +3 -1
- package/src/slash/delegation-request.ts +14 -0
- package/src/slash/slash-commands.ts +2 -1
- package/src/slash/subagents-admin.ts +11 -4
- package/src/tui/fleet-status.ts +164 -19
- package/src/tui/fleet.ts +27 -19
- package/src/tui/render.ts +172 -33
- package/src/watchdog/child-status.ts +8 -0
- package/src/watchdog/model-selection.ts +20 -0
- package/src/watchdog/permission-arbiter.ts +3 -1
- package/src/watchdog/register-child.ts +1 -0
- package/src/watchdog/register-main.ts +31 -27
- package/src/watchdog/review.ts +132 -67
- package/src/watchdog/runtime.ts +82 -20
- package/src/watchdog/scope.ts +1 -1
- package/src/watchdog/settings.ts +9 -3
- package/src/watchdog/tool-actions.ts +13 -12
- package/src/watchdog/turn-delta.ts +23 -0
- package/src/watchdog/types.ts +4 -0
- package/src/workflows/chat-progress.ts +3 -3
- package/src/workflows/scripted-workflow.ts +275 -17
- package/src/workflows/workflow-checklist.ts +13 -17
- package/src/workflows/workflow-child-summary.ts +57 -8
- package/src/workflows/workflow-preflight.ts +19 -19
- package/src/workflows/workflow-receipt.ts +3 -3
- package/src/workflows/workflow-resources.ts +96 -21
- package/src/workflows/workflow-settlement.ts +3 -0
- /package/src/inspectors/{herdr/shell-command.ts → shell-command.ts} +0 -0
|
@@ -94,7 +94,7 @@ function workflowResultChildren(status: AsyncStatus, childRunId: string, result:
|
|
|
94
94
|
}));
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
-
function reconcileWorkflowReceipt(status: AsyncStatus, childRunId: string, result: SingleResult, asyncDir: string, resolution: WorkflowTerminalResolution | undefined): WorkflowReceipt | undefined {
|
|
97
|
+
function reconcileWorkflowReceipt(status: AsyncStatus, childRunId: string, result: SingleResult, asyncDir: string, resolution: WorkflowTerminalResolution | undefined): { receipt: WorkflowReceipt; path: string } | undefined {
|
|
98
98
|
const receiptRoot = path.dirname(asyncDir);
|
|
99
99
|
const receiptPath = workflowReceiptPath(receiptRoot, status.runId);
|
|
100
100
|
if (!fs.existsSync(receiptPath)) return undefined;
|
|
@@ -155,8 +155,7 @@ function reconcileWorkflowReceipt(status: AsyncStatus, childRunId: string, resul
|
|
|
155
155
|
delete next.workflowResolution;
|
|
156
156
|
delete next.recovery;
|
|
157
157
|
}
|
|
158
|
-
writeWorkflowReceipt(asyncDir, next);
|
|
159
|
-
return next;
|
|
158
|
+
return { receipt: next, path: writeWorkflowReceipt(asyncDir, next) };
|
|
160
159
|
}
|
|
161
160
|
|
|
162
161
|
function appendDetachedWorkflowEvent(asyncDir: string, event: Record<string, unknown>): void {
|
|
@@ -209,10 +208,13 @@ export function reconcileDetachedWorkflowChildCompletion(input: {
|
|
|
209
208
|
if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
|
|
210
209
|
}
|
|
211
210
|
let receipt: WorkflowReceipt | undefined;
|
|
211
|
+
let receiptPath: string | undefined;
|
|
212
212
|
let receiptError: string | undefined;
|
|
213
213
|
const resolution = classifyWorkflowSettlement(next, input.result.interrupted);
|
|
214
214
|
try {
|
|
215
|
-
|
|
215
|
+
const publication = reconcileWorkflowReceipt(next, input.childRunId, input.result, asyncDir, resolution);
|
|
216
|
+
receipt = publication?.receipt;
|
|
217
|
+
receiptPath = publication?.path;
|
|
216
218
|
} catch (error) {
|
|
217
219
|
receiptError = `Failed to reconcile async workflow receipt: ${error instanceof Error ? error.message : String(error)}`;
|
|
218
220
|
}
|
|
@@ -241,9 +243,10 @@ export function reconcileDetachedWorkflowChildCompletion(input: {
|
|
|
241
243
|
cwd: next.cwd,
|
|
242
244
|
sessionId: next.sessionId ?? (typeof existing?.sessionId === "string" ? existing.sessionId : undefined),
|
|
243
245
|
completionOwnerId: next.completionOwnerId,
|
|
246
|
+
...(next.scheduleOrigin ? { scheduleOrigin: next.scheduleOrigin } : {}),
|
|
244
247
|
},
|
|
245
248
|
receipt,
|
|
246
|
-
receiptPath
|
|
249
|
+
receiptPath,
|
|
247
250
|
receiptPersistenceError: receiptError,
|
|
248
251
|
resolution,
|
|
249
252
|
terminalOutcome: receipt?.terminalOutcome,
|
|
@@ -283,6 +286,7 @@ export function reconcileDetachedWorkflowChildCompletion(input: {
|
|
|
283
286
|
agent: "workflow",
|
|
284
287
|
success: plan.status.state === "complete",
|
|
285
288
|
state: plan.status.state,
|
|
289
|
+
...(plan.publicResult.workflowReceipt ? { workflowReceipt: plan.publicResult.workflowReceipt } : {}),
|
|
286
290
|
...(resolution ? { workflowResolution: resolution } : {}),
|
|
287
291
|
...(plan.receipt?.terminalOutcome ? { terminalOutcome: plan.receipt.terminalOutcome } : {}),
|
|
288
292
|
recovery: plan.recovery,
|
|
@@ -291,6 +295,7 @@ export function reconcileDetachedWorkflowChildCompletion(input: {
|
|
|
291
295
|
results,
|
|
292
296
|
sessionId: plan.status.sessionId,
|
|
293
297
|
completionOwnerId: plan.status.completionOwnerId,
|
|
298
|
+
...(plan.status.scheduleOrigin ? { scheduleOrigin: plan.status.scheduleOrigin } : {}),
|
|
294
299
|
timestamp: Date.now(),
|
|
295
300
|
triggerTurn: true,
|
|
296
301
|
});
|
|
@@ -3,8 +3,10 @@ import * as path from "node:path";
|
|
|
3
3
|
import type { AgentToolResult } from "@earendil-works/pi-agent-core";
|
|
4
4
|
import type { Details, ForegroundRunControl, SubagentState } from "../../shared/types.ts";
|
|
5
5
|
import { readStatus } from "../../shared/utils.ts";
|
|
6
|
-
import { steeringReceipt } from "../background/steering.ts";
|
|
7
|
-
import type
|
|
6
|
+
import { steeringReceipt, waitForSteeringAction } from "../background/steering.ts";
|
|
7
|
+
import { requestAsyncSteer, type SteerDeliveryMode } from "../background/control-channel.ts";
|
|
8
|
+
import { currentCompletionOwnerId } from "../../shared/completion-owner.ts";
|
|
9
|
+
import { workflowAsyncChildSteeringGuidance } from "../shared/workflow-async-child-guidance.ts";
|
|
8
10
|
|
|
9
11
|
export interface WorkflowForegroundSteeringTarget {
|
|
10
12
|
control: ForegroundRunControl;
|
|
@@ -66,6 +68,58 @@ function managementError(message: string): AgentToolResult<Details> {
|
|
|
66
68
|
return { content: [{ type: "text", text: message }], isError: true, details: { mode: "management", results: [] } };
|
|
67
69
|
}
|
|
68
70
|
|
|
71
|
+
/** Local controllers remain authoritative; recorded foreign ownership permits enqueue, not takeover or proof of liveness. */
|
|
72
|
+
export async function steerWorkflowRun(input: {
|
|
73
|
+
state: SubagentState;
|
|
74
|
+
runId: string;
|
|
75
|
+
asyncDir: string;
|
|
76
|
+
message: string;
|
|
77
|
+
mode?: SteerDeliveryMode;
|
|
78
|
+
index?: number;
|
|
79
|
+
signal?: AbortSignal;
|
|
80
|
+
ackTimeoutMs?: number;
|
|
81
|
+
}): Promise<AgentToolResult<Details>> {
|
|
82
|
+
const { state, runId, asyncDir } = input;
|
|
83
|
+
const status = readStatus(asyncDir);
|
|
84
|
+
if (!status || status.mode !== "workflow" || status.runId !== runId || path.basename(asyncDir) !== runId) {
|
|
85
|
+
return managementError(`Workflow '${runId}' does not match its resolved run directory.`);
|
|
86
|
+
}
|
|
87
|
+
if (state.workflowControllers?.has(runId)) {
|
|
88
|
+
const route = resolveWorkflowForegroundSteeringTarget({ state, workflowRunId: runId, asyncDirRoot: path.dirname(asyncDir) });
|
|
89
|
+
if (!route.ok) return managementError([route.message, ...workflowAsyncChildSteeringGuidance(status, state)].join("\n"));
|
|
90
|
+
return steerWorkflowForegroundTarget({ target: route.target, message: input.message, mode: input.mode, index: input.index });
|
|
91
|
+
}
|
|
92
|
+
if (!state.currentSessionId) return managementError("Workflow steering requires an active parent session.");
|
|
93
|
+
if (status.sessionId !== state.currentSessionId) return managementError(`Workflow '${runId}' was not found in the active session.`);
|
|
94
|
+
if (status.state !== "running" && status.state !== "queued") return managementError(`Workflow '${runId}' is not running or queued and cannot be steered.`);
|
|
95
|
+
if (typeof status.completionOwnerId !== "string" || !status.completionOwnerId.trim()
|
|
96
|
+
|| status.completionOwnerId === (state.completionOwnerId ?? currentCompletionOwnerId())
|
|
97
|
+
|| [...state.foregroundControls.values()].some((control) => control.parentWorkflowRunId === runId)) {
|
|
98
|
+
return managementError(`Workflow '${runId}' has no live foreground child and is not a recorded foreign workflow.`);
|
|
99
|
+
}
|
|
100
|
+
if (input.index !== undefined) {
|
|
101
|
+
const step = status.steps?.[input.index];
|
|
102
|
+
if (!Number.isInteger(input.index) || input.index < 0 || !step) return managementError(`Workflow '${runId}' index ${input.index} is out of range or invalid.`);
|
|
103
|
+
if (step.status !== "running" && step.status !== "pending") return managementError(`Workflow '${runId}' child ${input.index} is ${step.status} and cannot be steered.`);
|
|
104
|
+
if (typeof step.workflowKey !== "string" || !step.workflowKey.trim()) return managementError(`Workflow '${runId}' child ${input.index} has no projected workflow key.`);
|
|
105
|
+
}
|
|
106
|
+
const requestId = randomUUID();
|
|
107
|
+
try {
|
|
108
|
+
requestAsyncSteer(asyncDir, { id: requestId, message: input.message, mode: input.mode, ...(input.index !== undefined ? { targetIndex: input.index } : {}), source: "steer-action" });
|
|
109
|
+
} catch (error) {
|
|
110
|
+
return managementError(`Failed to queue steering for workflow ${runId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
111
|
+
}
|
|
112
|
+
// Only the owner writes the ledger. No acknowledgment leaves an honest, unaddressed queued receipt.
|
|
113
|
+
const steering = await waitForSteeringAction({ asyncDir, sourceRunId: runId, requestId, timeoutMs: input.ackTimeoutMs ?? 3_000, signal: input.signal })
|
|
114
|
+
?? { requestId, state: "pending" as const, deliveryStatus: "queued" as const, sourceRunId: runId, targets: input.index === undefined ? [] : [{ index: input.index, state: "pending" as const }] };
|
|
115
|
+
const failed = steering.state === "failed" || steering.state === "partial";
|
|
116
|
+
return {
|
|
117
|
+
content: [{ type: "text", text: steeringReceipt(input.message, `Steering ${failed ? steering.state : steering.deliveryStatus === "delivered" ? "delivered" : "queued"} for workflow ${runId} (request ${requestId}).`) }],
|
|
118
|
+
...(failed ? { isError: true } : {}),
|
|
119
|
+
details: { mode: "management", results: [], steering },
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
69
123
|
/**
|
|
70
124
|
* Steer a live workflow-owned foreground child through its in-process session.
|
|
71
125
|
* `steer` (and `auto`) interrupt the child at its next safe point; `follow_up`
|
|
@@ -91,6 +145,7 @@ export async function steerWorkflowForegroundTarget(input: {
|
|
|
91
145
|
|
|
92
146
|
const message = input.message.trim();
|
|
93
147
|
const requestId = randomUUID();
|
|
148
|
+
|
|
94
149
|
const outcome = await child.steer({ message, ...(input.mode && input.mode !== "steer" ? { mode: input.mode } : {}) });
|
|
95
150
|
const target = outcome.state === "delivered"
|
|
96
151
|
? { index, state: "delivered" as const, deliveredAt: Date.now() }
|
|
@@ -24,7 +24,7 @@ import type {
|
|
|
24
24
|
ChildWatchdogProgress,
|
|
25
25
|
} from "../../shared/types.ts";
|
|
26
26
|
import { unresolvedChildWatchdogBlockers } from "../../watchdog/child-status.ts";
|
|
27
|
-
import {
|
|
27
|
+
import { isAgentContract } from "./agent-contract.ts";
|
|
28
28
|
import { classifyTaskMutationIntent, stripSeverityCompounds, taskMayMutate } from "./task-intent.ts";
|
|
29
29
|
|
|
30
30
|
const LEVEL_RANK: Record<Exclude<AcceptanceLevel, "auto">, number> = {
|
|
@@ -105,11 +105,14 @@ function inferLevel(input: {
|
|
|
105
105
|
const inferredReadOnly = readOnlyTask || ((readOnlyAgent || input.acceptanceRole === "read-only") && !taskMayWrite);
|
|
106
106
|
const roleResolvesReadOnly = input.acceptanceRole !== undefined && inferredReadOnly;
|
|
107
107
|
const dynamicResolvesReadOnly = inferredReadOnly && !writeTask;
|
|
108
|
-
const
|
|
108
|
+
const riskyKeywordPattern = /\b(?:release|migration|migrate|security|data[- ]loss|destructive|post-review|fix pass)\b/;
|
|
109
|
+
const keywordRiskReadOnly = input.acceptanceRole === undefined
|
|
110
|
+
? intent.kind === "read-only" && !riskyKeywordPattern.test(task)
|
|
111
|
+
: inferredReadOnly;
|
|
109
112
|
const risky = Boolean(input.async && writeTask)
|
|
110
113
|
|| (Boolean(input.dynamic) && !roleResolvesReadOnly && !dynamicResolvesReadOnly)
|
|
111
114
|
|| (Boolean(input.dynamicGroup) && !roleResolvesReadOnly && !dynamicResolvesReadOnly)
|
|
112
|
-
|| (!keywordRiskReadOnly &&
|
|
115
|
+
|| (!keywordRiskReadOnly && riskyKeywordPattern.test(task));
|
|
113
116
|
|
|
114
117
|
if (risky) {
|
|
115
118
|
reasons.push(input.async ? "async write-capable or risky run" : "risky write-capable run");
|
|
@@ -435,7 +438,7 @@ export function resolveEffectiveAcceptance(input: {
|
|
|
435
438
|
}): ResolvedAcceptanceConfig {
|
|
436
439
|
const explicit = normalizeAcceptanceInput(input.explicit);
|
|
437
440
|
const explicitLevel = normalizeLevel(explicit.level);
|
|
438
|
-
if (
|
|
441
|
+
if (isAgentContract(input.agentContract)) {
|
|
439
442
|
const level = explicitAcceptanceCanDisable(explicit) || explicitLevel === "auto"
|
|
440
443
|
? "none"
|
|
441
444
|
: explicitLevel;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { AgentContract, EffectsProjection, ExecutionProjection, ReviewProjection, SingleResult } from "../../shared/types.ts";
|
|
2
2
|
|
|
3
|
-
export function
|
|
3
|
+
export function isAgentContract(contract: AgentContract | undefined): boolean {
|
|
4
4
|
return contract?.version === 1;
|
|
5
5
|
}
|
|
6
6
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { sanitizeDisplayText, truncateDisplayText } from "../../shared/display-text.ts";
|
|
2
2
|
import { formatModelThinking } from "../../shared/formatters.ts";
|
|
3
|
-
import type { AsyncJobState, AsyncJobStep,
|
|
3
|
+
import type { AsyncJobState, AsyncJobStep, HostStepFreshness, HostStepMonitorKind, HostStepNode, HostStepState, HostStepVerdict, NestedRunSummary, NestedStepSummary, SubagentRunMode, WorkflowGraphSnapshot, WorkflowPreflightLane, WorkflowPreflight } from "../../shared/types.ts";
|
|
4
4
|
import { HOST_STEP_MAX_COUNT, HOST_STEP_MAX_DETAIL_CHARS, HOST_STEP_MAX_LABEL_CHARS, HOST_STEP_MAX_PROVIDER_CHARS, HOST_STEP_MAX_REASON_CHARS, HOST_STEP_MAX_REF_CHARS, HOST_STEP_MAX_ROLE_CHARS, HOST_STEP_MAX_TARGET_CHARS, hostStepReportName, parseHostStepNode, validHostStepNodes } from "./host-step-status.ts";
|
|
5
5
|
import { workflowPreflightLaneForRuntimeKey } from "../../workflows/workflow-preflight.ts";
|
|
6
6
|
import { workflowGraphStageNodes } from "./workflow-graph.ts";
|
|
@@ -32,7 +32,7 @@ function isAsyncStatusSnapshotState(value: string): value is AsyncStatusSnapshot
|
|
|
32
32
|
return Object.hasOwn(ASYNC_STATUS_SNAPSHOT_STATES, value);
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
export interface
|
|
35
|
+
export interface AsyncStatusSnapshotActivity {
|
|
36
36
|
state?: string;
|
|
37
37
|
currentTool?: string;
|
|
38
38
|
lastActivityAt?: number;
|
|
@@ -41,7 +41,7 @@ export interface AsyncStatusSnapshotActivityV1 {
|
|
|
41
41
|
toolCount?: number;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
export interface
|
|
44
|
+
export interface AsyncStatusSnapshotHostStep {
|
|
45
45
|
kind: HostStepMonitorKind;
|
|
46
46
|
provider?: string;
|
|
47
47
|
role?: string;
|
|
@@ -54,7 +54,7 @@ export interface AsyncStatusSnapshotHostStepV1 {
|
|
|
54
54
|
report?: string;
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
export interface
|
|
57
|
+
export interface AsyncStatusSnapshotNode {
|
|
58
58
|
id: string;
|
|
59
59
|
kind: AsyncStatusSnapshotKind | "host-step";
|
|
60
60
|
label: string;
|
|
@@ -62,12 +62,12 @@ export interface AsyncStatusSnapshotNodeV1 {
|
|
|
62
62
|
startedAt?: number;
|
|
63
63
|
updatedAt?: number;
|
|
64
64
|
endedAt?: number;
|
|
65
|
-
activity?:
|
|
66
|
-
hostStep?:
|
|
67
|
-
children?:
|
|
65
|
+
activity?: AsyncStatusSnapshotActivity;
|
|
66
|
+
hostStep?: AsyncStatusSnapshotHostStep;
|
|
67
|
+
children?: AsyncStatusSnapshotNode[];
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
export interface
|
|
70
|
+
export interface AsyncStatusSnapshotCaps {
|
|
71
71
|
maxRuns: number;
|
|
72
72
|
maxChildrenPerNode: number;
|
|
73
73
|
maxDepth: number;
|
|
@@ -75,19 +75,19 @@ export interface AsyncStatusSnapshotCapsV1 {
|
|
|
75
75
|
maxSerializedBytes: number;
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
export interface
|
|
78
|
+
export interface AsyncStatusSnapshotOmitted {
|
|
79
79
|
runs: number;
|
|
80
80
|
children: number;
|
|
81
81
|
byteLimitExceeded: boolean;
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
export interface
|
|
84
|
+
export interface AsyncStatusSnapshot {
|
|
85
85
|
kind: typeof ASYNC_STATUS_SNAPSHOT_KIND;
|
|
86
86
|
version: typeof ASYNC_STATUS_SNAPSHOT_VERSION;
|
|
87
87
|
generatedAt: number;
|
|
88
|
-
caps:
|
|
89
|
-
omitted:
|
|
90
|
-
runs:
|
|
88
|
+
caps: AsyncStatusSnapshotCaps;
|
|
89
|
+
omitted: AsyncStatusSnapshotOmitted;
|
|
90
|
+
runs: AsyncStatusSnapshotNode[];
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
export interface AsyncStatusSnapshotOptions {
|
|
@@ -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;
|
|
@@ -117,19 +119,19 @@ export interface AsyncStatusWorkflowRow {
|
|
|
117
119
|
reasonCode?: string;
|
|
118
120
|
detail?: string;
|
|
119
121
|
target?: string;
|
|
120
|
-
freshness?:
|
|
122
|
+
freshness?: HostStepFreshness;
|
|
121
123
|
reportPath?: string;
|
|
122
|
-
preflight?:
|
|
124
|
+
preflight?: WorkflowPreflightLane;
|
|
123
125
|
}
|
|
124
126
|
|
|
125
127
|
interface ProjectionContext {
|
|
126
|
-
caps:
|
|
127
|
-
omitted:
|
|
128
|
+
caps: AsyncStatusSnapshotCaps;
|
|
129
|
+
omitted: AsyncStatusSnapshotOmitted;
|
|
128
130
|
}
|
|
129
131
|
|
|
130
|
-
function validHostStepList(source: readonly
|
|
132
|
+
function validHostStepList(source: readonly HostStepNode[] | WorkflowGraphSnapshot | undefined): HostStepNode[] {
|
|
131
133
|
if (source && "nodes" in source) return validHostStepNodes(source);
|
|
132
|
-
const hostSteps:
|
|
134
|
+
const hostSteps: HostStepNode[] = [];
|
|
133
135
|
for (const [index, value] of (source ?? []).slice(0, HOST_STEP_MAX_COUNT).entries()) {
|
|
134
136
|
try {
|
|
135
137
|
hostSteps.push(parseHostStepNode(value, `hostSteps[${index}]`));
|
|
@@ -140,7 +142,7 @@ function validHostStepList(source: readonly HostStepNodeV1[] | WorkflowGraphSnap
|
|
|
140
142
|
return hostSteps;
|
|
141
143
|
}
|
|
142
144
|
|
|
143
|
-
function resolveCaps(options: AsyncStatusSnapshotOptions):
|
|
145
|
+
function resolveCaps(options: AsyncStatusSnapshotOptions): AsyncStatusSnapshotCaps {
|
|
144
146
|
return {
|
|
145
147
|
maxRuns: Math.max(0, Math.floor(options.maxRuns ?? DEFAULT_MAX_RUNS)),
|
|
146
148
|
maxChildrenPerNode: Math.max(0, Math.floor(options.maxChildrenPerNode ?? DEFAULT_MAX_CHILDREN_PER_NODE)),
|
|
@@ -199,13 +201,13 @@ function activityFor(source: {
|
|
|
199
201
|
currentToolStartedAt?: unknown;
|
|
200
202
|
turnCount?: unknown;
|
|
201
203
|
toolCount?: unknown;
|
|
202
|
-
}, ctx: ProjectionContext):
|
|
204
|
+
}, ctx: ProjectionContext): AsyncStatusSnapshotActivity | undefined {
|
|
203
205
|
const currentTool = publicOptionalText(source.currentTool, ctx.caps.maxStringLength);
|
|
204
206
|
const lastActivityAt = publicTime(source.lastActivityAt);
|
|
205
207
|
const currentToolStartedAt = publicTime(source.currentToolStartedAt);
|
|
206
208
|
const turnCount = publicCount(source.turnCount);
|
|
207
209
|
const toolCount = publicCount(source.toolCount);
|
|
208
|
-
const activity:
|
|
210
|
+
const activity: AsyncStatusSnapshotActivity = {
|
|
209
211
|
...(typeof source.activityState === "string" ? { state: publicText(source.activityState, "unknown", ctx.caps.maxStringLength) } : {}),
|
|
210
212
|
...(currentTool ? { currentTool } : {}),
|
|
211
213
|
...(lastActivityAt !== undefined ? { lastActivityAt } : {}),
|
|
@@ -216,19 +218,19 @@ function activityFor(source: {
|
|
|
216
218
|
return Object.keys(activity).length ? activity : undefined;
|
|
217
219
|
}
|
|
218
220
|
|
|
219
|
-
function appendBoundedChildren(children:
|
|
221
|
+
function appendBoundedChildren(children: AsyncStatusSnapshotNode[], source: readonly AsyncStatusSnapshotNode[], ctx: ProjectionContext): void {
|
|
220
222
|
const remaining = Math.max(0, ctx.caps.maxChildrenPerNode - children.length);
|
|
221
223
|
children.push(...source.slice(0, remaining));
|
|
222
224
|
ctx.omitted.children += Math.max(0, source.length - remaining);
|
|
223
225
|
}
|
|
224
226
|
|
|
225
|
-
function projectStep(step: AsyncJobStep | NestedStepSummary, index: number, depth: number, ctx: ProjectionContext):
|
|
227
|
+
function projectStep(step: AsyncJobStep | NestedStepSummary, index: number, depth: number, ctx: ProjectionContext): AsyncStatusSnapshotNode {
|
|
226
228
|
const state = normalizeState(step.status);
|
|
227
229
|
const startedAt = publicTime(step.startedAt);
|
|
228
230
|
const endedAt = publicTime(step.endedAt);
|
|
229
231
|
const updatedAt = endedAt ?? publicTime(step.lastActivityAt) ?? startedAt;
|
|
230
232
|
const activity = activityFor(step, ctx);
|
|
231
|
-
const node:
|
|
233
|
+
const node: AsyncStatusSnapshotNode = {
|
|
232
234
|
id: publicText("workflowKey" in step && step.workflowKey ? step.workflowKey : "runId" in step && step.runId ? step.runId : `step:${index}`, `step:${index}`, ctx.caps.maxStringLength),
|
|
233
235
|
kind: "step",
|
|
234
236
|
label: publicText("label" in step && step.label ? step.label : step.agent, "step", ctx.caps.maxStringLength),
|
|
@@ -240,7 +242,7 @@ function projectStep(step: AsyncJobStep | NestedStepSummary, index: number, dept
|
|
|
240
242
|
};
|
|
241
243
|
if (depth < ctx.caps.maxDepth && step.children?.length) {
|
|
242
244
|
const nested = step.children.map((child, childIndex) => projectNestedRun(child, childIndex, depth + 1, ctx));
|
|
243
|
-
const bounded:
|
|
245
|
+
const bounded: AsyncStatusSnapshotNode[] = [];
|
|
244
246
|
appendBoundedChildren(bounded, nested, ctx);
|
|
245
247
|
if (bounded.length) node.children = bounded;
|
|
246
248
|
} else if (step.children?.length) {
|
|
@@ -249,7 +251,7 @@ function projectStep(step: AsyncJobStep | NestedStepSummary, index: number, dept
|
|
|
249
251
|
return node;
|
|
250
252
|
}
|
|
251
253
|
|
|
252
|
-
function projectWorkflowGraphNode(node: WorkflowGraphSnapshot["nodes"][number], index: number, depth: number, ctx: ProjectionContext):
|
|
254
|
+
function projectWorkflowGraphNode(node: WorkflowGraphSnapshot["nodes"][number], index: number, depth: number, ctx: ProjectionContext): AsyncStatusSnapshotNode {
|
|
253
255
|
const step: AsyncJobStep = {
|
|
254
256
|
agent: node.agent ?? node.label,
|
|
255
257
|
status: workflowGraphStepStatus(node.status),
|
|
@@ -259,13 +261,13 @@ function projectWorkflowGraphNode(node: WorkflowGraphSnapshot["nodes"][number],
|
|
|
259
261
|
return projectStep(step, node.flatIndex ?? index, depth, ctx);
|
|
260
262
|
}
|
|
261
263
|
|
|
262
|
-
function projectNestedRun(child: NestedRunSummary, index: number, depth: number, ctx: ProjectionContext):
|
|
264
|
+
function projectNestedRun(child: NestedRunSummary, index: number, depth: number, ctx: ProjectionContext): AsyncStatusSnapshotNode {
|
|
263
265
|
const state = normalizeState(child.state);
|
|
264
266
|
const startedAt = publicTime(child.startedAt);
|
|
265
267
|
const endedAt = publicTime(child.endedAt);
|
|
266
268
|
const updatedAt = publicTime(child.lastUpdate) ?? endedAt ?? publicTime(child.lastActivityAt) ?? startedAt;
|
|
267
269
|
const activity = activityFor(child, ctx);
|
|
268
|
-
const node:
|
|
270
|
+
const node: AsyncStatusSnapshotNode = {
|
|
269
271
|
id: publicText(child.id, `nested:${index}`, ctx.caps.maxStringLength),
|
|
270
272
|
kind: kindForMode(child.mode),
|
|
271
273
|
label: child.agent ? publicText(child.agent, "subagent", ctx.caps.maxStringLength) : labelForAgents(child.agents, child.mode ?? "subagent", ctx.caps.maxStringLength),
|
|
@@ -278,7 +280,7 @@ function projectNestedRun(child: NestedRunSummary, index: number, depth: number,
|
|
|
278
280
|
if (depth < ctx.caps.maxDepth) {
|
|
279
281
|
const nestedSteps = child.steps?.map((step, stepIndex) => projectStep(step, stepIndex, depth + 1, ctx)) ?? [];
|
|
280
282
|
const nestedChildren = child.children?.map((nested, childIndex) => projectNestedRun(nested, childIndex, depth + 1, ctx)) ?? [];
|
|
281
|
-
const bounded:
|
|
283
|
+
const bounded: AsyncStatusSnapshotNode[] = [];
|
|
282
284
|
appendBoundedChildren(bounded, [...nestedSteps, ...nestedChildren], ctx);
|
|
283
285
|
if (bounded.length) node.children = bounded;
|
|
284
286
|
} else {
|
|
@@ -295,11 +297,11 @@ function hostStepSnapshotState(state: HostStepState, verdict: HostStepVerdict |
|
|
|
295
297
|
return verdict === undefined || verdict === "inconclusive" ? "partial" : "complete";
|
|
296
298
|
}
|
|
297
299
|
|
|
298
|
-
function projectHostStep(hostStep:
|
|
300
|
+
function projectHostStep(hostStep: HostStepNode, ctx: ProjectionContext): AsyncStatusSnapshotNode {
|
|
299
301
|
const state = hostStepSnapshotState(hostStep.state, hostStep.verdict);
|
|
300
302
|
const detail = publicOptionalText(hostStep.detail, ctx.caps.maxStringLength);
|
|
301
303
|
const report = publicOptionalText(hostStepReportName(hostStep.reportPath), ctx.caps.maxStringLength);
|
|
302
|
-
const hostMetadata:
|
|
304
|
+
const hostMetadata: AsyncStatusSnapshotHostStep = {
|
|
303
305
|
kind: hostStep.monitorKind,
|
|
304
306
|
state: hostStep.state,
|
|
305
307
|
...(hostStep.provider ? { provider: publicText(hostStep.provider, "provider", ctx.caps.maxStringLength) } : {}),
|
|
@@ -322,12 +324,12 @@ function projectHostStep(hostStep: HostStepNodeV1, ctx: ProjectionContext): Asyn
|
|
|
322
324
|
};
|
|
323
325
|
}
|
|
324
326
|
|
|
325
|
-
function projectRun(job: AsyncJobState, ctx: ProjectionContext):
|
|
327
|
+
function projectRun(job: AsyncJobState, ctx: ProjectionContext): AsyncStatusSnapshotNode {
|
|
326
328
|
const state = normalizeState(job.status);
|
|
327
329
|
const startedAt = publicTime(job.startedAt);
|
|
328
330
|
const updatedAt = publicTime(job.updatedAt) ?? startedAt;
|
|
329
331
|
const activity = activityFor(job, ctx);
|
|
330
|
-
const node:
|
|
332
|
+
const node: AsyncStatusSnapshotNode = {
|
|
331
333
|
id: publicText(job.asyncId, "async", ctx.caps.maxStringLength),
|
|
332
334
|
kind: kindForMode(job.mode),
|
|
333
335
|
label: labelForAgents(job.agents, job.mode ?? "subagent", ctx.caps.maxStringLength),
|
|
@@ -349,7 +351,7 @@ function projectRun(job: AsyncJobState, ctx: ProjectionContext): AsyncStatusSnap
|
|
|
349
351
|
const ordinaryChildren = [...stepChildren, ...graphChildren, ...nestedChildren];
|
|
350
352
|
const retainedHostSteps = hostStepChildren.slice(0, ctx.caps.maxChildrenPerNode);
|
|
351
353
|
const retainedOrdinaryChildren = ordinaryChildren.slice(0, ctx.caps.maxChildrenPerNode - retainedHostSteps.length);
|
|
352
|
-
const bounded:
|
|
354
|
+
const bounded: AsyncStatusSnapshotNode[] = [];
|
|
353
355
|
bounded.push(...retainedOrdinaryChildren, ...retainedHostSteps);
|
|
354
356
|
ctx.omitted.children += ordinaryChildren.length - retainedOrdinaryChildren.length + hostStepChildren.length - retainedHostSteps.length;
|
|
355
357
|
if (bounded.length) node.children = bounded;
|
|
@@ -362,11 +364,11 @@ function projectRun(job: AsyncJobState, ctx: ProjectionContext): AsyncStatusSnap
|
|
|
362
364
|
return node;
|
|
363
365
|
}
|
|
364
366
|
|
|
365
|
-
function snapshotBytes(snapshot:
|
|
367
|
+
function snapshotBytes(snapshot: AsyncStatusSnapshot): number {
|
|
366
368
|
return Buffer.byteLength(JSON.stringify(snapshot), "utf8");
|
|
367
369
|
}
|
|
368
370
|
|
|
369
|
-
function enforceByteLimit(snapshot:
|
|
371
|
+
function enforceByteLimit(snapshot: AsyncStatusSnapshot): void {
|
|
370
372
|
if (snapshotBytes(snapshot) <= snapshot.caps.maxSerializedBytes) return;
|
|
371
373
|
snapshot.omitted.byteLimitExceeded = true;
|
|
372
374
|
const runs = snapshot.runs;
|
|
@@ -401,7 +403,7 @@ function workflowStepName(step: AsyncJobStep, index: number): string {
|
|
|
401
403
|
return `${phase}${key}${label} (${step.agent})`;
|
|
402
404
|
}
|
|
403
405
|
|
|
404
|
-
function hostStepRow(hostStep:
|
|
406
|
+
function hostStepRow(hostStep: HostStepNode): AsyncStatusWorkflowRow {
|
|
405
407
|
const freshness = hostStep.freshness
|
|
406
408
|
? {
|
|
407
409
|
expectedRef: publicText(hostStep.freshness.expectedRef, "ref", HOST_STEP_MAX_REF_CHARS),
|
|
@@ -425,11 +427,11 @@ function hostStepRow(hostStep: HostStepNodeV1): AsyncStatusWorkflowRow {
|
|
|
425
427
|
}
|
|
426
428
|
|
|
427
429
|
|
|
428
|
-
function isWorkflowPreflight(value: readonly
|
|
430
|
+
function isWorkflowPreflight(value: readonly HostStepNode[] | WorkflowGraphSnapshot | WorkflowPreflight | undefined): value is WorkflowPreflight {
|
|
429
431
|
return value !== undefined && !Array.isArray(value) && "lanes" in value;
|
|
430
432
|
}
|
|
431
433
|
|
|
432
|
-
function isWorkflowGraph(value: readonly
|
|
434
|
+
function isWorkflowGraph(value: readonly HostStepNode[] | WorkflowGraphSnapshot | WorkflowPreflight | undefined): value is WorkflowGraphSnapshot {
|
|
433
435
|
return value !== undefined && !Array.isArray(value) && "nodes" in value;
|
|
434
436
|
}
|
|
435
437
|
|
|
@@ -465,7 +467,7 @@ function workflowGraphRowName(node: WorkflowGraphSnapshot["nodes"][number]): str
|
|
|
465
467
|
return `${phase ? `${phase}: ` : ""}${key}${label && label !== node.id ? ` · ${label}` : ""}${agent ? ` (${agent})` : ""}`;
|
|
466
468
|
}
|
|
467
469
|
|
|
468
|
-
function projectWorkflowGraphRow(node: WorkflowGraphSnapshot["nodes"][number], preflight?:
|
|
470
|
+
function projectWorkflowGraphRow(node: WorkflowGraphSnapshot["nodes"][number], preflight?: WorkflowPreflightLane): AsyncStatusWorkflowRow {
|
|
469
471
|
return {
|
|
470
472
|
name: workflowGraphRowName(node),
|
|
471
473
|
state: workflowGraphRowState(node.status),
|
|
@@ -476,14 +478,14 @@ function projectWorkflowGraphRow(node: WorkflowGraphSnapshot["nodes"][number], p
|
|
|
476
478
|
/** Project authoritative workflow facts into compact rows, annotated by preflight hints. */
|
|
477
479
|
export function projectAsyncWorkflowRows(
|
|
478
480
|
steps: readonly AsyncJobStep[] | undefined,
|
|
479
|
-
hostStepsOrPreflight?: readonly
|
|
480
|
-
preflightOverride?:
|
|
481
|
+
hostStepsOrPreflight?: readonly HostStepNode[] | WorkflowGraphSnapshot | WorkflowPreflight,
|
|
482
|
+
preflightOverride?: WorkflowPreflight,
|
|
481
483
|
): AsyncStatusWorkflowRow[] {
|
|
482
484
|
const preflight = preflightOverride ?? (isWorkflowPreflight(hostStepsOrPreflight) ? hostStepsOrPreflight : undefined);
|
|
483
485
|
const graph = isWorkflowGraph(hostStepsOrPreflight) ? hostStepsOrPreflight : undefined;
|
|
484
486
|
const hostSteps = isWorkflowPreflight(hostStepsOrPreflight) || graph ? undefined : hostStepsOrPreflight;
|
|
485
487
|
const loaded = steps ?? [];
|
|
486
|
-
const preflightForKey = (key: string, groupKeys: readonly (string | undefined)[] = []):
|
|
488
|
+
const preflightForKey = (key: string, groupKeys: readonly (string | undefined)[] = []): WorkflowPreflightLane | undefined =>
|
|
487
489
|
workflowPreflightLaneForRuntimeKey(preflight, key, groupKeys);
|
|
488
490
|
if (graph) {
|
|
489
491
|
const loadedIndexesByKey = new Map<string, number[]>();
|
|
@@ -526,7 +528,7 @@ export function projectAsyncWorkflowRows(
|
|
|
526
528
|
];
|
|
527
529
|
}
|
|
528
530
|
|
|
529
|
-
function projectLoadedWorkflowRow(step: AsyncJobStep, index: number, preflight?:
|
|
531
|
+
function projectLoadedWorkflowRow(step: AsyncJobStep, index: number, preflight?: WorkflowPreflightLane): AsyncStatusWorkflowRow {
|
|
530
532
|
const modelThinking = formatModelThinking(step.model, step.thinking) || undefined;
|
|
531
533
|
const activity = workflowStepActivity(step);
|
|
532
534
|
return {
|
|
@@ -536,6 +538,8 @@ function projectLoadedWorkflowRow(step: AsyncJobStep, index: number, preflight?:
|
|
|
536
538
|
...(modelThinking ? { modelThinking } : {}),
|
|
537
539
|
...(activity ? { activity } : {}),
|
|
538
540
|
...(step.startedAt !== undefined ? { startedAt: step.startedAt } : {}),
|
|
541
|
+
...(step.endedAt !== undefined ? { endedAt: step.endedAt } : {}),
|
|
542
|
+
...(step.durationMs !== undefined ? { durationMs: step.durationMs } : {}),
|
|
539
543
|
...(step.tokens?.total !== undefined ? { tokens: step.tokens.total } : {}),
|
|
540
544
|
...(step.tokens?.window !== undefined ? { window: step.tokens.window } : {}),
|
|
541
545
|
...(preflight ? { preflight } : {}),
|
|
@@ -543,7 +547,7 @@ function projectLoadedWorkflowRow(step: AsyncJobStep, index: number, preflight?:
|
|
|
543
547
|
}
|
|
544
548
|
|
|
545
549
|
/** Project already-loaded async status facts into the bounded public snapshot shape. */
|
|
546
|
-
export function projectAsyncStatusSnapshot(jobs: Iterable<AsyncJobState>, options: AsyncStatusSnapshotOptions = {}):
|
|
550
|
+
export function projectAsyncStatusSnapshot(jobs: Iterable<AsyncJobState>, options: AsyncStatusSnapshotOptions = {}): AsyncStatusSnapshot {
|
|
547
551
|
const caps = resolveCaps(options);
|
|
548
552
|
const ctx: ProjectionContext = { caps, omitted: { runs: 0, children: 0, byteLimitExceeded: false } };
|
|
549
553
|
const sorted = [...jobs].sort((left, right) => {
|
|
@@ -552,7 +556,7 @@ export function projectAsyncStatusSnapshot(jobs: Iterable<AsyncJobState>, option
|
|
|
552
556
|
return rightUpdated - leftUpdated || left.asyncId.localeCompare(right.asyncId);
|
|
553
557
|
});
|
|
554
558
|
ctx.omitted.runs += Math.max(0, sorted.length - caps.maxRuns);
|
|
555
|
-
const snapshot:
|
|
559
|
+
const snapshot: AsyncStatusSnapshot = {
|
|
556
560
|
kind: ASYNC_STATUS_SNAPSHOT_KIND,
|
|
557
561
|
version: ASYNC_STATUS_SNAPSHOT_VERSION,
|
|
558
562
|
generatedAt: options.generatedAt ?? Date.now(),
|
|
@@ -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 {
|