@contraagent/api 0.0.0-stage → 0.1.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.
Files changed (72) hide show
  1. package/README.md +102 -2
  2. package/dist/client.d.ts +54 -0
  3. package/dist/client.d.ts.map +1 -0
  4. package/dist/client.js +68 -0
  5. package/dist/client.js.map +1 -0
  6. package/dist/derive.d.ts +81 -0
  7. package/dist/derive.d.ts.map +1 -0
  8. package/dist/derive.js +89 -0
  9. package/dist/derive.js.map +1 -0
  10. package/dist/export.d.ts +36 -0
  11. package/dist/export.d.ts.map +1 -0
  12. package/dist/export.js +130 -0
  13. package/dist/export.js.map +1 -0
  14. package/dist/feed.d.ts +27 -0
  15. package/dist/feed.d.ts.map +1 -0
  16. package/dist/feed.js +39 -0
  17. package/dist/feed.js.map +1 -0
  18. package/dist/index.d.ts +13 -0
  19. package/dist/index.d.ts.map +1 -0
  20. package/dist/index.js +13 -0
  21. package/dist/index.js.map +1 -0
  22. package/dist/outcomes.d.ts +117 -0
  23. package/dist/outcomes.d.ts.map +1 -0
  24. package/dist/outcomes.js +201 -0
  25. package/dist/outcomes.js.map +1 -0
  26. package/dist/panels.d.ts +31 -0
  27. package/dist/panels.d.ts.map +1 -0
  28. package/dist/panels.js +12 -0
  29. package/dist/panels.js.map +1 -0
  30. package/dist/quota.d.ts +32 -0
  31. package/dist/quota.d.ts.map +1 -0
  32. package/dist/quota.js +46 -0
  33. package/dist/quota.js.map +1 -0
  34. package/dist/reasons.d.ts +25 -0
  35. package/dist/reasons.d.ts.map +1 -0
  36. package/dist/reasons.js +46 -0
  37. package/dist/reasons.js.map +1 -0
  38. package/dist/run-state.d.ts +62 -0
  39. package/dist/run-state.d.ts.map +1 -0
  40. package/dist/run-state.js +109 -0
  41. package/dist/run-state.js.map +1 -0
  42. package/dist/schema.d.ts +3718 -0
  43. package/dist/schema.d.ts.map +1 -0
  44. package/dist/schema.js +3 -0
  45. package/dist/schema.js.map +1 -0
  46. package/dist/spend.d.ts +46 -0
  47. package/dist/spend.d.ts.map +1 -0
  48. package/dist/spend.js +68 -0
  49. package/dist/spend.js.map +1 -0
  50. package/dist/store.d.ts +121 -0
  51. package/dist/store.d.ts.map +1 -0
  52. package/dist/store.js +286 -0
  53. package/dist/store.js.map +1 -0
  54. package/dist/types.d.ts +112 -0
  55. package/dist/types.d.ts.map +1 -0
  56. package/dist/types.js +19 -0
  57. package/dist/types.js.map +1 -0
  58. package/package.json +43 -4
  59. package/src/client.ts +119 -0
  60. package/src/derive.ts +155 -0
  61. package/src/export.ts +158 -0
  62. package/src/feed.ts +81 -0
  63. package/src/index.ts +12 -0
  64. package/src/outcomes.ts +267 -0
  65. package/src/panels.ts +51 -0
  66. package/src/quota.ts +78 -0
  67. package/src/reasons.ts +57 -0
  68. package/src/run-state.ts +166 -0
  69. package/src/schema.ts +3719 -0
  70. package/src/spend.ts +108 -0
  71. package/src/store.ts +338 -0
  72. package/src/types.ts +165 -0
