@outputai/cli 0.10.1-next.eaf62a3.0 → 0.10.1-next.f6a7c1a.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/generated/api.d.ts +12 -0
- package/dist/assets/docker/docker-compose-dev.yml +1 -1
- package/dist/commands/workflow/history.js +3 -3
- package/dist/commands/workflow/history.spec.js +31 -2
- package/dist/commands/workflow/monitor.d.ts +49 -0
- package/dist/commands/workflow/monitor.js +230 -0
- package/dist/commands/workflow/monitor.spec.d.ts +1 -0
- package/dist/commands/workflow/monitor.spec.js +243 -0
- package/dist/generated/framework_version.json +1 -1
- package/dist/services/workflow_history/correlator.d.ts +2 -0
- package/dist/services/workflow_history/correlator.js +2 -2
- package/dist/services/workflow_history.d.ts +28 -0
- package/dist/services/workflow_history.js +95 -12
- package/dist/services/workflow_history.spec.js +183 -1
- package/dist/utils/color.d.ts +7 -0
- package/dist/utils/color.js +12 -0
- package/dist/utils/color.spec.d.ts +1 -0
- package/dist/utils/color.spec.js +43 -0
- package/dist/utils/format_workflow_result.d.ts +1 -0
- package/dist/utils/format_workflow_result.js +4 -0
- package/dist/utils/monitor_log.d.ts +20 -0
- package/dist/utils/monitor_log.js +48 -0
- package/dist/utils/monitor_log.spec.d.ts +1 -0
- package/dist/utils/monitor_log.spec.js +71 -0
- package/dist/utils/waterfall.d.ts +3 -1
- package/dist/utils/waterfall.js +8 -2
- package/dist/views/dev/hooks/use_run_detail.js +7 -8
- package/dist/views/dev/hooks/use_step_graph.js +3 -1
- package/dist/views/dev/utils/bounded_cache.d.ts +14 -0
- package/dist/views/dev/utils/bounded_cache.js +42 -0
- package/dist/views/dev/utils/bounded_cache.spec.d.ts +1 -0
- package/dist/views/dev/utils/bounded_cache.spec.js +53 -0
- package/oclif.manifest.json +75 -1
- package/package.json +4 -4
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* Atlas's `StepGantt`; the bar geometry mirrors its leftPct/widthPct as integer
|
|
9
9
|
* terminal columns.
|
|
10
10
|
*/
|
|
11
|
-
import type { Span } from '#services/workflow_history/correlator.js';
|
|
11
|
+
import type { Span, SpanStatus } from '#services/workflow_history/correlator.js';
|
|
12
12
|
export interface WaterfallOptions {
|
|
13
13
|
width: number;
|
|
14
14
|
color: boolean;
|
|
@@ -22,6 +22,8 @@ export interface BarGeometry {
|
|
|
22
22
|
}
|
|
23
23
|
export declare const FULL_BLOCK = "\u2588";
|
|
24
24
|
export declare const THIN_BLOCK = "\u258F";
|
|
25
|
+
export declare const ANSI: Record<SpanStatus | 'dim' | 'reset', string>;
|
|
26
|
+
export declare function makeTint(color: boolean): (text: string, code: string) => string;
|
|
25
27
|
export declare function pickTickStep(totalMs: number): number;
|
|
26
28
|
export declare function buildTicks(totalMs: number): number[];
|
|
27
29
|
export declare function formatTickLabel(ms: number): string;
|
package/dist/utils/waterfall.js
CHANGED
|
@@ -12,7 +12,7 @@ const TICK_STEPS_MS = [
|
|
|
12
12
|
60 * 60_000, 2 * 60 * 60_000, 6 * 60 * 60_000, 12 * 60 * 60_000, 24 * 60 * 60_000
|
|
13
13
|
];
|
|
14
14
|
const TARGET_TICKS = 8;
|
|
15
|
-
const ANSI = {
|
|
15
|
+
export const ANSI = {
|
|
16
16
|
completed: '[92m',
|
|
17
17
|
running: '[33m',
|
|
18
18
|
failed: '[31m',
|
|
@@ -20,6 +20,12 @@ const ANSI = {
|
|
|
20
20
|
dim: '[2m',
|
|
21
21
|
reset: '[0m'
|
|
22
22
|
};
|
|
23
|
+
// Shared by renderWaterfall and monitor_log's live status lines so there's one
|
|
24
|
+
// place that knows how to wrap text in an ANSI code (and reset it) -- or not,
|
|
25
|
+
// when color is disabled.
|
|
26
|
+
export function makeTint(color) {
|
|
27
|
+
return (text, code) => (color ? `${code}${text}${ANSI.reset}` : text);
|
|
28
|
+
}
|
|
23
29
|
function clamp(value, lo, hi) {
|
|
24
30
|
return Math.min(Math.max(value, lo), hi);
|
|
25
31
|
}
|
|
@@ -162,7 +168,7 @@ export default function renderWaterfall(spans, totalDurationMs, options) {
|
|
|
162
168
|
if (spans.length === 0) {
|
|
163
169
|
return [header, 'No steps found for this run.'].filter(Boolean).join('\n\n');
|
|
164
170
|
}
|
|
165
|
-
const tint = (
|
|
171
|
+
const tint = makeTint(color);
|
|
166
172
|
const labelFor = (span) => labels?.get(span.id) ?? span.name;
|
|
167
173
|
const durationFor = (span) => formatDurationLabel(Math.max(0, span.durationMs));
|
|
168
174
|
const longestLabel = Math.max(...spans.map(s => labelFor(s).length));
|
|
@@ -2,13 +2,16 @@ import { useEffect, useRef, useState } from 'react';
|
|
|
2
2
|
import { readFile } from 'node:fs/promises';
|
|
3
3
|
import { getWorkflowIdResult, getWorkflowIdRunsRidResult, getWorkflowIdTraceLog, getWorkflowIdRunsRidTraceLog } from '#api/generated/api.js';
|
|
4
4
|
import { normalizeWorkflowStatus } from '#utils/normalize_workflow_status.js';
|
|
5
|
+
import { TERMINAL_STATUSES } from '#utils/format_workflow_result.js';
|
|
6
|
+
import { createBoundedCache } from '#views/dev/utils/bounded_cache.js';
|
|
5
7
|
const EMPTY_DETAIL = {
|
|
6
8
|
result: null,
|
|
7
9
|
trace: null,
|
|
8
10
|
steps: [],
|
|
9
11
|
loading: false
|
|
10
12
|
};
|
|
11
|
-
const
|
|
13
|
+
const RUN_DETAIL_CACHE_MAX = 50;
|
|
14
|
+
const runDetailCache = createBoundedCache(RUN_DETAIL_CACHE_MAX);
|
|
12
15
|
const stepNameOf = (node) => {
|
|
13
16
|
if (node.name) {
|
|
14
17
|
return node.name;
|
|
@@ -99,13 +102,9 @@ const fetchResult = async (workflowId, runId) => {
|
|
|
99
102
|
return null;
|
|
100
103
|
}
|
|
101
104
|
};
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
* running workflow would otherwise stick and stall the UI when the run
|
|
106
|
-
* eventually finishes.
|
|
107
|
-
*/
|
|
108
|
-
const TERMINAL_STATUSES = new Set(['completed', 'failed', 'canceled', 'terminated', 'timed_out']);
|
|
105
|
+
// The cache is intentionally only populated for terminal statuses — partial results
|
|
106
|
+
// from a still-running workflow would otherwise stick and stall the UI when the run
|
|
107
|
+
// eventually finishes. `TERMINAL_STATUSES` is shared with `workflow monitor`.
|
|
109
108
|
export const isTerminalRunStatus = (status) => Boolean(status && TERMINAL_STATUSES.has(status));
|
|
110
109
|
export const useRunDetail = (workflowId, runId, status) => {
|
|
111
110
|
const [detail, setDetail] = useState(EMPTY_DETAIL);
|
|
@@ -3,6 +3,7 @@ import { fetchWorkflowHistory } from '#services/workflow_history.js';
|
|
|
3
3
|
import buildSpanLabels from '#utils/span_labels.js';
|
|
4
4
|
import { isTerminalRunStatus } from '#views/dev/hooks/use_run_detail.js';
|
|
5
5
|
import { usePoll, POLL_INTERVAL_MS } from '#views/dev/hooks/use_poll.js';
|
|
6
|
+
import { createBoundedCache } from '#views/dev/utils/bounded_cache.js';
|
|
6
7
|
const EMPTY_GRAPH = {
|
|
7
8
|
spans: [],
|
|
8
9
|
totalDurationMs: 0,
|
|
@@ -11,7 +12,8 @@ const EMPTY_GRAPH = {
|
|
|
11
12
|
loading: false,
|
|
12
13
|
error: null
|
|
13
14
|
};
|
|
14
|
-
const
|
|
15
|
+
const STEP_GRAPH_CACHE_MAX = 50;
|
|
16
|
+
const stepGraphCache = createBoundedCache(STEP_GRAPH_CACHE_MAX);
|
|
15
17
|
/**
|
|
16
18
|
* Fetches a run's correlated step spans for the dev TUI's waterfall overlay,
|
|
17
19
|
* reusing the same `fetchWorkflowHistory` path as the `workflow history` CLI
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface BoundedCache<K, V extends {}> {
|
|
2
|
+
get(key: K): V | undefined;
|
|
3
|
+
set(key: K, value: V): void;
|
|
4
|
+
has(key: K): boolean;
|
|
5
|
+
clear(): void;
|
|
6
|
+
size(): number;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* A small LRU cache backed by an insertion-ordered `Map`, capped at `maxSize`
|
|
10
|
+
* entries. Reading a key refreshes its recency; once the cap is exceeded the
|
|
11
|
+
* least-recently-used entries are evicted. Drop-in compatible with the
|
|
12
|
+
* `Map.get` / `Map.set` calls it replaces.
|
|
13
|
+
*/
|
|
14
|
+
export declare const createBoundedCache: <K, V extends {}>(maxSize: number) => BoundedCache<K, V>;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A small LRU cache backed by an insertion-ordered `Map`, capped at `maxSize`
|
|
3
|
+
* entries. Reading a key refreshes its recency; once the cap is exceeded the
|
|
4
|
+
* least-recently-used entries are evicted. Drop-in compatible with the
|
|
5
|
+
* `Map.get` / `Map.set` calls it replaces.
|
|
6
|
+
*/
|
|
7
|
+
export const createBoundedCache = (maxSize) => {
|
|
8
|
+
if (maxSize < 1) {
|
|
9
|
+
throw new Error('createBoundedCache: maxSize must be >= 1');
|
|
10
|
+
}
|
|
11
|
+
const entries = new Map();
|
|
12
|
+
return {
|
|
13
|
+
get(key) {
|
|
14
|
+
const value = entries.get(key);
|
|
15
|
+
if (value === undefined) {
|
|
16
|
+
return value;
|
|
17
|
+
}
|
|
18
|
+
entries.delete(key);
|
|
19
|
+
entries.set(key, value);
|
|
20
|
+
return value;
|
|
21
|
+
},
|
|
22
|
+
set(key, value) {
|
|
23
|
+
entries.delete(key);
|
|
24
|
+
entries.set(key, value);
|
|
25
|
+
if (entries.size > maxSize) {
|
|
26
|
+
const oldest = entries.keys().next();
|
|
27
|
+
if (!oldest.done) {
|
|
28
|
+
entries.delete(oldest.value);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
has(key) {
|
|
33
|
+
return entries.has(key);
|
|
34
|
+
},
|
|
35
|
+
clear() {
|
|
36
|
+
entries.clear();
|
|
37
|
+
},
|
|
38
|
+
size() {
|
|
39
|
+
return entries.size;
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { createBoundedCache } from './bounded_cache.js';
|
|
3
|
+
describe('createBoundedCache', () => {
|
|
4
|
+
it('round-trips set and get', () => {
|
|
5
|
+
const cache = createBoundedCache(3);
|
|
6
|
+
cache.set('a', 1);
|
|
7
|
+
expect(cache.get('a')).toBe(1);
|
|
8
|
+
expect(cache.get('missing')).toBeUndefined();
|
|
9
|
+
});
|
|
10
|
+
it('evicts the oldest entry once maxSize is exceeded', () => {
|
|
11
|
+
const cache = createBoundedCache(2);
|
|
12
|
+
cache.set('a', 1);
|
|
13
|
+
cache.set('b', 2);
|
|
14
|
+
cache.set('c', 3);
|
|
15
|
+
expect(cache.has('a')).toBe(false);
|
|
16
|
+
expect(cache.get('b')).toBe(2);
|
|
17
|
+
expect(cache.get('c')).toBe(3);
|
|
18
|
+
});
|
|
19
|
+
it('refreshes recency on get so the touched entry survives eviction', () => {
|
|
20
|
+
const cache = createBoundedCache(2);
|
|
21
|
+
cache.set('a', 1);
|
|
22
|
+
cache.set('b', 2);
|
|
23
|
+
// Touch 'a' so 'b' becomes the least-recently-used entry.
|
|
24
|
+
expect(cache.get('a')).toBe(1);
|
|
25
|
+
cache.set('c', 3);
|
|
26
|
+
expect(cache.has('a')).toBe(true);
|
|
27
|
+
expect(cache.has('b')).toBe(false);
|
|
28
|
+
expect(cache.has('c')).toBe(true);
|
|
29
|
+
});
|
|
30
|
+
it('updates an existing key in place without growing', () => {
|
|
31
|
+
const cache = createBoundedCache(2);
|
|
32
|
+
cache.set('a', 1);
|
|
33
|
+
cache.set('a', 2);
|
|
34
|
+
expect(cache.get('a')).toBe(2);
|
|
35
|
+
expect(cache.size()).toBe(1);
|
|
36
|
+
});
|
|
37
|
+
it('supports has and clear', () => {
|
|
38
|
+
const cache = createBoundedCache(2);
|
|
39
|
+
cache.set('a', 1);
|
|
40
|
+
expect(cache.has('a')).toBe(true);
|
|
41
|
+
cache.clear();
|
|
42
|
+
expect(cache.has('a')).toBe(false);
|
|
43
|
+
expect(cache.size()).toBe(0);
|
|
44
|
+
});
|
|
45
|
+
it('never exceeds maxSize', () => {
|
|
46
|
+
const cache = createBoundedCache(3);
|
|
47
|
+
Array.from({ length: 100 }, (_, i) => i).forEach(i => {
|
|
48
|
+
cache.set(`key-${i}`, i);
|
|
49
|
+
expect(cache.size()).toBeLessThanOrEqual(3);
|
|
50
|
+
});
|
|
51
|
+
expect(cache.size()).toBe(3);
|
|
52
|
+
});
|
|
53
|
+
});
|
package/oclif.manifest.json
CHANGED
|
@@ -905,6 +905,80 @@
|
|
|
905
905
|
"list.js"
|
|
906
906
|
]
|
|
907
907
|
},
|
|
908
|
+
"workflow:monitor": {
|
|
909
|
+
"aliases": [],
|
|
910
|
+
"args": {
|
|
911
|
+
"workflowId": {
|
|
912
|
+
"description": "The workflow ID to monitor",
|
|
913
|
+
"name": "workflowId",
|
|
914
|
+
"required": true
|
|
915
|
+
}
|
|
916
|
+
},
|
|
917
|
+
"description": "Attach to a workflow run and stream status updates until it ends",
|
|
918
|
+
"examples": [
|
|
919
|
+
"<%= config.bin %> <%= command.id %> wf-12345",
|
|
920
|
+
"<%= config.bin %> <%= command.id %> wf-12345 --run-id 2fe0b36b-...",
|
|
921
|
+
"<%= config.bin %> <%= command.id %> wf-12345 --format json"
|
|
922
|
+
],
|
|
923
|
+
"flags": {
|
|
924
|
+
"run-id": {
|
|
925
|
+
"char": "r",
|
|
926
|
+
"description": "Monitor a specific run (defaults to the latest run; continue-as-new chains are followed regardless)",
|
|
927
|
+
"name": "run-id",
|
|
928
|
+
"hasDynamicHelp": false,
|
|
929
|
+
"multiple": false,
|
|
930
|
+
"type": "option"
|
|
931
|
+
},
|
|
932
|
+
"format": {
|
|
933
|
+
"char": "f",
|
|
934
|
+
"description": "Output format",
|
|
935
|
+
"name": "format",
|
|
936
|
+
"default": "text",
|
|
937
|
+
"hasDynamicHelp": false,
|
|
938
|
+
"multiple": false,
|
|
939
|
+
"options": [
|
|
940
|
+
"text",
|
|
941
|
+
"json"
|
|
942
|
+
],
|
|
943
|
+
"type": "option"
|
|
944
|
+
},
|
|
945
|
+
"include-payloads": {
|
|
946
|
+
"description": "Include decoded step input/output payloads",
|
|
947
|
+
"name": "include-payloads",
|
|
948
|
+
"allowNo": false,
|
|
949
|
+
"type": "boolean"
|
|
950
|
+
},
|
|
951
|
+
"interval": {
|
|
952
|
+
"description": "Poll interval in milliseconds. Once a resumed poll is long-polling server-side for new events, this also bounds how long that block may last (capped at the server's configured max), so an idle workflow's update cadence is roughly twice this value (the long-poll bound, then this sleep) rather than a much longer, separate server default.",
|
|
953
|
+
"name": "interval",
|
|
954
|
+
"default": 2500,
|
|
955
|
+
"hasDynamicHelp": false,
|
|
956
|
+
"multiple": false,
|
|
957
|
+
"type": "option"
|
|
958
|
+
},
|
|
959
|
+
"color": {
|
|
960
|
+
"description": "Colorize status output (use --no-color to disable)",
|
|
961
|
+
"name": "color",
|
|
962
|
+
"allowNo": true,
|
|
963
|
+
"type": "boolean"
|
|
964
|
+
}
|
|
965
|
+
},
|
|
966
|
+
"hasDynamicHelp": false,
|
|
967
|
+
"hiddenAliases": [],
|
|
968
|
+
"id": "workflow:monitor",
|
|
969
|
+
"pluginAlias": "@outputai/cli",
|
|
970
|
+
"pluginName": "@outputai/cli",
|
|
971
|
+
"pluginType": "core",
|
|
972
|
+
"strict": true,
|
|
973
|
+
"enableJsonFlag": false,
|
|
974
|
+
"isESM": true,
|
|
975
|
+
"relativePath": [
|
|
976
|
+
"dist",
|
|
977
|
+
"commands",
|
|
978
|
+
"workflow",
|
|
979
|
+
"monitor.js"
|
|
980
|
+
]
|
|
981
|
+
},
|
|
908
982
|
"workflow:plan": {
|
|
909
983
|
"aliases": [],
|
|
910
984
|
"args": {},
|
|
@@ -1597,5 +1671,5 @@
|
|
|
1597
1671
|
]
|
|
1598
1672
|
}
|
|
1599
1673
|
},
|
|
1600
|
-
"version": "0.10.1-next.
|
|
1674
|
+
"version": "0.10.1-next.f6a7c1a.0"
|
|
1601
1675
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@outputai/cli",
|
|
3
|
-
"version": "0.10.1-next.
|
|
3
|
+
"version": "0.10.1-next.f6a7c1a.0",
|
|
4
4
|
"description": "CLI for Output.ai workflow generation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -38,9 +38,9 @@
|
|
|
38
38
|
"semver": "7.7.4",
|
|
39
39
|
"undici": "8.5.0",
|
|
40
40
|
"yaml": "^2.8.3",
|
|
41
|
-
"@outputai/credentials": "0.10.1-next.
|
|
42
|
-
"@outputai/
|
|
43
|
-
"@outputai/
|
|
41
|
+
"@outputai/credentials": "0.10.1-next.f6a7c1a.0",
|
|
42
|
+
"@outputai/evals": "0.10.1-next.f6a7c1a.0",
|
|
43
|
+
"@outputai/llm": "0.10.1-next.f6a7c1a.0"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"@types/cli-progress": "3.11.6",
|