@selesai/code 0.13.2 → 0.13.4
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 +20 -0
- package/dist/defaults/models.json +85 -13
- package/dist/extensions/cost-reconcile.test.ts +200 -4
- package/dist/extensions/cost-reconcile.ts +88 -102
- package/dist/extensions/pi-intercom/CHANGELOG.md +13 -0
- package/dist/extensions/pi-intercom/README.md +4 -5
- package/dist/extensions/pi-intercom/config.test.ts +3 -31
- package/dist/extensions/pi-intercom/config.ts +0 -15
- package/dist/extensions/pi-intercom/index.ts +9 -46
- package/dist/extensions/pi-intercom/intercom.integration.test.ts +49 -57
- package/dist/extensions/pi-intercom/package.json +1 -1
- package/dist/extensions/pi-intercom/reply-tracker.test.ts +20 -0
- package/dist/extensions/pi-intercom/reply-tracker.ts +8 -0
- package/dist/extensions/pi-subagents/CHANGELOG.md +27 -0
- package/dist/extensions/pi-subagents/docs/tool-reference.md +4 -1
- package/dist/extensions/pi-subagents/docs/workflows.md +2 -2
- package/dist/extensions/pi-subagents/package-lock.json +2 -2
- package/dist/extensions/pi-subagents/package.json +1 -1
- package/dist/extensions/pi-subagents/skills/council-mode/SKILL.md +48 -243
- package/dist/extensions/pi-subagents/skills/council-mode/references/pass-contracts.md +150 -0
- package/dist/extensions/pi-subagents/skills/pi-subagents/SKILL.md +87 -37
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/constraints-and-recipes.md +29 -233
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/execution-controls.md +49 -8
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/management-authoring-rpc.md +2 -2
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/multi-lane-orchestration.md +13 -1
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/prompting-and-roles.md +34 -27
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/review-and-validation.md +73 -0
- package/dist/extensions/pi-subagents/src/agents/agent-management.ts +157 -28
- package/dist/extensions/pi-subagents/src/api/shared-types.ts +2 -0
- package/dist/extensions/pi-subagents/src/extension/public-execution.ts +1 -0
- package/dist/extensions/pi-subagents/src/extension/schemas.ts +1 -0
- package/dist/extensions/pi-subagents/src/extension/tool-description.ts +4 -1
- package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +2 -2
- package/dist/extensions/pi-subagents/src/runs/background/async-job-tracker.ts +3 -0
- package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +45 -2
- package/dist/extensions/pi-subagents/src/runs/background/control-channel.ts +3 -2
- package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +13 -2
- package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +5 -1
- package/dist/extensions/pi-subagents/src/runs/background/subagent-wait.ts +10 -2
- package/dist/extensions/pi-subagents/src/runs/background/wait-completions.ts +3 -0
- package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +11 -2
- package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +98 -1
- package/dist/extensions/pi-subagents/src/runs/shared/async-status-projection.ts +138 -4
- package/dist/extensions/pi-subagents/src/runs/shared/background-process-options.ts +9 -0
- package/dist/extensions/pi-subagents/src/runs/shared/mcp-direct-tool-grant.ts +2 -5
- package/dist/extensions/pi-subagents/src/runs/shared/mutation-evidence.ts +52 -3
- package/dist/extensions/pi-subagents/src/runs/shared/pi-args.ts +47 -1
- package/dist/extensions/pi-subagents/src/runs/shared/single-output.ts +45 -18
- package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +21 -2
- package/dist/extensions/pi-subagents/src/runs/shared/workflow-graph.ts +15 -0
- package/dist/extensions/pi-subagents/src/shared/types.ts +34 -1
- package/dist/extensions/pi-subagents/src/tui/fleet-status.ts +11 -3
- package/dist/extensions/pi-subagents/src/tui/render-helpers.ts +31 -0
- package/dist/extensions/pi-subagents/src/tui/render.ts +597 -112
- package/dist/extensions/pi-subagents/src/watchdog/change-signature.ts +40 -1
- package/dist/extensions/pi-subagents/src/workflows/host-command.ts +6 -1
- package/dist/extensions/pi-subagents/src/workflows/scripted-workflow.ts +53 -2
- package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +55 -6
- package/dist/extensions/pi-subagents/test/integration/async-status.test.ts +111 -1
- package/dist/extensions/pi-subagents/test/integration/render-fork-badge.test.ts +206 -38
- package/dist/extensions/pi-subagents/test/integration/render-widget.test.ts +522 -31
- package/dist/extensions/pi-subagents/test/integration/single-execution.test.ts +123 -0
- package/dist/extensions/pi-subagents/test/unit/agent-management.test.ts +48 -0
- package/dist/extensions/pi-subagents/test/unit/async-status-projection.test.ts +57 -1
- package/dist/extensions/pi-subagents/test/unit/background-process-options.test.ts +17 -0
- package/dist/extensions/pi-subagents/test/unit/external-cli-runner.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/unit/fleet-status.test.ts +44 -4
- package/dist/extensions/pi-subagents/test/unit/fork-cache-key.test.ts +91 -0
- package/dist/extensions/pi-subagents/test/unit/host-command.test.ts +1 -0
- package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +0 -1
- package/dist/extensions/pi-subagents/test/unit/mcp-direct-tool-grant.test.ts +20 -3
- package/dist/extensions/pi-subagents/test/unit/mutation-evidence.test.ts +27 -0
- package/dist/extensions/pi-subagents/test/unit/pi-args.test.ts +93 -17
- package/dist/extensions/pi-subagents/test/unit/public-execution.test.ts +1 -0
- package/dist/extensions/pi-subagents/test/unit/render-helpers.test.ts +103 -26
- package/dist/extensions/pi-subagents/test/unit/run-status.test.ts +58 -0
- package/dist/extensions/pi-subagents/test/unit/schemas.test.ts +22 -2
- package/dist/extensions/pi-subagents/test/unit/scripted-workflow.test.ts +21 -0
- package/dist/extensions/pi-subagents/test/unit/single-output.test.ts +13 -0
- package/dist/extensions/pi-subagents/test/unit/subagent-wait.test.ts +54 -0
- package/dist/extensions/pi-subagents/test/unit/tool-description.test.ts +2 -0
- package/dist/extensions/pi-subagents/test/unit/wait-completions.test.ts +32 -0
- package/dist/extensions/pi-subagents/test/unit/watchdog-change-signature.test.ts +48 -1
- package/dist/extensions/pi-subagents/test/unit/widget-nested-render.test.ts +21 -6
- package/dist/extensions/pi-subagents/test/unit/windows-hide-spawn.test.ts +11 -0
- package/dist/extensions/pi-web-agent/CHANGELOG.md +410 -0
- package/dist/extensions/pi-web-agent/README.md +131 -0
- package/dist/extensions/pi-web-agent/package.json +4 -2
- package/dist/extensions/pi-web-agent/src/backends/config.ts +62 -5
- package/dist/extensions/pi-web-agent/src/backends/doctor.ts +136 -0
- package/dist/extensions/pi-web-agent/src/backends/factory.ts +96 -6
- package/dist/extensions/pi-web-agent/src/commands/web-agent-config.ts +183 -47
- package/dist/extensions/pi-web-agent/src/extension.ts +62 -25
- package/dist/extensions/pi-web-agent/src/extract/readability.ts +19 -11
- package/dist/extensions/pi-web-agent/src/orchestration/candidate-selector.ts +5 -4
- package/dist/extensions/pi-web-agent/src/orchestration/direct-url.ts +2 -25
- package/dist/extensions/pi-web-agent/src/orchestration/evidence-quality.ts +5 -2
- package/dist/extensions/pi-web-agent/src/orchestration/evidence-ranker.ts +2 -0
- package/dist/extensions/pi-web-agent/src/orchestration/research-orchestrator.ts +72 -7
- package/dist/extensions/pi-web-agent/src/orchestration/research-types.ts +8 -1
- package/dist/extensions/pi-web-agent/src/orchestration/research-worker.ts +28 -3
- package/dist/extensions/pi-web-agent/src/orchestration/source-profile.ts +4 -0
- package/dist/extensions/pi-web-agent/src/orchestration/url.ts +35 -0
- package/dist/extensions/pi-web-agent/src/presentation/explore-presentation.ts +18 -6
- package/dist/extensions/pi-web-agent/src/presentation/search-presentation.ts +14 -2
- package/dist/extensions/pi-web-agent/src/readers/github-reader.ts +150 -0
- package/dist/extensions/pi-web-agent/src/readers/limits.ts +3 -0
- package/dist/extensions/pi-web-agent/src/readers/pdf-reader.ts +87 -0
- package/dist/extensions/pi-web-agent/src/readers/resolver.ts +25 -0
- package/dist/extensions/pi-web-agent/src/readers/types.ts +11 -0
- package/dist/extensions/pi-web-agent/src/readers/youtube-reader.ts +79 -0
- package/dist/extensions/pi-web-agent/src/search/duckduckgo.ts +32 -5
- package/dist/extensions/pi-web-agent/src/search/exa.ts +109 -0
- package/dist/extensions/pi-web-agent/src/search/fanout.ts +154 -0
- package/dist/extensions/pi-web-agent/src/search/tavily.ts +113 -0
- package/dist/extensions/pi-web-agent/src/search/youcom.ts +109 -0
- package/dist/extensions/pi-web-agent/src/tools/web-search.ts +22 -9
- package/dist/extensions/pi-web-agent/src/types.ts +19 -4
- package/dist/extensions/tokenin-onboarding.ts +302 -0
- package/package.json +1 -1
|
@@ -4,6 +4,7 @@ import type { AsyncRunSummary } from "./async-status.ts";
|
|
|
4
4
|
import { readCompletionReplay, writeCompletionReplay } from "./completion-replay.ts";
|
|
5
5
|
import { fallbackResultPayloadPathForSessionRun, resultFilePath, resultPayloadPathForSessionRun } from "./result-files.ts";
|
|
6
6
|
import { parseWorkflowChildSummary } from "../../workflows/workflow-child-summary.ts";
|
|
7
|
+
import { projectTimeoutRecovery } from "../shared/mutation-evidence.ts";
|
|
7
8
|
|
|
8
9
|
function asNonEmptyString(value: unknown): string | undefined {
|
|
9
10
|
return typeof value === "string" && value ? value : undefined;
|
|
@@ -65,6 +66,7 @@ export function toWaitCompletion(data: Record<string, unknown>, runId: string):
|
|
|
65
66
|
const error = asNonEmptyString(child.error);
|
|
66
67
|
const model = asNonEmptyString(child.model);
|
|
67
68
|
const contextOverflow = child.contextOverflow === true;
|
|
69
|
+
const timeoutRecovery = projectTimeoutRecovery(child.timeoutRecovery);
|
|
68
70
|
return [{
|
|
69
71
|
...(agent ? { agent } : {}),
|
|
70
72
|
...(childRunId ? { runId: childRunId } : {}),
|
|
@@ -76,6 +78,7 @@ export function toWaitCompletion(data: Record<string, unknown>, runId: string):
|
|
|
76
78
|
...(model ? { model } : {}),
|
|
77
79
|
...(contextOverflow ? { contextOverflow: true } : {}),
|
|
78
80
|
...(artifactPaths ? { artifactPaths } : {}),
|
|
81
|
+
...(timeoutRecovery ? { timeoutRecovery } : {}),
|
|
79
82
|
}];
|
|
80
83
|
})
|
|
81
84
|
: undefined;
|
|
@@ -66,7 +66,7 @@ import { createJsonlWriter } from "../../shared/jsonl-writer.ts";
|
|
|
66
66
|
import { createOrcaProgressTab, type OrcaProgressTab } from "../shared/orca-progress-tabs.ts";
|
|
67
67
|
import { attachPostExitStdioGuard, trySignalChild } from "../../shared/post-exit-stdio-guard.ts";
|
|
68
68
|
import { resolvePermissionRules } from "../shared/permissions.ts";
|
|
69
|
-
import { applyThinkingSuffix, buildPiArgs, cleanupTempDir, projectLaunchResolvedChildExtensions, resolvePiLaunchToolPlan, type SubagentTaskDelivery } from "../shared/pi-args.ts";
|
|
69
|
+
import { applyThinkingSuffix, buildPiArgs, cleanupTempDir, deriveForkPromptCacheKey, projectLaunchResolvedChildExtensions, resolvePiLaunchToolPlan, type SubagentTaskDelivery } from "../shared/pi-args.ts";
|
|
70
70
|
import { deriveChildSessionName } from "../../shared/child-session-name.ts";
|
|
71
71
|
import { readRuntimeAcknowledgedExtensions } from "../shared/runtime-acknowledged-extensions.ts";
|
|
72
72
|
import { assertAgentAllowedByCapabilityCeiling, decodeSubagentCapabilityCeiling, intersectSubagentCapabilityCeilings, resolveCurrentSubagentCapabilityCeiling, SUBAGENT_CAPABILITY_CEILING_ENV } from "../shared/capability-ceiling.ts";
|
|
@@ -76,7 +76,7 @@ import { MISSING_STRUCTURED_OUTPUT_CALL_ERROR, readStructuredOutput, readStructu
|
|
|
76
76
|
import { formatMidToolExitError, formatProcessSignalError, isOrdinaryToolForMidToolExit, isUnexplainedProcessSignal } from "../shared/process-signal.ts";
|
|
77
77
|
import { readChildToolDiagnosticError } from "../shared/tool-availability.ts";
|
|
78
78
|
import { buildTimeoutRecoverySummary, collectTrackedMutationEvidence, snapshotTrackedMutations } from "../shared/mutation-evidence.ts";
|
|
79
|
-
import { captureSingleOutputSnapshot, extractChildWrittenOutput, finalizeSingleOutput, formatSavedOutputReference, injectOutputPathSystemPrompt, resolveSingleOutput, validateFileOnlyOutputMode, type SingleOutputSnapshot } from "../shared/single-output.ts";
|
|
79
|
+
import { captureSingleOutputSnapshot, extractChildWrittenOutput, finalizeSingleOutput, formatSavedOutputReference, hasSingleOutputChangedSinceSnapshot, injectOutputPathSystemPrompt, resolveSingleOutput, validateFileOnlyOutputMode, type SingleOutputSnapshot } from "../shared/single-output.ts";
|
|
80
80
|
import {
|
|
81
81
|
buildModelCandidates,
|
|
82
82
|
formatSubagentModelVerificationError,
|
|
@@ -380,6 +380,7 @@ async function runSingleAttempt(
|
|
|
380
380
|
parentCapabilityToken: options.nestedRoute?.capabilityToken,
|
|
381
381
|
runFanoutBudget: options.runFanoutBudget,
|
|
382
382
|
parentSessionId: options.parentSessionId,
|
|
383
|
+
forkCacheKey: options.context === "fork" ? deriveForkPromptCacheKey(options.parentSessionId) : undefined,
|
|
383
384
|
steerInboxDir: options.steerInboxDir,
|
|
384
385
|
steerCapabilityPath: options.steerCapabilityPath,
|
|
385
386
|
steerAckDir: options.steerAckDir,
|
|
@@ -1527,9 +1528,17 @@ async function runSingleAttempt(
|
|
|
1527
1528
|
result.outputState = fullOutput.trim() || result.structuredOutput !== undefined ? "present" : "absent";
|
|
1528
1529
|
if (result.timedOut) {
|
|
1529
1530
|
const timeoutMessage = formatTimeoutMessage(options.timeoutMs ?? 0);
|
|
1531
|
+
let requiredOutputMissing: boolean | undefined;
|
|
1532
|
+
if (options.outputMode === "file-only" && options.outputPath) {
|
|
1533
|
+
const outputChanged = hasSingleOutputChangedSinceSnapshot(options.outputPath, shared.outputSnapshot);
|
|
1534
|
+
requiredOutputMissing = outputChanged === undefined ? undefined : !outputChanged;
|
|
1535
|
+
} else if (options.structuredOutput) {
|
|
1536
|
+
requiredOutputMissing = !existsSync(options.structuredOutput.outputPath);
|
|
1537
|
+
}
|
|
1530
1538
|
result.timeoutRecovery = buildTimeoutRecoverySummary({
|
|
1531
1539
|
termination: "timed-out",
|
|
1532
1540
|
evidence: mutationEvidence,
|
|
1541
|
+
requiredOutputMissing,
|
|
1533
1542
|
currentTool: progress.currentTool,
|
|
1534
1543
|
currentToolArgs: progress.currentToolArgs,
|
|
1535
1544
|
currentPath: progress.currentPath,
|
|
@@ -113,7 +113,7 @@ import { createMissionWorkflowState } from "../../missions/workflow-state.ts";
|
|
|
113
113
|
import { resolveAuthorityDecision } from "../../policy/authority.ts";
|
|
114
114
|
import { handleHerdrInspectorAction, HERDR_INSPECTOR_ACTIONS } from "../../inspectors/herdr/actions.ts";
|
|
115
115
|
import { handleHerdrProjectPaneAction, HERDR_PROJECT_PANE_ACTIONS } from "../../inspectors/herdr/project-panes.ts";
|
|
116
|
-
import { previewSimpleWorkflowRun, runWorkflowScript, validateWorkflowScript, WorkflowScriptError, type WorkflowReceiptResumeReference, type WorkflowScriptChildResult, type WorkflowSteerOptions, type WorkflowSteerResult } from "../../workflows/scripted-workflow.ts";
|
|
116
|
+
import { previewSimpleWorkflowRun, runWorkflowScript, validateWorkflowScript, WorkflowScriptError, type WorkflowLanePlan, type WorkflowReceiptResumeReference, type WorkflowScriptChildResult, type WorkflowScriptTraceEntry, type WorkflowSteerOptions, type WorkflowSteerResult } from "../../workflows/scripted-workflow.ts";
|
|
117
117
|
import { executeWorkflowHostCommand, resolveWorkflowHostOutputClaimPath, type WorkflowHostCommandParams, type WorkflowHostCommandResult } from "../../workflows/host-command.ts";
|
|
118
118
|
import { buildWorkflowReceipt, resolveWorkflowReceiptResumeEntry, writeWorkflowReceipt, type WorkflowReceipt, type WorkflowReceiptState } from "../../workflows/workflow-receipt.ts";
|
|
119
119
|
import { upsertHostStep, validHostStepNodes } from "../shared/host-step-status.ts";
|
|
@@ -161,6 +161,9 @@ import {
|
|
|
161
161
|
type ToolBudgetConfig,
|
|
162
162
|
type Usage,
|
|
163
163
|
type UsageBudgetConfig,
|
|
164
|
+
type WorkflowGraphNode,
|
|
165
|
+
type WorkflowGraphSnapshot,
|
|
166
|
+
type WorkflowNodeStatus,
|
|
164
167
|
type WorkflowTerminalOutcome,
|
|
165
168
|
type SubagentRunMode,
|
|
166
169
|
type SubagentState,
|
|
@@ -296,6 +299,7 @@ export interface SubagentParamsLike {
|
|
|
296
299
|
type?: string;
|
|
297
300
|
agent?: string;
|
|
298
301
|
task?: string;
|
|
302
|
+
capabilities?: boolean;
|
|
299
303
|
extensionBindings?: ExtensionBindings;
|
|
300
304
|
/** Retained async child run id. Valid only on workflow runs.run items. */
|
|
301
305
|
resume?: string;
|
|
@@ -4412,6 +4416,90 @@ function formatWorkflowValue(value: unknown): string {
|
|
|
4412
4416
|
}
|
|
4413
4417
|
}
|
|
4414
4418
|
|
|
4419
|
+
function buildWorkflowLaneGraph(runId: string, lanes: WorkflowLanePlan[], existing?: WorkflowGraphSnapshot): WorkflowGraphSnapshot {
|
|
4420
|
+
const plannedIds = new Set(lanes.flatMap((lane) => lane.stages.map((stage) => stage.generatedKey)));
|
|
4421
|
+
const previousById = new Map((existing?.nodes ?? []).map((node) => [node.id, node]));
|
|
4422
|
+
const nodes: WorkflowGraphNode[] = [];
|
|
4423
|
+
const phases: WorkflowGraphSnapshot["phases"] = [];
|
|
4424
|
+
let flatIndex = 0;
|
|
4425
|
+
for (const lane of lanes) {
|
|
4426
|
+
const nodeIds: string[] = [];
|
|
4427
|
+
for (const [stageIndex, stage] of lane.stages.entries()) {
|
|
4428
|
+
const previous = previousById.get(stage.generatedKey);
|
|
4429
|
+
const outputName = stage.outputName || previous?.outputName;
|
|
4430
|
+
const structured = stage.structured ?? previous?.structured;
|
|
4431
|
+
const node: WorkflowGraphNode = {
|
|
4432
|
+
id: stage.generatedKey,
|
|
4433
|
+
kind: "step",
|
|
4434
|
+
agent: stage.agent ?? previous?.agent,
|
|
4435
|
+
phase: stage.phase ?? previous?.phase,
|
|
4436
|
+
label: stage.label ?? stage.key,
|
|
4437
|
+
status: previous?.status ?? (stageIndex === 0 ? "running" : "pending"),
|
|
4438
|
+
flatIndex,
|
|
4439
|
+
stepIndex: flatIndex,
|
|
4440
|
+
...(outputName ? { outputName } : {}),
|
|
4441
|
+
...(structured !== undefined ? { structured } : {}),
|
|
4442
|
+
...(previous?.acceptanceStatus ? { acceptanceStatus: previous.acceptanceStatus } : {}),
|
|
4443
|
+
...(previous?.error ? { error: previous.error } : {}),
|
|
4444
|
+
};
|
|
4445
|
+
nodes.push(node);
|
|
4446
|
+
nodeIds.push(node.id);
|
|
4447
|
+
flatIndex++;
|
|
4448
|
+
}
|
|
4449
|
+
if (nodeIds.length > 0) phases.push({ title: lane.key, nodeIds });
|
|
4450
|
+
}
|
|
4451
|
+
for (const node of existing?.nodes ?? []) {
|
|
4452
|
+
if (!plannedIds.has(node.id)) nodes.push(node);
|
|
4453
|
+
}
|
|
4454
|
+
for (const phase of existing?.phases ?? []) {
|
|
4455
|
+
const retainedNodeIds = phase.nodeIds.filter((nodeId) => !plannedIds.has(nodeId));
|
|
4456
|
+
if (retainedNodeIds.length === 0) continue;
|
|
4457
|
+
const currentPhase = phases.find((candidate) => candidate.title === phase.title);
|
|
4458
|
+
if (currentPhase) currentPhase.nodeIds.push(...retainedNodeIds);
|
|
4459
|
+
else phases.push({ title: phase.title, nodeIds: retainedNodeIds });
|
|
4460
|
+
}
|
|
4461
|
+
const currentNodeId = nodes.find((node) => node.status === "running")?.id ?? existing?.currentNodeId;
|
|
4462
|
+
return { runId, mode: "workflow", phases, nodes, ...(currentNodeId ? { currentNodeId } : {}) };
|
|
4463
|
+
}
|
|
4464
|
+
|
|
4465
|
+
function workflowLaneTraceStatus(state: WorkflowScriptTraceEntry["state"]): WorkflowNodeStatus | undefined {
|
|
4466
|
+
switch (state) {
|
|
4467
|
+
case "started":
|
|
4468
|
+
return "running";
|
|
4469
|
+
case "reused":
|
|
4470
|
+
return undefined;
|
|
4471
|
+
case "completed":
|
|
4472
|
+
return "completed";
|
|
4473
|
+
case "failed":
|
|
4474
|
+
return "failed";
|
|
4475
|
+
case "detached":
|
|
4476
|
+
return "detached";
|
|
4477
|
+
case "stopped":
|
|
4478
|
+
return "stopped";
|
|
4479
|
+
default:
|
|
4480
|
+
return undefined;
|
|
4481
|
+
}
|
|
4482
|
+
}
|
|
4483
|
+
|
|
4484
|
+
function applyWorkflowLaneTrace(graph: WorkflowGraphSnapshot, trace: WorkflowScriptTraceEntry[]): void {
|
|
4485
|
+
const nodesById = new Map(graph.nodes.map((node) => [node.id, node]));
|
|
4486
|
+
for (const entry of trace) {
|
|
4487
|
+
if (entry.operation !== "run") continue;
|
|
4488
|
+
const node = nodesById.get(entry.key);
|
|
4489
|
+
if (!node) continue;
|
|
4490
|
+
const status = workflowLaneTraceStatus(entry.state);
|
|
4491
|
+
if (status) node.status = status;
|
|
4492
|
+
if (entry.agent) node.agent = entry.agent;
|
|
4493
|
+
if (entry.phase && (entry.phase !== "auto-resume" || node.phase === undefined)) node.phase = entry.phase;
|
|
4494
|
+
if (entry.label) node.label = entry.label;
|
|
4495
|
+
if (entry.error) node.error = entry.error;
|
|
4496
|
+
else if (status === "completed" || status === "running") delete node.error;
|
|
4497
|
+
}
|
|
4498
|
+
const currentNodeId = graph.nodes.find((node) => node.status === "running")?.id;
|
|
4499
|
+
if (currentNodeId) graph.currentNodeId = currentNodeId;
|
|
4500
|
+
else delete graph.currentNodeId;
|
|
4501
|
+
}
|
|
4502
|
+
|
|
4415
4503
|
function workflowChatProgressUpdate(
|
|
4416
4504
|
runId: string,
|
|
4417
4505
|
chatProgress: WorkflowChatProgressProjection,
|
|
@@ -4801,6 +4889,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4801
4889
|
liveJob.toolCount = status.toolCount;
|
|
4802
4890
|
liveJob.currentStep = status.currentStep;
|
|
4803
4891
|
liveJob.preflight = status.preflight;
|
|
4892
|
+
liveJob.workflowGraph = status.workflowGraph;
|
|
4804
4893
|
if (status.steps) {
|
|
4805
4894
|
liveJob.steps = status.steps.map((step, index) => ({ ...step, index }));
|
|
4806
4895
|
liveJob.agents = status.steps.map((step) => step.agent);
|
|
@@ -4884,6 +4973,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4884
4973
|
trace: projectedTrace,
|
|
4885
4974
|
...(preflightWarnings.length ? { preflightWarnings } : {}),
|
|
4886
4975
|
};
|
|
4976
|
+
if (status.workflowGraph) applyWorkflowLaneTrace(status.workflowGraph, trace);
|
|
4887
4977
|
const rebuild = trace.length < projectedTraceLength
|
|
4888
4978
|
|| (projectedTraceLength > 0 && trace[projectedTraceLength - 1] !== projectedTraceTail);
|
|
4889
4979
|
if (rebuild) {
|
|
@@ -4987,6 +5077,11 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4987
5077
|
},
|
|
4988
5078
|
...(workflowState ? { state: workflowState } : {}),
|
|
4989
5079
|
onTrace: updateTrace,
|
|
5080
|
+
onLanePlan: (lanes) => {
|
|
5081
|
+
status.workflowGraph = buildWorkflowLaneGraph(workflowRunId, lanes, status.workflowGraph);
|
|
5082
|
+
applyWorkflowLaneTrace(status.workflowGraph, status.workflow?.trace ?? []);
|
|
5083
|
+
persist({ tolerateStatusWriteFailure: true });
|
|
5084
|
+
},
|
|
4990
5085
|
host: runHostCommand,
|
|
4991
5086
|
onHostStep: (hostStep) => {
|
|
4992
5087
|
status = upsertHostStep({ status, hostStep, persist: (nextStatus) => {
|
|
@@ -5122,6 +5217,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5122
5217
|
}
|
|
5123
5218
|
if (!writeWorkflowResult({ id: workflowRunId, runId: workflowRunId, toolCallId, agent: "workflow", mode: "workflow", success: true, state: "complete", summary: resultSummary, output: resultSummary, workflowChildren, results: workflow.children.map((child) => ({ workflowKey: child.key, ...(child.agent ? { agent: child.agent } : {}), ...(child.runId ? { runId: child.runId } : {}), ...(status.steps?.find((step) => step.workflowKey === child.key)?.sessionName ? { sessionName: status.steps?.find((step) => step.workflowKey === child.key)?.sessionName } : {}), ...workflowChildAccountingFields(child), output: child.output, outputState: child.output.trim() || child.structuredOutput !== undefined ? "present" : "absent", structuredOutput: child.structuredOutput, success: child.ok, ...(child.outputReference ? { outputReference: child.outputReference } : {}), ...(child.outputPathMapping ? { outputPathMapping: child.outputPathMapping } : {}), ...(child.stopped ? { stopped: true } : {}), ...(child.interrupted ? { interrupted: true } : {}), ...(child.artifactPaths[0] ? { artifactPaths: { outputPath: child.artifactPaths[0] } } : {}) })), workflow: status.workflow, ...(workflowReceipt ? { workflowReceipt } : {}), asyncDir, cwd: workflowCwd, sessionId: currentSessionId, completionOwnerId, ...(requestParams.scheduleOrigin ? { scheduleOrigin: requestParams.scheduleOrigin } : {}), timestamp: Date.now(), durationMs: Date.now() - startedAt })) return;
|
|
5124
5219
|
persist();
|
|
5220
|
+
deps.refreshResultDelivery?.();
|
|
5125
5221
|
persistClosed = true;
|
|
5126
5222
|
appendWorkflowEvent({ type: "subagent.workflow.completed", state: status.state, ...(status.error ? { error: status.error } : {}) });
|
|
5127
5223
|
} catch (error) {
|
|
@@ -5167,6 +5263,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5167
5263
|
}
|
|
5168
5264
|
if (!writeWorkflowResult({ id: workflowRunId, runId: workflowRunId, toolCallId, agent: "workflow", mode: "workflow", success: status.state === "complete", state: status.state, summary: resultSummary, error: status.state === "complete" ? undefined : status.error, stopped: status.stopped, activityState: status.activityState, workflowChildren, ...(terminalOutcome ? { terminalOutcome } : {}), results: partial.children.map((child) => ({ workflowKey: child.key, ...(child.agent ? { agent: child.agent } : {}), ...(child.runId ? { runId: child.runId } : {}), ...(status.steps?.find((step) => step.workflowKey === child.key)?.sessionName ? { sessionName: status.steps?.find((step) => step.workflowKey === child.key)?.sessionName } : {}), ...workflowChildAccountingFields(child), output: child.output, outputState: child.output.trim() || child.structuredOutput !== undefined ? "present" : "absent", structuredOutput: child.structuredOutput, success: child.ok, ...(child.outputReference ? { outputReference: child.outputReference } : {}), ...(child.terminalOutcome ? { terminalOutcome: child.terminalOutcome } : {}), ...(child.outputPathMapping ? { outputPathMapping: child.outputPathMapping } : {}), ...(child.stopped ? { stopped: true } : {}), ...(child.interrupted ? { interrupted: true } : {}), ...(child.detached && status.state !== "complete" ? { detached: true } : {}), ...(child.artifactPaths[0] ? { artifactPaths: { outputPath: child.artifactPaths[0] } } : {}) })), workflow: status.workflow, ...(workflowReceipt ? { workflowReceipt } : {}), asyncDir, cwd: workflowCwd, sessionId: currentSessionId, completionOwnerId, ...(requestParams.scheduleOrigin ? { scheduleOrigin: requestParams.scheduleOrigin } : {}), timestamp: Date.now(), durationMs: Date.now() - startedAt })) return;
|
|
5169
5265
|
persist();
|
|
5266
|
+
deps.refreshResultDelivery?.();
|
|
5170
5267
|
persistClosed = true;
|
|
5171
5268
|
appendWorkflowEvent({ type: "subagent.workflow.completed", state: status.state, ...(terminalOutcome ? { terminalOutcome } : {}), ...(status.error ? { error: status.error } : {}), ...(status.activityState ? { activityState: status.activityState } : {}) });
|
|
5172
5269
|
} finally {
|
|
@@ -2,6 +2,7 @@ import { sanitizeDisplayText, truncateDisplayText } from "../../shared/display-t
|
|
|
2
2
|
import { formatModelThinking } from "../../shared/formatters.ts";
|
|
3
3
|
import type { AsyncJobState, AsyncJobStep, HostStepFreshnessV1, HostStepMonitorKind, HostStepNodeV1, HostStepState, HostStepVerdict, NestedRunSummary, NestedStepSummary, SubagentRunMode, WorkflowGraphSnapshot, WorkflowPreflightLaneV1, WorkflowPreflightV1 } 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
|
+
import { workflowGraphStageNodes } from "./workflow-graph.ts";
|
|
5
6
|
|
|
6
7
|
export const ASYNC_STATUS_SNAPSHOT_KIND = "pi-subagents.async-status-snapshot";
|
|
7
8
|
export const ASYNC_STATUS_SNAPSHOT_VERSION = 1;
|
|
@@ -15,6 +16,21 @@ const DEFAULT_MAX_SERIALIZED_BYTES = 32 * 1024;
|
|
|
15
16
|
export type AsyncStatusSnapshotState = "queued" | "running" | "complete" | "failed" | "partial" | "paused" | "stopped" | "rejected";
|
|
16
17
|
export type AsyncStatusSnapshotKind = "subagent" | "workflow" | "step";
|
|
17
18
|
|
|
19
|
+
const ASYNC_STATUS_SNAPSHOT_STATES: Record<AsyncStatusSnapshotState, true> = {
|
|
20
|
+
queued: true,
|
|
21
|
+
running: true,
|
|
22
|
+
complete: true,
|
|
23
|
+
failed: true,
|
|
24
|
+
partial: true,
|
|
25
|
+
paused: true,
|
|
26
|
+
stopped: true,
|
|
27
|
+
rejected: true,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
function isAsyncStatusSnapshotState(value: string): value is AsyncStatusSnapshotState {
|
|
31
|
+
return Object.hasOwn(ASYNC_STATUS_SNAPSHOT_STATES, value);
|
|
32
|
+
}
|
|
33
|
+
|
|
18
34
|
export interface AsyncStatusSnapshotActivityV1 {
|
|
19
35
|
state?: string;
|
|
20
36
|
currentTool?: string;
|
|
@@ -87,6 +103,7 @@ export interface AsyncStatusWorkflowRow {
|
|
|
87
103
|
state: AsyncJobStep["status"] | HostStepState | "planned";
|
|
88
104
|
/** Present only for typed host-owned monitor rows; legacy child rows omit it. */
|
|
89
105
|
kind?: HostStepMonitorKind;
|
|
106
|
+
context?: AsyncJobStep["context"];
|
|
90
107
|
modelThinking?: string;
|
|
91
108
|
activity?: string;
|
|
92
109
|
startedAt?: number;
|
|
@@ -155,7 +172,8 @@ function publicCount(value: unknown): number | undefined {
|
|
|
155
172
|
function normalizeState(value: string): AsyncStatusSnapshotState {
|
|
156
173
|
if (value === "completed") return "complete";
|
|
157
174
|
if (value === "pending") return "queued";
|
|
158
|
-
|
|
175
|
+
if (isAsyncStatusSnapshotState(value)) return value;
|
|
176
|
+
return "partial";
|
|
159
177
|
}
|
|
160
178
|
|
|
161
179
|
function terminalState(state: AsyncStatusSnapshotState): boolean {
|
|
@@ -230,6 +248,16 @@ function projectStep(step: AsyncJobStep | NestedStepSummary, index: number, dept
|
|
|
230
248
|
return node;
|
|
231
249
|
}
|
|
232
250
|
|
|
251
|
+
function projectWorkflowGraphNode(node: WorkflowGraphSnapshot["nodes"][number], index: number, depth: number, ctx: ProjectionContext): AsyncStatusSnapshotNodeV1 {
|
|
252
|
+
const step: AsyncJobStep = {
|
|
253
|
+
agent: node.agent ?? node.label,
|
|
254
|
+
status: workflowGraphStepStatus(node.status),
|
|
255
|
+
workflowKey: node.id,
|
|
256
|
+
label: node.label,
|
|
257
|
+
};
|
|
258
|
+
return projectStep(step, node.flatIndex ?? index, depth, ctx);
|
|
259
|
+
}
|
|
260
|
+
|
|
233
261
|
function projectNestedRun(child: NestedRunSummary, index: number, depth: number, ctx: ProjectionContext): AsyncStatusSnapshotNodeV1 {
|
|
234
262
|
const state = normalizeState(child.state);
|
|
235
263
|
const startedAt = publicTime(child.startedAt);
|
|
@@ -310,9 +338,14 @@ function projectRun(job: AsyncJobState, ctx: ProjectionContext): AsyncStatusSnap
|
|
|
310
338
|
};
|
|
311
339
|
if (ctx.caps.maxDepth > 0) {
|
|
312
340
|
const stepChildren = job.steps?.map((step, index) => projectStep(step, step.index ?? index, 1, ctx)) ?? [];
|
|
341
|
+
const loadedKeys = new Set(job.steps?.flatMap((step) => step.workflowKey ? [step.workflowKey] : []) ?? []);
|
|
342
|
+
const graphStages = job.mode === "workflow" ? workflowGraphStageNodes(job.workflowGraph) : [];
|
|
343
|
+
const graphChildren = graphStages
|
|
344
|
+
.filter((graphNode) => !loadedKeys.has(graphNode.id))
|
|
345
|
+
.map((graphNode, index) => projectWorkflowGraphNode(graphNode, index, 1, ctx));
|
|
313
346
|
const nestedChildren = job.nestedChildren?.map((child, index) => projectNestedRun(child, index, 1, ctx)) ?? [];
|
|
314
347
|
const hostStepChildren = validHostStepList(job.hostSteps).map((hostStep) => projectHostStep(hostStep, ctx));
|
|
315
|
-
const ordinaryChildren = [...stepChildren, ...nestedChildren];
|
|
348
|
+
const ordinaryChildren = [...stepChildren, ...graphChildren, ...nestedChildren];
|
|
316
349
|
const retainedHostSteps = hostStepChildren.slice(0, ctx.caps.maxChildrenPerNode);
|
|
317
350
|
const retainedOrdinaryChildren = ordinaryChildren.slice(0, ctx.caps.maxChildrenPerNode - retainedHostSteps.length);
|
|
318
351
|
const bounded: AsyncStatusSnapshotNodeV1[] = [];
|
|
@@ -320,7 +353,10 @@ function projectRun(job: AsyncJobState, ctx: ProjectionContext): AsyncStatusSnap
|
|
|
320
353
|
ctx.omitted.children += ordinaryChildren.length - retainedOrdinaryChildren.length + hostStepChildren.length - retainedHostSteps.length;
|
|
321
354
|
if (bounded.length) node.children = bounded;
|
|
322
355
|
} else {
|
|
323
|
-
|
|
356
|
+
const loadedKeys = new Set(job.steps?.flatMap((step) => step.workflowKey ? [step.workflowKey] : []) ?? []);
|
|
357
|
+
const graphStages = job.mode === "workflow" ? workflowGraphStageNodes(job.workflowGraph) : [];
|
|
358
|
+
const graphCount = graphStages.filter((graphNode) => !loadedKeys.has(graphNode.id)).length;
|
|
359
|
+
ctx.omitted.children += (job.steps?.length ?? 0) + graphCount + (job.nestedChildren?.length ?? 0) + validHostStepList(job.hostSteps).length;
|
|
324
360
|
}
|
|
325
361
|
return node;
|
|
326
362
|
}
|
|
@@ -392,6 +428,50 @@ function isWorkflowPreflight(value: readonly HostStepNodeV1[] | WorkflowGraphSna
|
|
|
392
428
|
return value !== undefined && !Array.isArray(value) && "lanes" in value;
|
|
393
429
|
}
|
|
394
430
|
|
|
431
|
+
function isWorkflowGraph(value: readonly HostStepNodeV1[] | WorkflowGraphSnapshot | WorkflowPreflightV1 | undefined): value is WorkflowGraphSnapshot {
|
|
432
|
+
return value !== undefined && !Array.isArray(value) && "nodes" in value;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
function workflowGraphRowState(status: WorkflowGraphSnapshot["nodes"][number]["status"]): AsyncStatusWorkflowRow["state"] {
|
|
436
|
+
switch (status) {
|
|
437
|
+
case "pending":
|
|
438
|
+
return "planned";
|
|
439
|
+
case "completed":
|
|
440
|
+
return "complete";
|
|
441
|
+
case "detached":
|
|
442
|
+
return "paused";
|
|
443
|
+
default:
|
|
444
|
+
return status;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
function workflowGraphStepStatus(status: WorkflowGraphSnapshot["nodes"][number]["status"]): AsyncJobStep["status"] {
|
|
449
|
+
switch (status) {
|
|
450
|
+
case "completed":
|
|
451
|
+
return "complete";
|
|
452
|
+
case "detached":
|
|
453
|
+
return "paused";
|
|
454
|
+
default:
|
|
455
|
+
return status;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
function workflowGraphRowName(node: WorkflowGraphSnapshot["nodes"][number]): string {
|
|
460
|
+
const key = publicText(node.id, "stage", HOST_STEP_MAX_LABEL_CHARS);
|
|
461
|
+
const label = publicOptionalText(node.label, HOST_STEP_MAX_LABEL_CHARS);
|
|
462
|
+
const phase = publicOptionalText(node.phase, HOST_STEP_MAX_LABEL_CHARS);
|
|
463
|
+
const agent = publicOptionalText(node.agent, HOST_STEP_MAX_LABEL_CHARS);
|
|
464
|
+
return `${phase ? `${phase}: ` : ""}${key}${label && label !== node.id ? ` · ${label}` : ""}${agent ? ` (${agent})` : ""}`;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
function projectWorkflowGraphRow(node: WorkflowGraphSnapshot["nodes"][number], preflight?: WorkflowPreflightLaneV1): AsyncStatusWorkflowRow {
|
|
468
|
+
return {
|
|
469
|
+
name: workflowGraphRowName(node),
|
|
470
|
+
state: workflowGraphRowState(node.status),
|
|
471
|
+
...(preflight ? { preflight } : {}),
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
|
|
395
475
|
/** Project loaded workflow child facts plus stored preflight hints into compact rows. */
|
|
396
476
|
export function projectAsyncWorkflowRows(
|
|
397
477
|
steps: readonly AsyncJobStep[] | undefined,
|
|
@@ -399,9 +479,62 @@ export function projectAsyncWorkflowRows(
|
|
|
399
479
|
preflightOverride?: WorkflowPreflightV1,
|
|
400
480
|
): AsyncStatusWorkflowRow[] {
|
|
401
481
|
const preflight = preflightOverride ?? (isWorkflowPreflight(hostStepsOrPreflight) ? hostStepsOrPreflight : undefined);
|
|
402
|
-
const
|
|
482
|
+
const graph = isWorkflowGraph(hostStepsOrPreflight) ? hostStepsOrPreflight : undefined;
|
|
483
|
+
const hostSteps = isWorkflowPreflight(hostStepsOrPreflight) || graph ? undefined : hostStepsOrPreflight;
|
|
403
484
|
const loaded = steps ?? [];
|
|
404
485
|
const declared = new Map<string, WorkflowPreflightLaneV1>();
|
|
486
|
+
if (graph) {
|
|
487
|
+
const loadedIndexesByKey = new Map<string, number[]>();
|
|
488
|
+
for (const [index, step] of loaded.entries()) {
|
|
489
|
+
if (!step.workflowKey) continue;
|
|
490
|
+
const indexes = loadedIndexesByKey.get(step.workflowKey) ?? [];
|
|
491
|
+
indexes.push(index);
|
|
492
|
+
loadedIndexesByKey.set(step.workflowKey, indexes);
|
|
493
|
+
}
|
|
494
|
+
const consumed = new Set<number>();
|
|
495
|
+
const childRows: AsyncStatusWorkflowRow[] = [];
|
|
496
|
+
for (const lane of preflight?.lanes ?? []) declared.set(lane.key, lane);
|
|
497
|
+
const graphStages = workflowGraphStageNodes(graph);
|
|
498
|
+
const graphKeys = new Set(graphStages.map((node) => node.id));
|
|
499
|
+
const graphPhaseByNodeId = new Map<string, string>();
|
|
500
|
+
for (const phase of graph.phases) {
|
|
501
|
+
for (const nodeId of phase.nodeIds) {
|
|
502
|
+
if (graphKeys.has(nodeId) && !graphPhaseByNodeId.has(nodeId)) graphPhaseByNodeId.set(nodeId, phase.title);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
const graphLaneKeys = new Set(graphPhaseByNodeId.values());
|
|
506
|
+
const preflightForNode = (node: WorkflowGraphSnapshot["nodes"][number]): WorkflowPreflightLaneV1 | undefined => {
|
|
507
|
+
const phaseTitle = graphPhaseByNodeId.get(node.id);
|
|
508
|
+
return declared.get(node.id) ?? (node.phase ? declared.get(node.phase) : undefined) ?? (phaseTitle ? declared.get(phaseTitle) : undefined);
|
|
509
|
+
};
|
|
510
|
+
for (const node of graphStages) {
|
|
511
|
+
const indexes = (loadedIndexesByKey.get(node.id) ?? []).filter((index) => !consumed.has(index));
|
|
512
|
+
if (indexes.length > 0) {
|
|
513
|
+
for (const index of indexes) {
|
|
514
|
+
consumed.add(index);
|
|
515
|
+
childRows.push(projectLoadedWorkflowRow(loaded[index]!, index, preflightForNode(node)));
|
|
516
|
+
}
|
|
517
|
+
} else {
|
|
518
|
+
childRows.push(projectWorkflowGraphRow(node, preflightForNode(node)));
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
for (const lane of preflight?.lanes ?? []) {
|
|
522
|
+
if (graphKeys.has(lane.key) || graphLaneKeys.has(lane.key)) continue;
|
|
523
|
+
const indexes = (loadedIndexesByKey.get(lane.key) ?? []).filter((index) => !consumed.has(index));
|
|
524
|
+
if (indexes.length > 0) {
|
|
525
|
+
for (const index of indexes) {
|
|
526
|
+
consumed.add(index);
|
|
527
|
+
childRows.push(projectLoadedWorkflowRow(loaded[index]!, index, lane));
|
|
528
|
+
}
|
|
529
|
+
} else {
|
|
530
|
+
childRows.push({ name: lane.key, state: "planned", preflight: lane });
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
for (const [index, step] of loaded.entries()) {
|
|
534
|
+
if (!consumed.has(index)) childRows.push(projectLoadedWorkflowRow(step, index, step.workflowKey ? declared.get(step.workflowKey) : undefined));
|
|
535
|
+
}
|
|
536
|
+
return [...childRows, ...validHostStepList(graph).map(hostStepRow)];
|
|
537
|
+
}
|
|
405
538
|
const loadedIndexByKey = new Map<string, number>();
|
|
406
539
|
for (const [index, step] of loaded.entries()) {
|
|
407
540
|
if (step.workflowKey !== undefined && !loadedIndexByKey.has(step.workflowKey)) loadedIndexByKey.set(step.workflowKey, index);
|
|
@@ -431,6 +564,7 @@ function projectLoadedWorkflowRow(step: AsyncJobStep, index: number, preflight?:
|
|
|
431
564
|
return {
|
|
432
565
|
name: workflowStepName(step, index),
|
|
433
566
|
state: step.status,
|
|
567
|
+
...(step.context ? { context: step.context } : {}),
|
|
434
568
|
...(modelThinking ? { modelThinking } : {}),
|
|
435
569
|
...(activity ? { activity } : {}),
|
|
436
570
|
...(step.startedAt !== undefined ? { startedAt: step.startedAt } : {}),
|
|
@@ -125,11 +125,8 @@ export function formatUnresolvedMcpDirectToolSelectors(selectors: readonly strin
|
|
|
125
125
|
|
|
126
126
|
function getServerPrefix(serverName: string, mode: McpToolPrefix): string {
|
|
127
127
|
if (mode === "none") return "";
|
|
128
|
-
if (mode === "short")
|
|
129
|
-
|
|
130
|
-
return short || "mcp";
|
|
131
|
-
}
|
|
132
|
-
return serverName.replace(/-/g, "_");
|
|
128
|
+
if (mode === "short") return serverName.replace(/-?mcp$/i, "") || "mcp";
|
|
129
|
+
return serverName;
|
|
133
130
|
}
|
|
134
131
|
|
|
135
132
|
function formatToolName(toolName: string, serverName: string, prefix: McpToolPrefix): string {
|
|
@@ -3,7 +3,7 @@ import { createHash } from "node:crypto";
|
|
|
3
3
|
import * as fs from "node:fs";
|
|
4
4
|
import * as os from "node:os";
|
|
5
5
|
import * as path from "node:path";
|
|
6
|
-
import type { ArtifactPaths, TimeoutRecoverySummary, TrackedMutationEvidence, TrackedMutationFingerprint, TrackedMutationSnapshot } from "../../shared/types.ts";
|
|
6
|
+
import type { ArtifactPaths, TimeoutRecoveryProjection, TimeoutRecoverySummary, TrackedMutationEvidence, TrackedMutationFingerprint, TrackedMutationSnapshot } from "../../shared/types.ts";
|
|
7
7
|
|
|
8
8
|
const MAX_TRACKED_PATHS = 500;
|
|
9
9
|
const MAX_HASH_BYTES = 1024 * 1024;
|
|
@@ -15,7 +15,7 @@ function gitArguments(args: string[]): string[] {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
function gitOutput(cwd: string, args: string[], maxBuffer = MAX_HASH_BYTES): string {
|
|
18
|
-
return execFileSync("git", gitArguments(args), { cwd, encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"], maxBuffer });
|
|
18
|
+
return execFileSync("git", gitArguments(args), { cwd, encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"], maxBuffer, windowsHide: true });
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
function splitNul(output: string): string[] {
|
|
@@ -26,7 +26,7 @@ function hashLargeDiff(cwd: string, relativePath: string): string {
|
|
|
26
26
|
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-subagents-tracked-diff-"));
|
|
27
27
|
const diffPath = path.join(tempDir, "diff.patch");
|
|
28
28
|
try {
|
|
29
|
-
execFileSync("git", gitArguments(["diff", "--no-ext-diff", "--binary", `--output=${diffPath}`, "HEAD", "--", relativePath]), { cwd, stdio: "ignore" });
|
|
29
|
+
execFileSync("git", gitArguments(["diff", "--no-ext-diff", "--binary", `--output=${diffPath}`, "HEAD", "--", relativePath]), { cwd, stdio: "ignore", windowsHide: true });
|
|
30
30
|
const hash = createHash("sha256");
|
|
31
31
|
const buffer = Buffer.allocUnsafe(64 * 1024);
|
|
32
32
|
const fd = fs.openSync(diffPath, "r");
|
|
@@ -110,9 +110,48 @@ function formatPathList(paths: string[]): string {
|
|
|
110
110
|
return paths.length > MAX_TIMEOUT_FILES ? `${shown}, ... (${paths.length - MAX_TIMEOUT_FILES} more)` : shown;
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
+
/** Keep status and completion details to bounded routing evidence, not raw output or effects. */
|
|
114
|
+
export function projectTimeoutRecovery(value: unknown): TimeoutRecoveryProjection | undefined {
|
|
115
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return undefined;
|
|
116
|
+
const source = value as Record<string, unknown>;
|
|
117
|
+
const termination = source.termination === "timed-out" || source.termination === "stopped" ? source.termination : undefined;
|
|
118
|
+
if (!termination || !Array.isArray(source.changedFiles)) return undefined;
|
|
119
|
+
const allChangedFiles = source.changedFiles.filter((file): file is string => typeof file === "string");
|
|
120
|
+
const changedFiles = allChangedFiles.slice(0, MAX_TIMEOUT_FILES);
|
|
121
|
+
const reportStatus = source.reportStatus === "missing"
|
|
122
|
+
|| source.reportStatus === "written"
|
|
123
|
+
|| source.reportStatus === "not-requested"
|
|
124
|
+
|| source.reportStatus === "unknown"
|
|
125
|
+
? source.reportStatus
|
|
126
|
+
: undefined;
|
|
127
|
+
return {
|
|
128
|
+
termination,
|
|
129
|
+
changedFiles,
|
|
130
|
+
...(source.truncated === true || allChangedFiles.length > changedFiles.length ? { truncated: true } : {}),
|
|
131
|
+
...(source.recoveryNeeded === true ? { recoveryNeeded: true } : {}),
|
|
132
|
+
...(source.reason === "timed-out-with-dirty-worktree" ? { reason: source.reason } : {}),
|
|
133
|
+
...(reportStatus ? { reportStatus } : {}),
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** Render only the bounded recovery route needed by a parent/operator. */
|
|
138
|
+
export function formatTimeoutRecoveryLines(value: unknown, indent = ""): string[] {
|
|
139
|
+
const recovery = projectTimeoutRecovery(value);
|
|
140
|
+
if (!recovery?.recoveryNeeded) return [];
|
|
141
|
+
const changedFiles = recovery.changedFiles.length > 0
|
|
142
|
+
? `${recovery.changedFiles.join(", ")}${recovery.truncated ? ", …" : ""}`
|
|
143
|
+
: "none";
|
|
144
|
+
const changedFileCount = recovery.changedFiles.length > 0 ? `${recovery.changedFiles.length}${recovery.truncated ? "+" : ""}` : "0";
|
|
145
|
+
return [
|
|
146
|
+
`${indent}Recovery needed: review the diff and artifacts before resuming or launching dependent stages.`,
|
|
147
|
+
`${indent}Recovery evidence: requested report: ${recovery.reportStatus ?? "unknown"}; changed tracked files: ${changedFiles} (${changedFileCount}); classification: ${recovery.reason ?? recovery.termination}`,
|
|
148
|
+
];
|
|
149
|
+
}
|
|
150
|
+
|
|
113
151
|
export function buildTimeoutRecoverySummary(input: {
|
|
114
152
|
termination: "timed-out" | "stopped";
|
|
115
153
|
evidence: TrackedMutationEvidence;
|
|
154
|
+
requiredOutputMissing?: boolean;
|
|
116
155
|
currentTool?: string;
|
|
117
156
|
currentToolArgs?: string;
|
|
118
157
|
currentPath?: string;
|
|
@@ -122,11 +161,19 @@ export function buildTimeoutRecoverySummary(input: {
|
|
|
122
161
|
}): TimeoutRecoverySummary {
|
|
123
162
|
const warning = "Inspect partial changes before retrying or resuming the child.";
|
|
124
163
|
const changedFiles = input.evidence.changedFiles.slice(0, MAX_TIMEOUT_FILES);
|
|
164
|
+
let reportStatus: "missing" | "written" | "not-requested" = "not-requested";
|
|
165
|
+
if (input.requiredOutputMissing === true) reportStatus = "missing";
|
|
166
|
+
else if (input.requiredOutputMissing === false) reportStatus = "written";
|
|
167
|
+
const recoveryNeeded = input.termination === "timed-out"
|
|
168
|
+
&& reportStatus === "missing"
|
|
169
|
+
&& input.evidence.changedFiles.length > 0;
|
|
125
170
|
const lines = [
|
|
126
171
|
"Recovery summary:",
|
|
127
172
|
`- termination: ${input.termination}`,
|
|
128
173
|
`- changed tracked files: ${input.evidence.unavailable ? `unavailable (${input.evidence.unavailable})` : formatPathList(input.evidence.changedFiles)}`,
|
|
129
174
|
];
|
|
175
|
+
if (input.requiredOutputMissing !== undefined) lines.push(`- requested report: ${reportStatus}`);
|
|
176
|
+
if (recoveryNeeded) lines.push("- Recovery needed: review the diff and artifacts before resuming or launching dependent stages.");
|
|
130
177
|
if (input.currentTool) lines.push(`- active tool: ${input.currentTool}${input.currentToolArgs ? ` — ${input.currentToolArgs}` : ""}`);
|
|
131
178
|
if (input.currentPath) lines.push(`- active path: ${input.currentPath}`);
|
|
132
179
|
if (input.sessionFile) lines.push(`- session file: ${input.sessionFile}`);
|
|
@@ -138,6 +185,8 @@ export function buildTimeoutRecoverySummary(input: {
|
|
|
138
185
|
termination: input.termination,
|
|
139
186
|
changedFiles,
|
|
140
187
|
truncated: input.evidence.changedFiles.length > changedFiles.length || input.evidence.truncated || undefined,
|
|
188
|
+
...(recoveryNeeded ? { recoveryNeeded: true, reason: "timed-out-with-dirty-worktree" as const } : {}),
|
|
189
|
+
reportStatus,
|
|
141
190
|
currentTool: input.currentTool,
|
|
142
191
|
currentToolArgs: input.currentToolArgs,
|
|
143
192
|
currentPath: input.currentPath,
|