agent-relay 3.2.8 → 3.2.10
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/bin/agent-relay-broker-darwin-arm64 +0 -0
- package/bin/agent-relay-broker-darwin-x64 +0 -0
- package/bin/agent-relay-broker-linux-arm64 +0 -0
- package/bin/agent-relay-broker-linux-x64 +0 -0
- package/dist/index.cjs +45361 -23429
- package/dist/src/cli/commands/setup.d.ts +8 -0
- package/dist/src/cli/commands/setup.d.ts.map +1 -1
- package/dist/src/cli/commands/setup.js +42 -0
- package/dist/src/cli/commands/setup.js.map +1 -1
- package/dist/src/cli/relaycast-mcp.d.ts.map +1 -1
- package/dist/src/cli/relaycast-mcp.js +8 -1
- package/dist/src/cli/relaycast-mcp.js.map +1 -1
- package/package.json +13 -11
- package/packages/acp-bridge/package.json +2 -2
- package/packages/config/package.json +1 -1
- package/packages/hooks/package.json +4 -4
- package/packages/memory/package.json +2 -2
- package/packages/openclaw/package.json +3 -3
- package/packages/policy/package.json +2 -2
- package/packages/sdk/dist/workflows/__tests__/cli-session-collector.test.d.ts +2 -0
- package/packages/sdk/dist/workflows/__tests__/cli-session-collector.test.d.ts.map +1 -0
- package/packages/sdk/dist/workflows/__tests__/cli-session-collector.test.js +54 -0
- package/packages/sdk/dist/workflows/__tests__/cli-session-collector.test.js.map +1 -0
- package/packages/sdk/dist/workflows/__tests__/collectors/claude.test.d.ts +2 -0
- package/packages/sdk/dist/workflows/__tests__/collectors/claude.test.d.ts.map +1 -0
- package/packages/sdk/dist/workflows/__tests__/collectors/claude.test.js +85 -0
- package/packages/sdk/dist/workflows/__tests__/collectors/claude.test.js.map +1 -0
- package/packages/sdk/dist/workflows/__tests__/collectors/codex.test.d.ts +2 -0
- package/packages/sdk/dist/workflows/__tests__/collectors/codex.test.d.ts.map +1 -0
- package/packages/sdk/dist/workflows/__tests__/collectors/codex.test.js +67 -0
- package/packages/sdk/dist/workflows/__tests__/collectors/codex.test.js.map +1 -0
- package/packages/sdk/dist/workflows/__tests__/collectors/opencode.test.d.ts +2 -0
- package/packages/sdk/dist/workflows/__tests__/collectors/opencode.test.d.ts.map +1 -0
- package/packages/sdk/dist/workflows/__tests__/collectors/opencode.test.js +119 -0
- package/packages/sdk/dist/workflows/__tests__/collectors/opencode.test.js.map +1 -0
- package/packages/sdk/dist/workflows/__tests__/run-summary-table.test.d.ts +2 -0
- package/packages/sdk/dist/workflows/__tests__/run-summary-table.test.d.ts.map +1 -0
- package/packages/sdk/dist/workflows/__tests__/run-summary-table.test.js +130 -0
- package/packages/sdk/dist/workflows/__tests__/run-summary-table.test.js.map +1 -0
- package/packages/sdk/dist/workflows/__tests__/step-cwd.test.d.ts +2 -0
- package/packages/sdk/dist/workflows/__tests__/step-cwd.test.d.ts.map +1 -0
- package/packages/sdk/dist/workflows/__tests__/step-cwd.test.js +42 -0
- package/packages/sdk/dist/workflows/__tests__/step-cwd.test.js.map +1 -0
- package/packages/sdk/dist/workflows/builder.d.ts +2 -0
- package/packages/sdk/dist/workflows/builder.d.ts.map +1 -1
- package/packages/sdk/dist/workflows/builder.js +4 -0
- package/packages/sdk/dist/workflows/builder.js.map +1 -1
- package/packages/sdk/dist/workflows/cli-session-collector.d.ts +39 -0
- package/packages/sdk/dist/workflows/cli-session-collector.d.ts.map +1 -0
- package/packages/sdk/dist/workflows/cli-session-collector.js +23 -0
- package/packages/sdk/dist/workflows/cli-session-collector.js.map +1 -0
- package/packages/sdk/dist/workflows/cli.js +228 -48
- package/packages/sdk/dist/workflows/cli.js.map +1 -1
- package/packages/sdk/dist/workflows/collectors/claude.d.ts +6 -0
- package/packages/sdk/dist/workflows/collectors/claude.d.ts.map +1 -0
- package/packages/sdk/dist/workflows/collectors/claude.js +330 -0
- package/packages/sdk/dist/workflows/collectors/claude.js.map +1 -0
- package/packages/sdk/dist/workflows/collectors/codex.d.ts +18 -0
- package/packages/sdk/dist/workflows/collectors/codex.d.ts.map +1 -0
- package/packages/sdk/dist/workflows/collectors/codex.js +265 -0
- package/packages/sdk/dist/workflows/collectors/codex.js.map +1 -0
- package/packages/sdk/dist/workflows/collectors/opencode.d.ts +6 -0
- package/packages/sdk/dist/workflows/collectors/opencode.d.ts.map +1 -0
- package/packages/sdk/dist/workflows/collectors/opencode.js +178 -0
- package/packages/sdk/dist/workflows/collectors/opencode.js.map +1 -0
- package/packages/sdk/dist/workflows/index.d.ts +3 -0
- package/packages/sdk/dist/workflows/index.d.ts.map +1 -1
- package/packages/sdk/dist/workflows/index.js +3 -0
- package/packages/sdk/dist/workflows/index.js.map +1 -1
- package/packages/sdk/dist/workflows/listr-renderer.d.ts +26 -0
- package/packages/sdk/dist/workflows/listr-renderer.d.ts.map +1 -0
- package/packages/sdk/dist/workflows/listr-renderer.js +232 -0
- package/packages/sdk/dist/workflows/listr-renderer.js.map +1 -0
- package/packages/sdk/dist/workflows/run-summary-table.d.ts +4 -0
- package/packages/sdk/dist/workflows/run-summary-table.d.ts.map +1 -0
- package/packages/sdk/dist/workflows/run-summary-table.js +98 -0
- package/packages/sdk/dist/workflows/run-summary-table.js.map +1 -0
- package/packages/sdk/dist/workflows/runner.d.ts +11 -0
- package/packages/sdk/dist/workflows/runner.d.ts.map +1 -1
- package/packages/sdk/dist/workflows/runner.js +91 -26
- package/packages/sdk/dist/workflows/runner.js.map +1 -1
- package/packages/sdk/dist/workflows/types.d.ts +2 -0
- package/packages/sdk/dist/workflows/types.d.ts.map +1 -1
- package/packages/sdk/dist/workflows/types.js.map +1 -1
- package/packages/sdk/package.json +5 -3
- package/packages/sdk/src/workflows/__tests__/cli-session-collector.test.ts +64 -0
- package/packages/sdk/src/workflows/__tests__/collectors/claude.test.ts +104 -0
- package/packages/sdk/src/workflows/__tests__/collectors/codex.test.ts +82 -0
- package/packages/sdk/src/workflows/__tests__/collectors/opencode.test.ts +178 -0
- package/packages/sdk/src/workflows/__tests__/run-summary-table.test.ts +160 -0
- package/packages/sdk/src/workflows/__tests__/step-cwd.test.ts +72 -0
- package/packages/sdk/src/workflows/builder.ts +4 -0
- package/packages/sdk/src/workflows/cli-session-collector.ts +58 -0
- package/packages/sdk/src/workflows/cli.ts +289 -50
- package/packages/sdk/src/workflows/collectors/claude.ts +415 -0
- package/packages/sdk/src/workflows/collectors/codex.ts +351 -0
- package/packages/sdk/src/workflows/collectors/opencode.ts +279 -0
- package/packages/sdk/src/workflows/index.ts +3 -0
- package/packages/sdk/src/workflows/listr-renderer.ts +278 -0
- package/packages/sdk/src/workflows/run-summary-table.ts +110 -0
- package/packages/sdk/src/workflows/runner.ts +122 -28
- package/packages/sdk/src/workflows/types.ts +2 -0
- package/packages/sdk/vitest.config.ts +1 -1
- package/packages/sdk-py/pyproject.toml +1 -1
- package/packages/telemetry/package.json +1 -1
- package/packages/trajectory/package.json +2 -2
- package/packages/user-directory/package.json +2 -2
- package/packages/utils/package.json +2 -2
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
vi.mock('@relaycast/sdk', () => ({
|
|
5
|
+
RelayCast: vi.fn(),
|
|
6
|
+
RelayError: class RelayError extends Error {},
|
|
7
|
+
}));
|
|
8
|
+
|
|
9
|
+
vi.mock('../../relay.js', () => ({
|
|
10
|
+
AgentRelay: vi.fn(),
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
const { WorkflowRunner } = await import('../runner.js');
|
|
14
|
+
|
|
15
|
+
describe('WorkflowRunner step cwd resolution', () => {
|
|
16
|
+
it('prefers step.cwd over agent.cwd and runner cwd', () => {
|
|
17
|
+
const runnerRoot = '/runner-root';
|
|
18
|
+
const runner = new WorkflowRunner({ cwd: runnerRoot });
|
|
19
|
+
|
|
20
|
+
const resolved = (runner as any).resolveEffectiveCwd(
|
|
21
|
+
{ name: 'generate', agent: 'worker', task: 'Generate', cwd: 'steps/generate' },
|
|
22
|
+
{ name: 'worker', cli: 'claude', cwd: 'agents/worker' },
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
expect(resolved).toBe(path.resolve(runnerRoot, 'steps/generate'));
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('respects step.cwd for deterministic steps', () => {
|
|
29
|
+
const runnerRoot = '/runner-root';
|
|
30
|
+
const runner = new WorkflowRunner({ cwd: runnerRoot });
|
|
31
|
+
|
|
32
|
+
const resolved = (runner as any).resolveEffectiveCwd({
|
|
33
|
+
name: 'scaffold',
|
|
34
|
+
type: 'deterministic',
|
|
35
|
+
command: 'mkdir -p out',
|
|
36
|
+
cwd: 'deterministic/setup',
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
expect(resolved).toBe(path.resolve(runnerRoot, 'deterministic/setup'));
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('falls back through step.cwd to step.workdir to agent.cwd to runner.cwd', () => {
|
|
43
|
+
const runnerRoot = '/runner-root';
|
|
44
|
+
const namedPath = '/named/workdir';
|
|
45
|
+
const runner = new WorkflowRunner({ cwd: runnerRoot });
|
|
46
|
+
(runner as any).resolvedPaths.set('generated', namedPath);
|
|
47
|
+
|
|
48
|
+
const agentDef = { name: 'worker', cli: 'claude', cwd: 'agents/worker' } as const;
|
|
49
|
+
|
|
50
|
+
expect(
|
|
51
|
+
(runner as any).resolveEffectiveCwd(
|
|
52
|
+
{ name: 's1', agent: 'worker', task: 'Do work', cwd: 'steps/explicit', workdir: 'generated' },
|
|
53
|
+
agentDef,
|
|
54
|
+
),
|
|
55
|
+
).toBe(path.resolve(runnerRoot, 'steps/explicit'));
|
|
56
|
+
|
|
57
|
+
expect(
|
|
58
|
+
(runner as any).resolveEffectiveCwd(
|
|
59
|
+
{ name: 's2', agent: 'worker', task: 'Do work', workdir: 'generated' },
|
|
60
|
+
agentDef,
|
|
61
|
+
),
|
|
62
|
+
).toBe(namedPath);
|
|
63
|
+
|
|
64
|
+
expect(
|
|
65
|
+
(runner as any).resolveEffectiveCwd({ name: 's3', agent: 'worker', task: 'Do work' }, agentDef),
|
|
66
|
+
).toBe(path.resolve(runnerRoot, 'agents/worker'));
|
|
67
|
+
|
|
68
|
+
expect(
|
|
69
|
+
(runner as any).resolveEffectiveCwd({ name: 's4', type: 'deterministic', command: 'pwd' }),
|
|
70
|
+
).toBe(runnerRoot);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
@@ -47,6 +47,7 @@ export interface AgentOptions {
|
|
|
47
47
|
export interface AgentStepOptions {
|
|
48
48
|
agent: string;
|
|
49
49
|
task: string;
|
|
50
|
+
cwd?: string;
|
|
50
51
|
dependsOn?: string[];
|
|
51
52
|
verification?: VerificationCheck;
|
|
52
53
|
timeoutMs?: number;
|
|
@@ -57,6 +58,7 @@ export interface AgentStepOptions {
|
|
|
57
58
|
export interface DeterministicStepOptions {
|
|
58
59
|
type: 'deterministic';
|
|
59
60
|
command: string;
|
|
61
|
+
cwd?: string;
|
|
60
62
|
/** Capture stdout as step output for downstream steps. Default: true. */
|
|
61
63
|
captureOutput?: boolean;
|
|
62
64
|
/** Fail if command exit code is non-zero. Default: true. */
|
|
@@ -256,6 +258,7 @@ export class WorkflowBuilder {
|
|
|
256
258
|
}
|
|
257
259
|
step.type = 'deterministic';
|
|
258
260
|
step.command = options.command;
|
|
261
|
+
if (options.cwd !== undefined) step.cwd = options.cwd;
|
|
259
262
|
if (options.captureOutput !== undefined) step.captureOutput = options.captureOutput;
|
|
260
263
|
if (options.failOnError !== undefined) step.failOnError = options.failOnError;
|
|
261
264
|
if (options.dependsOn !== undefined) step.dependsOn = options.dependsOn;
|
|
@@ -280,6 +283,7 @@ export class WorkflowBuilder {
|
|
|
280
283
|
}
|
|
281
284
|
step.agent = agentOpts.agent;
|
|
282
285
|
step.task = agentOpts.task;
|
|
286
|
+
if (agentOpts.cwd !== undefined) step.cwd = agentOpts.cwd;
|
|
283
287
|
if (agentOpts.dependsOn !== undefined) step.dependsOn = agentOpts.dependsOn;
|
|
284
288
|
if (agentOpts.verification !== undefined) step.verification = agentOpts.verification;
|
|
285
289
|
if (agentOpts.timeoutMs !== undefined) step.timeoutMs = agentOpts.timeoutMs;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { AgentCli } from './types.js';
|
|
2
|
+
import { ClaudeCodeCollector } from './collectors/claude.js';
|
|
3
|
+
import { CodexCollector } from './collectors/codex.js';
|
|
4
|
+
import { OpenCodeCollector } from './collectors/opencode.js';
|
|
5
|
+
|
|
6
|
+
export interface CliSessionReport {
|
|
7
|
+
cli: AgentCli;
|
|
8
|
+
sessionId: string | null;
|
|
9
|
+
model: string | null;
|
|
10
|
+
provider: string | null;
|
|
11
|
+
durationMs: number | null;
|
|
12
|
+
cost: number | null;
|
|
13
|
+
tokens: {
|
|
14
|
+
input: number;
|
|
15
|
+
output: number;
|
|
16
|
+
cacheRead: number;
|
|
17
|
+
} | null;
|
|
18
|
+
turns: number;
|
|
19
|
+
toolCalls: { name: string; count: number }[];
|
|
20
|
+
errors: { turn: number; text: string }[];
|
|
21
|
+
finalStatus: 'completed' | 'failed' | 'unknown';
|
|
22
|
+
summary: string | null;
|
|
23
|
+
raw?: object;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface CliSessionQuery {
|
|
27
|
+
cli: AgentCli;
|
|
28
|
+
cwd: string;
|
|
29
|
+
startedAt: number;
|
|
30
|
+
completedAt: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface CliSessionCollector {
|
|
34
|
+
canCollect(): boolean;
|
|
35
|
+
collect(query: CliSessionQuery): Promise<CliSessionReport | null>;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function createCollector(cli: AgentCli): CliSessionCollector | null {
|
|
39
|
+
switch (cli) {
|
|
40
|
+
case 'opencode':
|
|
41
|
+
return new OpenCodeCollector();
|
|
42
|
+
case 'claude':
|
|
43
|
+
return new ClaudeCodeCollector();
|
|
44
|
+
case 'codex':
|
|
45
|
+
return new CodexCollector();
|
|
46
|
+
default:
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export async function collectCliSession(query: CliSessionQuery): Promise<CliSessionReport | null> {
|
|
52
|
+
const collector = createCollector(query.cli);
|
|
53
|
+
if (!collector || !collector.canCollect()) {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return collector.collect(query);
|
|
58
|
+
}
|
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import path from 'node:path';
|
|
13
|
+
import chalk from 'chalk';
|
|
14
|
+
|
|
13
15
|
import type { WorkflowEvent } from './runner.js';
|
|
14
16
|
import { WorkflowRunner } from './runner.js';
|
|
15
17
|
import { JsonFileWorkflowDb } from './file-db.js';
|
|
@@ -41,43 +43,261 @@ Examples:
|
|
|
41
43
|
);
|
|
42
44
|
}
|
|
43
45
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
return
|
|
74
|
-
case 'broker:event':
|
|
75
|
-
return `[broker] ${event.event.kind}`;
|
|
76
|
-
default: {
|
|
77
|
-
const _exhaustive: never = event;
|
|
78
|
-
return `[unknown event] ${(_exhaustive as WorkflowEvent).type}`;
|
|
46
|
+
type RunnerConfig = Awaited<ReturnType<WorkflowRunner['parseYamlFile']>>;
|
|
47
|
+
|
|
48
|
+
type RunnerResult = Awaited<ReturnType<WorkflowRunner['execute']>>;
|
|
49
|
+
|
|
50
|
+
type ExecuteOptions = {
|
|
51
|
+
startFrom: string;
|
|
52
|
+
previousRunId?: string;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
interface RenderableTask {
|
|
56
|
+
output?: string;
|
|
57
|
+
title: string;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
interface StepHandle {
|
|
61
|
+
resolve: () => void;
|
|
62
|
+
reject: (error: Error) => void;
|
|
63
|
+
setOutput: (text: string) => void;
|
|
64
|
+
markSkipped: () => void;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Filter [broker] and [workflow HH:MM] noise while listr owns the terminal,
|
|
68
|
+
// but let the observer URL and channel name through.
|
|
69
|
+
function installOutputFilter(): () => void {
|
|
70
|
+
const orig = console.log.bind(console);
|
|
71
|
+
console.log = (...args: unknown[]) => {
|
|
72
|
+
const str = String(args[0] ?? '');
|
|
73
|
+
if (str.includes('Observer:') || str.includes('agentrelay.dev') || str.includes('Channel: wf-')) {
|
|
74
|
+
orig(...args);
|
|
75
|
+
return;
|
|
79
76
|
}
|
|
80
|
-
|
|
77
|
+
if (/\[broker\]/.test(str) || /\[workflow\s+\d{2}:\d{2}\]/.test(str)) return;
|
|
78
|
+
orig(...args);
|
|
79
|
+
};
|
|
80
|
+
return () => { console.log = orig; };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async function runWithListr(
|
|
84
|
+
runner: WorkflowRunner,
|
|
85
|
+
config: RunnerConfig,
|
|
86
|
+
workflowName: string | undefined,
|
|
87
|
+
executeOptions: ExecuteOptions | undefined,
|
|
88
|
+
): Promise<RunnerResult> {
|
|
89
|
+
const stepHandles = new Map<string, StepHandle>();
|
|
90
|
+
const restoreConsole = installOutputFilter();
|
|
91
|
+
|
|
92
|
+
let resolveWorkflow!: () => void;
|
|
93
|
+
let rejectWorkflow!: (error: Error) => void;
|
|
94
|
+
const workflowDone = new Promise<void>((resolve, reject) => {
|
|
95
|
+
resolveWorkflow = resolve;
|
|
96
|
+
rejectWorkflow = reject;
|
|
97
|
+
});
|
|
98
|
+
workflowDone.catch(() => {});
|
|
99
|
+
|
|
100
|
+
let setHeader: (text: string) => void = () => {};
|
|
101
|
+
|
|
102
|
+
const { Listr } = await import('listr2');
|
|
103
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
104
|
+
const listr = new (Listr as any)(
|
|
105
|
+
[
|
|
106
|
+
{
|
|
107
|
+
title: chalk.dim('Workflow starting...'),
|
|
108
|
+
task: async (_ctx: unknown, task: any): Promise<void> => {
|
|
109
|
+
setHeader = (text: string): void => {
|
|
110
|
+
task.title = text;
|
|
111
|
+
};
|
|
112
|
+
await workflowDone;
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
],
|
|
116
|
+
{
|
|
117
|
+
concurrent: true,
|
|
118
|
+
renderer: process.stdout.isTTY ? 'default' : 'verbose',
|
|
119
|
+
rendererOptions: {
|
|
120
|
+
collapseErrors: false,
|
|
121
|
+
showErrorMessage: true,
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
runner.on((event: WorkflowEvent) => {
|
|
127
|
+
switch (event.type) {
|
|
128
|
+
case 'run:started': {
|
|
129
|
+
setHeader(chalk.dim(`[workflow] run ${event.runId.slice(0, 8)}...`));
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
case 'step:started': {
|
|
134
|
+
let resolveStep!: () => void;
|
|
135
|
+
let rejectStep!: (error: Error) => void;
|
|
136
|
+
let taskRef: RenderableTask | null = null;
|
|
137
|
+
let skipped = false;
|
|
138
|
+
|
|
139
|
+
const done = new Promise<void>((resolve, reject) => {
|
|
140
|
+
resolveStep = resolve;
|
|
141
|
+
rejectStep = reject;
|
|
142
|
+
});
|
|
143
|
+
done.catch(() => {});
|
|
144
|
+
|
|
145
|
+
stepHandles.set(event.stepName, {
|
|
146
|
+
resolve: resolveStep,
|
|
147
|
+
reject: rejectStep,
|
|
148
|
+
setOutput: (text: string) => {
|
|
149
|
+
if (taskRef) {
|
|
150
|
+
taskRef.output = text;
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
markSkipped: () => {
|
|
154
|
+
skipped = true;
|
|
155
|
+
if (taskRef) {
|
|
156
|
+
taskRef.title = chalk.dim(`${event.stepName} (skipped)`);
|
|
157
|
+
}
|
|
158
|
+
},
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
listr.add({
|
|
162
|
+
title: chalk.white(event.stepName),
|
|
163
|
+
task: async (_ctx: unknown, task: any): Promise<void> => {
|
|
164
|
+
taskRef = task as RenderableTask;
|
|
165
|
+
if (skipped) {
|
|
166
|
+
taskRef.title = chalk.dim(`${event.stepName} (skipped)`);
|
|
167
|
+
}
|
|
168
|
+
await done;
|
|
169
|
+
},
|
|
170
|
+
rendererOptions: {
|
|
171
|
+
persistentOutput: true,
|
|
172
|
+
},
|
|
173
|
+
});
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
case 'step:owner-assigned': {
|
|
178
|
+
const handle = stepHandles.get(event.stepName);
|
|
179
|
+
if (handle) {
|
|
180
|
+
handle.setOutput(
|
|
181
|
+
chalk.dim(`> Owner: ${event.ownerName}`) +
|
|
182
|
+
(event.specialistName ? chalk.dim(` - specialist: ${event.specialistName}`) : '')
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
case 'step:retrying': {
|
|
189
|
+
const handle = stepHandles.get(event.stepName);
|
|
190
|
+
if (handle) {
|
|
191
|
+
handle.setOutput(chalk.yellow(`Retrying (attempt ${event.attempt})`));
|
|
192
|
+
}
|
|
193
|
+
break;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
case 'step:nudged': {
|
|
197
|
+
const handle = stepHandles.get(event.stepName);
|
|
198
|
+
if (handle) {
|
|
199
|
+
handle.setOutput(chalk.dim(`> Nudge #${event.nudgeCount}`));
|
|
200
|
+
}
|
|
201
|
+
break;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
case 'step:force-released': {
|
|
205
|
+
const handle = stepHandles.get(event.stepName);
|
|
206
|
+
if (handle) {
|
|
207
|
+
handle.setOutput(chalk.yellow('> Force-released'));
|
|
208
|
+
}
|
|
209
|
+
break;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
case 'step:review-completed': {
|
|
213
|
+
const handle = stepHandles.get(event.stepName);
|
|
214
|
+
if (handle) {
|
|
215
|
+
handle.setOutput(chalk.dim(`> Review: ${event.decision} by ${event.reviewerName}`));
|
|
216
|
+
}
|
|
217
|
+
break;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
case 'step:owner-timeout': {
|
|
221
|
+
const handle = stepHandles.get(event.stepName);
|
|
222
|
+
if (handle) {
|
|
223
|
+
handle.setOutput(chalk.red(`> Owner ${event.ownerName} timed out`));
|
|
224
|
+
}
|
|
225
|
+
break;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
case 'step:agent-report': {
|
|
229
|
+
const handle = stepHandles.get(event.stepName);
|
|
230
|
+
if (handle) {
|
|
231
|
+
const model = event.report.model ? `:${event.report.model}` : '';
|
|
232
|
+
handle.setOutput(chalk.dim(`> Report collected (${event.report.cli}${model})`));
|
|
233
|
+
}
|
|
234
|
+
break;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
case 'step:completed': {
|
|
238
|
+
stepHandles.get(event.stepName)?.resolve();
|
|
239
|
+
break;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
case 'step:skipped': {
|
|
243
|
+
const handle = stepHandles.get(event.stepName);
|
|
244
|
+
if (handle) {
|
|
245
|
+
handle.markSkipped();
|
|
246
|
+
handle.resolve();
|
|
247
|
+
} else {
|
|
248
|
+
// Step was skipped without ever being started (downstream of a failure).
|
|
249
|
+
// Add an already-resolved task so it shows in the listr output.
|
|
250
|
+
listr.add({
|
|
251
|
+
title: chalk.dim(`${event.stepName} (skipped)`),
|
|
252
|
+
task: async (): Promise<void> => {},
|
|
253
|
+
rendererOptions: { persistentOutput: true },
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
break;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
case 'step:failed': {
|
|
260
|
+
stepHandles.get(event.stepName)?.reject(new Error(event.error ?? 'Step failed'));
|
|
261
|
+
break;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
case 'run:completed': {
|
|
265
|
+
setHeader(chalk.green('Workflow completed'));
|
|
266
|
+
resolveWorkflow();
|
|
267
|
+
break;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
case 'run:failed': {
|
|
271
|
+
setHeader(chalk.red(`Workflow failed: ${event.error}`));
|
|
272
|
+
rejectWorkflow(new Error(event.error ?? 'Workflow failed'));
|
|
273
|
+
break;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
case 'run:cancelled': {
|
|
277
|
+
setHeader(chalk.yellow('Workflow cancelled'));
|
|
278
|
+
resolveWorkflow();
|
|
279
|
+
break;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
case 'broker:event':
|
|
283
|
+
break;
|
|
284
|
+
|
|
285
|
+
default: {
|
|
286
|
+
const _exhaustive: never = event;
|
|
287
|
+
void _exhaustive;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
const [result] = await Promise.all([
|
|
293
|
+
runner.execute(config, workflowName, undefined, executeOptions),
|
|
294
|
+
listr.run().catch(() => {
|
|
295
|
+
// Step failures are already represented in runner result.
|
|
296
|
+
}),
|
|
297
|
+
]);
|
|
298
|
+
|
|
299
|
+
restoreConsole();
|
|
300
|
+
return result;
|
|
81
301
|
}
|
|
82
302
|
|
|
83
303
|
async function main(): Promise<void> {
|
|
@@ -96,6 +316,7 @@ async function main(): Promise<void> {
|
|
|
96
316
|
`[workflow] warning: cannot write to ${dbPath} — run state will not be persisted (--resume unavailable)`
|
|
97
317
|
);
|
|
98
318
|
}
|
|
319
|
+
|
|
99
320
|
const runner = new WorkflowRunner({ db: fileDb });
|
|
100
321
|
let shuttingDown = false;
|
|
101
322
|
const shutdown = async (signal: string): Promise<void> => {
|
|
@@ -113,17 +334,37 @@ async function main(): Promise<void> {
|
|
|
113
334
|
if (resumeIdx !== -1) {
|
|
114
335
|
const runId = args[resumeIdx + 1];
|
|
115
336
|
if (!runId) {
|
|
116
|
-
console.error('Error: --resume requires a run ID');
|
|
337
|
+
console.error(chalk.red('Error: --resume requires a run ID'));
|
|
117
338
|
process.exit(1);
|
|
118
339
|
}
|
|
119
|
-
|
|
120
|
-
|
|
340
|
+
|
|
341
|
+
console.log(chalk.dim(`Resuming run ${runId}...`));
|
|
342
|
+
runner.on((event: WorkflowEvent) => {
|
|
343
|
+
const ts = new Date().toISOString().slice(11, 19);
|
|
344
|
+
switch (event.type) {
|
|
345
|
+
case 'step:started':
|
|
346
|
+
console.log(chalk.dim(`[${ts}]`), chalk.white(event.stepName), chalk.dim('started'));
|
|
347
|
+
break;
|
|
348
|
+
case 'step:completed':
|
|
349
|
+
console.log(chalk.dim(`[${ts}]`), chalk.green('✔'), event.stepName);
|
|
350
|
+
break;
|
|
351
|
+
case 'step:failed':
|
|
352
|
+
console.log(chalk.dim(`[${ts}]`), chalk.red('✗'), event.stepName, chalk.red(event.error ?? ''));
|
|
353
|
+
break;
|
|
354
|
+
case 'step:skipped':
|
|
355
|
+
console.log(chalk.dim(`[${ts}]`), chalk.dim('⊘'), chalk.dim(event.stepName));
|
|
356
|
+
break;
|
|
357
|
+
default:
|
|
358
|
+
break;
|
|
359
|
+
}
|
|
360
|
+
});
|
|
121
361
|
const result = await runner.resume(runId);
|
|
362
|
+
|
|
122
363
|
if (result.status === 'completed') {
|
|
123
|
-
console.log(
|
|
364
|
+
console.log(chalk.green('\nWorkflow completed successfully.'));
|
|
124
365
|
process.exit(0);
|
|
125
366
|
} else {
|
|
126
|
-
console.error(`\nWorkflow ${result.status}${result.error ? `: ${result.error}` : ''}`);
|
|
367
|
+
console.error(chalk.red(`\nWorkflow ${result.status}${result.error ? `: ${result.error}` : ''}`));
|
|
127
368
|
process.exit(1);
|
|
128
369
|
}
|
|
129
370
|
return;
|
|
@@ -151,18 +392,17 @@ async function main(): Promise<void> {
|
|
|
151
392
|
}
|
|
152
393
|
|
|
153
394
|
const isValidate = args.includes('--validate');
|
|
154
|
-
|
|
155
|
-
console.log(`Running workflow from ${yamlPath}...`);
|
|
156
|
-
|
|
157
395
|
const isDryRun = !!process.env.DRY_RUN;
|
|
158
396
|
|
|
159
397
|
const config = await runner.parseYamlFile(yamlPath);
|
|
398
|
+
|
|
160
399
|
if (isValidate) {
|
|
161
400
|
const { validateWorkflow, formatValidationReport } = await import('./validator.js');
|
|
162
401
|
const issues = validateWorkflow(config);
|
|
163
402
|
console.log(formatValidationReport(issues, yamlPath));
|
|
164
|
-
process.exit(issues.some((
|
|
403
|
+
process.exit(issues.some((issue) => issue.severity === 'error') ? 1 : 0);
|
|
165
404
|
}
|
|
405
|
+
|
|
166
406
|
if (isDryRun) {
|
|
167
407
|
const { formatDryRunReport } = await import('./dry-run-format.js');
|
|
168
408
|
const report = runner.dryRun(config, workflowName);
|
|
@@ -170,20 +410,19 @@ async function main(): Promise<void> {
|
|
|
170
410
|
process.exit(report.valid ? 0 : 1);
|
|
171
411
|
}
|
|
172
412
|
|
|
173
|
-
runner.on((event) => console.log(formatEvent(event)));
|
|
174
413
|
const executeOptions = startFromStep ? { startFrom: startFromStep, previousRunId } : undefined;
|
|
175
|
-
const result = await runner
|
|
414
|
+
const result = await runWithListr(runner, config, workflowName, executeOptions);
|
|
176
415
|
|
|
177
416
|
if (result.status === 'completed') {
|
|
178
|
-
console.log(
|
|
417
|
+
console.log(chalk.green('\nWorkflow completed successfully.'));
|
|
179
418
|
process.exit(0);
|
|
180
419
|
} else {
|
|
181
|
-
console.error(`\nWorkflow ${result.status}${result.error ? `: ${result.error}` : ''}`);
|
|
420
|
+
console.error(chalk.red(`\nWorkflow ${result.status}${result.error ? `: ${result.error}` : ''}`));
|
|
182
421
|
process.exit(1);
|
|
183
422
|
}
|
|
184
423
|
}
|
|
185
424
|
|
|
186
425
|
main().catch((err: Error) => {
|
|
187
|
-
console.error(`Error: ${err.message}`);
|
|
426
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
188
427
|
process.exit(1);
|
|
189
428
|
});
|