package/README.md CHANGED
@@ -1,3 +1,103 @@
1
- # Temporary Holding Version
1
+ # @contraagent/api
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ A typed client for the contraagent monitor's HTTP API: types generated from the
4
+ monitor's OpenAPI document, a REST client, the two live feeds, and a store that
5
+ folds a run's feed into one typed state. No UI: `@contraagent/ui` draws it.
6
+
7
+ The types are generated from the monitor's pydantic models via OpenAPI, so a
8
+ field the server removes fails your type check, not your page.
9
+
10
+ A `::code` line quotes a file of the package; the [documentation site](https://t-tani.github.io/contraagent/) shows the quoted lines in place. The same declarations, with their comments, are in `dist/**/*.d.ts`.
11
+
12
+ ## Getting it
13
+
14
+ The package is not in the npm registry; install it from a checkout of the
15
+ repository. Every monitor also serves the client, the feeds and the store as
16
+ exports of its `/app.js` (`@contraagent/ui`'s documentation).
17
+
18
+ ## Use it from a checkout
19
+
20
+ In the repository's `web/` (Node 22, pnpm through `corepack enable`):
21
+
22
+ ```
23
+ pnpm install && pnpm tarballs
24
+ ```
25
+
26
+ `pnpm tarballs` builds the packages and writes one `.tgz` file for each to
27
+ `web/tarballs/`. Then, in your project, with npm or pnpm:
28
+
29
+ ```
30
+ npm install <checkout>/web/tarballs/contraagent-api-*.tgz
31
+ pnpm add <checkout>/web/tarballs/contraagent-api-*.tgz
32
+ ```
33
+
34
+ ## Types
35
+
36
+ Every response and feed message has a type, with readable aliases:
37
+ `JournalRow`, `RunSummary`, `Plan`, `Outputs`, `TaskDetail`, `StreamPage`,
38
+ `TranscriptEvent`, and so on. Fields the server may leave out are optional.
39
+ Closed vocabularies are unions: `Stop` (how an attempt ended, `JournalRow['stop']`),
40
+ `TaskState`, `ContractStatus`.
41
+
42
+ The live feeds are discriminated unions on the SSE event name, so a `switch` on
43
+ `msg.event` narrows `msg.data`:
44
+
45
+ ::code{file="src/run-state.ts" from="export function reduceRun" to="// --- selectors"}
46
+
47
+ ## The client
48
+
49
+ `createMonitorClient({ baseUrl })` has one typed method per JSON endpoint
50
+ (`src/client.ts`); each throws `ApiError` when it gets no JSON answer: its
51
+ `failure` is `network`, `status` or `not-json` (such as an HTML page from a
52
+ server without the API), with the `url`, `status` and media `type`.
53
+
54
+ ## The live feeds
55
+
56
+ - `openRunFeed(run, panels, handlers)`: the run, live. On every (re)connect the
57
+ server replays the whole journal and then sends `ready`; every message after
58
+ `ready` is new.
59
+ - `openStreamFeed(run, key, handlers)`: one transcript, tailed: the tail page
60
+ first, then only what is new.
61
+
62
+ Each returns a function that closes it.
63
+
64
+ ## An export
65
+
66
+ An export (`contraagent monitor export`) is the monitor's views of some runs,
67
+ written as files and read with no server. `createExportClient(url)` is a client of
68
+ its `data/` directory: the same methods as the server's client, each read from its
69
+ file. `openExportFeed(client)` is the run feed for it: it plays a run's files once
70
+ and ends with `ready`. Give both to `MonitorStore` (`client` and `openRunFeed`) to
71
+ read an export as a server is read.
72
+
73
+ ## The store
74
+
75
+ `MonitorStore` keeps the run list fresh, opens the selected run's feed, folds
76
+ its messages into one `RunState`, and tells subscribers. It knows nothing of
77
+ any UI framework: a Lit controller, a React hook or plain code subscribe alike.
78
+ Its snapshot's `error` is the `ApiError` of the last request for the title or
79
+ the run list that got no answer, and
80
+ `runsLoaded` says the run list has been answered.
81
+ The reducer behind it, `reduceRun(state, actions)`, is pure: use it with your
82
+ own state management if you prefer. `@contraagent/ui`'s documentation shows
83
+ the store feeding its elements.
84
+
85
+ ## Tasks and their outcomes
86
+
87
+ A task is one task key. `deriveRun(journal, plan)` gives what every view counts
88
+ and lists about the run's tasks, computed once per state (the same `journal` and
89
+ `plan` objects give the same result):
90
+
91
+ - `outcomes` and `rows`: every task of the run, the plan joined with the
92
+ journal, with its outcome (`done`, `recovered`, `partial`, `failed`,
93
+ `not_started`, `running`, `waiting`, `skipped`; `partial`: ok, its output a
94
+ placeholder, neither done nor failed);
95
+ - `counts`: tasks by outcome, `doneTotal` (done and recovered: "N of M tasks
96
+ done") and `finished` (done, recovered, partial and skipped);
97
+ - `problems`: failed tasks with the tasks they stopped, tasks not started for a
98
+ reason of their own, and recovered tasks;
99
+ - `kinds` and `tries`.
100
+
101
+ `latestTask(rows)` is the task a page opens by default. A refusal before launch
102
+ (`invalid_input`) is not a try: a task refused and then done is `done`, not
103
+ `recovered`.
@@ -0,0 +1,54 @@
1
+ import type { Hosts, Info, JournalPage, JsonObject, RunSummary, StreamPage, TaskDefinition, TaskDetail, Trends, TrendsQuery } from './types.js';
2
+ /**
3
+ * Why a request got no JSON answer: `network`, no response (a network error, a
4
+ * refused connection, or a cross-origin response the browser blocked);
5
+ * `status`, a status outside 2xx; `not-json`, a body that is not JSON (empty,
6
+ * or the HTML page a development server answers every path with).
7
+ */
8
+ export type ApiFailure = 'network' | 'status' | 'not-json';
9
+ /**
10
+ * A request to the monitor's API that got no JSON answer. Its message states
11
+ * the URL, the failure, the status and the media type
12
+ * ("No JSON answer from http://host/api/runs: not-json 200 text/html");
13
+ * `@contraagent/ui` words it for the reader.
14
+ */
15
+ export declare class ApiError extends Error {
16
+ /** The URL requested. */
17
+ readonly url: string;
18
+ readonly failure: ApiFailure;
19
+ /** The response's status; 0 with no response. */
20
+ readonly status: number;
21
+ /** The response's media type, such as `text/html`; '' when it states none. */
22
+ readonly type: string;
23
+ constructor(
24
+ /** The URL requested. */
25
+ url: string, failure: ApiFailure,
26
+ /** The response's status; 0 with no response. */
27
+ status?: number,
28
+ /** The response's media type, such as `text/html`; '' when it states none. */
29
+ type?: string);
30
+ }
31
+ export interface ClientOptions {
32
+ /** Where the monitor is served; '' (the default) is the page's own origin. */
33
+ baseUrl?: string;
34
+ fetch?: typeof globalThis.fetch;
35
+ }
36
+ /** Typed calls for every JSON endpoint of the monitor. Each throws `ApiError` when it gets no JSON answer. */
37
+ export interface MonitorClient {
38
+ readonly baseUrl: string;
39
+ info(): Promise<Info>;
40
+ runs(): Promise<RunSummary[]>;
41
+ /** The hosts behind the runs (a hub: `Info.hosts`). */
42
+ hosts(): Promise<Hosts>;
43
+ journal(run: string, since?: number): Promise<JournalPage>;
44
+ definitions(run: string): Promise<TaskDefinition[]>;
45
+ task(run: string, key: string): Promise<TaskDetail>;
46
+ stream(run: string, key: string, since?: number): Promise<StreamPage>;
47
+ panel(run: string, name: string): Promise<JsonObject>;
48
+ /** What the runs of a period add up to. */
49
+ trends(query?: TrendsQuery): Promise<Trends>;
50
+ /** A trend panel's body over the runs of a period (`Panel(scope="trends")`). */
51
+ trendsPanel(name: string, query?: TrendsQuery): Promise<JsonObject>;
52
+ }
53
+ export declare function createMonitorClient(options?: ClientOptions): MonitorClient;
54
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,KAAK,EACL,IAAI,EACJ,WAAW,EACX,UAAU,EACV,UAAU,EACV,UAAU,EACV,cAAc,EACd,UAAU,EACV,MAAM,EACN,WAAW,EAEZ,MAAM,YAAY,CAAC;AAEpB;;;;;GAKG;AACH,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,QAAQ,GAAG,UAAU,CAAC;AAE3D;;;;;GAKG;AACH,qBAAa,QAAS,SAAQ,KAAK;IAE/B,yBAAyB;IACzB,QAAQ,CAAC,GAAG,EAAE,MAAM;IACpB,QAAQ,CAAC,OAAO,EAAE,UAAU;IAC5B,iDAAiD;IACjD,QAAQ,CAAC,MAAM;IACf,8EAA8E;IAC9E,QAAQ,CAAC,IAAI;;IANb,yBAAyB;IAChB,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,UAAU;IAC5B,iDAAiD;IACxC,MAAM,SAAI;IACnB,8EAA8E;IACrE,IAAI,SAAK;CAKrB;AAED,MAAM,WAAW,aAAa;IAC5B,8EAA8E;IAC9E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;CACjC;AAED,8GAA8G;AAC9G,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACtB,IAAI,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC;IAC9B,uDAAuD;IACvD,KAAK,IAAI,OAAO,CAAC,KAAK,CAAC,CAAC;IACxB,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;IAC3D,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC,CAAC;IACpD,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IACpD,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IACtE,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IACtD,2CAA2C;IAC3C,MAAM,CAAC,KAAK,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC7C,gFAAgF;IAChF,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;CACrE;AAWD,wBAAgB,mBAAmB,CAAC,OAAO,GAAE,aAAkB,GAAG,aAAa,CAyC9E"}
package/dist/client.js ADDED
@@ -0,0 +1,68 @@
1
+ /**
2
+ * A request to the monitor's API that got no JSON answer. Its message states
3
+ * the URL, the failure, the status and the media type
4
+ * ("No JSON answer from http://host/api/runs: not-json 200 text/html");
5
+ * `@contraagent/ui` words it for the reader.
6
+ */
7
+ export class ApiError extends Error {
8
+ constructor(
9
+ /** The URL requested. */
10
+ url, failure,
11
+ /** The response's status; 0 with no response. */
12
+ status = 0,
13
+ /** The response's media type, such as `text/html`; '' when it states none. */
14
+ type = '') {
15
+ super(`No JSON answer from ${url}: ${failure} ${status} ${type}`.trim());
16
+ this.url = url;
17
+ this.failure = failure;
18
+ this.status = status;
19
+ this.type = type;
20
+ this.name = 'ApiError';
21
+ }
22
+ }
23
+ export function createMonitorClient(options = {}) {
24
+ const baseUrl = (options.baseUrl ?? '').replace(/\/$/, '');
25
+ const origin = (baseUrl || globalThis.location?.origin || '').replace(/\/$/, '');
26
+ const fetch = options.fetch ?? globalThis.fetch;
27
+ /** The JSON answer of GET `endpoint`; `ApiError` when there is none. */
28
+ async function get(endpoint, params) {
29
+ const { path = {}, query = {} } = (params ?? {});
30
+ const url = origin + endpoint.replace(/\{(\w+)\}/g, (part, name) => (name in path ? encodeURIComponent(path[name]) : part));
31
+ const search = Object.entries(query)
32
+ .filter(([, v]) => v !== undefined && v !== null)
33
+ .map(([k, v]) => `${k}=${encodeURIComponent(v)}`)
34
+ .join('&');
35
+ const target = search ? `${url}?${search}` : url;
36
+ let response;
37
+ try {
38
+ response = await fetch(new Request(target));
39
+ }
40
+ catch {
41
+ throw new ApiError(target, 'network');
42
+ }
43
+ const { status, ok } = response;
44
+ const type = (response.headers.get('Content-Type') ?? '').split(';')[0].trim().toLowerCase();
45
+ try {
46
+ if (ok)
47
+ return (await response.json());
48
+ }
49
+ catch {
50
+ // An empty body, an HTML page, or text that is not JSON.
51
+ }
52
+ throw new ApiError(target, ok ? 'not-json' : 'status', status, type);
53
+ }
54
+ return {
55
+ baseUrl,
56
+ info: () => get('/api/info'),
57
+ runs: () => get('/api/runs'),
58
+ hosts: () => get('/api/hosts'),
59
+ journal: (run, since = 0) => get('/api/runs/{run}/journal', { path: { run }, query: { since } }),
60
+ definitions: (run) => get('/api/runs/{run}/definitions', { path: { run } }),
61
+ task: (run, key) => get('/api/runs/{run}/task/{key}', { path: { run, key } }),
62
+ stream: (run, key, since = 0) => get('/api/runs/{run}/stream/{key}', { path: { run, key }, query: { since } }),
63
+ panel: (run, name) => get('/api/runs/{run}/panels/{name}', { path: { run, name } }),
64
+ trends: (query = {}) => get('/api/trends', { query: { ...query } }),
65
+ trendsPanel: (name, query = {}) => get('/api/trends/panels/{name}', { path: { name }, query: { ...query } }),
66
+ };
67
+ }
68
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,81 @@
1
+ import type { Outcome, Problems, TaskOutcome, TaskRow } from './outcomes.js';
2
+ import type { HostSummary, JournalRow, Plan, RunSummary } from './types.js';
3
+ export interface RunCounts extends Record<Outcome, number> {
4
+ total: number;
5
+ /** done + recovered: "N of M tasks done" in every view. */
6
+ doneTotal: number;
7
+ /** done + recovered + partial + skipped: nothing is left to do. */
8
+ finished: number;
9
+ }
10
+ /** Tries (journal rows that ran) and rows of tasks that never started (a refusal, a skip). */
11
+ export interface TryCounts {
12
+ tries: number;
13
+ notStarted: number;
14
+ }
15
+ export interface RunDerived {
16
+ /** Every task of the run, by key: the plan joined with the journal. */
17
+ outcomes: ReadonlyMap<string, TaskOutcome>;
18
+ /** The same tasks as rows, in plan order. */
19
+ rows: readonly TaskRow[];
20
+ counts: RunCounts;
21
+ problems: Problems;
22
+ /** The tasks' kinds, sorted. */
23
+ kinds: readonly string[];
24
+ tries: TryCounts;
25
+ }
26
+ /**
27
+ * What the views show about a run's tasks. Without a plan, the tasks are the
28
+ * journal's only. The same `journal` and `plan` objects give the same result object.
29
+ */
30
+ export declare function deriveRun(journal: readonly JournalRow[], plan?: Plan | null | undefined): RunDerived;
31
+ /** Tries (journal rows that ran) and rows of tasks that never started: the one way the UI counts them. */
32
+ export declare function tryCounts(journal: readonly JournalRow[]): TryCounts;
33
+ export interface Progress {
34
+ total: number;
35
+ /** Plan state `done`: done and done after retrying. */
36
+ done: number;
37
+ /** Plan state `partial`: ok, the output a placeholder. */
38
+ partial: number;
39
+ skipped: number;
40
+ /** Finished (done, partial and skipped) out of all, in percent. */
41
+ pct: number;
42
+ }
43
+ /** How far a run is, from the plan's groups. */
44
+ export declare function progress(plan: Plan): Progress;
45
+ /** How a run's tasks ended, from the plan: for a run that is over, in place of a progress bar. */
46
+ export declare function result(plan: Plan): {
47
+ done: number;
48
+ partial: number;
49
+ skipped: number;
50
+ failed: number;
51
+ notStarted: number;
52
+ left: number;
53
+ };
54
+ /** How long a run took, wall clock: from its start to its last activity (null when unknown). */
55
+ export declare function elapsedS(r: Pick<RunSummary, 'started_at' | 'last_ts'> | undefined): number | null;
56
+ /**
57
+ * A run's process, as every view names it: `running`; `unresponsive`, its host
58
+ * last reported it alive and has stopped reporting (a hub only), so whether it
59
+ * still runs is not known; or `stopped`.
60
+ */
61
+ export type RunActivity = 'running' | 'unresponsive' | 'stopped';
62
+ /** What a run's process is doing, from the run list and (when its feed is open) the feed's `procs.alive`. */
63
+ export declare function runActivity(summary: Pick<RunSummary, 'running' | 'unresponsive'> | undefined, alive?: boolean): RunActivity;
64
+ /** The host that drives a run, from the host list. */
65
+ export declare const hostOf: (hosts: readonly HostSummary[] | undefined, summary: Pick<RunSummary, "host_id"> | undefined) => HostSummary | undefined;
66
+ /** A run's name without its host: a hub names a run `<run_id>@<host_id>`; any other name as it is. */
67
+ export declare function runIdOf(r: Pick<RunSummary, 'run' | 'host_id'>): string;
68
+ /** One host's runs. */
69
+ export interface HostGroup {
70
+ hostId: string;
71
+ /** The host as the host list has it; undefined for a host the list does not have yet. */
72
+ host: HostSummary | undefined;
73
+ runs: RunSummary[];
74
+ }
75
+ /**
76
+ * Runs by host, in the host list's order (then the host ids it does not have,
77
+ * sorted): in each, the running runs, then those whose host stopped reporting,
78
+ * then the rest, each in the order given (the run list's: most recent first).
79
+ */
80
+ export declare function hostGroups(runs: readonly RunSummary[], hosts?: readonly HostSummary[]): HostGroup[];
81
+ //# sourceMappingURL=derive.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"derive.d.ts","sourceRoot":"","sources":["../src/derive.ts"],"names":[],"mappings":"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"}
package/dist/derive.js ADDED
@@ -0,0 +1,89 @@
1
+ // Everything the views count and list about a run's tasks, derived once per
2
+ // state change: the outcomes, the rows, the counts and the problems. Every view
3
+ // reads `deriveRun(journal, plan)`, so "N of M tasks done" and every cause read
4
+ // the same on every screen. Pure; memoized on the identity of its inputs.
5
+ import { isLaunched, outcomeCounts, problems, taskOutcomes, taskRows } from './outcomes.js';
6
+ const NO_PLAN = { nodes: [], groups: [] };
7
+ /** Run state replaces the journal and the plan arrays on every change (`reduceRun`), so their identity is the key. */
8
+ const memo = new WeakMap();
9
+ /**
10
+ * What the views show about a run's tasks. Without a plan, the tasks are the
11
+ * journal's only. The same `journal` and `plan` objects give the same result object.
12
+ */
13
+ export function deriveRun(journal, plan = NO_PLAN) {
14
+ const p = plan ?? NO_PLAN;
15
+ let byPlan = memo.get(journal);
16
+ if (!byPlan)
17
+ memo.set(journal, (byPlan = new WeakMap()));
18
+ let d = byPlan.get(p);
19
+ if (!d)
20
+ byPlan.set(p, (d = derive(journal, p)));
21
+ return d;
22
+ }
23
+ function derive(journal, plan) {
24
+ const outcomes = taskOutcomes(journal, plan);
25
+ const c = outcomeCounts(outcomes);
26
+ return {
27
+ outcomes,
28
+ rows: taskRows(outcomes, plan),
29
+ counts: { ...c, doneTotal: c.done + c.recovered, finished: c.done + c.recovered + c.partial + c.skipped },
30
+ problems: problems(outcomes),
31
+ kinds: [...new Set([...outcomes.values()].map((t) => t.kind))].sort(),
32
+ tries: tryCounts(journal),
33
+ };
34
+ }
35
+ /** Tries (journal rows that ran) and rows of tasks that never started: the one way the UI counts them. */
36
+ export function tryCounts(journal) {
37
+ const tries = journal.filter(isLaunched).length;
38
+ return { tries, notStarted: journal.length - tries };
39
+ }
40
+ /** How far a run is, from the plan's groups. */
41
+ export function progress(plan) {
42
+ const total = plan.groups.reduce((a, g) => a + g.total, 0);
43
+ const done = plan.groups.reduce((a, g) => a + g.done, 0);
44
+ const partial = plan.groups.reduce((a, g) => a + (g.partial ?? 0), 0);
45
+ const skipped = plan.groups.reduce((a, g) => a + g.skipped, 0);
46
+ return { total, done, partial, skipped, pct: Math.round((100 * (done + partial + skipped)) / Math.max(1, total)) };
47
+ }
48
+ /** How a run's tasks ended, from the plan: for a run that is over, in place of a progress bar. */
49
+ export function result(plan) {
50
+ const count = (...states) => plan.nodes.filter((n) => states.includes(n.state)).length;
51
+ return { done: count('done'), partial: count('partial'), skipped: count('skipped'), failed: count('failed'), notStarted: count('blocked'), left: count('pending', 'running') };
52
+ }
53
+ /** How long a run took, wall clock: from its start to its last activity (null when unknown). */
54
+ export function elapsedS(r) {
55
+ const a = Date.parse(r?.started_at ?? '');
56
+ const b = Date.parse(r?.last_ts ?? '');
57
+ return Number.isNaN(a) || Number.isNaN(b) ? null : Math.max(0, (b - a) / 1000);
58
+ }
59
+ /** What a run's process is doing, from the run list and (when its feed is open) the feed's `procs.alive`. */
60
+ export function runActivity(summary, alive = false) {
61
+ if (alive || summary?.running)
62
+ return 'running';
63
+ return summary?.unresponsive ? 'unresponsive' : 'stopped';
64
+ }
65
+ /** The host that drives a run, from the host list. */
66
+ export const hostOf = (hosts, summary) => summary?.host_id ? hosts?.find((h) => h.host_id === summary.host_id) : undefined;
67
+ /** A run's name without its host: a hub names a run `<run_id>@<host_id>`; any other name as it is. */
68
+ export function runIdOf(r) {
69
+ const n = r.run.length - (r.host_id?.length ?? 0) - 1;
70
+ return r.host_id && n > 0 && r.run.endsWith(`@${r.host_id}`) ? r.run.slice(0, n) : r.run;
71
+ }
72
+ const ACTIVITY_ORDER = ['running', 'unresponsive', 'stopped'];
73
+ /**
74
+ * Runs by host, in the host list's order (then the host ids it does not have,
75
+ * sorted): in each, the running runs, then those whose host stopped reporting,
76
+ * then the rest, each in the order given (the run list's: most recent first).
77
+ */
78
+ export function hostGroups(runs, hosts = []) {
79
+ const ids = [...new Set(runs.map((r) => r.host_id ?? ''))];
80
+ const known = hosts.map((h) => h.host_id);
81
+ const rank = (r) => ACTIVITY_ORDER.indexOf(runActivity(r));
82
+ return [...known.filter((id) => ids.includes(id)), ...ids.filter((id) => !known.includes(id)).sort()].map((hostId) => ({
83
+ hostId,
84
+ host: hosts.find((h) => h.host_id === hostId),
85
+ // A stable sort: equal runs keep the order given.
86
+ runs: runs.filter((r) => (r.host_id ?? '') === hostId).sort((a, b) => rank(a) - rank(b)),
87
+ }));
88
+ }
89
+ //# sourceMappingURL=derive.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"derive.js","sourceRoot":"","sources":["../src/derive.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ import { type MonitorClient } from './client.js';
2
+ import type { FeedHandlers, Unsubscribe } from './feed.js';
3
+ import type { RunFeedMessage } from './types.js';
4
+ /** A client of an export: the monitor's client, and the files the server sends over its feed instead. */
5
+ export interface ExportClient extends MonitorClient {
6
+ /** A file of the export, `data/<path>` (cached): the endpoints' answers, and `plan`, `outputs`, `cursor`. */
7
+ file<T>(path: string): Promise<T>;
8
+ }
9
+ export interface ExportClientOptions {
10
+ fetch?: typeof globalThis.fetch;
11
+ }
12
+ /**
13
+ * The name task `key`'s files take in a run directory and in an export, without a
14
+ * suffix: `key` with every character but a letter, a digit, `-`, `_` and `.` turned
15
+ * into `_`, as are a leading `.` and each `..` (Python's `file_name`, `runs/layout.py`).
16
+ */
17
+ export declare function taskFileName(key: string): string;
18
+ /**
19
+ * A `MonitorClient` over an export at `baseUrl` (its `data/`, relative to the page or
20
+ * absolute). An endpoint is a file: `info.json`, `runs.json`, `runs/<run>/journal.json`,
21
+ * `runs/<run>/tasks/<file>.json` (`taskFileName`) ... A task's file leaves out its rows and its
22
+ * definition, which the run's journal and definitions hold: the client puts them back.
23
+ * A view the export does not hold (a transcript, the hosts) answers `ApiError` 404.
24
+ * An endpoint of no run is a file under `data/` (`trends.json`, `trends/panels/<name>.json`):
25
+ * a new endpoint is one method here, `file<T>('<its file>')`, beside its entry in the
26
+ * Python `VIEWS` (`contraagent/monitor/views.py`) or `SITE_FILES` (`contraagent/monitor/export.py`).
27
+ */
28
+ export declare function createExportClient(baseUrl: string, options?: ExportClientOptions): ExportClient;
29
+ /** A run feed opener, as `openRunFeed` is: what `MonitorStore` opens a run's feed with. */
30
+ export type RunFeedOpener = (run: string, panels: readonly string[], on: FeedHandlers<RunFeedMessage>) => Unsubscribe;
31
+ /**
32
+ * The run feed of an export: the run's journal rows, plan, outputs, cursor and panels,
33
+ * each once, then `ready`; nothing after. A file it cannot read stops it with `error`.
34
+ */
35
+ export declare function openExportFeed(client: ExportClient): RunFeedOpener;
36
+ //# sourceMappingURL=export.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"export.d.ts","sourceRoot":"","sources":["../src/export.ts"],"names":[],"mappings":"AAGA,OAAO,EAAY,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAC3D,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAC3D,OAAO,KAAK,EAOV,cAAc,EAKf,MAAM,YAAY,CAAC;AAEpB,yGAAyG;AACzG,MAAM,WAAW,YAAa,SAAQ,aAAa;IACjD,6GAA6G;IAC7G,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;CACnC;AAED,MAAM,WAAW,mBAAmB;IAClC,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;CACjC;AAID;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAKhD;AAED;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,GAAE,mBAAwB,GAAG,YAAY,CA+DnG;AAKD,2FAA2F;AAC3F,MAAM,MAAM,aAAa,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,MAAM,EAAE,EAAE,EAAE,EAAE,YAAY,CAAC,cAAc,CAAC,KAAK,WAAW,CAAC;AAEtH;;;GAGG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,YAAY,GAAG,aAAa,CA6BlE"}
package/dist/export.js ADDED
@@ -0,0 +1,130 @@
1
+ // An export (`contraagent monitor export`): the monitor's views of some runs,
2
+ // written as files and read with no server. The client maps each endpoint to its
3
+ // file; the feed replays a run's files once and ends with `ready`.
4
+ import { ApiError } from './client.js';
5
+ const seg = encodeURIComponent;
6
+ /**
7
+ * The name task `key`'s files take in a run directory and in an export, without a
8
+ * suffix: `key` with every character but a letter, a digit, `-`, `_` and `.` turned
9
+ * into `_`, as are a leading `.` and each `..` (Python's `file_name`, `runs/layout.py`).
10
+ */
11
+ export function taskFileName(key) {
12
+ const name = Array.from(key, (ch) => (/^[\p{L}\p{N}._-]$/u.test(ch) ? ch : '_'))
13
+ .join('')
14
+ .replace(/\.\.+/g, (m) => '_'.repeat(m.length));
15
+ return name.startsWith('.') ? `_${name.slice(1)}` : name || '_';
16
+ }
17
+ /**
18
+ * A `MonitorClient` over an export at `baseUrl` (its `data/`, relative to the page or
19
+ * absolute). An endpoint is a file: `info.json`, `runs.json`, `runs/<run>/journal.json`,
20
+ * `runs/<run>/tasks/<file>.json` (`taskFileName`) ... A task's file leaves out its rows and its
21
+ * definition, which the run's journal and definitions hold: the client puts them back.
22
+ * A view the export does not hold (a transcript, the hosts) answers `ApiError` 404.
23
+ * An endpoint of no run is a file under `data/` (`trends.json`, `trends/panels/<name>.json`):
24
+ * a new endpoint is one method here, `file<T>('<its file>')`, beside its entry in the
25
+ * Python `VIEWS` (`contraagent/monitor/views.py`) or `SITE_FILES` (`contraagent/monitor/export.py`).
26
+ */
27
+ export function createExportClient(baseUrl, options = {}) {
28
+ const base = baseUrl.endsWith('/') ? baseUrl : `${baseUrl}/`;
29
+ const fetch = options.fetch ?? globalThis.fetch;
30
+ const cache = new Map();
31
+ function file(path) {
32
+ let p = cache.get(path);
33
+ if (!p) {
34
+ p = load(base + path);
35
+ // A failed read is tried again next time.
36
+ p.catch(() => cache.delete(path));
37
+ cache.set(path, p);
38
+ }
39
+ return p;
40
+ }
41
+ async function load(url) {
42
+ let response;
43
+ try {
44
+ response = await fetch(new Request(url));
45
+ }
46
+ catch {
47
+ throw new ApiError(url, 'network');
48
+ }
49
+ const type = (response.headers.get('Content-Type') ?? '').split(';')[0].trim().toLowerCase();
50
+ if (!response.ok)
51
+ throw new ApiError(url, 'status', response.status, type);
52
+ try {
53
+ return (await response.json());
54
+ }
55
+ catch {
56
+ throw new ApiError(url, 'not-json', response.status, type);
57
+ }
58
+ }
59
+ const run = (r, path) => `runs/${seg(r)}/${path}`;
60
+ const absent = (path) => Promise.reject(new ApiError(base + path, 'status', 404));
61
+ return {
62
+ baseUrl: base.replace(/\/$/, ''),
63
+ file,
64
+ info: () => file('info.json'),
65
+ runs: () => file('runs.json'),
66
+ hosts: () => absent('hosts.json'),
67
+ async journal(r, since = 0) {
68
+ const page = await file(run(r, 'journal.json'));
69
+ return since > 0 ? { entries: page.entries.slice(since), total: page.total } : page;
70
+ },
71
+ definitions: (r) => file(run(r, 'definitions.json')),
72
+ async task(r, key) {
73
+ const [detail, journal, definitions, plan] = await Promise.all([
74
+ file(run(r, `tasks/${seg(taskFileName(key))}.json`)),
75
+ file(run(r, 'journal.json')),
76
+ file(run(r, 'definitions.json')),
77
+ file(run(r, 'plan.json')),
78
+ ]);
79
+ const rows = journal.entries.filter((e) => e.key === key);
80
+ const kind = rows.at(-1)?.kind ?? plan.nodes.find((n) => n.key === key)?.kind;
81
+ return { ...detail, rows, definition: definitions.find((d) => d.kind === kind) ?? null };
82
+ },
83
+ stream: (r, key) => absent(run(r, `streams/${seg(taskFileName(key))}.json`)),
84
+ panel: (r, name) => file(run(r, `panels/${seg(name)}.json`)),
85
+ // An export holds one period's trends (`Info.export`): a query for another is answered with it.
86
+ trends: () => file('trends.json'),
87
+ trendsPanel: (name) => file(`trends/panels/${seg(name)}.json`),
88
+ };
89
+ }
90
+ /** A cursor file: `{}` when the run keeps no cursor. */
91
+ const isCursor = (c) => Object.keys(c).length > 0;
92
+ /**
93
+ * The run feed of an export: the run's journal rows, plan, outputs, cursor and panels,
94
+ * each once, then `ready`; nothing after. A file it cannot read stops it with `error`.
95
+ */
96
+ export function openExportFeed(client) {
97
+ return (r, panels, on) => {
98
+ let closed = false;
99
+ void (async () => {
100
+ try {
101
+ const [journal, plan, outputs, cursor, bodies] = await Promise.all([
102
+ client.journal(r),
103
+ client.file(`runs/${seg(r)}/plan.json`),
104
+ client.file(`runs/${seg(r)}/outputs.json`),
105
+ client.file(`runs/${seg(r)}/cursor.json`),
106
+ Promise.all(panels.map((p) => client.panel(r, p).catch((e) => ({ error: String(e) })))),
107
+ ]);
108
+ if (closed)
109
+ return;
110
+ on.open?.();
111
+ const send = (m) => !closed && on.message(m);
112
+ for (const row of journal.entries)
113
+ send({ event: 'journal', data: row });
114
+ send({ event: 'plan', data: plan });
115
+ send({ event: 'outputs', data: outputs });
116
+ send({ event: 'cursor', data: isCursor(cursor) ? cursor : null });
117
+ panels.forEach((p, i) => send({ event: `panel:${p}`, data: bodies[i] ?? {} }));
118
+ send({ event: 'ready', data: { journal: journal.total } });
119
+ }
120
+ catch {
121
+ if (!closed)
122
+ on.error?.();
123
+ }
124
+ })();
125
+ return () => {
126
+ closed = true;
127
+ };
128
+ };
129
+ }
130
+ //# sourceMappingURL=export.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"export.js","sourceRoot":"","sources":["../src/export.ts"],"names":[],"mappings":"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"}