@rk0429/agentic-relay 21.0.0 → 21.1.2
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/application/projection-service.js +3 -0
- package/dist/application/projection-service.js.map +1 -1
- package/dist/application/routine-catalog-service.d.ts +1 -0
- package/dist/application/routine-catalog-service.js +15 -1
- package/dist/application/routine-catalog-service.js.map +1 -1
- package/dist/application/routine-daemon-service.d.ts +9 -0
- package/dist/application/routine-daemon-service.js +67 -1
- package/dist/application/routine-daemon-service.js.map +1 -1
- package/dist/application/routine-event-log-sink.js +1 -1
- package/dist/application/routine-event-log-sink.js.map +1 -1
- package/dist/application/routine-run-service.d.ts +5 -1
- package/dist/application/routine-run-service.js +26 -4
- package/dist/application/routine-run-service.js.map +1 -1
- package/dist/application/routine-status-query-service.d.ts +34 -9
- package/dist/application/routine-status-query-service.js +115 -43
- package/dist/application/routine-status-query-service.js.map +1 -1
- package/dist/bin/relay.d.ts +4 -2
- package/dist/bin/relay.js +171 -18
- package/dist/bin/relay.js.map +1 -1
- package/dist/core/types.d.ts +1 -1
- package/dist/domain/routine-name.js +3 -3
- package/dist/domain/routine-name.js.map +1 -1
- package/dist/domain/routine-schedule.d.ts +5 -1
- package/dist/domain/routine-schedule.js +20 -4
- package/dist/domain/routine-schedule.js.map +1 -1
- package/dist/domain/trigger-config.js +2 -2
- package/dist/domain/trigger-config.js.map +1 -1
- package/dist/infrastructure/store/routine-runtime-projection-repository.d.ts +2 -0
- package/dist/infrastructure/store/routine-runtime-projection-repository.js +16 -0
- package/dist/infrastructure/store/routine-runtime-projection-repository.js.map +1 -1
- package/dist/interfaces/cli/relay-cli-args.d.ts +1 -0
- package/dist/interfaces/cli/relay-cli-args.js +13 -0
- package/dist/interfaces/cli/relay-cli-args.js.map +1 -1
- package/dist/interfaces/mcp/relay-mcp-server.js +1 -1
- package/package.json +1 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ValidationError } from "../core/errors.js";
|
|
2
2
|
import { isProcessAlive } from "./housekeeping.js";
|
|
3
3
|
import { RoutineName } from "../domain/routine-name.js";
|
|
4
|
+
const STALE_HEARTBEAT_THRESHOLD_MS = 60_000;
|
|
4
5
|
export class RoutineStatusQueryService {
|
|
5
6
|
deps;
|
|
6
7
|
checkProcessAlive;
|
|
@@ -10,17 +11,24 @@ export class RoutineStatusQueryService {
|
|
|
10
11
|
}
|
|
11
12
|
async list() {
|
|
12
13
|
const merged = await this.buildMergedView();
|
|
13
|
-
return
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
return {
|
|
15
|
+
entries: merged.entries.map((entry) => ({
|
|
16
|
+
name: entry.name,
|
|
17
|
+
status: entry.status,
|
|
18
|
+
schedule: entry.schedule,
|
|
19
|
+
missedPolicy: entry.missedPolicy,
|
|
20
|
+
nextScheduledAt: entry.nextScheduledAt,
|
|
21
|
+
lastRun: entry.lastRun,
|
|
22
|
+
lastScheduledAt: entry.lastScheduledAt,
|
|
23
|
+
warnings: entry.warnings,
|
|
24
|
+
})),
|
|
25
|
+
invalid: merged.invalid,
|
|
26
|
+
};
|
|
19
27
|
}
|
|
20
28
|
async status(name, options = {}) {
|
|
21
29
|
const routineName = name instanceof RoutineName ? name.value : name;
|
|
22
30
|
const merged = await this.buildMergedView(options.limit ?? 10);
|
|
23
|
-
const entry = merged.find((candidate) => candidate.name === routineName);
|
|
31
|
+
const entry = merged.entries.find((candidate) => candidate.name === routineName);
|
|
24
32
|
if (!entry) {
|
|
25
33
|
throw new ValidationError(`Routine not found: ${routineName}`, {
|
|
26
34
|
recoveryHint: "Run a catalog rescan and confirm the routine name.",
|
|
@@ -30,52 +38,56 @@ export class RoutineStatusQueryService {
|
|
|
30
38
|
}
|
|
31
39
|
async buildMergedView(limit = 10) {
|
|
32
40
|
const now = new Date();
|
|
33
|
-
let
|
|
34
|
-
if (catalog.size === 0) {
|
|
35
|
-
|
|
41
|
+
let snapshot = this.deps.catalogService.getSnapshot();
|
|
42
|
+
if (snapshot.catalog.size === 0 && snapshot.invalid.length === 0) {
|
|
43
|
+
snapshot = await this.deps.catalogService.rescan();
|
|
36
44
|
}
|
|
37
45
|
const [states, runtimes, logs] = await Promise.all([
|
|
38
46
|
this.deps.stateRepo.list(),
|
|
39
47
|
this.deps.runtimeRepo.list(),
|
|
40
48
|
this.deps.store.listLogs(),
|
|
41
49
|
]);
|
|
50
|
+
const catalog = snapshot.catalog;
|
|
42
51
|
const stateMap = new Map(states.map((entry) => [entry.name.value, entry]));
|
|
43
52
|
const runtimeMap = new Map(runtimes.map((entry) => [entry.name, entry.projection]));
|
|
53
|
+
const warningsByName = buildWarningsByName(snapshot);
|
|
44
54
|
const names = new Set([
|
|
45
55
|
...catalog.keys(),
|
|
46
56
|
...stateMap.keys(),
|
|
47
57
|
...runtimeMap.keys(),
|
|
48
58
|
]);
|
|
49
|
-
return
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
59
|
+
return {
|
|
60
|
+
entries: [...names]
|
|
61
|
+
.sort((left, right) => left.localeCompare(right))
|
|
62
|
+
.map((name) => {
|
|
63
|
+
const catalogEntry = catalog.get(name);
|
|
64
|
+
const stateEntry = stateMap.get(name);
|
|
65
|
+
const runtime = runtimeMap.get(name);
|
|
66
|
+
const recentExecutions = filterRoutineLogs(logs, name).slice(0, limit);
|
|
67
|
+
const lastRun = stateEntry?.state.lastExecutedAt?.toISOString() ?? null;
|
|
68
|
+
const status = resolveStatus(runtime, stateEntry?.state.lastStatus, this.checkProcessAlive, now);
|
|
69
|
+
return {
|
|
70
|
+
name,
|
|
71
|
+
status,
|
|
72
|
+
schedule: catalogEntry?.definition.trigger?.schedule.value ?? null,
|
|
73
|
+
missedPolicy: catalogEntry?.definition.trigger?.missedPolicy ?? null,
|
|
74
|
+
nextScheduledAt: stateEntry?.state.nextScheduledAt?.toISOString() ??
|
|
75
|
+
catalogEntry?.definition.trigger?.schedule.nextAfter(now)?.toISOString() ??
|
|
76
|
+
null,
|
|
77
|
+
lastRun,
|
|
78
|
+
lastScheduledAt: stateEntry?.state.lastScheduledAt?.toISOString() ?? null,
|
|
79
|
+
warnings: warningsByName.get(name) ?? [],
|
|
80
|
+
definition: buildDefinitionDto(catalogEntry),
|
|
81
|
+
state: buildRoutineStateDto(stateEntry),
|
|
82
|
+
runtime,
|
|
83
|
+
recentExecutions,
|
|
84
|
+
};
|
|
85
|
+
}),
|
|
86
|
+
invalid: snapshot.invalid.map((issue) => ({
|
|
87
|
+
yamlPath: issue.yamlPath,
|
|
88
|
+
error: issue.error,
|
|
89
|
+
})),
|
|
90
|
+
};
|
|
79
91
|
}
|
|
80
92
|
}
|
|
81
93
|
function filterRoutineLogs(logs, routineName) {
|
|
@@ -86,13 +98,73 @@ function filterRoutineLogs(logs, routineName) {
|
|
|
86
98
|
})
|
|
87
99
|
.sort((left, right) => right.timestamp.localeCompare(left.timestamp));
|
|
88
100
|
}
|
|
89
|
-
function resolveStatus(runtime, lastStatus, checkProcessAlive) {
|
|
101
|
+
function resolveStatus(runtime, lastStatus, checkProcessAlive, now) {
|
|
90
102
|
if (runtime) {
|
|
91
|
-
|
|
103
|
+
if (!checkProcessAlive(runtime.pid)) {
|
|
104
|
+
return "stale_runtime";
|
|
105
|
+
}
|
|
106
|
+
if (now.getTime() - new Date(runtime.lastHeartbeatAt).getTime() >
|
|
107
|
+
STALE_HEARTBEAT_THRESHOLD_MS) {
|
|
108
|
+
return "stale_runtime";
|
|
109
|
+
}
|
|
110
|
+
return "running";
|
|
92
111
|
}
|
|
93
|
-
if (lastStatus === "completed" || lastStatus === "failed") {
|
|
112
|
+
if (lastStatus === "completed" || lastStatus === "failed" || lastStatus === "skipped") {
|
|
94
113
|
return lastStatus;
|
|
95
114
|
}
|
|
96
115
|
return "idle";
|
|
97
116
|
}
|
|
117
|
+
function buildRoutineStateDto(stateEntry) {
|
|
118
|
+
if (!stateEntry) {
|
|
119
|
+
return undefined;
|
|
120
|
+
}
|
|
121
|
+
return {
|
|
122
|
+
routineName: stateEntry.name.value,
|
|
123
|
+
workflowPath: stateEntry.workflowPath,
|
|
124
|
+
lastExecutedAt: stateEntry.state.lastExecutedAt?.toISOString() ?? null,
|
|
125
|
+
lastScheduledAt: stateEntry.state.lastScheduledAt?.toISOString() ?? null,
|
|
126
|
+
lastStatus: stateEntry.state.lastStatus,
|
|
127
|
+
nextScheduledAt: stateEntry.state.nextScheduledAt?.toISOString() ?? null,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
function buildDefinitionDto(catalogEntry) {
|
|
131
|
+
if (!catalogEntry) {
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
return {
|
|
135
|
+
description: catalogEntry.definition.description ?? null,
|
|
136
|
+
yamlPath: catalogEntry.yamlPath,
|
|
137
|
+
scheduled: catalogEntry.definition.isScheduled(),
|
|
138
|
+
schedule: catalogEntry.definition.trigger?.schedule.value ?? null,
|
|
139
|
+
missedPolicy: catalogEntry.definition.trigger?.missedPolicy ?? null,
|
|
140
|
+
catchUpLimit: catalogEntry.definition.trigger?.catchUpLimit.value ?? null,
|
|
141
|
+
stepSummary: catalogEntry.definition.workflow.steps.map((step) => `${step.id} (${resolveStepKind(step)})`),
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
function buildWarningsByName(snapshot) {
|
|
145
|
+
const warnings = new Map();
|
|
146
|
+
for (const issue of snapshot.invalid) {
|
|
147
|
+
if (issue.kind !== "duplicate_name" || !issue.routineName) {
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
const entry = warnings.get(issue.routineName) ?? [];
|
|
151
|
+
entry.push(formatDuplicateWarning(issue));
|
|
152
|
+
warnings.set(issue.routineName, entry);
|
|
153
|
+
}
|
|
154
|
+
return warnings;
|
|
155
|
+
}
|
|
156
|
+
function formatDuplicateWarning(issue) {
|
|
157
|
+
const ignored = issue.yamlPath;
|
|
158
|
+
const retained = issue.duplicateOf ?? "the earlier file";
|
|
159
|
+
return `Duplicate routine name detected. Ignoring ${ignored} because ${retained} is loaded first.`;
|
|
160
|
+
}
|
|
161
|
+
function resolveStepKind(step) {
|
|
162
|
+
if ("workflow" in step && step.workflow) {
|
|
163
|
+
return "workflow";
|
|
164
|
+
}
|
|
165
|
+
if ("command" in step && step.command) {
|
|
166
|
+
return "command";
|
|
167
|
+
}
|
|
168
|
+
return "prompt";
|
|
169
|
+
}
|
|
98
170
|
//# sourceMappingURL=routine-status-query-service.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"routine-status-query-service.js","sourceRoot":"","sources":["../../src/application/routine-status-query-service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEpD,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"routine-status-query-service.js","sourceRoot":"","sources":["../../src/application/routine-status-query-service.ts"],"names":[],"mappings":"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"}
|
package/dist/bin/relay.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
1
|
+
#!/usr/bin/env -S node --disable-warning=ExperimentalWarning
|
|
2
2
|
import { type ForkOptions } from "node:child_process";
|
|
3
3
|
import { CoreManagementService } from "../application/core-management-service.js";
|
|
4
4
|
import { reconcileAbandonedRunningTaskSessions, runStartupHousekeeping } from "../application/housekeeping.js";
|
|
5
5
|
import { RelayCliService } from "../application/relay-cli-service.js";
|
|
6
|
-
import { RoutineDaemonService, type ProcessStatus } from "../application/routine-daemon-service.js";
|
|
6
|
+
import { RoutineDaemonService, type ProcessStatus, type RoutineDigestEvent } from "../application/routine-daemon-service.js";
|
|
7
7
|
import { RoutineRunService } from "../application/routine-run-service.js";
|
|
8
8
|
import { RoutineStatusQueryService } from "../application/routine-status-query-service.js";
|
|
9
9
|
import { TaskService } from "../application/task-service.js";
|
|
@@ -29,6 +29,7 @@ type RoutineCommand = Extract<RelayCliCommand, {
|
|
|
29
29
|
}>;
|
|
30
30
|
interface TextWriter {
|
|
31
31
|
write(chunk: string): unknown;
|
|
32
|
+
isTTY?: boolean;
|
|
32
33
|
}
|
|
33
34
|
interface CoreCommandHandlerDependencies {
|
|
34
35
|
service: Pick<CoreManagementService, "setup" | "update" | "remove">;
|
|
@@ -63,6 +64,7 @@ interface RoutineCommandHandlerDependencies {
|
|
|
63
64
|
createRoutineDaemonService: (options: {
|
|
64
65
|
configDir: string;
|
|
65
66
|
allowCommandRun: boolean;
|
|
67
|
+
onRoutineEvent?: (event: RoutineDigestEvent) => void;
|
|
66
68
|
}) => Promise<Pick<RoutineDaemonService, "start">>;
|
|
67
69
|
createRoutineStatusQueryService: (options: {
|
|
68
70
|
configDir: string;
|
package/dist/bin/relay.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
1
|
+
#!/usr/bin/env -S node --disable-warning=ExperimentalWarning
|
|
2
2
|
import { fork } from "node:child_process";
|
|
3
3
|
import { randomUUID } from "node:crypto";
|
|
4
4
|
import { access, readFile, realpath, rm } from "node:fs/promises";
|
|
@@ -136,7 +136,7 @@ async function main() {
|
|
|
136
136
|
stateRepo: new RoutineStateRepository(store),
|
|
137
137
|
eventLog: new RoutineEventLogSink(store),
|
|
138
138
|
});
|
|
139
|
-
const createRoutineDaemonService = async ({ configDir, allowCommandRun }) => {
|
|
139
|
+
const createRoutineDaemonService = async ({ configDir, allowCommandRun, onRoutineEvent }) => {
|
|
140
140
|
const catalogService = new RoutineCatalogService({
|
|
141
141
|
configDir,
|
|
142
142
|
workflowRepo,
|
|
@@ -163,6 +163,8 @@ async function main() {
|
|
|
163
163
|
runService,
|
|
164
164
|
stateRepo,
|
|
165
165
|
leaseRepo,
|
|
166
|
+
}, {
|
|
167
|
+
onRoutineEvent,
|
|
166
168
|
});
|
|
167
169
|
};
|
|
168
170
|
const createRoutineStatusQueryService = ({ configDir }) => new RoutineStatusQueryService({
|
|
@@ -334,11 +336,19 @@ export async function handleRoutineCommand(command, dependencies) {
|
|
|
334
336
|
switch (command.kind) {
|
|
335
337
|
case "routine-run": {
|
|
336
338
|
const workflowPath = resolveRoutineWorkflowPath(command.workflowPath, command.configDir, dependencies.cwd);
|
|
339
|
+
const dryRunTime = new Date().toISOString();
|
|
340
|
+
const routineVars = command.dryRun
|
|
341
|
+
? {
|
|
342
|
+
...(command.vars ?? {}),
|
|
343
|
+
scheduled_time: dryRunTime,
|
|
344
|
+
trigger_time: dryRunTime,
|
|
345
|
+
}
|
|
346
|
+
: (command.vars ?? {});
|
|
337
347
|
await dependencies.runStartupHousekeeping(dependencies.store, {
|
|
338
348
|
protectedLoopStateFile: command.resumeStateFile,
|
|
339
349
|
});
|
|
340
350
|
await dependencies.reconcileAbandonedRunningTaskSessions(dependencies.store, dependencies.taskService);
|
|
341
|
-
const loadedRoutine = await dependencies.loadRoutine(workflowPath, dependencies.workflowRepo,
|
|
351
|
+
const loadedRoutine = await dependencies.loadRoutine(workflowPath, dependencies.workflowRepo, routineVars);
|
|
342
352
|
if (command.dryRun) {
|
|
343
353
|
dependencies.stdout.write(renderRoutineDryRun(loadedRoutine, workflowPath));
|
|
344
354
|
return 0;
|
|
@@ -425,6 +435,14 @@ export async function handleRoutineCommand(command, dependencies) {
|
|
|
425
435
|
const service = await dependencies.createRoutineDaemonService({
|
|
426
436
|
configDir,
|
|
427
437
|
allowCommandRun: command.allowCommandRun ?? false,
|
|
438
|
+
onRoutineEvent: command.daemon
|
|
439
|
+
? undefined
|
|
440
|
+
: (event) => {
|
|
441
|
+
if (event.eventType === "routine_state_updated") {
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
dependencies.stdout.write(`${renderRoutineDigestLine(event, dependencies.stdout)}\n`);
|
|
445
|
+
},
|
|
428
446
|
});
|
|
429
447
|
await service.start();
|
|
430
448
|
return 0;
|
|
@@ -437,7 +455,7 @@ export async function handleRoutineCommand(command, dependencies) {
|
|
|
437
455
|
const service = dependencies.createRoutineStatusQueryService({
|
|
438
456
|
configDir: resolveRoutineConfigDir(command.configDir, dependencies.store, dependencies.cwd),
|
|
439
457
|
});
|
|
440
|
-
dependencies.stdout.write(renderRoutineList(await service.list()));
|
|
458
|
+
dependencies.stdout.write(renderRoutineList(await service.list(), command.format ?? "table"));
|
|
441
459
|
return 0;
|
|
442
460
|
}
|
|
443
461
|
case "routine-status": {
|
|
@@ -445,7 +463,7 @@ export async function handleRoutineCommand(command, dependencies) {
|
|
|
445
463
|
configDir: resolveRoutineConfigDir(command.configDir, dependencies.store, dependencies.cwd),
|
|
446
464
|
});
|
|
447
465
|
if (!command.name) {
|
|
448
|
-
dependencies.stdout.write(renderRoutineList(await service.list()));
|
|
466
|
+
dependencies.stdout.write(renderRoutineList(await service.list(), "table"));
|
|
449
467
|
return 0;
|
|
450
468
|
}
|
|
451
469
|
dependencies.stdout.write(renderRoutineStatus(await service.status(command.name, { limit: command.limit })));
|
|
@@ -465,7 +483,7 @@ export function renderHelpText(topic, scriptName = path.basename(fileURLToPath(i
|
|
|
465
483
|
` ${scriptName} routine run <path> [--config <dir>] [--var key=value ...] [--dry-run] [--resume-state <file>] [--force-resume] [--allow-command-run]`,
|
|
466
484
|
` ${scriptName} routine start [--config <dir>] [--daemon] [--allow-command-run]`,
|
|
467
485
|
` ${scriptName} routine stop`,
|
|
468
|
-
` ${scriptName} routine list [--config <dir>]`,
|
|
486
|
+
` ${scriptName} routine list [--config <dir>] [--format <table|tsv|json>]`,
|
|
469
487
|
` ${scriptName} routine status [name] [--config <dir>] [--limit <n>]`,
|
|
470
488
|
` ${scriptName} core <setup|update|remove> [options]`,
|
|
471
489
|
` ${scriptName} setup-vscode [--target <code|code-insiders|cursor>] [--uninstall]`,
|
|
@@ -549,6 +567,7 @@ function renderRoutineSubcommandHelp(scriptName) {
|
|
|
549
567
|
"",
|
|
550
568
|
"Options:",
|
|
551
569
|
" --config <dir> Override the routine config directory",
|
|
570
|
+
" --format <fmt> Output format for `routine list` (table, tsv, json)",
|
|
552
571
|
" --daemon Start the daemon in the background",
|
|
553
572
|
" --allow-command-run Allow trusted workflows that declare command.run",
|
|
554
573
|
" --resume-state <file> Resume a loop-capable routine from checkpoint state",
|
|
@@ -585,7 +604,7 @@ function enforceAllowCommandRun(workflowPath, capabilitySummary, allowCommandRun
|
|
|
585
604
|
}
|
|
586
605
|
const offendingPaths = capabilitySummary.commandRunWorkflowPaths.join(", ");
|
|
587
606
|
throw new ValidationError(`Workflow "${path.resolve(workflowPath)}" requires --allow-command-run because it declares command.run in: ${offendingPaths}.`, {
|
|
588
|
-
recoveryHint: "Re-run with --allow-command-run only for trusted workflows.",
|
|
607
|
+
recoveryHint: "Re-run with --allow-command-run only for trusted workflows, e.g. `relay routine run workflow.yaml --allow-command-run`.",
|
|
589
608
|
});
|
|
590
609
|
}
|
|
591
610
|
function isRoutineDaemonChild(env) {
|
|
@@ -794,8 +813,12 @@ function printLoopResult(result, writer = process.stderr) {
|
|
|
794
813
|
function printRoutineRunResult(result, writer = process.stderr) {
|
|
795
814
|
const lines = [
|
|
796
815
|
`Routine ${result.status}.`,
|
|
797
|
-
|
|
798
|
-
|
|
816
|
+
...(result.hasTrigger === false
|
|
817
|
+
? [`Run time: ${result.scheduledTime}`]
|
|
818
|
+
: [
|
|
819
|
+
`Scheduled time: ${result.scheduledTime}`,
|
|
820
|
+
...(result.triggerTime ? [`Trigger time: ${result.triggerTime}`] : []),
|
|
821
|
+
]),
|
|
799
822
|
...(result.routineRunId ? [`Routine run id: ${result.routineRunId}`] : []),
|
|
800
823
|
...(result.workflowExecutionId
|
|
801
824
|
? [`Workflow execution id: ${result.workflowExecutionId}`]
|
|
@@ -821,21 +844,88 @@ function renderRoutineDryRun(loadedRoutine, workflowPath) {
|
|
|
821
844
|
];
|
|
822
845
|
return `${lines.join("\n")}\n`;
|
|
823
846
|
}
|
|
824
|
-
function renderRoutineList(
|
|
847
|
+
function renderRoutineList(result, format = "table") {
|
|
848
|
+
const { entries, invalid } = result;
|
|
849
|
+
if (format === "json") {
|
|
850
|
+
return `${JSON.stringify({ routines: entries, invalid }, null, 2)}\n`;
|
|
851
|
+
}
|
|
825
852
|
if (entries.length === 0) {
|
|
826
|
-
return
|
|
853
|
+
return invalid.length === 0
|
|
854
|
+
? "No routines found.\n"
|
|
855
|
+
: `No routines found.\n\n${renderInvalidRoutineFiles(invalid)}\n`;
|
|
827
856
|
}
|
|
828
|
-
|
|
829
|
-
|
|
857
|
+
if (format === "tsv") {
|
|
858
|
+
const lines = [
|
|
859
|
+
"NAME\tSCHEDULE\tMISSED\tLAST_RUN\tNEXT_RUN\tSTATUS",
|
|
860
|
+
...entries.map((entry) => [
|
|
861
|
+
entry.name,
|
|
862
|
+
formatRoutineSchedule(entry.schedule),
|
|
863
|
+
formatRoutineMissedPolicy(entry.missedPolicy),
|
|
864
|
+
formatOptionalTimestamp(entry.lastRun),
|
|
865
|
+
formatOptionalTimestamp(entry.nextScheduledAt),
|
|
866
|
+
entry.status,
|
|
867
|
+
].join("\t")),
|
|
868
|
+
...invalid.map((entry) => `# Skipped: ${path.basename(entry.yamlPath)}: ${entry.error.replace(/\r?\n/g, " | ")}`),
|
|
869
|
+
];
|
|
870
|
+
return `${lines.join("\n")}\n`;
|
|
871
|
+
}
|
|
872
|
+
const rows = entries.map((entry) => [
|
|
873
|
+
entry.name,
|
|
874
|
+
formatRoutineSchedule(entry.schedule),
|
|
875
|
+
formatRoutineMissedPolicy(entry.missedPolicy),
|
|
876
|
+
formatOptionalTimestamp(entry.lastRun),
|
|
877
|
+
formatOptionalTimestamp(entry.nextScheduledAt),
|
|
878
|
+
entry.status,
|
|
879
|
+
]);
|
|
880
|
+
const headers = ["NAME", "SCHEDULE", "MISSED", "LAST RUN", "NEXT RUN", "STATUS"];
|
|
881
|
+
const widths = headers.map((header, column) => Math.max(header.length, ...rows.map((row) => row[column].length)));
|
|
882
|
+
const lines = [
|
|
883
|
+
headers.map((header, column) => header.padEnd(widths[column])).join(" "),
|
|
884
|
+
...rows.map((row) => row.map((cell, column) => cell.padEnd(widths[column])).join(" ")),
|
|
885
|
+
];
|
|
886
|
+
const sections = [`${lines.join("\n")}\n`];
|
|
887
|
+
const warnings = [...new Set(entries.flatMap((entry) => entry.warnings))];
|
|
888
|
+
if (warnings.length > 0) {
|
|
889
|
+
sections.push(`Warnings:\n${warnings.map((warning) => ` - ${warning}`).join("\n")}\n`);
|
|
890
|
+
}
|
|
891
|
+
if (invalid.length > 0) {
|
|
892
|
+
sections.push(`${renderInvalidRoutineFiles(invalid)}\n`);
|
|
893
|
+
}
|
|
894
|
+
return sections.join("\n");
|
|
895
|
+
}
|
|
896
|
+
function renderInvalidRoutineFiles(invalid) {
|
|
897
|
+
return [
|
|
898
|
+
`Skipped ${invalid.length} file(s):`,
|
|
899
|
+
...invalid.map((entry) => ` - ${path.basename(entry.yamlPath)}: ${firstLine(entry.error)}`),
|
|
900
|
+
].join("\n");
|
|
901
|
+
}
|
|
902
|
+
function firstLine(value) {
|
|
903
|
+
return value.split(/\r?\n/u, 1)[0] ?? "";
|
|
830
904
|
}
|
|
831
905
|
function renderRoutineStatus(entry) {
|
|
906
|
+
const definition = entry.definition;
|
|
907
|
+
const nextScheduledAt = entry.state?.nextScheduledAt
|
|
908
|
+
? entry.state.nextScheduledAt
|
|
909
|
+
: entry.nextScheduledAt
|
|
910
|
+
? `${entry.nextScheduledAt} (estimated)`
|
|
911
|
+
: "-";
|
|
832
912
|
const lines = [
|
|
833
913
|
`Name: ${entry.name}`,
|
|
834
914
|
`Status: ${entry.status}`,
|
|
835
|
-
`
|
|
836
|
-
`
|
|
837
|
-
|
|
838
|
-
|
|
915
|
+
`Description: ${definition?.description ?? "-"}`,
|
|
916
|
+
`YAML: ${definition?.yamlPath ?? "-"}`,
|
|
917
|
+
`Schedule: ${definition?.schedule ?? "manual"}`,
|
|
918
|
+
`Missed policy: ${definition?.missedPolicy ?? "-"}`,
|
|
919
|
+
`Catch-up limit: ${definition?.catchUpLimit ?? "-"}`,
|
|
920
|
+
`Steps: ${definition?.stepSummary.length ? definition.stepSummary.join(", ") : "-"}`,
|
|
921
|
+
`Next scheduled: ${nextScheduledAt}`,
|
|
922
|
+
`Last executed: ${formatOptionalTimestamp(entry.lastRun)}`,
|
|
923
|
+
`Last scheduled: ${formatOptionalTimestamp(entry.lastScheduledAt)}`,
|
|
924
|
+
`Workflow path: ${entry.state?.workflowPath ?? "-"}`,
|
|
925
|
+
`Last status: ${entry.state?.lastStatus ?? "-"}`,
|
|
926
|
+
...(entry.warnings.length > 0
|
|
927
|
+
? ["Warnings:", ...entry.warnings.map((warning) => ` - ${warning}`)]
|
|
928
|
+
: []),
|
|
839
929
|
...(entry.runtime
|
|
840
930
|
? [
|
|
841
931
|
`Running pid: ${entry.runtime.pid}`,
|
|
@@ -845,7 +935,7 @@ function renderRoutineStatus(entry) {
|
|
|
845
935
|
"",
|
|
846
936
|
"Recent events:",
|
|
847
937
|
...(entry.recentExecutions.length > 0
|
|
848
|
-
? entry.recentExecutions.map((event) => ` - ${event
|
|
938
|
+
? entry.recentExecutions.map((event) => ` - ${renderRoutineEventSummary(event)}`)
|
|
849
939
|
: [" - none"]),
|
|
850
940
|
];
|
|
851
941
|
return `${lines.join("\n")}\n`;
|
|
@@ -853,6 +943,66 @@ function renderRoutineStatus(entry) {
|
|
|
853
943
|
function formatOptionalTimestamp(value) {
|
|
854
944
|
return value ?? "-";
|
|
855
945
|
}
|
|
946
|
+
function formatRoutineSchedule(value) {
|
|
947
|
+
return value ?? "manual";
|
|
948
|
+
}
|
|
949
|
+
function formatRoutineMissedPolicy(value) {
|
|
950
|
+
return value ?? "-";
|
|
951
|
+
}
|
|
952
|
+
const DIGEST_EVENT_TYPE_WIDTH = 20;
|
|
953
|
+
function renderRoutineDigestLine(event, writer = process.stdout) {
|
|
954
|
+
const timestamp = new Date().toISOString().slice(11, 19);
|
|
955
|
+
const eventType = colorizeDigestEventType(event.eventType.padEnd(DIGEST_EVENT_TYPE_WIDTH), event.eventType, writer);
|
|
956
|
+
return `[${timestamp}] ${eventType} ${event.routineName} ${formatRoutineDigestDetail(event)}`;
|
|
957
|
+
}
|
|
958
|
+
function colorizeDigestEventType(value, eventType, writer) {
|
|
959
|
+
if (!writer.isTTY) {
|
|
960
|
+
return value;
|
|
961
|
+
}
|
|
962
|
+
const color = eventType === "routine_triggered" || eventType === "routine_catch_up"
|
|
963
|
+
? 36
|
|
964
|
+
: eventType === "routine_completed"
|
|
965
|
+
? 32
|
|
966
|
+
: eventType === "routine_failed"
|
|
967
|
+
? 31
|
|
968
|
+
: eventType === "routine_skipped"
|
|
969
|
+
? 33
|
|
970
|
+
: null;
|
|
971
|
+
return color === null ? value : `\u001B[${color}m${value}\u001B[0m`;
|
|
972
|
+
}
|
|
973
|
+
function formatRoutineDigestDetail(event) {
|
|
974
|
+
if (typeof event.detail.message === "string") {
|
|
975
|
+
return event.detail.message;
|
|
976
|
+
}
|
|
977
|
+
if (typeof event.detail.reason === "string") {
|
|
978
|
+
return `reason=${event.detail.reason}`;
|
|
979
|
+
}
|
|
980
|
+
if (typeof event.detail.scheduledTime === "string") {
|
|
981
|
+
return `scheduled_time=${event.detail.scheduledTime}`;
|
|
982
|
+
}
|
|
983
|
+
if (typeof event.detail.durationMs === "number") {
|
|
984
|
+
return `duration=${formatDigestDuration(event.detail.durationMs)}`;
|
|
985
|
+
}
|
|
986
|
+
if (typeof event.detail.error === "string") {
|
|
987
|
+
return `error="${event.detail.error}"`;
|
|
988
|
+
}
|
|
989
|
+
return Object.entries(event.detail)
|
|
990
|
+
.map(([key, value]) => `${key}=${String(value)}`)
|
|
991
|
+
.join(" ");
|
|
992
|
+
}
|
|
993
|
+
function formatDigestDuration(durationMs) {
|
|
994
|
+
if (durationMs % 1000 === 0) {
|
|
995
|
+
return `${durationMs / 1000}s`;
|
|
996
|
+
}
|
|
997
|
+
return `${(durationMs / 1000).toFixed(1)}s`;
|
|
998
|
+
}
|
|
999
|
+
function renderRoutineEventSummary(event) {
|
|
1000
|
+
const detail = (event.details?.routine ?? {});
|
|
1001
|
+
if (event.event_type === "routine_failed") {
|
|
1002
|
+
return `${event.timestamp} ${event.event_type} error_code=${detail.error_code ?? "-"} error_message=${detail.error_message ?? "-"} workflow_execution_id=${detail.workflow_execution_id ?? "-"}`;
|
|
1003
|
+
}
|
|
1004
|
+
return `${event.timestamp} ${event.event_type}`;
|
|
1005
|
+
}
|
|
856
1006
|
function renderDryRun(workflow, workflowPath) {
|
|
857
1007
|
const lines = [];
|
|
858
1008
|
lines.push(`Workflow: ${workflow.name}`);
|
|
@@ -1034,6 +1184,9 @@ function handleFatalError(error) {
|
|
|
1034
1184
|
const message = error instanceof Error ? error.message : "agentic-relay failed unexpectedly.";
|
|
1035
1185
|
if (error instanceof ValidationError) {
|
|
1036
1186
|
console.error(message);
|
|
1187
|
+
if (error.recoveryHint) {
|
|
1188
|
+
console.error(`Hint: ${error.recoveryHint}`);
|
|
1189
|
+
}
|
|
1037
1190
|
process.exitCode = 1;
|
|
1038
1191
|
return;
|
|
1039
1192
|
}
|