@relauts/spotcheck-service 1.0.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 (124) hide show
  1. package/LICENSE +661 -0
  2. package/NOTICE +19 -0
  3. package/README.md +169 -0
  4. package/dist/api/auth.d.ts +11 -0
  5. package/dist/api/auth.js +32 -0
  6. package/dist/api/auth.js.map +1 -0
  7. package/dist/api/history.d.ts +46 -0
  8. package/dist/api/history.js +172 -0
  9. package/dist/api/history.js.map +1 -0
  10. package/dist/api/saved-run.d.ts +35 -0
  11. package/dist/api/saved-run.js +234 -0
  12. package/dist/api/saved-run.js.map +1 -0
  13. package/dist/api/saved.d.ts +33 -0
  14. package/dist/api/saved.js +209 -0
  15. package/dist/api/saved.js.map +1 -0
  16. package/dist/api/server.d.ts +23 -0
  17. package/dist/api/server.js +852 -0
  18. package/dist/api/server.js.map +1 -0
  19. package/dist/computer-use/actions.d.ts +14 -0
  20. package/dist/computer-use/actions.js +212 -0
  21. package/dist/computer-use/actions.js.map +1 -0
  22. package/dist/computer-use/args.d.ts +11 -0
  23. package/dist/computer-use/args.js +74 -0
  24. package/dist/computer-use/args.js.map +1 -0
  25. package/dist/computer-use/cli.d.ts +1 -0
  26. package/dist/computer-use/cli.js +8 -0
  27. package/dist/computer-use/cli.js.map +1 -0
  28. package/dist/computer-use/client.d.ts +3 -0
  29. package/dist/computer-use/client.js +68 -0
  30. package/dist/computer-use/client.js.map +1 -0
  31. package/dist/computer-use/coords.d.ts +5 -0
  32. package/dist/computer-use/coords.js +21 -0
  33. package/dist/computer-use/coords.js.map +1 -0
  34. package/dist/computer-use/history.d.ts +11 -0
  35. package/dist/computer-use/history.js +59 -0
  36. package/dist/computer-use/history.js.map +1 -0
  37. package/dist/computer-use/index.d.ts +17 -0
  38. package/dist/computer-use/index.js +75 -0
  39. package/dist/computer-use/index.js.map +1 -0
  40. package/dist/computer-use/keys.d.ts +3 -0
  41. package/dist/computer-use/keys.js +50 -0
  42. package/dist/computer-use/keys.js.map +1 -0
  43. package/dist/computer-use/loop.d.ts +22 -0
  44. package/dist/computer-use/loop.js +90 -0
  45. package/dist/computer-use/loop.js.map +1 -0
  46. package/dist/computer-use/safety.d.ts +15 -0
  47. package/dist/computer-use/safety.js +45 -0
  48. package/dist/computer-use/safety.js.map +1 -0
  49. package/dist/computer-use/screenshot.d.ts +5 -0
  50. package/dist/computer-use/screenshot.js +16 -0
  51. package/dist/computer-use/screenshot.js.map +1 -0
  52. package/dist/computer-use/system-prompt.d.ts +11 -0
  53. package/dist/computer-use/system-prompt.js +41 -0
  54. package/dist/computer-use/system-prompt.js.map +1 -0
  55. package/dist/computer-use/types.d.ts +83 -0
  56. package/dist/computer-use/types.js +31 -0
  57. package/dist/computer-use/types.js.map +1 -0
  58. package/dist/playwright/actions.d.ts +81 -0
  59. package/dist/playwright/actions.js +170 -0
  60. package/dist/playwright/actions.js.map +1 -0
  61. package/dist/playwright/chromium.d.ts +10 -0
  62. package/dist/playwright/chromium.js +27 -0
  63. package/dist/playwright/chromium.js.map +1 -0
  64. package/dist/playwright/index.d.ts +2 -0
  65. package/dist/playwright/index.js +64 -0
  66. package/dist/playwright/index.js.map +1 -0
  67. package/dist/playwright/processed.d.ts +11 -0
  68. package/dist/playwright/processed.js +183 -0
  69. package/dist/playwright/processed.js.map +1 -0
  70. package/dist/playwright/session.d.ts +28 -0
  71. package/dist/playwright/session.js +165 -0
  72. package/dist/playwright/session.js.map +1 -0
  73. package/dist/shared/config.d.ts +38 -0
  74. package/dist/shared/config.js +227 -0
  75. package/dist/shared/config.js.map +1 -0
  76. package/dist/shared/cost.d.ts +15 -0
  77. package/dist/shared/cost.js +40 -0
  78. package/dist/shared/cost.js.map +1 -0
  79. package/dist/shared/logger.d.ts +4 -0
  80. package/dist/shared/logger.js +10 -0
  81. package/dist/shared/logger.js.map +1 -0
  82. package/dist/shared/main-module.d.ts +1 -0
  83. package/dist/shared/main-module.js +19 -0
  84. package/dist/shared/main-module.js.map +1 -0
  85. package/dist/shared/models.d.ts +12 -0
  86. package/dist/shared/models.js +40 -0
  87. package/dist/shared/models.js.map +1 -0
  88. package/dist/shared/telemetry.d.ts +9 -0
  89. package/dist/shared/telemetry.js +43 -0
  90. package/dist/shared/telemetry.js.map +1 -0
  91. package/files/model.json +26 -0
  92. package/files/system_prompt.txt +15 -0
  93. package/openapi.yaml +901 -0
  94. package/package.json +60 -0
  95. package/relauts-spotcheck-service-config.example.json +19 -0
  96. package/src/api/auth.ts +40 -0
  97. package/src/api/history.ts +251 -0
  98. package/src/api/saved-run.ts +346 -0
  99. package/src/api/saved.ts +275 -0
  100. package/src/api/server.ts +1198 -0
  101. package/src/computer-use/actions.ts +273 -0
  102. package/src/computer-use/args.ts +97 -0
  103. package/src/computer-use/cli.ts +8 -0
  104. package/src/computer-use/client.ts +81 -0
  105. package/src/computer-use/coords.ts +26 -0
  106. package/src/computer-use/history.ts +80 -0
  107. package/src/computer-use/index.ts +121 -0
  108. package/src/computer-use/keys.ts +54 -0
  109. package/src/computer-use/loop.ts +140 -0
  110. package/src/computer-use/safety.ts +59 -0
  111. package/src/computer-use/screenshot.ts +20 -0
  112. package/src/computer-use/system-prompt.ts +65 -0
  113. package/src/computer-use/types.ts +126 -0
  114. package/src/playwright/actions.ts +332 -0
  115. package/src/playwright/chromium.ts +30 -0
  116. package/src/playwright/index.ts +77 -0
  117. package/src/playwright/processed.ts +253 -0
  118. package/src/playwright/session.ts +220 -0
  119. package/src/shared/config.ts +320 -0
  120. package/src/shared/cost.ts +58 -0
  121. package/src/shared/logger.ts +9 -0
  122. package/src/shared/main-module.ts +20 -0
  123. package/src/shared/models.ts +62 -0
  124. package/src/shared/telemetry.ts +63 -0
