@selesai/code 0.1.4 → 0.1.6
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/dist/config.d.ts +21 -4
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +71 -4
- package/dist/config.js.map +1 -1
- package/dist/core/package-manager.d.ts +1 -0
- package/dist/core/package-manager.d.ts.map +1 -1
- package/dist/core/package-manager.js +6 -1
- package/dist/core/package-manager.js.map +1 -1
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +3 -2
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/settings-manager.d.ts +1 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/extensions/pi-intercom/LICENSE +21 -0
- package/dist/extensions/pi-intercom/README.md +484 -0
- package/dist/extensions/pi-intercom/broker/broker.ts +345 -0
- package/dist/extensions/pi-intercom/broker/client.ts +535 -0
- package/dist/extensions/pi-intercom/broker/framing.ts +57 -0
- package/dist/extensions/pi-intercom/broker/paths.ts +20 -0
- package/dist/extensions/pi-intercom/broker/spawn.ts +307 -0
- package/dist/extensions/pi-intercom/config.ts +108 -0
- package/dist/extensions/pi-intercom/index.ts +1778 -0
- package/dist/extensions/pi-intercom/package.json +38 -0
- package/dist/extensions/pi-intercom/reply-tracker.ts +102 -0
- package/dist/extensions/pi-intercom/skills/pi-intercom/SKILL.md +512 -0
- package/dist/extensions/pi-intercom/types.ts +46 -0
- package/dist/extensions/pi-intercom/ui/compose.ts +139 -0
- package/dist/extensions/pi-intercom/ui/inline-message.ts +76 -0
- package/dist/extensions/pi-intercom/ui/session-list.ts +162 -0
- package/dist/extensions/pi-rewind-hook/README.md +245 -0
- package/dist/extensions/pi-rewind-hook/index.ts +1445 -0
- package/dist/extensions/pi-rewind-hook/package.json +29 -0
- package/dist/extensions/pi-subagents/README.md +1290 -0
- package/dist/extensions/pi-subagents/agents/context-builder.md +46 -0
- package/dist/extensions/pi-subagents/agents/delegate.md +12 -0
- package/dist/extensions/pi-subagents/agents/oracle.md +73 -0
- package/dist/extensions/pi-subagents/agents/planner.md +55 -0
- package/dist/extensions/pi-subagents/agents/researcher.md +52 -0
- package/dist/extensions/pi-subagents/agents/reviewer.md +79 -0
- package/dist/extensions/pi-subagents/agents/scout.md +50 -0
- package/dist/extensions/pi-subagents/agents/worker.md +55 -0
- package/dist/extensions/pi-subagents/package.json +83 -0
- package/dist/extensions/pi-subagents/prompts/gather-context-and-clarify.md +13 -0
- package/dist/extensions/pi-subagents/prompts/parallel-cleanup.md +59 -0
- package/dist/extensions/pi-subagents/prompts/parallel-context-build.md +55 -0
- package/dist/extensions/pi-subagents/prompts/parallel-handoff-plan.md +61 -0
- package/dist/extensions/pi-subagents/prompts/parallel-research.md +50 -0
- package/dist/extensions/pi-subagents/prompts/parallel-review.md +54 -0
- package/dist/extensions/pi-subagents/prompts/review-loop.md +41 -0
- package/dist/extensions/pi-subagents/skills/pi-subagents/SKILL.md +855 -0
- package/dist/extensions/pi-subagents/src/agents/agent-management.ts +875 -0
- package/dist/extensions/pi-subagents/src/agents/agent-scope.ts +6 -0
- package/dist/extensions/pi-subagents/src/agents/agent-selection.ts +25 -0
- package/dist/extensions/pi-subagents/src/agents/agent-serializer.ts +110 -0
- package/dist/extensions/pi-subagents/src/agents/agents.ts +1379 -0
- package/dist/extensions/pi-subagents/src/agents/chain-serializer.ts +252 -0
- package/dist/extensions/pi-subagents/src/agents/frontmatter.ts +93 -0
- package/dist/extensions/pi-subagents/src/agents/identity.ts +30 -0
- package/dist/extensions/pi-subagents/src/agents/proactive-skills.ts +191 -0
- package/dist/extensions/pi-subagents/src/agents/skills.ts +729 -0
- package/dist/extensions/pi-subagents/src/extension/config.ts +16 -0
- package/dist/extensions/pi-subagents/src/extension/control-notices.ts +92 -0
- package/dist/extensions/pi-subagents/src/extension/doctor.ts +220 -0
- package/dist/extensions/pi-subagents/src/extension/fanout-child.ts +171 -0
- package/dist/extensions/pi-subagents/src/extension/index.ts +627 -0
- package/dist/extensions/pi-subagents/src/extension/schemas.ts +265 -0
- package/dist/extensions/pi-subagents/src/intercom/intercom-bridge.ts +412 -0
- package/dist/extensions/pi-subagents/src/intercom/result-intercom.ts +377 -0
- package/dist/extensions/pi-subagents/src/profiles/profiles.ts +637 -0
- package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +916 -0
- package/dist/extensions/pi-subagents/src/runs/background/async-job-tracker.ts +355 -0
- package/dist/extensions/pi-subagents/src/runs/background/async-resume.ts +391 -0
- package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +338 -0
- package/dist/extensions/pi-subagents/src/runs/background/chain-append.ts +282 -0
- package/dist/extensions/pi-subagents/src/runs/background/chain-root-attachment.ts +161 -0
- package/dist/extensions/pi-subagents/src/runs/background/completion-dedupe.ts +63 -0
- package/dist/extensions/pi-subagents/src/runs/background/control-channel.ts +177 -0
- package/dist/extensions/pi-subagents/src/runs/background/notify.ts +108 -0
- package/dist/extensions/pi-subagents/src/runs/background/parallel-groups.ts +45 -0
- package/dist/extensions/pi-subagents/src/runs/background/result-watcher.ts +316 -0
- package/dist/extensions/pi-subagents/src/runs/background/run-id-resolver.ts +83 -0
- package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +273 -0
- package/dist/extensions/pi-subagents/src/runs/background/stale-run-reconciler.ts +341 -0
- package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +2458 -0
- package/dist/extensions/pi-subagents/src/runs/background/top-level-async.ts +13 -0
- package/dist/extensions/pi-subagents/src/runs/foreground/chain-clarify.ts +1333 -0
- package/dist/extensions/pi-subagents/src/runs/foreground/chain-execution.ts +1217 -0
- package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +1092 -0
- package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +3125 -0
- package/dist/extensions/pi-subagents/src/runs/shared/acceptance.ts +856 -0
- package/dist/extensions/pi-subagents/src/runs/shared/chain-outputs.ts +116 -0
- package/dist/extensions/pi-subagents/src/runs/shared/completion-guard.ts +147 -0
- package/dist/extensions/pi-subagents/src/runs/shared/dynamic-fanout.ts +295 -0
- package/dist/extensions/pi-subagents/src/runs/shared/long-running-guard.ts +175 -0
- package/dist/extensions/pi-subagents/src/runs/shared/mcp-direct-tool-allowlist.ts +365 -0
- package/dist/extensions/pi-subagents/src/runs/shared/model-fallback.ts +141 -0
- package/dist/extensions/pi-subagents/src/runs/shared/nested-events.ts +819 -0
- package/dist/extensions/pi-subagents/src/runs/shared/nested-path.ts +52 -0
- package/dist/extensions/pi-subagents/src/runs/shared/nested-render.ts +115 -0
- package/dist/extensions/pi-subagents/src/runs/shared/parallel-utils.ts +149 -0
- package/dist/extensions/pi-subagents/src/runs/shared/pi-args.ts +240 -0
- package/dist/extensions/pi-subagents/src/runs/shared/pi-spawn.ts +115 -0
- package/dist/extensions/pi-subagents/src/runs/shared/run-history.ts +60 -0
- package/dist/extensions/pi-subagents/src/runs/shared/single-output.ts +178 -0
- package/dist/extensions/pi-subagents/src/runs/shared/structured-output.ts +77 -0
- package/dist/extensions/pi-subagents/src/runs/shared/subagent-control.ts +223 -0
- package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +222 -0
- package/dist/extensions/pi-subagents/src/runs/shared/workflow-graph.ts +206 -0
- package/dist/extensions/pi-subagents/src/runs/shared/worktree.ts +577 -0
- package/dist/extensions/pi-subagents/src/shared/artifacts.ts +98 -0
- package/dist/extensions/pi-subagents/src/shared/atomic-json.ts +73 -0
- package/dist/extensions/pi-subagents/src/shared/file-coalescer.ts +40 -0
- package/dist/extensions/pi-subagents/src/shared/fork-context.ts +76 -0
- package/dist/extensions/pi-subagents/src/shared/formatters.ts +133 -0
- package/dist/extensions/pi-subagents/src/shared/jsonl-writer.ts +81 -0
- package/dist/extensions/pi-subagents/src/shared/model-info.ts +78 -0
- package/dist/extensions/pi-subagents/src/shared/post-exit-stdio-guard.ts +85 -0
- package/dist/extensions/pi-subagents/src/shared/session-identity.ts +10 -0
- package/dist/extensions/pi-subagents/src/shared/session-tokens.ts +44 -0
- package/dist/extensions/pi-subagents/src/shared/settings.ts +448 -0
- package/dist/extensions/pi-subagents/src/shared/status-format.ts +49 -0
- package/dist/extensions/pi-subagents/src/shared/types.ts +1064 -0
- package/dist/extensions/pi-subagents/src/shared/utils.ts +469 -0
- package/dist/extensions/pi-subagents/src/slash/prompt-template-bridge.ts +420 -0
- package/dist/extensions/pi-subagents/src/slash/slash-bridge.ts +176 -0
- package/dist/extensions/pi-subagents/src/slash/slash-commands.ts +1165 -0
- package/dist/extensions/pi-subagents/src/slash/slash-live-state.ts +292 -0
- package/dist/extensions/pi-subagents/src/tui/render-helpers.ts +80 -0
- package/dist/extensions/pi-subagents/src/tui/render.ts +1728 -0
- package/dist/extensions/quick.ts +630 -0
- package/dist/modes/index.d.ts +1 -1
- package/dist/modes/index.d.ts.map +1 -1
- package/dist/modes/index.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +4 -3
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-types.d.ts +3 -2
- package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-types.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { formatDuration, formatModelThinking, formatTokens, shortenPath } from "../../shared/formatters.ts";
|
|
4
|
+
import { formatActivityLabel, formatParallelOutcome } from "../../shared/status-format.ts";
|
|
5
|
+
import { type ActivityState, type AsyncJobStep, type AsyncParallelGroupStatus, type AsyncStatus, type NestedRunSummary, type SubagentRunMode, type TokenUsage } from "../../shared/types.ts";
|
|
6
|
+
import { readStatus } from "../../shared/utils.ts";
|
|
7
|
+
import { attachRootChildrenToSteps, findNestedRouteForRootId, projectNestedRegistryForRoot } from "../shared/nested-events.ts";
|
|
8
|
+
import { formatNestedRunStatusLines } from "../shared/nested-render.ts";
|
|
9
|
+
import { flatToLogicalStepIndex, normalizeParallelGroups } from "./parallel-groups.ts";
|
|
10
|
+
import { reconcileAsyncRun, reconcileNestedAsyncDescendants } from "./stale-run-reconciler.ts";
|
|
11
|
+
|
|
12
|
+
interface AsyncRunStepSummary {
|
|
13
|
+
index: number;
|
|
14
|
+
agent: string;
|
|
15
|
+
label?: string;
|
|
16
|
+
phase?: string;
|
|
17
|
+
outputName?: string;
|
|
18
|
+
structured?: boolean;
|
|
19
|
+
status: AsyncJobStep["status"];
|
|
20
|
+
activityState?: ActivityState;
|
|
21
|
+
lastActivityAt?: number;
|
|
22
|
+
currentTool?: string;
|
|
23
|
+
currentToolArgs?: string;
|
|
24
|
+
currentToolStartedAt?: number;
|
|
25
|
+
currentPath?: string;
|
|
26
|
+
recentTools?: Array<{ tool: string; args: string; endMs: number }>;
|
|
27
|
+
recentOutput?: string[];
|
|
28
|
+
turnCount?: number;
|
|
29
|
+
toolCount?: number;
|
|
30
|
+
durationMs?: number;
|
|
31
|
+
tokens?: TokenUsage;
|
|
32
|
+
skills?: string[];
|
|
33
|
+
model?: string;
|
|
34
|
+
thinking?: string;
|
|
35
|
+
attemptedModels?: string[];
|
|
36
|
+
error?: string;
|
|
37
|
+
children?: NestedRunSummary[];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface AsyncRunSummary {
|
|
41
|
+
id: string;
|
|
42
|
+
asyncDir: string;
|
|
43
|
+
sessionId?: string;
|
|
44
|
+
state: "queued" | "running" | "complete" | "failed" | "paused";
|
|
45
|
+
activityState?: ActivityState;
|
|
46
|
+
lastActivityAt?: number;
|
|
47
|
+
currentTool?: string;
|
|
48
|
+
currentToolStartedAt?: number;
|
|
49
|
+
currentPath?: string;
|
|
50
|
+
turnCount?: number;
|
|
51
|
+
toolCount?: number;
|
|
52
|
+
mode: SubagentRunMode;
|
|
53
|
+
cwd?: string;
|
|
54
|
+
startedAt: number;
|
|
55
|
+
lastUpdate?: number;
|
|
56
|
+
endedAt?: number;
|
|
57
|
+
currentStep?: number;
|
|
58
|
+
chainStepCount?: number;
|
|
59
|
+
pendingAppends?: number;
|
|
60
|
+
parallelGroups?: AsyncParallelGroupStatus[];
|
|
61
|
+
steps: AsyncRunStepSummary[];
|
|
62
|
+
sessionDir?: string;
|
|
63
|
+
outputFile?: string;
|
|
64
|
+
totalTokens?: TokenUsage;
|
|
65
|
+
sessionFile?: string;
|
|
66
|
+
nestedChildren?: NestedRunSummary[];
|
|
67
|
+
nestedWarnings?: string[];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
interface AsyncRunListOptions {
|
|
71
|
+
states?: Array<AsyncRunSummary["state"]>;
|
|
72
|
+
sessionId?: string;
|
|
73
|
+
limit?: number;
|
|
74
|
+
resultsDir?: string;
|
|
75
|
+
kill?: (pid: number, signal?: NodeJS.Signals | 0) => boolean;
|
|
76
|
+
now?: () => number;
|
|
77
|
+
reconcile?: boolean;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function getErrorMessage(error: unknown): string {
|
|
81
|
+
return error instanceof Error ? error.message : String(error);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function isNotFoundError(error: unknown): boolean {
|
|
85
|
+
return typeof error === "object"
|
|
86
|
+
&& error !== null
|
|
87
|
+
&& "code" in error
|
|
88
|
+
&& (error as NodeJS.ErrnoException).code === "ENOENT";
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function isAsyncRunDir(root: string, entry: string): boolean {
|
|
92
|
+
const entryPath = path.join(root, entry);
|
|
93
|
+
try {
|
|
94
|
+
return fs.statSync(entryPath).isDirectory();
|
|
95
|
+
} catch (error) {
|
|
96
|
+
if (isNotFoundError(error)) return false;
|
|
97
|
+
throw new Error(`Failed to inspect async run path '${entryPath}': ${getErrorMessage(error)}`, {
|
|
98
|
+
cause: error instanceof Error ? error : undefined,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function outputFileMtime(outputFile: string | undefined): number | undefined {
|
|
104
|
+
if (!outputFile) return undefined;
|
|
105
|
+
try {
|
|
106
|
+
return fs.statSync(outputFile).mtimeMs;
|
|
107
|
+
} catch (error) {
|
|
108
|
+
if (isNotFoundError(error)) return undefined;
|
|
109
|
+
throw new Error(`Failed to inspect async output file '${outputFile}': ${getErrorMessage(error)}`, {
|
|
110
|
+
cause: error instanceof Error ? error : undefined,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function deriveAsyncActivityState(asyncDir: string, status: AsyncStatus): { activityState?: ActivityState; lastActivityAt?: number } {
|
|
116
|
+
if (status.state !== "running") return { activityState: status.activityState, lastActivityAt: status.lastActivityAt };
|
|
117
|
+
const outputPath = status.outputFile ? (path.isAbsolute(status.outputFile) ? status.outputFile : path.join(asyncDir, status.outputFile)) : undefined;
|
|
118
|
+
const currentStep = typeof status.currentStep === "number" ? status.steps?.[status.currentStep] : undefined;
|
|
119
|
+
return {
|
|
120
|
+
activityState: status.activityState,
|
|
121
|
+
lastActivityAt: status.lastActivityAt ?? outputFileMtime(outputPath) ?? currentStep?.lastActivityAt ?? currentStep?.startedAt ?? status.startedAt,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function statusToSummary(asyncDir: string, status: AsyncStatus & { cwd?: string }, nestedWarnings: string[] = []): AsyncRunSummary {
|
|
126
|
+
if (status.sessionId !== undefined && typeof status.sessionId !== "string") {
|
|
127
|
+
throw new Error(`Invalid async status '${path.join(asyncDir, "status.json")}': sessionId must be a string.`);
|
|
128
|
+
}
|
|
129
|
+
const { activityState, lastActivityAt } = deriveAsyncActivityState(asyncDir, status);
|
|
130
|
+
const steps = status.steps ?? [];
|
|
131
|
+
const chainStepCount = status.chainStepCount ?? steps.length;
|
|
132
|
+
const parallelGroups = normalizeParallelGroups(status.parallelGroups, steps.length, chainStepCount);
|
|
133
|
+
let nestedChildren: NestedRunSummary[] = [];
|
|
134
|
+
if (nestedWarnings.length === 0) {
|
|
135
|
+
try {
|
|
136
|
+
nestedChildren = projectNestedRegistryForRoot(status.runId || path.basename(asyncDir))?.children ?? [];
|
|
137
|
+
} catch (error) {
|
|
138
|
+
nestedWarnings.push(`Nested status unavailable: ${getErrorMessage(error)}`);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
const summarizedSteps = steps.map((step, index) => {
|
|
142
|
+
const stepActivityState = step.activityState;
|
|
143
|
+
const stepLastActivityAt = step.lastActivityAt;
|
|
144
|
+
return {
|
|
145
|
+
index,
|
|
146
|
+
agent: step.agent,
|
|
147
|
+
...(step.label ? { label: step.label } : {}),
|
|
148
|
+
...(step.phase ? { phase: step.phase } : {}),
|
|
149
|
+
...(step.outputName ? { outputName: step.outputName } : {}),
|
|
150
|
+
...(step.structured ? { structured: step.structured } : {}),
|
|
151
|
+
status: step.status,
|
|
152
|
+
...(stepActivityState ? { activityState: stepActivityState } : {}),
|
|
153
|
+
...(stepLastActivityAt ? { lastActivityAt: stepLastActivityAt } : {}),
|
|
154
|
+
...(step.currentTool ? { currentTool: step.currentTool } : {}),
|
|
155
|
+
...(step.currentToolArgs ? { currentToolArgs: step.currentToolArgs } : {}),
|
|
156
|
+
...(step.currentToolStartedAt ? { currentToolStartedAt: step.currentToolStartedAt } : {}),
|
|
157
|
+
...(step.currentPath ? { currentPath: step.currentPath } : {}),
|
|
158
|
+
...(step.recentTools ? { recentTools: step.recentTools.map((tool) => ({ ...tool })) } : {}),
|
|
159
|
+
...(step.recentOutput ? { recentOutput: [...step.recentOutput] } : {}),
|
|
160
|
+
...(step.turnCount !== undefined ? { turnCount: step.turnCount } : {}),
|
|
161
|
+
...(step.toolCount !== undefined ? { toolCount: step.toolCount } : {}),
|
|
162
|
+
...(step.durationMs !== undefined ? { durationMs: step.durationMs } : {}),
|
|
163
|
+
...(step.tokens ? { tokens: step.tokens } : {}),
|
|
164
|
+
...(step.skills ? { skills: step.skills } : {}),
|
|
165
|
+
...(step.model ? { model: step.model } : {}),
|
|
166
|
+
...(step.thinking ? { thinking: step.thinking } : {}),
|
|
167
|
+
...(step.attemptedModels ? { attemptedModels: step.attemptedModels } : {}),
|
|
168
|
+
...(step.error ? { error: step.error } : {}),
|
|
169
|
+
...(step.children?.length ? { children: step.children } : {}),
|
|
170
|
+
};
|
|
171
|
+
});
|
|
172
|
+
attachRootChildrenToSteps(status.runId || path.basename(asyncDir), summarizedSteps, nestedChildren);
|
|
173
|
+
return {
|
|
174
|
+
id: status.runId || path.basename(asyncDir),
|
|
175
|
+
asyncDir,
|
|
176
|
+
...(status.sessionId ? { sessionId: status.sessionId } : {}),
|
|
177
|
+
state: status.state,
|
|
178
|
+
activityState,
|
|
179
|
+
lastActivityAt,
|
|
180
|
+
currentTool: status.currentTool,
|
|
181
|
+
currentToolStartedAt: status.currentToolStartedAt,
|
|
182
|
+
currentPath: status.currentPath,
|
|
183
|
+
turnCount: status.turnCount,
|
|
184
|
+
toolCount: status.toolCount,
|
|
185
|
+
mode: status.mode,
|
|
186
|
+
cwd: status.cwd,
|
|
187
|
+
startedAt: status.startedAt,
|
|
188
|
+
lastUpdate: status.lastUpdate,
|
|
189
|
+
endedAt: status.endedAt,
|
|
190
|
+
currentStep: status.currentStep,
|
|
191
|
+
...(status.chainStepCount !== undefined ? { chainStepCount: status.chainStepCount } : {}),
|
|
192
|
+
...(status.pendingAppends !== undefined ? { pendingAppends: status.pendingAppends } : {}),
|
|
193
|
+
...(parallelGroups.length ? { parallelGroups } : {}),
|
|
194
|
+
steps: summarizedSteps,
|
|
195
|
+
...(nestedChildren.length ? { nestedChildren } : {}),
|
|
196
|
+
...(nestedWarnings.length ? { nestedWarnings } : {}),
|
|
197
|
+
...(status.sessionDir ? { sessionDir: status.sessionDir } : {}),
|
|
198
|
+
...(status.outputFile ? { outputFile: status.outputFile } : {}),
|
|
199
|
+
...(status.totalTokens ? { totalTokens: status.totalTokens } : {}),
|
|
200
|
+
...(status.sessionFile ? { sessionFile: status.sessionFile } : {}),
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function sortRuns(runs: AsyncRunSummary[]): AsyncRunSummary[] {
|
|
205
|
+
const rank = (state: AsyncRunSummary["state"]): number => {
|
|
206
|
+
switch (state) {
|
|
207
|
+
case "running": return 0;
|
|
208
|
+
case "queued": return 1;
|
|
209
|
+
case "failed": return 2;
|
|
210
|
+
case "paused": return 2;
|
|
211
|
+
case "complete": return 3;
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
return [...runs].sort((a, b) => {
|
|
215
|
+
const byState = rank(a.state) - rank(b.state);
|
|
216
|
+
if (byState !== 0) return byState;
|
|
217
|
+
const aTime = a.lastUpdate ?? a.endedAt ?? a.startedAt;
|
|
218
|
+
const bTime = b.lastUpdate ?? b.endedAt ?? b.startedAt;
|
|
219
|
+
return bTime - aTime;
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export function listAsyncRuns(asyncDirRoot: string, options: AsyncRunListOptions = {}): AsyncRunSummary[] {
|
|
224
|
+
let entries: string[];
|
|
225
|
+
try {
|
|
226
|
+
entries = fs.readdirSync(asyncDirRoot).filter((entry) => isAsyncRunDir(asyncDirRoot, entry));
|
|
227
|
+
} catch (error) {
|
|
228
|
+
if (isNotFoundError(error)) return [];
|
|
229
|
+
throw new Error(`Failed to list async runs in '${asyncDirRoot}': ${getErrorMessage(error)}`, {
|
|
230
|
+
cause: error instanceof Error ? error : undefined,
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const allowedStates = options.states ? new Set(options.states) : undefined;
|
|
235
|
+
const runs: AsyncRunSummary[] = [];
|
|
236
|
+
for (const entry of entries) {
|
|
237
|
+
const asyncDir = path.join(asyncDirRoot, entry);
|
|
238
|
+
const reconciliation = options.reconcile === false
|
|
239
|
+
? undefined
|
|
240
|
+
: reconcileAsyncRun(asyncDir, { resultsDir: options.resultsDir, kill: options.kill, now: options.now });
|
|
241
|
+
const status = (reconciliation?.status ?? readStatus(asyncDir)) as (AsyncStatus & { cwd?: string }) | null;
|
|
242
|
+
if (!status) continue;
|
|
243
|
+
const nestedWarnings: string[] = [];
|
|
244
|
+
try {
|
|
245
|
+
const nestedRoute = findNestedRouteForRootId(status.runId || path.basename(asyncDir));
|
|
246
|
+
if (nestedRoute) reconcileNestedAsyncDescendants(nestedRoute, { resultsDir: options.resultsDir, kill: options.kill, now: options.now });
|
|
247
|
+
} catch (error) {
|
|
248
|
+
nestedWarnings.push(`Nested status unavailable: ${getErrorMessage(error)}`);
|
|
249
|
+
}
|
|
250
|
+
const summary = statusToSummary(asyncDir, status, nestedWarnings);
|
|
251
|
+
if (allowedStates && !allowedStates.has(summary.state)) continue;
|
|
252
|
+
if (options.sessionId && summary.sessionId !== options.sessionId) continue;
|
|
253
|
+
runs.push(summary);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const sorted = sortRuns(runs);
|
|
257
|
+
return options.limit !== undefined ? sorted.slice(0, options.limit) : sorted;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function formatActivityFacts(input: { activityState?: ActivityState; lastActivityAt?: number; currentTool?: string; currentToolStartedAt?: number; currentPath?: string; turnCount?: number; toolCount?: number }): string | undefined {
|
|
261
|
+
const facts: string[] = [];
|
|
262
|
+
if (input.currentTool && input.currentToolStartedAt !== undefined) facts.push(`tool ${input.currentTool} ${formatDuration(Math.max(0, Date.now() - input.currentToolStartedAt))}`);
|
|
263
|
+
else if (input.currentTool) facts.push(`tool ${input.currentTool}`);
|
|
264
|
+
if (input.currentPath) facts.push(shortenPath(input.currentPath));
|
|
265
|
+
if (input.turnCount !== undefined) facts.push(`${input.turnCount} turns`);
|
|
266
|
+
if (input.toolCount !== undefined) facts.push(`${input.toolCount} tools`);
|
|
267
|
+
const activity = formatActivityLabel(input.lastActivityAt, input.activityState);
|
|
268
|
+
return activity || facts.length ? [activity, ...facts].filter(Boolean).join(" | ") : undefined;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function formatStepLine(step: AsyncRunStepSummary): string {
|
|
272
|
+
const display = step.label ? `${step.label} (${step.agent})` : step.agent;
|
|
273
|
+
const phase = step.phase ? `[${step.phase}] ` : "";
|
|
274
|
+
const parts = [`${step.index + 1}. ${phase}${display}`, step.status];
|
|
275
|
+
const activity = formatActivityFacts(step);
|
|
276
|
+
if (activity) parts.push(activity);
|
|
277
|
+
const modelThinking = formatModelThinking(step.model, step.thinking);
|
|
278
|
+
if (modelThinking) parts.push(modelThinking);
|
|
279
|
+
if (step.durationMs !== undefined) parts.push(formatDuration(step.durationMs));
|
|
280
|
+
if (step.tokens) parts.push(`${formatTokens(step.tokens.total)} tok`);
|
|
281
|
+
return parts.join(" | ");
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
export function formatAsyncRunOutputPath(run: Pick<AsyncRunSummary, "asyncDir" | "outputFile">): string | undefined {
|
|
285
|
+
if (!run.outputFile) return undefined;
|
|
286
|
+
return path.isAbsolute(run.outputFile) ? run.outputFile : path.join(run.asyncDir, run.outputFile);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
export function formatAsyncRunProgressLabel(run: Pick<AsyncRunSummary, "mode" | "state" | "currentStep" | "chainStepCount" | "parallelGroups" | "steps">): string {
|
|
290
|
+
const stepCount = run.steps.length || 1;
|
|
291
|
+
const chainStepCount = run.chainStepCount ?? stepCount;
|
|
292
|
+
const groups = normalizeParallelGroups(run.parallelGroups, run.steps.length, chainStepCount);
|
|
293
|
+
const activeGroup = run.currentStep !== undefined
|
|
294
|
+
? groups.find((group) => run.currentStep! >= group.start && run.currentStep! < group.start + group.count)
|
|
295
|
+
: undefined;
|
|
296
|
+
if (activeGroup) {
|
|
297
|
+
const groupSteps = run.steps.slice(activeGroup.start, activeGroup.start + activeGroup.count);
|
|
298
|
+
const groupLabel = formatParallelOutcome(groupSteps, activeGroup.count, { showRunning: run.state === "running" });
|
|
299
|
+
if (run.mode === "parallel") return groupLabel;
|
|
300
|
+
return `step ${activeGroup.stepIndex + 1}/${chainStepCount} · parallel group: ${groupLabel}`;
|
|
301
|
+
}
|
|
302
|
+
if (run.mode === "parallel") return formatParallelOutcome(run.steps, stepCount, { showRunning: run.state === "running" });
|
|
303
|
+
if (run.mode === "chain" && run.currentStep !== undefined && groups.length > 0) {
|
|
304
|
+
const logicalStep = flatToLogicalStepIndex(run.currentStep, chainStepCount, groups);
|
|
305
|
+
return `step ${logicalStep + 1}/${chainStepCount}`;
|
|
306
|
+
}
|
|
307
|
+
return run.currentStep !== undefined ? `step ${run.currentStep + 1}/${stepCount}` : `steps ${stepCount}`;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function formatRunHeader(run: AsyncRunSummary): string {
|
|
311
|
+
const stepLabel = formatAsyncRunProgressLabel(run);
|
|
312
|
+
const cwd = run.cwd ? shortenPath(run.cwd) : shortenPath(run.asyncDir);
|
|
313
|
+
const activity = formatActivityFacts(run);
|
|
314
|
+
const pending = run.pendingAppends ? ` | ${run.pendingAppends} pending append${run.pendingAppends === 1 ? "" : "s"}` : "";
|
|
315
|
+
return `${run.id} | ${run.state}${activity ? ` | ${activity}` : ""} | ${run.mode} | ${stepLabel}${pending} | ${cwd}`;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
export function formatAsyncRunList(runs: AsyncRunSummary[], heading = "Active async runs"): string {
|
|
319
|
+
if (runs.length === 0) return `No ${heading.toLowerCase()}.`;
|
|
320
|
+
|
|
321
|
+
const lines = [`${heading}: ${runs.length}`, ""];
|
|
322
|
+
for (const run of runs) {
|
|
323
|
+
lines.push(`- ${formatRunHeader(run)}`);
|
|
324
|
+
for (const step of run.steps) {
|
|
325
|
+
lines.push(` ${formatStepLine(step)}`);
|
|
326
|
+
lines.push(...formatNestedRunStatusLines(step.children, { indent: " ", maxLines: 12 }));
|
|
327
|
+
}
|
|
328
|
+
const attached = new Set(run.steps.flatMap((step) => step.children?.map((child) => child.id) ?? []));
|
|
329
|
+
const unattached = run.nestedChildren?.filter((child) => !attached.has(child.id)) ?? [];
|
|
330
|
+
lines.push(...formatNestedRunStatusLines(unattached, { indent: " ", maxLines: 12 }));
|
|
331
|
+
for (const warning of run.nestedWarnings ?? []) lines.push(` Warning: ${warning}`);
|
|
332
|
+
const outputPath = formatAsyncRunOutputPath(run);
|
|
333
|
+
if (outputPath) lines.push(` output: ${shortenPath(outputPath)}`);
|
|
334
|
+
if (run.sessionFile) lines.push(` session: ${shortenPath(run.sessionFile)}`);
|
|
335
|
+
lines.push("");
|
|
336
|
+
}
|
|
337
|
+
return lines.join("\n").trimEnd();
|
|
338
|
+
}
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import * as fs from "node:fs";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
import { writeAtomicJson } from "../../shared/atomic-json.ts";
|
|
5
|
+
import { appendJsonl } from "../../shared/artifacts.ts";
|
|
6
|
+
import type { AsyncParallelGroupStatus, AsyncStatus, WorkflowGraphNode, WorkflowGraphSnapshot } from "../../shared/types.ts";
|
|
7
|
+
import { readStatus } from "../../shared/utils.ts";
|
|
8
|
+
import type { DynamicRunnerGroup, ParallelStepGroup, RunnerStep, RunnerSubagentStep } from "../shared/parallel-utils.ts";
|
|
9
|
+
import { isDynamicRunnerGroup, isParallelGroup } from "../shared/parallel-utils.ts";
|
|
10
|
+
|
|
11
|
+
const APPEND_REQUESTS_DIR = "append-requests";
|
|
12
|
+
|
|
13
|
+
export interface ChainAppendRequest {
|
|
14
|
+
id: string;
|
|
15
|
+
createdAt: number;
|
|
16
|
+
steps: RunnerStep[];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface ChainAppendResult {
|
|
20
|
+
request: ChainAppendRequest;
|
|
21
|
+
pendingCount: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
type StatusStep = NonNullable<AsyncStatus["steps"]>[number];
|
|
25
|
+
|
|
26
|
+
function appendDir(asyncDir: string): string {
|
|
27
|
+
return path.join(asyncDir, APPEND_REQUESTS_DIR);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function appendRequestPath(asyncDir: string, request: ChainAppendRequest): string {
|
|
31
|
+
return path.join(appendDir(asyncDir), `${request.createdAt}-${request.id}.json`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function listAppendRequestFiles(asyncDir: string): string[] {
|
|
35
|
+
const dir = appendDir(asyncDir);
|
|
36
|
+
try {
|
|
37
|
+
return fs.readdirSync(dir)
|
|
38
|
+
.filter((entry) => entry.endsWith(".json"))
|
|
39
|
+
.map((entry) => path.join(dir, entry))
|
|
40
|
+
.sort();
|
|
41
|
+
} catch (error) {
|
|
42
|
+
if ((error as NodeJS.ErrnoException).code === "ENOENT") return [];
|
|
43
|
+
throw error;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function countPendingChainAppendRequests(asyncDir: string): number {
|
|
48
|
+
return listAppendRequestFiles(asyncDir).length;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function runnerStepOutputNames(steps: RunnerStep[]): string[] {
|
|
52
|
+
const names: string[] = [];
|
|
53
|
+
for (const step of steps) {
|
|
54
|
+
if (isParallelGroup(step)) {
|
|
55
|
+
names.push(...step.parallel.map((task) => task.outputName).filter((name): name is string => Boolean(name)));
|
|
56
|
+
} else if (isDynamicRunnerGroup(step)) {
|
|
57
|
+
if (step.collect.as) names.push(step.collect.as);
|
|
58
|
+
} else if (step.outputName) {
|
|
59
|
+
names.push(step.outputName);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return names;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function enqueueChainAppendRequest(input: {
|
|
66
|
+
asyncDir: string;
|
|
67
|
+
runId: string;
|
|
68
|
+
steps: RunnerStep[];
|
|
69
|
+
now?: number;
|
|
70
|
+
}): ChainAppendResult {
|
|
71
|
+
const status = readStatus(input.asyncDir);
|
|
72
|
+
if (!status) throw new Error(`No async run status found for '${input.runId}'.`);
|
|
73
|
+
if (status.runId !== input.runId) throw new Error(`Async run id mismatch: expected '${input.runId}', found '${status.runId}'.`);
|
|
74
|
+
if (status.mode !== "chain") throw new Error(`Run '${input.runId}' is ${status.mode}; only active chain runs accept appended steps.`);
|
|
75
|
+
if (status.state !== "running") throw new Error(`Run '${input.runId}' is ${status.state}; only running chain runs accept appended steps.`);
|
|
76
|
+
const stillInProgress = (status.steps ?? []).some((step) => step.status === "running" || step.status === "pending") || (status.pendingAppends ?? 0) > 0;
|
|
77
|
+
if (!stillInProgress) throw new Error(`Run '${input.runId}' has no running or pending chain steps left; append-step must target an in-progress chain.`);
|
|
78
|
+
if (input.steps.length === 0) throw new Error("append-step requires one chain step.");
|
|
79
|
+
|
|
80
|
+
const request: ChainAppendRequest = {
|
|
81
|
+
id: randomUUID(),
|
|
82
|
+
createdAt: input.now ?? Date.now(),
|
|
83
|
+
steps: input.steps,
|
|
84
|
+
};
|
|
85
|
+
fs.mkdirSync(appendDir(input.asyncDir), { recursive: true });
|
|
86
|
+
writeAtomicJson(appendRequestPath(input.asyncDir, request), request);
|
|
87
|
+
const pendingCount = countPendingChainAppendRequests(input.asyncDir);
|
|
88
|
+
const statusPath = path.join(input.asyncDir, "status.json");
|
|
89
|
+
const updatedStatus = { ...status, pendingAppends: pendingCount, lastUpdate: request.createdAt };
|
|
90
|
+
writeAtomicJson(statusPath, updatedStatus);
|
|
91
|
+
appendJsonl(path.join(input.asyncDir, "events.jsonl"), JSON.stringify({
|
|
92
|
+
type: "subagent.chain.append.requested",
|
|
93
|
+
ts: request.createdAt,
|
|
94
|
+
runId: input.runId,
|
|
95
|
+
requestId: request.id,
|
|
96
|
+
stepCount: input.steps.length,
|
|
97
|
+
pendingAppends: pendingCount,
|
|
98
|
+
}));
|
|
99
|
+
return { request, pendingCount };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function readAppendRequest(filePath: string): ChainAppendRequest | undefined {
|
|
103
|
+
const raw = JSON.parse(fs.readFileSync(filePath, "utf-8")) as Partial<ChainAppendRequest>;
|
|
104
|
+
if (!raw.id || typeof raw.id !== "string") return undefined;
|
|
105
|
+
if (!Number.isFinite(raw.createdAt)) return undefined;
|
|
106
|
+
if (!Array.isArray(raw.steps) || raw.steps.length === 0) return undefined;
|
|
107
|
+
return { id: raw.id, createdAt: raw.createdAt, steps: raw.steps as RunnerStep[] };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function readPendingChainAppendRequests(asyncDir: string): ChainAppendRequest[] {
|
|
111
|
+
return listAppendRequestFiles(asyncDir)
|
|
112
|
+
.map((filePath) => readAppendRequest(filePath))
|
|
113
|
+
.filter((request): request is ChainAppendRequest => Boolean(request))
|
|
114
|
+
.sort((left, right) => left.createdAt - right.createdAt || left.id.localeCompare(right.id));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function consumeChainAppendRequests(asyncDir: string): ChainAppendRequest[] {
|
|
118
|
+
const requests: ChainAppendRequest[] = [];
|
|
119
|
+
for (const filePath of listAppendRequestFiles(asyncDir)) {
|
|
120
|
+
const request = readAppendRequest(filePath);
|
|
121
|
+
try {
|
|
122
|
+
fs.unlinkSync(filePath);
|
|
123
|
+
} catch {
|
|
124
|
+
// The runner should not execute a consumed request twice.
|
|
125
|
+
}
|
|
126
|
+
if (request) requests.push(request);
|
|
127
|
+
}
|
|
128
|
+
return requests.sort((left, right) => left.createdAt - right.createdAt || left.id.localeCompare(right.id));
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function statusStepForTask(task: RunnerSubagentStep): StatusStep {
|
|
132
|
+
return {
|
|
133
|
+
agent: task.agent,
|
|
134
|
+
phase: task.phase,
|
|
135
|
+
label: task.label,
|
|
136
|
+
outputName: task.outputName,
|
|
137
|
+
structured: task.structured,
|
|
138
|
+
status: "pending",
|
|
139
|
+
...(task.sessionFile ? { sessionFile: task.sessionFile } : {}),
|
|
140
|
+
skills: task.skills,
|
|
141
|
+
model: task.model,
|
|
142
|
+
thinking: task.thinking,
|
|
143
|
+
attemptedModels: task.modelCandidates && task.modelCandidates.length > 0 ? task.modelCandidates : task.model ? [task.model] : undefined,
|
|
144
|
+
recentTools: [],
|
|
145
|
+
recentOutput: [],
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function statusStepsForRunnerStep(step: RunnerStep): StatusStep[] {
|
|
150
|
+
if (isParallelGroup(step)) return step.parallel.map(statusStepForTask);
|
|
151
|
+
if (isDynamicRunnerGroup(step)) {
|
|
152
|
+
return [{
|
|
153
|
+
agent: `expand:${step.parallel.agent}`,
|
|
154
|
+
phase: step.phase ?? step.parallel.phase,
|
|
155
|
+
label: step.label ?? step.parallel.label ?? `Dynamic fanout (${step.collect.as})`,
|
|
156
|
+
outputName: step.collect.as,
|
|
157
|
+
structured: Boolean(step.collect.outputSchema),
|
|
158
|
+
status: "pending",
|
|
159
|
+
recentTools: [],
|
|
160
|
+
recentOutput: [],
|
|
161
|
+
}];
|
|
162
|
+
}
|
|
163
|
+
return [statusStepForTask(step)];
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function pushPhase(graph: WorkflowGraphSnapshot, phase: string | undefined, nodeId: string): void {
|
|
167
|
+
if (!phase) return;
|
|
168
|
+
let group = graph.phases.find((candidate) => candidate.title === phase);
|
|
169
|
+
if (!group) {
|
|
170
|
+
group = { title: phase, nodeIds: [] };
|
|
171
|
+
graph.phases.push(group);
|
|
172
|
+
}
|
|
173
|
+
group.nodeIds.push(nodeId);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function graphNodeForSequential(step: RunnerSubagentStep, stepIndex: number, flatIndex: number): WorkflowGraphNode {
|
|
177
|
+
return {
|
|
178
|
+
id: `step-${stepIndex}`,
|
|
179
|
+
kind: "step",
|
|
180
|
+
agent: step.agent,
|
|
181
|
+
phase: step.phase,
|
|
182
|
+
label: step.label?.trim() || step.agent || `Step ${stepIndex + 1}`,
|
|
183
|
+
status: "pending",
|
|
184
|
+
flatIndex,
|
|
185
|
+
stepIndex,
|
|
186
|
+
outputName: step.outputName,
|
|
187
|
+
structured: step.structured,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function graphNodeForParallel(step: ParallelStepGroup, stepIndex: number, flatIndex: number, graph: WorkflowGraphSnapshot): WorkflowGraphNode {
|
|
192
|
+
const children = step.parallel.map((task, taskIndex) => {
|
|
193
|
+
const childId = `step-${stepIndex}-agent-${taskIndex}`;
|
|
194
|
+
pushPhase(graph, task.phase, childId);
|
|
195
|
+
return {
|
|
196
|
+
id: childId,
|
|
197
|
+
kind: "agent" as const,
|
|
198
|
+
agent: task.agent,
|
|
199
|
+
phase: task.phase,
|
|
200
|
+
label: task.label?.trim() || task.agent || `Agent ${taskIndex + 1}`,
|
|
201
|
+
status: "pending" as const,
|
|
202
|
+
flatIndex: flatIndex + taskIndex,
|
|
203
|
+
stepIndex,
|
|
204
|
+
outputName: task.outputName,
|
|
205
|
+
structured: task.structured,
|
|
206
|
+
};
|
|
207
|
+
});
|
|
208
|
+
return {
|
|
209
|
+
id: `step-${stepIndex}`,
|
|
210
|
+
kind: "parallel-group",
|
|
211
|
+
label: step.parallel.length === 1 ? "Parallel task" : `Parallel group (${step.parallel.length})`,
|
|
212
|
+
status: "pending",
|
|
213
|
+
stepIndex,
|
|
214
|
+
children,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function graphNodeForDynamic(step: DynamicRunnerGroup, stepIndex: number): WorkflowGraphNode {
|
|
219
|
+
return {
|
|
220
|
+
id: `step-${stepIndex}`,
|
|
221
|
+
kind: "dynamic-parallel-group",
|
|
222
|
+
label: step.label?.trim() || step.parallel.label?.trim() || `Dynamic fanout (${step.collect.as})`,
|
|
223
|
+
status: "pending",
|
|
224
|
+
stepIndex,
|
|
225
|
+
outputName: step.collect.as,
|
|
226
|
+
structured: Boolean(step.collect.outputSchema),
|
|
227
|
+
dynamic: {
|
|
228
|
+
sourceOutput: step.expand.from.output,
|
|
229
|
+
sourcePath: step.expand.from.path,
|
|
230
|
+
itemName: step.expand.item ?? "item",
|
|
231
|
+
maxItems: step.expand.maxItems,
|
|
232
|
+
collectAs: step.collect.as,
|
|
233
|
+
},
|
|
234
|
+
children: [],
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function appendWorkflowNode(graph: WorkflowGraphSnapshot | undefined, step: RunnerStep, stepIndex: number, flatIndex: number): void {
|
|
239
|
+
if (!graph) return;
|
|
240
|
+
if (isParallelGroup(step)) {
|
|
241
|
+
graph.nodes.push(graphNodeForParallel(step, stepIndex, flatIndex, graph));
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
if (isDynamicRunnerGroup(step)) {
|
|
245
|
+
graph.nodes.push(graphNodeForDynamic(step, stepIndex));
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
const node = graphNodeForSequential(step, stepIndex, flatIndex);
|
|
249
|
+
graph.nodes.push(node);
|
|
250
|
+
pushPhase(graph, step.phase, node.id);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export function appendRunnerStepsToStatus(input: {
|
|
254
|
+
status: AsyncStatus;
|
|
255
|
+
steps: RunnerStep[];
|
|
256
|
+
now?: number;
|
|
257
|
+
pendingAppends?: number;
|
|
258
|
+
}): { addedChainSteps: number; addedFlatSteps: number } {
|
|
259
|
+
let addedChainSteps = 0;
|
|
260
|
+
let addedFlatSteps = 0;
|
|
261
|
+
for (const step of input.steps) {
|
|
262
|
+
const stepIndex = input.status.chainStepCount ?? input.status.steps?.length ?? 0;
|
|
263
|
+
const flatIndex = input.status.steps?.length ?? 0;
|
|
264
|
+
const statusSteps = statusStepsForRunnerStep(step);
|
|
265
|
+
input.status.steps ??= [];
|
|
266
|
+
input.status.steps.push(...statusSteps);
|
|
267
|
+
if (isParallelGroup(step)) {
|
|
268
|
+
input.status.parallelGroups ??= [];
|
|
269
|
+
input.status.parallelGroups.push({ start: flatIndex, count: step.parallel.length, stepIndex });
|
|
270
|
+
} else if (isDynamicRunnerGroup(step)) {
|
|
271
|
+
input.status.parallelGroups ??= [];
|
|
272
|
+
input.status.parallelGroups.push({ start: flatIndex, count: 1, stepIndex });
|
|
273
|
+
}
|
|
274
|
+
appendWorkflowNode(input.status.workflowGraph, step, stepIndex, flatIndex);
|
|
275
|
+
input.status.chainStepCount = stepIndex + 1;
|
|
276
|
+
addedChainSteps++;
|
|
277
|
+
addedFlatSteps += statusSteps.length;
|
|
278
|
+
}
|
|
279
|
+
input.status.pendingAppends = input.pendingAppends ?? 0;
|
|
280
|
+
input.status.lastUpdate = input.now ?? Date.now();
|
|
281
|
+
return { addedChainSteps, addedFlatSteps };
|
|
282
|
+
}
|