@clemrig/flow-ui 0.1.2 → 0.3.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/README.md +3 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/server.d.ts +12 -1
- package/dist/server.js +21 -7
- package/dist/server.js.map +1 -1
- package/dist/static/dashboard.js +5 -1
- package/dist/static/follow.js +41 -5
- package/dist/static/index.html +2 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ npm install -g @clemrig/flow-ui
|
|
|
11
11
|
flow-ui [RUNS_DIR] [--port PORT]
|
|
12
12
|
```
|
|
13
13
|
|
|
14
|
-
`flow-ui` serves the runs kept in `RUNS_DIR` (default: `.flow/runs`, where `@clemrig/flow` keeps them; a `RUNS_DIR` given must be an existing directory) on `127.0.0.1`, on `PORT` or a port the system chooses, prints its URL and stops on Ctrl-C. The page lists the runs with their workflow, status, current stage, duration, estimated cost and last update, and for a run its runtimes, a timeline with one block per stage (the agent call's model and settings, its tools and tokens, journal, decision), its correction counters and its result. It follows a running run as it goes. A result shows the run's validated data as JSON.
|
|
14
|
+
`flow-ui` serves the runs kept in `RUNS_DIR` (default: `.flow/runs`, where `@clemrig/flow` keeps them; a `RUNS_DIR` given must be an existing directory) on `127.0.0.1`, on `PORT` or a port the system chooses, prints its URL and stops on Ctrl-C. The page lists the runs with their workflow, status, current stage, duration, estimated cost and last update, and for a run its runtimes, a timeline with one block per stage (the agent call's model and settings, its tools and tokens, journal, decision), its correction counters and its result. It follows a running run as it goes, and follows the selected run again when it is resumed. A result shows the run's validated data as JSON.
|
|
15
15
|
|
|
16
16
|
To watch a run on another machine, forward the same port through SSH: `ssh -L 4321:127.0.0.1:4321 machine`, run `flow-ui --port 4321` there, and open `http://localhost:4321/`.
|
|
17
17
|
|
|
@@ -32,6 +32,8 @@ console.log(dashboard.url);
|
|
|
32
32
|
// later: await dashboard.close();
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
+
`dashboardHandler(options)` gives the same dashboard as a request handler, for a server that serves it beside other pages: it answers `(request, response, url)`, where `url` is the request's address relative to where the dashboard is mounted (`/api/runs` for `/runs/api/runs`), and refuses, like `startDashboard`, every method but GET and any `Host` other than its own address. The page's own addresses are relative, so it works under any path that ends with `/`.
|
|
36
|
+
|
|
35
37
|
## License
|
|
36
38
|
|
|
37
39
|
MIT
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** Public API of the dashboard: serve a read-only view of runs kept by `@clemrig/flow`. */
|
|
2
|
-
export { type DashboardOptions, startDashboard } from "./server.js";
|
|
2
|
+
export { type DashboardHandler, type DashboardOptions, dashboardHandler, startDashboard, } from "./server.js";
|
package/dist/index.js
CHANGED
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,2FAA2F;AAE3F,OAAO,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,2FAA2F;AAE3F,OAAO,EAGL,gBAAgB,EAChB,cAAc,GACf,MAAM,aAAa,CAAC"}
|
package/dist/server.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type Server } from "node:http";
|
|
1
|
+
import { type IncomingMessage, type Server, type ServerResponse } from "node:http";
|
|
2
2
|
import { type RunState } from "@clemrig/flow";
|
|
3
3
|
export interface DashboardOptions {
|
|
4
4
|
/** Where the runs are kept. */
|
|
@@ -10,6 +10,17 @@ export interface DashboardOptions {
|
|
|
10
10
|
/** A run's result lines; by default, its validated data as JSON. */
|
|
11
11
|
presentResult?(state: Readonly<RunState>): string[];
|
|
12
12
|
}
|
|
13
|
+
/**
|
|
14
|
+
* Answers one request; `url` is its address relative to where the dashboard is
|
|
15
|
+
* served: a server that mounts it under `/runs/` passes `/api/runs` for
|
|
16
|
+
* `/runs/api/runs`. The page's own addresses are relative.
|
|
17
|
+
*/
|
|
18
|
+
export type DashboardHandler = (request: IncomingMessage, response: ServerResponse, url: URL) => Promise<void>;
|
|
19
|
+
/**
|
|
20
|
+
* The dashboard as a request handler, for a server that serves it beside other
|
|
21
|
+
* pages: it refuses other hosts than its own address and every method but GET.
|
|
22
|
+
*/
|
|
23
|
+
export declare function dashboardHandler(options: DashboardOptions): Promise<DashboardHandler>;
|
|
13
24
|
/** A read-only view of the runs on 127.0.0.1, on `port` or one chosen by the system. */
|
|
14
25
|
export declare function startDashboard(options: DashboardOptions, listen?: number): Promise<{
|
|
15
26
|
url: string;
|
package/dist/server.js
CHANGED
|
@@ -52,18 +52,20 @@ function send(response, status, body, type = "text/plain; charset=utf-8") {
|
|
|
52
52
|
});
|
|
53
53
|
response.end(body);
|
|
54
54
|
}
|
|
55
|
-
/**
|
|
56
|
-
|
|
57
|
-
|
|
55
|
+
/**
|
|
56
|
+
* The dashboard as a request handler, for a server that serves it beside other
|
|
57
|
+
* pages: it refuses other hosts than its own address and every method but GET.
|
|
58
|
+
*/
|
|
59
|
+
export async function dashboardHandler(options) {
|
|
58
60
|
const page = await loadPage();
|
|
59
|
-
|
|
61
|
+
return async (request, response, url) => {
|
|
60
62
|
// Another Host means another site reached this port (DNS rebinding).
|
|
63
|
+
const port = request.socket.localPort;
|
|
61
64
|
if (request.headers.host !== `127.0.0.1:${port}` &&
|
|
62
65
|
request.headers.host !== `localhost:${port}`)
|
|
63
66
|
return send(response, 403, "Forbidden");
|
|
64
67
|
if (request.method !== "GET")
|
|
65
68
|
return send(response, 405, "Method Not Allowed");
|
|
66
|
-
const url = new URL(request.url ?? "/", "http://127.0.0.1");
|
|
67
69
|
const file = page.get(url.pathname);
|
|
68
70
|
if (file)
|
|
69
71
|
return send(response, 200, file.body, file.type);
|
|
@@ -87,14 +89,26 @@ export async function startDashboard(options, listen = 0) {
|
|
|
87
89
|
}
|
|
88
90
|
return send(response, 200, JSON.stringify(body), "application/json");
|
|
89
91
|
};
|
|
92
|
+
}
|
|
93
|
+
/** A read-only view of the runs on 127.0.0.1, on `port` or one chosen by the system. */
|
|
94
|
+
export async function startDashboard(options, listen = 0) {
|
|
95
|
+
const handle = await dashboardHandler(options);
|
|
90
96
|
const server = createServer((request, response) => {
|
|
91
|
-
|
|
97
|
+
let url;
|
|
98
|
+
try {
|
|
99
|
+
url = new URL(request.url ?? "/", "http://127.0.0.1");
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
// An address that is not one, such as `http://[`.
|
|
103
|
+
return send(response, 400, "Bad Request");
|
|
104
|
+
}
|
|
105
|
+
handle(request, response, url).catch((error) => send(response, 500, error instanceof Error ? error.message : String(error)));
|
|
92
106
|
});
|
|
93
107
|
await new Promise((resolve, reject) => {
|
|
94
108
|
server.once("error", reject);
|
|
95
109
|
server.listen(listen, "127.0.0.1", () => resolve());
|
|
96
110
|
});
|
|
97
|
-
port = server.address()
|
|
111
|
+
const { port } = server.address();
|
|
98
112
|
return {
|
|
99
113
|
url: `http://127.0.0.1:${port}/`,
|
|
100
114
|
server,
|
package/dist/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EACL,YAAY,GAIb,MAAM,WAAW,CAAC;AAEnB,OAAO,EACL,QAAQ,EACR,OAAO,EACP,aAAa,EACb,aAAa,EAEb,oBAAoB,EACpB,YAAY,EACZ,eAAe,GAChB,MAAM,eAAe,CAAC;AAavB,gEAAgE;AAChE,KAAK,UAAU,IAAI,CAAC,OAAyB;IAC3C,OAAO;QACL,KAAK,EAAE,OAAO,CAAC,KAAK;QACpB,KAAK,EAAE,OAAO,CAAC,KAAK;QACpB,IAAI,EAAE,CAAC,MAAM,QAAQ,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;aACpC,OAAO,EAAE;aACT,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,OAAO,IAAI,KAAK,CAAC,CAAC,CAAC,YAAY,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;KAC1E,CAAC;AACJ,CAAC;AAED,kGAAkG;AAClG,KAAK,UAAU,MAAM,CAAC,OAAyB,EAAE,EAAU,EAAE,KAAa;IACxE,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;IACjD,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;IACxD,OAAO;QACL,GAAG,EAAE,YAAY,CAAC,KAAK,CAAC;QACxB,QAAQ,EAAE,KAAK,CAAC,QAAQ,IAAI,EAAE;QAC9B,KAAK,EAAE,KAAK,CAAC,KAAK,IAAI,IAAI;QAC1B,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC;QAC3B,IAAI,EAAE,MAAM,CAAC,MAAM;QACnB,4EAA4E;QAC5E,WAAW,EAAE,oBAAoB,CAAC,KAAK,EAAE,MAAM,CAAC;QAChD,MAAM,EAAE,aAAa,CAAC,OAAO,EAAE,KAAK,CAAC;KACtC,CAAC;AACJ,CAAC;AAED,+EAA+E;AAC/E,MAAM,SAAS,GAAG;IAChB,GAAG,EAAE,CAAC,YAAY,EAAE,0BAA0B,CAAC;IAC/C,YAAY,EAAE,CAAC,WAAW,EAAE,yBAAyB,CAAC;IACtD,eAAe,EAAE,CAAC,cAAc,EAAE,gCAAgC,CAAC;IACnE,cAAc,EAAE,CAAC,aAAa,EAAE,gCAAgC,CAAC;IACjE,YAAY,EAAE,CAAC,WAAW,EAAE,gCAAgC,CAAC;CACrD,CAAC;AAEX,KAAK,UAAU,QAAQ;IACrB,MAAM,IAAI,GAAG,IAAI,GAAG,EAA0C,CAAC;IAC/D,KAAK,MAAM,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC;QAC1D,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE;YACb,IAAI,EAAE,MAAM,QAAQ,CAClB,IAAI,GAAG,CAAC,YAAY,IAAI,EAAE,EAAE,OAAO,IAAI,CAAC,GAAG,CAAC,EAC5C,MAAM,CACP;YACD,IAAI;SACL,CAAC,CAAC;IACL,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,IAAI,CACX,QAAwB,EACxB,MAAc,EACd,IAAY,EACZ,IAAI,GAAG,2BAA2B;IAElC,QAAQ,CAAC,SAAS,CAAC,MAAM,EAAE;QACzB,cAAc,EAAE,IAAI;QACpB,eAAe,EAAE,UAAU;QAC3B,wBAAwB,EAAE,SAAS;QACnC,yBAAyB,EACvB,0IAA0I;KAC7I,CAAC,CAAC;IACH,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;AACrB,CAAC;
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|
package/dist/static/dashboard.js
CHANGED
|
@@ -212,11 +212,15 @@ function updated(message) {
|
|
|
212
212
|
|
|
213
213
|
async function refreshRuns() {
|
|
214
214
|
try {
|
|
215
|
-
|
|
215
|
+
/** @type {{ title: string; label: string; runs: Summary[] }} */
|
|
216
|
+
const { title, label, runs } = await load("api/runs");
|
|
216
217
|
byId("title").textContent = title;
|
|
217
218
|
document.title = title;
|
|
218
219
|
byId("label").textContent = label;
|
|
219
220
|
showRuns(runs);
|
|
221
|
+
// A resumed run goes on under the same ID, running or already ended again.
|
|
222
|
+
const listed = runs.find((run) => run.id === selected);
|
|
223
|
+
if (listed?.updatedAt) follow.listed(selected, listed.updatedAt);
|
|
220
224
|
updated(`Updated ${new Date().toLocaleTimeString()}`);
|
|
221
225
|
} catch (error) {
|
|
222
226
|
unavailable(error);
|
package/dist/static/follow.js
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Follows the selected run: asks only for the events not received yet, one
|
|
4
4
|
* request after the answer to the previous one, drops an answer for a selection
|
|
5
|
-
* that has changed since, and stops once the run has ended or does not exist
|
|
5
|
+
* that has changed since, and stops once the run has ended or does not exist,
|
|
6
|
+
* until the run list shows it updated since, as a resumed run is.
|
|
6
7
|
* Requests and timers are given, so that this logic is tested without a browser.
|
|
7
8
|
*/
|
|
8
9
|
|
|
@@ -10,7 +11,7 @@
|
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* @typedef {object} Answer
|
|
13
|
-
* @property {{ status: string }} run
|
|
14
|
+
* @property {{ status: string; updatedAt?: string }} run
|
|
14
15
|
* @property {RunEvent[]} events Events from the index the request asked for.
|
|
15
16
|
* @property {number} next The index to ask for next.
|
|
16
17
|
*/
|
|
@@ -26,7 +27,15 @@
|
|
|
26
27
|
*/
|
|
27
28
|
export function follower({ load, schedule, cancel, show, fail }) {
|
|
28
29
|
/** One selection: a new object each time, so that an old answer never matches. */
|
|
29
|
-
let current = {
|
|
30
|
+
let current = {
|
|
31
|
+
id: "",
|
|
32
|
+
events: /** @type {RunEvent[]} */ ([]),
|
|
33
|
+
next: 0,
|
|
34
|
+
/** Whether following stopped, on the run's end or absence. */
|
|
35
|
+
stopped: false,
|
|
36
|
+
/** The run's last update, as the last answer showed it. */
|
|
37
|
+
updatedAt: /** @type {string | undefined} */ (undefined),
|
|
38
|
+
};
|
|
30
39
|
/** @type {Timer | undefined} */
|
|
31
40
|
let timer;
|
|
32
41
|
|
|
@@ -37,7 +46,7 @@ export function follower({ load, schedule, cancel, show, fail }) {
|
|
|
37
46
|
let answer;
|
|
38
47
|
try {
|
|
39
48
|
answer = await load(
|
|
40
|
-
|
|
49
|
+
`api/runs/${encodeURIComponent(target.id)}?after=${target.next}`,
|
|
41
50
|
);
|
|
42
51
|
} catch (error) {
|
|
43
52
|
if (target !== current) return;
|
|
@@ -45,14 +54,17 @@ export function follower({ load, schedule, cancel, show, fail }) {
|
|
|
45
54
|
// A run that does not exist will not appear later.
|
|
46
55
|
const status = /** @type {{ status?: number }} */ (error).status;
|
|
47
56
|
if (status !== 404) timer = schedule(() => refresh(target));
|
|
57
|
+
else target.stopped = true;
|
|
48
58
|
return;
|
|
49
59
|
}
|
|
50
60
|
if (target !== current) return;
|
|
51
61
|
target.events.push(...answer.events);
|
|
52
62
|
target.next = answer.next;
|
|
63
|
+
target.updatedAt = answer.run.updatedAt;
|
|
53
64
|
show(answer, target.events);
|
|
54
65
|
if (answer.run.status === "running")
|
|
55
66
|
timer = schedule(() => refresh(target));
|
|
67
|
+
else target.stopped = true;
|
|
56
68
|
}
|
|
57
69
|
|
|
58
70
|
return {
|
|
@@ -60,7 +72,31 @@ export function follower({ load, schedule, cancel, show, fail }) {
|
|
|
60
72
|
select(id) {
|
|
61
73
|
if (id === current.id) return;
|
|
62
74
|
cancel(timer);
|
|
63
|
-
current = {
|
|
75
|
+
current = {
|
|
76
|
+
id,
|
|
77
|
+
events: [],
|
|
78
|
+
next: 0,
|
|
79
|
+
stopped: false,
|
|
80
|
+
updatedAt: undefined,
|
|
81
|
+
};
|
|
82
|
+
refresh(current);
|
|
83
|
+
},
|
|
84
|
+
/**
|
|
85
|
+
* The run list shows `id` last updated at `updatedAt`: following starts
|
|
86
|
+
* again, from the events already received, if it had stopped on an earlier
|
|
87
|
+
* state of the selected run, as when it was resumed since, even if the
|
|
88
|
+
* resumed run already ended again.
|
|
89
|
+
* @param {string} id
|
|
90
|
+
* @param {string} updatedAt
|
|
91
|
+
*/
|
|
92
|
+
listed(id, updatedAt) {
|
|
93
|
+
if (
|
|
94
|
+
id !== current.id ||
|
|
95
|
+
!current.stopped ||
|
|
96
|
+
updatedAt === current.updatedAt
|
|
97
|
+
)
|
|
98
|
+
return;
|
|
99
|
+
current.stopped = false;
|
|
64
100
|
refresh(current);
|
|
65
101
|
},
|
|
66
102
|
};
|
package/dist/static/index.html
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
<meta charset="utf-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
6
|
<title>runs</title>
|
|
7
|
-
<link rel="stylesheet" href="
|
|
8
|
-
<script type="module" src="
|
|
7
|
+
<link rel="stylesheet" href="style.css">
|
|
8
|
+
<script type="module" src="dashboard.js"></script>
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
|
11
11
|
<header><h1 id="title">runs</h1><span id="label"></span><span id="updated"></span></header>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@clemrig/flow-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "A local read-only dashboard of the runs of @clemrig/flow workflows",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -34,6 +34,6 @@
|
|
|
34
34
|
"prepack": "npm run build"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@clemrig/flow": "0.
|
|
37
|
+
"@clemrig/flow": "0.5.0"
|
|
38
38
|
}
|
|
39
39
|
}
|