package/README.md ADDED
@@ -0,0 +1,169 @@
1
+ # @relauts/spotcheck-service
2
+
3
+ Local HTTP API that runs a Gemini computer-use agent in a Playwright Chromium session.
4
+
5
+ No UI. The server always binds to `127.0.0.1`. Callers send a Bearer token. You can save flows, replay them, and read run history.
6
+
7
+ License: AGPL-3.0-or-later. See [LICENSE](LICENSE) and [NOTICE](NOTICE).
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ npm install @relauts/spotcheck-service
13
+ npx playwright install chromium
14
+ ```
15
+
16
+ Copy the example config into the folder where you will run the CLI:
17
+
18
+ ```bash
19
+ cp node_modules/@relauts/spotcheck-service/relauts-spotcheck-service-config.example.json ./relauts-spotcheck-service-config.json
20
+ ```
21
+
22
+ Set at least `apiToken` and `geminiApiKey`. Then:
23
+
24
+ ```bash
25
+ npx spotcheck-service
26
+ ```
27
+
28
+ The service listens on `http://127.0.0.1:18732` by default.
29
+
30
+ Config is read from `relauts-spotcheck-service-config.json` in the current working directory. If that file is missing, the service looks next to the package folder (git clone layout).
31
+
32
+ ## Requirements
33
+
34
+ - Node.js `>=18.18.0`
35
+ - Chromium for Playwright: `npx playwright install chromium`
36
+
37
+ ## From source
38
+
39
+ Config can also sit **next to this repo**, not inside it:
40
+
41
+ ```
42
+ parent/
43
+ relauts-spotcheck-service/
44
+ relauts-spotcheck-service-config.json
45
+ ```
46
+
47
+ ```bash
48
+ cp relauts-spotcheck-service-config.example.json ../relauts-spotcheck-service-config.json
49
+ ```
50
+
51
+ Set at least `apiToken` and `geminiApiKey`. Do not commit the real config file.
52
+
53
+ ```bash
54
+ npm install
55
+ npx playwright install chromium
56
+ npm run dev
57
+ ```
58
+
59
+ Or:
60
+
61
+ ```bash
62
+ npm start
63
+ ```
64
+
65
+ CLI after build: `spotcheck-service`.
66
+
67
+ ## Auth
68
+
69
+ All `/v1/*` routes require:
70
+
71
+ ```http
72
+ Authorization: Bearer <apiToken>
73
+ ```
74
+
75
+ Exception: `GET /v1/health` (no auth).
76
+
77
+ Token compare uses SHA-256 digests + `timingSafeEqual`.
78
+
79
+ ## Endpoints
80
+
81
+ | Method | Path | Auth | Body / notes |
82
+ |---|---|---|---|
83
+ | `GET` | `/v1/health` | no | `{ "ok": true }` |
84
+ | `GET` | `/v1/models` | yes | model list from `files/model.json` |
85
+ | `POST` | `/v1/agent` | yes | `{ "task", "model" }` → NDJSON stream |
86
+ | `POST` | `/v1/agent/safety` | yes | `{ "id", "confirmed" }` |
87
+ | `GET` | `/v1/session` | yes | `{ "open", "url" }` |
88
+ | `POST` | `/v1/session/back` | yes | navigate back |
89
+ | `POST` | `/v1/session/forward` | yes | navigate forward |
90
+ | `POST` | `/v1/session/reload` | yes | reload |
91
+ | `GET` | `/v1/screenshot` | yes | `{ "imageName": "..." }` |
92
+ | `GET` | `/v1/saved` | yes | `{ "files": [...] }` |
93
+ | `GET` | `/v1/saved/running` | yes | `{ "files": [...] }` currently running saved runs |
94
+ | `POST` | `/v1/saved` | yes | `{ "fileName", "items" }` |
95
+ | `GET` | `/v1/saved/:fileName` | yes | saved flow |
96
+ | `PUT` | `/v1/saved/:fileName` | yes | `{ "items" }` |
97
+ | `DELETE` | `/v1/saved/:fileName` | yes | delete |
98
+ | `GET` | `/v1/saved/create-default.json` | yes | create-tab default |
99
+ | `PUT` | `/v1/saved/create-default.json` | yes | `{ "items" }` |
100
+ | `GET` | `/v1/history` | yes | `{ "runs": [{ "historyFile", "status" }] }` all history runs |
101
+ | `POST` | `/v1/history/detail` | yes | `{ "historyFile" }` prompts, results, models, screenshots |
102
+ | `POST` | `/v1/saved/run` | yes | `{ "fileName" }` start a saved run |
103
+ | `POST` | `/v1/history/status` | yes | `{ "fileName" }` latest run status |
104
+ | `POST` | `/v1/history/stop` | yes | `{ "fileName" }` stop latest run |
105
+
106
+ Errors: `{ "error": "message" }` with HTTP status.
107
+
108
+ Agent NDJSON events match the original Spotcheck UI contract (`intent`, `safety_confirm`, `done`, `error`).
109
+
110
+ Full request and response shapes: [openapi.yaml](openapi.yaml).
111
+
112
+ ## Curl examples
113
+
114
+ Health:
115
+
116
+ ```bash
117
+ curl -s http://127.0.0.1:18732/v1/health
118
+ ```
119
+
120
+ Models:
121
+
122
+ ```bash
123
+ curl -s http://127.0.0.1:18732/v1/models \
124
+ -H "Authorization: Bearer <apiToken>"
125
+ ```
126
+
127
+ Screenshot:
128
+
129
+ ```bash
130
+ curl -s http://127.0.0.1:18732/v1/screenshot \
131
+ -H "Authorization: Bearer <apiToken>"
132
+ ```
133
+
134
+ Run agent:
135
+
136
+ ```bash
137
+ curl -N http://127.0.0.1:18732/v1/agent \
138
+ -H "Authorization: Bearer <apiToken>" \
139
+ -H "Content-Type: application/json" \
140
+ -d '{"task":"go to https://example.com and check the page loads","model":"gemini-3.6-flash"}'
141
+ ```
142
+
143
+ ## CORS
144
+
145
+ Set `corsOrigins` in the config JSON to an allow list. No credentials. Preflight: `OPTIONS`.
146
+
147
+ ## Develop
148
+
149
+ ```bash
150
+ npm test
151
+ npm run typecheck
152
+ ```
153
+
154
+ How to send a PR: [CONTRIBUTING.md](CONTRIBUTING.md).
155
+
156
+ Install from npm: `npm install @relauts/spotcheck-service`.
157
+
158
+ Local run data (`saved/`, `processed/`, `history/`) stays on disk and is gitignored.
159
+
160
+ ## License
161
+
162
+ Spotcheck is free software: you can redistribute it and/or modify it under
163
+ the terms of the GNU Affero General Public License as published by the
164
+ Free Software Foundation, either version 3 of the License, or (at your
165
+ option) any later version.
166
+
167
+ See `LICENSE` and `NOTICE`.
168
+
169
+ The names Relauts and Spotcheck are trademarks of Relauts Pvt. Ltd.
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Extracts the raw token from an Authorization header.
3
+ * Only the Bearer scheme is accepted.
4
+ */
5
+ export declare function extractBearerToken(authorizationHeader: string | undefined): string | undefined;
6
+ /**
7
+ * Compares tokens in constant time by hashing both to fixed-length digests first.
8
+ * Avoids leaking token length via early return on Buffer length mismatch.
9
+ */
10
+ export declare function tokensEqual(expected: string, provided: string): boolean;
11
+ export declare function isAuthorized(authorizationHeader: string | undefined, expectedToken: string): boolean;
@@ -0,0 +1,32 @@
1
+ import { createHash, timingSafeEqual } from "node:crypto";
2
+ /**
3
+ * Extracts the raw token from an Authorization header.
4
+ * Only the Bearer scheme is accepted.
5
+ */
6
+ export function extractBearerToken(authorizationHeader) {
7
+ if (!authorizationHeader) {
8
+ return undefined;
9
+ }
10
+ const match = /^Bearer\s+(\S+)\s*$/i.exec(authorizationHeader);
11
+ if (!match) {
12
+ return undefined;
13
+ }
14
+ return match[1];
15
+ }
16
+ /**
17
+ * Compares tokens in constant time by hashing both to fixed-length digests first.
18
+ * Avoids leaking token length via early return on Buffer length mismatch.
19
+ */
20
+ export function tokensEqual(expected, provided) {
21
+ const expectedDigest = createHash("sha256").update(expected, "utf8").digest();
22
+ const providedDigest = createHash("sha256").update(provided, "utf8").digest();
23
+ return timingSafeEqual(expectedDigest, providedDigest);
24
+ }
25
+ export function isAuthorized(authorizationHeader, expectedToken) {
26
+ const provided = extractBearerToken(authorizationHeader);
27
+ if (provided === undefined) {
28
+ return false;
29
+ }
30
+ return tokensEqual(expectedToken, provided);
31
+ }
32
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/api/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAE1D;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAAC,mBAAuC;IACxE,IAAI,CAAC,mBAAmB,EAAE,CAAC;QACzB,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,MAAM,KAAK,GAAG,sBAAsB,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC;IAC/D,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,QAAgB,EAAE,QAAgB;IAC5D,MAAM,cAAc,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,MAAM,EAAE,CAAC;IAC9E,MAAM,cAAc,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,MAAM,EAAE,CAAC;IAC9E,OAAO,eAAe,CAAC,cAAc,EAAE,cAAc,CAAC,CAAC;AACzD,CAAC;AAED,MAAM,UAAU,YAAY,CAC1B,mBAAuC,EACvC,aAAqB;IAErB,MAAM,QAAQ,GAAG,kBAAkB,CAAC,mBAAmB,CAAC,CAAC;IACzD,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;QAC3B,OAAO,KAAK,CAAC;IACf,CAAC;IAED,OAAO,WAAW,CAAC,aAAa,EAAE,QAAQ,CAAC,CAAC;AAC9C,CAAC"}
@@ -0,0 +1,46 @@
1
+ import { type SavedPromptItem } from "./saved.js";
2
+ export declare const HISTORY_DIR: string;
3
+ export declare const HISTORY_RUN_STATUSES: readonly ["running", "done", "error", "stopped"];
4
+ export type HistoryRunStatus = (typeof HISTORY_RUN_STATUSES)[number];
5
+ export type HistoryItemStatus = "pending" | "running" | "done" | "error" | "skipped";
6
+ export interface HistoryRunSummary {
7
+ readonly historyFile: string;
8
+ readonly status: HistoryRunStatus;
9
+ }
10
+ export interface HistoryDetailItem {
11
+ readonly sequence: number;
12
+ readonly prompt: string;
13
+ readonly model: string;
14
+ readonly resultText?: string;
15
+ readonly screenshots: readonly string[];
16
+ }
17
+ export interface HistoryItem {
18
+ readonly sequence: number;
19
+ readonly id: string;
20
+ readonly prompt: string;
21
+ readonly model: string;
22
+ readonly status: HistoryItemStatus;
23
+ readonly resultText?: string;
24
+ readonly costUsd?: number;
25
+ readonly error?: string;
26
+ }
27
+ export interface HistoryDocument {
28
+ readonly fileName: string;
29
+ readonly status: HistoryRunStatus;
30
+ readonly current: number;
31
+ readonly total: number;
32
+ readonly items: readonly HistoryItem[];
33
+ }
34
+ export declare function makeHistoryFileName(savedFileName: string, now?: Date): string;
35
+ export declare function createHistoryDocument(savedFileName: string, items: readonly SavedPromptItem[]): HistoryDocument;
36
+ export declare function serializeHistoryDetailItem(item: HistoryItem, screenshots: readonly string[]): HistoryDetailItem;
37
+ export declare function serializeHistoryItem(item: HistoryItem): HistoryItem;
38
+ export declare function serializeHistoryDocument(document: HistoryDocument): HistoryDocument;
39
+ export declare function writeHistoryFile(historyFileName: string, document: HistoryDocument, directory?: string): Promise<string>;
40
+ export declare function readHistoryFile(historyFileName: string, directory?: string): Promise<HistoryDocument>;
41
+ export interface LatestHistoryMatch {
42
+ readonly historyFile: string;
43
+ readonly document: HistoryDocument;
44
+ }
45
+ export declare function listHistoryRuns(directory?: string): Promise<HistoryRunSummary[]>;
46
+ export declare function findLatestHistory(savedFileName: string, directory?: string): Promise<LatestHistoryMatch>;
@@ -0,0 +1,172 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { SavedNameError, SavedNotFoundError, normalizeSavedFileName } from "./saved.js";
4
+ export const HISTORY_DIR = path.resolve(process.cwd(), "history");
5
+ export const HISTORY_RUN_STATUSES = ["running", "done", "error", "stopped"];
6
+ function isHistoryRunStatus(value) {
7
+ return typeof value === "string" && HISTORY_RUN_STATUSES.includes(value);
8
+ }
9
+ export function makeHistoryFileName(savedFileName, now = new Date()) {
10
+ const name = normalizeSavedFileName(savedFileName);
11
+ const stem = name.slice(0, -".json".length);
12
+ const stamp = now.toISOString().replace(/[:.]/g, "-");
13
+ return `${stem}-${stamp}.json`;
14
+ }
15
+ export function createHistoryDocument(savedFileName, items) {
16
+ return {
17
+ fileName: normalizeSavedFileName(savedFileName),
18
+ status: "running",
19
+ current: 0,
20
+ total: items.length,
21
+ items: items.map((item) => ({
22
+ sequence: item.sequence,
23
+ id: item.id,
24
+ prompt: item.prompt,
25
+ model: item.model,
26
+ status: "pending",
27
+ })),
28
+ };
29
+ }
30
+ export function serializeHistoryDetailItem(item, screenshots) {
31
+ return {
32
+ sequence: item.sequence,
33
+ prompt: item.prompt,
34
+ model: item.model,
35
+ ...(item.resultText ? { resultText: item.resultText } : {}),
36
+ screenshots: [...screenshots],
37
+ };
38
+ }
39
+ export function serializeHistoryItem(item) {
40
+ return {
41
+ sequence: item.sequence,
42
+ id: item.id,
43
+ prompt: item.prompt,
44
+ model: item.model,
45
+ status: item.status,
46
+ ...(item.resultText ? { resultText: item.resultText } : {}),
47
+ ...(typeof item.costUsd === "number" ? { costUsd: item.costUsd } : {}),
48
+ ...(item.error ? { error: item.error } : {}),
49
+ };
50
+ }
51
+ export function serializeHistoryDocument(document) {
52
+ return {
53
+ fileName: document.fileName,
54
+ status: document.status,
55
+ current: document.current,
56
+ total: document.total,
57
+ items: document.items.map(serializeHistoryItem),
58
+ };
59
+ }
60
+ export async function writeHistoryFile(historyFileName, document, directory = HISTORY_DIR) {
61
+ const name = normalizeSavedFileName(historyFileName);
62
+ await fs.mkdir(directory, { recursive: true });
63
+ const body = `${JSON.stringify(serializeHistoryDocument(document), null, 2)}\n`;
64
+ const filePath = path.join(directory, name);
65
+ const tempPath = `${filePath}.${process.pid}.tmp`;
66
+ await fs.writeFile(tempPath, body);
67
+ await fs.rename(tempPath, filePath);
68
+ return name;
69
+ }
70
+ export async function readHistoryFile(historyFileName, directory = HISTORY_DIR) {
71
+ const name = normalizeSavedFileName(historyFileName);
72
+ const filePath = path.join(directory, name);
73
+ let raw;
74
+ try {
75
+ raw = await fs.readFile(filePath, "utf8");
76
+ }
77
+ catch (error) {
78
+ if (error.code === "ENOENT") {
79
+ throw new SavedNotFoundError(`History file not found: ${name}`);
80
+ }
81
+ throw error;
82
+ }
83
+ let parsed;
84
+ try {
85
+ parsed = JSON.parse(raw);
86
+ }
87
+ catch {
88
+ throw new SavedNameError("History file is not valid JSON");
89
+ }
90
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
91
+ throw new SavedNameError("History file must be a JSON object");
92
+ }
93
+ return parsed;
94
+ }
95
+ export async function listHistoryRuns(directory = HISTORY_DIR) {
96
+ let names;
97
+ try {
98
+ names = await fs.readdir(directory);
99
+ }
100
+ catch (error) {
101
+ if (error.code === "ENOENT") {
102
+ return [];
103
+ }
104
+ throw error;
105
+ }
106
+ const ranked = [];
107
+ for (const name of names) {
108
+ if (!name.toLowerCase().endsWith(".json")) {
109
+ continue;
110
+ }
111
+ try {
112
+ const document = await readHistoryFile(name, directory);
113
+ if (!isHistoryRunStatus(document.status)) {
114
+ continue;
115
+ }
116
+ const stats = await fs.stat(path.join(directory, name));
117
+ ranked.push({ historyFile: name, status: document.status, mtimeMs: stats.mtimeMs });
118
+ }
119
+ catch {
120
+ // Skip unreadable or invalid files.
121
+ }
122
+ }
123
+ ranked.sort((left, right) => {
124
+ if (right.mtimeMs !== left.mtimeMs) {
125
+ return right.mtimeMs - left.mtimeMs;
126
+ }
127
+ return right.historyFile.localeCompare(left.historyFile);
128
+ });
129
+ return ranked.map(({ historyFile, status }) => ({ historyFile, status }));
130
+ }
131
+ export async function findLatestHistory(savedFileName, directory = HISTORY_DIR) {
132
+ const wanted = normalizeSavedFileName(savedFileName);
133
+ let names;
134
+ try {
135
+ names = await fs.readdir(directory);
136
+ }
137
+ catch (error) {
138
+ if (error.code === "ENOENT") {
139
+ throw new SavedNotFoundError(`History file not found for: ${wanted}`);
140
+ }
141
+ throw error;
142
+ }
143
+ const ranked = [];
144
+ for (const name of names) {
145
+ if (!name.toLowerCase().endsWith(".json")) {
146
+ continue;
147
+ }
148
+ try {
149
+ const document = await readHistoryFile(name, directory);
150
+ if (normalizeSavedFileName(document.fileName) !== wanted) {
151
+ continue;
152
+ }
153
+ const stats = await fs.stat(path.join(directory, name));
154
+ ranked.push({ historyFile: name, document, mtimeMs: stats.mtimeMs });
155
+ }
156
+ catch {
157
+ // Skip unreadable or invalid files.
158
+ }
159
+ }
160
+ ranked.sort((left, right) => {
161
+ if (right.mtimeMs !== left.mtimeMs) {
162
+ return right.mtimeMs - left.mtimeMs;
163
+ }
164
+ return right.historyFile.localeCompare(left.historyFile);
165
+ });
166
+ const latest = ranked[0];
167
+ if (!latest) {
168
+ throw new SavedNotFoundError(`History file not found for: ${wanted}`);
169
+ }
170
+ return { historyFile: latest.historyFile, document: latest.document };
171
+ }
172
+ //# sourceMappingURL=history.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"history.js","sourceRoot":"","sources":["../../src/api/history.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,35 @@
1
+ import { type AgentRunExtras, type AgentRunResult } from "../computer-use/index.js";
2
+ import { type BrowserSession } from "../playwright/session.js";
3
+ import type { AppConfig } from "../shared/config.js";
4
+ import { type AutomationReporter } from "../shared/telemetry.js";
5
+ export declare class SavedRunBusyError extends Error {
6
+ constructor(message?: string);
7
+ }
8
+ export declare class SavedRunLimitError extends Error {
9
+ constructor(message?: string);
10
+ }
11
+ export declare class SavedRunNotRunningError extends Error {
12
+ constructor(message?: string);
13
+ }
14
+ export declare class SavedRunFinishedError extends Error {
15
+ constructor(message?: string);
16
+ }
17
+ export type SavedRunTask = (task: string, model: string, session: BrowserSession, extras?: AgentRunExtras, screenshotDir?: string) => Promise<AgentRunResult>;
18
+ export interface SavedRunManagerDeps {
19
+ readonly savedDir: string;
20
+ readonly historyDir: string;
21
+ readonly processedDir: string;
22
+ readonly config: AppConfig;
23
+ readonly runTask?: SavedRunTask;
24
+ readonly createSession?: (screenshotDir: string) => BrowserSession;
25
+ readonly now?: () => Date;
26
+ readonly reportAutomation?: AutomationReporter;
27
+ }
28
+ export declare function denySavedRunSafety(): Promise<boolean>;
29
+ export declare function createSavedRunManager(deps: SavedRunManagerDeps): {
30
+ start(savedFileName: string): Promise<string>;
31
+ stop(historyFileName: string): Promise<void>;
32
+ close(): Promise<void>;
33
+ listRunning(): string[];
34
+ };
35
+ export type SavedRunManager = ReturnType<typeof createSavedRunManager>;