@tangle-network/browser-agent-driver 0.30.0 → 0.31.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 (235) hide show
  1. package/dist/cli-ack-patch.d.ts +39 -0
  2. package/dist/cli-ack-patch.d.ts.map +1 -0
  3. package/dist/cli-ack-patch.js +47 -0
  4. package/dist/cli-ack-patch.js.map +1 -0
  5. package/dist/cli-design-audit.d.ts +12 -0
  6. package/dist/cli-design-audit.d.ts.map +1 -1
  7. package/dist/cli-design-audit.js +88 -0
  8. package/dist/cli-design-audit.js.map +1 -1
  9. package/dist/cli-jobs.d.ts +13 -0
  10. package/dist/cli-jobs.d.ts.map +1 -0
  11. package/dist/cli-jobs.js +262 -0
  12. package/dist/cli-jobs.js.map +1 -0
  13. package/dist/cli-patterns.d.ts +26 -0
  14. package/dist/cli-patterns.d.ts.map +1 -0
  15. package/dist/cli-patterns.js +49 -0
  16. package/dist/cli-patterns.js.map +1 -0
  17. package/dist/cli-reports.d.ts +12 -0
  18. package/dist/cli-reports.d.ts.map +1 -0
  19. package/dist/cli-reports.js +97 -0
  20. package/dist/cli-reports.js.map +1 -0
  21. package/dist/cli.js +29 -1
  22. package/dist/cli.js.map +1 -1
  23. package/dist/design/audit/attribution/aggregate.d.ts +16 -0
  24. package/dist/design/audit/attribution/aggregate.d.ts.map +1 -0
  25. package/dist/design/audit/attribution/aggregate.js +81 -0
  26. package/dist/design/audit/attribution/aggregate.js.map +1 -0
  27. package/dist/design/audit/attribution/store.d.ts +36 -0
  28. package/dist/design/audit/attribution/store.d.ts.map +1 -0
  29. package/dist/design/audit/attribution/store.js +108 -0
  30. package/dist/design/audit/attribution/store.js.map +1 -0
  31. package/dist/design/audit/attribution/types.d.ts +13 -0
  32. package/dist/design/audit/attribution/types.d.ts.map +1 -0
  33. package/dist/design/audit/attribution/types.js +10 -0
  34. package/dist/design/audit/attribution/types.js.map +1 -0
  35. package/dist/design/audit/classify-ensemble.d.ts +46 -0
  36. package/dist/design/audit/classify-ensemble.d.ts.map +1 -0
  37. package/dist/design/audit/classify-ensemble.js +207 -0
  38. package/dist/design/audit/classify-ensemble.js.map +1 -0
  39. package/dist/design/audit/ethics/check.d.ts +59 -0
  40. package/dist/design/audit/ethics/check.d.ts.map +1 -0
  41. package/dist/design/audit/ethics/check.js +137 -0
  42. package/dist/design/audit/ethics/check.js.map +1 -0
  43. package/dist/design/audit/ethics/loader.d.ts +21 -0
  44. package/dist/design/audit/ethics/loader.d.ts.map +1 -0
  45. package/dist/design/audit/ethics/loader.js +207 -0
  46. package/dist/design/audit/ethics/loader.js.map +1 -0
  47. package/dist/design/audit/ethics/rules/finance.yaml +37 -0
  48. package/dist/design/audit/ethics/rules/kids.yaml +37 -0
  49. package/dist/design/audit/ethics/rules/legal.yaml +35 -0
  50. package/dist/design/audit/ethics/rules/medical.yaml +36 -0
  51. package/dist/design/audit/evaluate.d.ts.map +1 -1
  52. package/dist/design/audit/evaluate.js +4 -1
  53. package/dist/design/audit/evaluate.js.map +1 -1
  54. package/dist/design/audit/first-principles-mode.d.ts +42 -0
  55. package/dist/design/audit/first-principles-mode.d.ts.map +1 -0
  56. package/dist/design/audit/first-principles-mode.js +77 -0
  57. package/dist/design/audit/first-principles-mode.js.map +1 -0
  58. package/dist/design/audit/modality/android.d.ts +14 -0
  59. package/dist/design/audit/modality/android.d.ts.map +1 -0
  60. package/dist/design/audit/modality/android.js +18 -0
  61. package/dist/design/audit/modality/android.js.map +1 -0
  62. package/dist/design/audit/modality/html.d.ts +22 -0
  63. package/dist/design/audit/modality/html.d.ts.map +1 -0
  64. package/dist/design/audit/modality/html.js +51 -0
  65. package/dist/design/audit/modality/html.js.map +1 -0
  66. package/dist/design/audit/modality/index.d.ts +8 -0
  67. package/dist/design/audit/modality/index.d.ts.map +1 -0
  68. package/dist/design/audit/modality/index.js +15 -0
  69. package/dist/design/audit/modality/index.js.map +1 -0
  70. package/dist/design/audit/modality/ios.d.ts +16 -0
  71. package/dist/design/audit/modality/ios.d.ts.map +1 -0
  72. package/dist/design/audit/modality/ios.js +19 -0
  73. package/dist/design/audit/modality/ios.js.map +1 -0
  74. package/dist/design/audit/modality/types.d.ts +9 -0
  75. package/dist/design/audit/modality/types.d.ts.map +1 -0
  76. package/dist/design/audit/modality/types.js +9 -0
  77. package/dist/design/audit/modality/types.js.map +1 -0
  78. package/dist/design/audit/patches/index.d.ts +8 -0
  79. package/dist/design/audit/patches/index.d.ts.map +1 -0
  80. package/dist/design/audit/patches/index.js +5 -0
  81. package/dist/design/audit/patches/index.js.map +1 -0
  82. package/dist/design/audit/patches/parse.d.ts +29 -0
  83. package/dist/design/audit/patches/parse.d.ts.map +1 -0
  84. package/dist/design/audit/patches/parse.js +153 -0
  85. package/dist/design/audit/patches/parse.js.map +1 -0
  86. package/dist/design/audit/patches/render.d.ts +20 -0
  87. package/dist/design/audit/patches/render.d.ts.map +1 -0
  88. package/dist/design/audit/patches/render.js +55 -0
  89. package/dist/design/audit/patches/render.js.map +1 -0
  90. package/dist/design/audit/patches/severity-enforcement.d.ts +23 -0
  91. package/dist/design/audit/patches/severity-enforcement.d.ts.map +1 -0
  92. package/dist/design/audit/patches/severity-enforcement.js +42 -0
  93. package/dist/design/audit/patches/severity-enforcement.js.map +1 -0
  94. package/dist/design/audit/patches/validate.d.ts +34 -0
  95. package/dist/design/audit/patches/validate.d.ts.map +1 -0
  96. package/dist/design/audit/patches/validate.js +53 -0
  97. package/dist/design/audit/patches/validate.js.map +1 -0
  98. package/dist/design/audit/patterns/match.d.ts +24 -0
  99. package/dist/design/audit/patterns/match.d.ts.map +1 -0
  100. package/dist/design/audit/patterns/match.js +48 -0
  101. package/dist/design/audit/patterns/match.js.map +1 -0
  102. package/dist/design/audit/patterns/mine.d.ts +31 -0
  103. package/dist/design/audit/patterns/mine.d.ts.map +1 -0
  104. package/dist/design/audit/patterns/mine.js +35 -0
  105. package/dist/design/audit/patterns/mine.js.map +1 -0
  106. package/dist/design/audit/patterns/store.d.ts +14 -0
  107. package/dist/design/audit/patterns/store.d.ts.map +1 -0
  108. package/dist/design/audit/patterns/store.js +53 -0
  109. package/dist/design/audit/patterns/store.js.map +1 -0
  110. package/dist/design/audit/patterns/types.d.ts +53 -0
  111. package/dist/design/audit/patterns/types.d.ts.map +1 -0
  112. package/dist/design/audit/patterns/types.js +13 -0
  113. package/dist/design/audit/patterns/types.js.map +1 -0
  114. package/dist/design/audit/pipeline.d.ts +12 -0
  115. package/dist/design/audit/pipeline.d.ts.map +1 -1
  116. package/dist/design/audit/pipeline.js +65 -3
  117. package/dist/design/audit/pipeline.js.map +1 -1
  118. package/dist/design/audit/rubric/anchor-loader.d.ts +44 -0
  119. package/dist/design/audit/rubric/anchor-loader.d.ts.map +1 -0
  120. package/dist/design/audit/rubric/anchor-loader.js +210 -0
  121. package/dist/design/audit/rubric/anchor-loader.js.map +1 -0
  122. package/dist/design/audit/rubric/anchors/blog.yaml +33 -0
  123. package/dist/design/audit/rubric/anchors/dashboard.yaml +35 -0
  124. package/dist/design/audit/rubric/anchors/docs.yaml +35 -0
  125. package/dist/design/audit/rubric/anchors/ecommerce.yaml +33 -0
  126. package/dist/design/audit/rubric/anchors/marketing.yaml +34 -0
  127. package/dist/design/audit/rubric/anchors/saas-app.yaml +38 -0
  128. package/dist/design/audit/rubric/anchors/social.yaml +33 -0
  129. package/dist/design/audit/rubric/anchors/tool.yaml +33 -0
  130. package/dist/design/audit/rubric/anchors/utility.yaml +33 -0
  131. package/dist/design/audit/rubric/fragments/audience-clinician.md +42 -0
  132. package/dist/design/audit/rubric/fragments/audience-developer.md +40 -0
  133. package/dist/design/audit/rubric/fragments/audience-kids.md +35 -0
  134. package/dist/design/audit/rubric/fragments/audience-vulnerability-minor-facing.md +37 -0
  135. package/dist/design/audit/rubric/fragments/first-principles.md +65 -0
  136. package/dist/design/audit/rubric/fragments/modality-mobile.md +39 -0
  137. package/dist/design/audit/rubric/fragments/modality-tablet.md +35 -0
  138. package/dist/design/audit/rubric/fragments/regulatory-coppa.md +32 -0
  139. package/dist/design/audit/rubric/fragments/regulatory-gdpr.md +33 -0
  140. package/dist/design/audit/rubric/fragments/regulatory-hipaa.md +36 -0
  141. package/dist/design/audit/rubric/loader.d.ts +26 -6
  142. package/dist/design/audit/rubric/loader.d.ts.map +1 -1
  143. package/dist/design/audit/rubric/loader.js +45 -26
  144. package/dist/design/audit/rubric/loader.js.map +1 -1
  145. package/dist/design/audit/rubric/rollup-weights.d.ts +25 -0
  146. package/dist/design/audit/rubric/rollup-weights.d.ts.map +1 -0
  147. package/dist/design/audit/rubric/rollup-weights.js +51 -0
  148. package/dist/design/audit/rubric/rollup-weights.js.map +1 -0
  149. package/dist/design/audit/types.d.ts +28 -0
  150. package/dist/design/audit/types.d.ts.map +1 -1
  151. package/dist/design/audit/v2/build-result.d.ts +47 -0
  152. package/dist/design/audit/v2/build-result.d.ts.map +1 -0
  153. package/dist/design/audit/v2/build-result.js +163 -0
  154. package/dist/design/audit/v2/build-result.js.map +1 -0
  155. package/dist/design/audit/v2/score.d.ts +49 -0
  156. package/dist/design/audit/v2/score.d.ts.map +1 -0
  157. package/dist/design/audit/v2/score.js +211 -0
  158. package/dist/design/audit/v2/score.js.map +1 -0
  159. package/dist/design/audit/v2/types.d.ts +431 -0
  160. package/dist/design/audit/v2/types.d.ts.map +1 -0
  161. package/dist/design/audit/v2/types.js +26 -0
  162. package/dist/design/audit/v2/types.js.map +1 -0
  163. package/dist/discover/index.d.ts +16 -0
  164. package/dist/discover/index.d.ts.map +1 -0
  165. package/dist/discover/index.js +25 -0
  166. package/dist/discover/index.js.map +1 -0
  167. package/dist/discover/wayback.d.ts +49 -0
  168. package/dist/discover/wayback.d.ts.map +1 -0
  169. package/dist/discover/wayback.js +123 -0
  170. package/dist/discover/wayback.js.map +1 -0
  171. package/dist/jobs/anti-bot.d.ts +19 -0
  172. package/dist/jobs/anti-bot.d.ts.map +1 -0
  173. package/dist/jobs/anti-bot.js +64 -0
  174. package/dist/jobs/anti-bot.js.map +1 -0
  175. package/dist/jobs/cost-estimate.d.ts +12 -0
  176. package/dist/jobs/cost-estimate.d.ts.map +1 -0
  177. package/dist/jobs/cost-estimate.js +21 -0
  178. package/dist/jobs/cost-estimate.js.map +1 -0
  179. package/dist/jobs/cost-history.d.ts +19 -0
  180. package/dist/jobs/cost-history.d.ts.map +1 -0
  181. package/dist/jobs/cost-history.js +42 -0
  182. package/dist/jobs/cost-history.js.map +1 -0
  183. package/dist/jobs/index.d.ts +31 -0
  184. package/dist/jobs/index.d.ts.map +1 -0
  185. package/dist/jobs/index.js +37 -0
  186. package/dist/jobs/index.js.map +1 -0
  187. package/dist/jobs/orchestrator.d.ts +42 -0
  188. package/dist/jobs/orchestrator.d.ts.map +1 -0
  189. package/dist/jobs/orchestrator.js +282 -0
  190. package/dist/jobs/orchestrator.js.map +1 -0
  191. package/dist/jobs/queue.d.ts +45 -0
  192. package/dist/jobs/queue.d.ts.map +1 -0
  193. package/dist/jobs/queue.js +114 -0
  194. package/dist/jobs/queue.js.map +1 -0
  195. package/dist/jobs/retry.d.ts +36 -0
  196. package/dist/jobs/retry.d.ts.map +1 -0
  197. package/dist/jobs/retry.js +77 -0
  198. package/dist/jobs/retry.js.map +1 -0
  199. package/dist/jobs/store.d.ts +31 -0
  200. package/dist/jobs/store.d.ts.map +1 -0
  201. package/dist/jobs/store.js +92 -0
  202. package/dist/jobs/store.js.map +1 -0
  203. package/dist/jobs/types.d.ts +106 -0
  204. package/dist/jobs/types.d.ts.map +1 -0
  205. package/dist/jobs/types.js +13 -0
  206. package/dist/jobs/types.js.map +1 -0
  207. package/dist/reports/aggregate.d.ts +38 -0
  208. package/dist/reports/aggregate.d.ts.map +1 -0
  209. package/dist/reports/aggregate.js +145 -0
  210. package/dist/reports/aggregate.js.map +1 -0
  211. package/dist/reports/index.d.ts +21 -0
  212. package/dist/reports/index.d.ts.map +1 -0
  213. package/dist/reports/index.js +15 -0
  214. package/dist/reports/index.js.map +1 -0
  215. package/dist/reports/narrate.d.ts +19 -0
  216. package/dist/reports/narrate.d.ts.map +1 -0
  217. package/dist/reports/narrate.js +40 -0
  218. package/dist/reports/narrate.js.map +1 -0
  219. package/dist/reports/templates.d.ts +43 -0
  220. package/dist/reports/templates.d.ts.map +1 -0
  221. package/dist/reports/templates.js +200 -0
  222. package/dist/reports/templates.js.map +1 -0
  223. package/dist/reports/tokens.d.ts +77 -0
  224. package/dist/reports/tokens.d.ts.map +1 -0
  225. package/dist/reports/tokens.js +88 -0
  226. package/dist/reports/tokens.js.map +1 -0
  227. package/dist/reports/tools.d.ts +76 -0
  228. package/dist/reports/tools.d.ts.map +1 -0
  229. package/dist/reports/tools.js +205 -0
  230. package/dist/reports/tools.js.map +1 -0
  231. package/dist/reports/types.d.ts +51 -0
  232. package/dist/reports/types.d.ts.map +1 -0
  233. package/dist/reports/types.js +9 -0
  234. package/dist/reports/types.js.map +1 -0
  235. package/package.json +2 -1
@@ -0,0 +1,282 @@
1
+ /**
2
+ * Agentic orchestrator — wraps `runJob` with a control loop that handles
3
+ * strategic decisions (re-sample broken wayback snapshots, retry vs skip,
4
+ * widen the window when too many fail, conclude) via LLM tool calls.
5
+ *
6
+ * Design: protocols are deterministic (retry/backoff lives in `retry.ts`,
7
+ * anti-bot detection lives in `anti-bot.ts`). The orchestrator only steps
8
+ * in for *judgment* calls where a hand-tuned heuristic would break the
9
+ * moment the world changes. Cost ceiling: hard-capped at spec.maxCostUSD,
10
+ * and each control-loop iteration is a single LLM call (~1.5s, ~$0.005).
11
+ *
12
+ * Tool surface (LLM-callable):
13
+ * getJobState() — what's done, what failed, current cost
14
+ * resampleWayback(...) — replace targets for a URL with a new sample
15
+ * retryTarget(...) — re-attempt a single failed/skipped target
16
+ * markSkipped(...) — terminal skip with reason
17
+ * concludeJob() — exit the loop
18
+ *
19
+ * The orchestrator runs the initial fan-out via `runJob` first (so the
20
+ * deterministic happy path doesn't pay LLM tax), then enters the loop only
21
+ * if the result needs intervention.
22
+ */
23
+ import { generateText, tool, jsonSchema, stepCountIs } from 'ai';
24
+ import { Brain } from '../brain/index.js';
25
+ import { resolveProviderApiKey, resolveProviderModelName } from '../provider-defaults.js';
26
+ import { runJob } from './queue.js';
27
+ import { saveJob, appendIndexEntry } from './store.js';
28
+ import { discoverWaybackSnapshots } from '../discover/wayback.js';
29
+ const SYSTEM_PROMPT = `You are an audit-job orchestrator.
30
+
31
+ Your job: bring this audit job to a high-quality completion. After the initial fan-out has run, decide whether and how to fix gaps:
32
+ - If multiple wayback snapshots for the same URL came back blocked or scored 0, the snapshots are likely broken archive captures. Re-sample more snapshots in the same window using the resampleWayback tool.
33
+ - If a single target failed with a transient error, retry it.
34
+ - If a target is structurally unfixable (anti-bot, persistent 4xx, total content failure), mark it skipped with a clear reason.
35
+ - When the job is in good shape OR you've hit diminishing returns OR cost is approaching the cap, call concludeJob.
36
+
37
+ Rules:
38
+ - You MUST stay under spec.maxCostUSD when set.
39
+ - Do not retry the same target more than twice.
40
+ - Do not resample the same URL more than once.
41
+ - When in doubt, conclude. A clean partial job is better than an over-fitted one.
42
+ - Reply only with tool calls until you call concludeJob.
43
+ `;
44
+ export async function orchestrateJob(job, opts) {
45
+ const verbose = opts.verbose ?? false;
46
+ // Phase 1: deterministic fan-out (no LLM in the loop).
47
+ await runJob(job, { auditFn: opts.auditFn, dir: opts.dir });
48
+ const state = {
49
+ job,
50
+ resamples: new Map(),
51
+ retries: new Map(),
52
+ concluded: false,
53
+ reason: '',
54
+ };
55
+ // Phase 2: only invoke the agent if the run needs intervention.
56
+ if (!needsIntervention(job)) {
57
+ if (verbose)
58
+ console.log(' [orchestrator] no intervention needed');
59
+ return job;
60
+ }
61
+ if (verbose) {
62
+ const failed = job.results.filter(r => r.status === 'failed').length;
63
+ const skipped = job.results.filter(r => r.status === 'skipped').length;
64
+ console.log(` [orchestrator] entering loop · failed=${failed} · skipped=${skipped} · cost=$${job.totalCostUSD.toFixed(2)}`);
65
+ }
66
+ // Default to the same provider as the audit pipeline (claude-code via
67
+ // subscription, no API key required). If the operator already supplied a
68
+ // brain, respect that.
69
+ const brain = opts.brain ?? defaultBrain();
70
+ const tools = buildTools(state, opts, verbose);
71
+ const model = await brain.getLanguageModel({ provider: 'claude-code' });
72
+ const result = await generateText({
73
+ model,
74
+ system: SYSTEM_PROMPT,
75
+ prompt: 'The fan-out has completed. Call getJobState first to see what happened, then take corrective actions, then call concludeJob.',
76
+ tools,
77
+ stopWhen: stepCountIs(opts.maxIterations ?? 8),
78
+ });
79
+ if (verbose) {
80
+ console.log(` [orchestrator] finishReason=${result.finishReason} steps=${result.steps.length} toolCalls=${result.steps.reduce((acc, s) => acc + s.toolCalls.length, 0)}`);
81
+ if (result.text)
82
+ console.log(` [orchestrator] final-text: ${result.text.slice(0, 200)}`);
83
+ }
84
+ if (!state.concluded) {
85
+ state.reason = 'orchestrator: max iterations reached without explicit conclude';
86
+ }
87
+ // Persist final state.
88
+ saveJob(state.job, opts.dir);
89
+ appendIndexEntry(state.job, opts.dir);
90
+ return state.job;
91
+ }
92
+ /** Cheap deterministic check: did the fan-out leave anything worth fixing? */
93
+ function needsIntervention(job) {
94
+ const failed = job.results.filter(r => r.status === 'failed').length;
95
+ const ok = job.results.filter(r => r.status === 'ok');
96
+ const skipped = job.results.filter(r => r.status === 'skipped').length;
97
+ // Coverage: anything missing or failed is an intervention candidate.
98
+ if (failed > 0)
99
+ return true;
100
+ if (ok.length + skipped < job.targets.length)
101
+ return true;
102
+ // Quality: zero-scored ok results from wayback snapshots almost always
103
+ // indicate a broken archive capture (anti-bot got past detection, or the
104
+ // archived page is truncated). Treat as intervention-worthy.
105
+ const zeroScored = ok.filter(r => r.snapshotUrl && (r.rollupScore ?? -1) === 0).length;
106
+ if (zeroScored > 0)
107
+ return true;
108
+ return false;
109
+ }
110
+ function buildTools(state, opts, verbose) {
111
+ return {
112
+ getJobState: tool({
113
+ description: 'Return the current job state — targets, results, totalCostUSD, maxCostUSD, recent failures, and which URLs already have re-sampled targets.',
114
+ inputSchema: jsonSchema({ type: 'object', properties: {} }),
115
+ execute: async () => {
116
+ const j = state.job;
117
+ const groupedByUrl = new Map();
118
+ for (const r of j.results) {
119
+ const k = r.url;
120
+ if (!groupedByUrl.has(k))
121
+ groupedByUrl.set(k, []);
122
+ groupedByUrl.get(k).push(r);
123
+ }
124
+ const summary = {
125
+ jobId: j.jobId,
126
+ status: j.status,
127
+ totalCostUSD: j.totalCostUSD,
128
+ maxCostUSD: j.spec.maxCostUSD ?? null,
129
+ targets: j.targets.length,
130
+ ok: j.results.filter(r => r.status === 'ok').length,
131
+ failed: j.results.filter(r => r.status === 'failed').length,
132
+ skipped: j.results.filter(r => r.status === 'skipped').length,
133
+ retriesUsed: Array.from(state.retries.entries()).map(([k, n]) => ({ key: k, retries: n })),
134
+ resamplesUsed: Array.from(state.resamples.values()).filter(r => r.attempted).map(r => r.url),
135
+ byUrl: Array.from(groupedByUrl.entries()).map(([url, rows]) => ({
136
+ url,
137
+ snapshots: rows.length,
138
+ ok: rows.filter(r => r.status === 'ok').length,
139
+ failed: rows.filter(r => r.status === 'failed').length,
140
+ skipped: rows.filter(r => r.status === 'skipped').length,
141
+ zeroScored: rows.filter(r => r.status === 'ok' && (r.rollupScore ?? 0) === 0).length,
142
+ })),
143
+ failureSamples: j.results.filter(r => r.status === 'failed').slice(0, 5).map(r => ({
144
+ url: r.url, snapshotUrl: r.snapshotUrl, error: r.error,
145
+ })),
146
+ skippedSamples: j.results.filter(r => r.status === 'skipped').slice(0, 5).map(r => ({
147
+ url: r.url, snapshotUrl: r.snapshotUrl, reason: r.error,
148
+ })),
149
+ };
150
+ if (verbose)
151
+ console.log(' [orchestrator] getJobState');
152
+ return summary;
153
+ },
154
+ }),
155
+ resampleWayback: tool({
156
+ description: 'Discover N additional wayback snapshots for a URL and audit them. Use when several existing snapshots for that URL came back blocked or zero-scored — broken archive captures cluster, so re-sampling a different month often produces good ones. Each URL can only be resampled once per orchestrator run.',
157
+ inputSchema: jsonSchema({
158
+ type: 'object',
159
+ properties: {
160
+ url: { type: 'string' },
161
+ count: { type: 'integer', minimum: 1, maximum: 8 },
162
+ since: { type: 'string', description: 'ISO date lower bound' },
163
+ until: { type: 'string', description: 'ISO date upper bound' },
164
+ },
165
+ required: ['url', 'count'],
166
+ }),
167
+ execute: async ({ url, count, since, until }) => {
168
+ if (state.resamples.has(url)) {
169
+ return { error: `already resampled ${url} — skip and conclude or retry individual targets instead` };
170
+ }
171
+ if (overBudget(state))
172
+ return { error: 'cost cap reached — conclude job' };
173
+ const action = { url, count, since, until, attempted: true };
174
+ state.resamples.set(url, action);
175
+ if (verbose)
176
+ console.log(` [orchestrator] resampleWayback ${url} count=${count}`);
177
+ try {
178
+ const targets = await discoverWaybackSnapshots(url, { count, since, until });
179
+ if (targets.length === 0)
180
+ return { added: 0, error: 'CDX returned zero captures for that window' };
181
+ // De-dupe against snapshots we already audited.
182
+ const existing = new Set(state.job.results.map(r => r.snapshotUrl ?? r.url));
183
+ const fresh = targets.filter(t => !existing.has(t.snapshotUrl ?? t.url));
184
+ if (fresh.length === 0)
185
+ return { added: 0, note: 'all sampled snapshots were already audited; widen the window or pick a different URL' };
186
+ state.job.targets.push(...fresh);
187
+ // Run only the new targets through the queue with resume=true.
188
+ await runJob(state.job, { auditFn: opts.auditFn, dir: opts.dir, resume: true });
189
+ const newOk = fresh.filter(t => state.job.results.some(r => (r.snapshotUrl ?? r.url) === (t.snapshotUrl ?? t.url) && r.status === 'ok')).length;
190
+ return { added: fresh.length, newlyOk: newOk, totalCostUSD: state.job.totalCostUSD };
191
+ }
192
+ catch (err) {
193
+ return { error: err.message };
194
+ }
195
+ },
196
+ }),
197
+ retryTarget: tool({
198
+ description: 'Re-attempt a single failed or skipped target. Use only for transient failures. Hard cap: 2 retries per target per orchestrator run.',
199
+ inputSchema: jsonSchema({
200
+ type: 'object',
201
+ properties: {
202
+ url: { type: 'string' },
203
+ snapshotUrl: { type: 'string' },
204
+ },
205
+ required: ['url'],
206
+ }),
207
+ execute: async ({ url, snapshotUrl }) => {
208
+ const key = snapshotUrl ?? url;
209
+ const used = state.retries.get(key) ?? 0;
210
+ if (used >= 2)
211
+ return { error: `retry budget for ${key} exhausted (2 used)` };
212
+ if (overBudget(state))
213
+ return { error: 'cost cap reached — conclude job' };
214
+ state.retries.set(key, used + 1);
215
+ if (verbose)
216
+ console.log(` [orchestrator] retryTarget ${key} attempt=${used + 1}`);
217
+ // Drop the existing failed/skipped entry, then resume — the queue will pick the missing target up.
218
+ state.job.results = state.job.results.filter(r => (r.snapshotUrl ?? r.url) !== key);
219
+ await runJob(state.job, { auditFn: opts.auditFn, dir: opts.dir, resume: true });
220
+ const newEntry = state.job.results.find(r => (r.snapshotUrl ?? r.url) === key);
221
+ return { status: newEntry?.status ?? 'unknown', error: newEntry?.error, totalCostUSD: state.job.totalCostUSD };
222
+ },
223
+ }),
224
+ markSkipped: tool({
225
+ description: 'Terminally mark a target as skipped (no further retries). Use for structurally unfixable failures — persistent 4xx, anti-bot, dead URL.',
226
+ inputSchema: jsonSchema({
227
+ type: 'object',
228
+ properties: {
229
+ url: { type: 'string' },
230
+ snapshotUrl: { type: 'string' },
231
+ reason: { type: 'string' },
232
+ },
233
+ required: ['url', 'reason'],
234
+ }),
235
+ execute: async ({ url, snapshotUrl, reason }) => {
236
+ const key = snapshotUrl ?? url;
237
+ const idx = state.job.results.findIndex(r => (r.snapshotUrl ?? r.url) === key);
238
+ if (idx >= 0) {
239
+ state.job.results[idx] = { ...state.job.results[idx], status: 'skipped', error: reason };
240
+ }
241
+ else {
242
+ state.job.results.push({ url, snapshotUrl, status: 'skipped', error: reason });
243
+ }
244
+ saveJob(state.job, opts.dir);
245
+ if (verbose)
246
+ console.log(` [orchestrator] markSkipped ${key}: ${reason}`);
247
+ return { ok: true };
248
+ },
249
+ }),
250
+ concludeJob: tool({
251
+ description: 'End the orchestrator loop. Provide a one-sentence reason summarizing the final state.',
252
+ inputSchema: jsonSchema({
253
+ type: 'object',
254
+ properties: { reason: { type: 'string' } },
255
+ required: ['reason'],
256
+ }),
257
+ execute: async ({ reason }) => {
258
+ state.concluded = true;
259
+ state.reason = reason;
260
+ if (verbose)
261
+ console.log(` [orchestrator] concludeJob: ${reason}`);
262
+ return { concluded: true };
263
+ },
264
+ }),
265
+ };
266
+ }
267
+ function overBudget(state) {
268
+ const cap = state.job.spec.maxCostUSD;
269
+ if (typeof cap !== 'number')
270
+ return false;
271
+ // Leave 10% headroom so the next single audit doesn't push us over.
272
+ return state.job.totalCostUSD >= cap * 0.9;
273
+ }
274
+ /** Re-export for tests / callers that want to inspect what would have run. */
275
+ export { needsIntervention };
276
+ function defaultBrain() {
277
+ const provider = 'claude-code';
278
+ const model = resolveProviderModelName(provider);
279
+ const apiKey = resolveProviderApiKey(provider);
280
+ return new Brain({ provider, model, apiKey, vision: false, llmTimeoutMs: 60_000 });
281
+ }
282
+ //# sourceMappingURL=orchestrator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"orchestrator.js","sourceRoot":"","sources":["../../src/jobs/orchestrator.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Job runner — bounded-concurrency fan-out over the audit pipeline.
3
+ *
4
+ * Synchronous in the sense that `runJob()` returns when every target has been
5
+ * processed (ok, failed, or skipped). Crash safety: every individual result
6
+ * is persisted to disk as soon as it lands, so killing the process leaves a
7
+ * resumable record (resume is a future addition; today the partial state
8
+ * is just observable).
9
+ *
10
+ * `auditFn` is injected so tests can run the queue without touching
11
+ * Playwright/LLMs.
12
+ */
13
+ import type { Job, JobTarget } from './types.js';
14
+ import { type RetryPolicy } from './retry.js';
15
+ export interface AuditFn {
16
+ (target: JobTarget, opts: Job['spec']['audit']): Promise<{
17
+ runId: string;
18
+ resultPath: string;
19
+ rollupScore?: number;
20
+ pageType?: string;
21
+ costUSD?: number;
22
+ tokensPath?: string;
23
+ /**
24
+ * When set, the result was deterministically classified as blocked /
25
+ * anti-bot — the queue records `status: 'skipped'` with this reason
26
+ * instead of `'ok'`, so leaderboards don't include a misleading score.
27
+ */
28
+ blockedReason?: string;
29
+ }>;
30
+ }
31
+ export interface RunJobOptions {
32
+ auditFn: AuditFn;
33
+ /** Persistence dir override (tests). */
34
+ dir?: string;
35
+ /** Concurrency override; falls back to spec.concurrency, then 2. */
36
+ concurrency?: number;
37
+ /** Per-target failure swallower — defaults to recording the error and continuing. */
38
+ onError?: (target: JobTarget, error: Error) => 'continue' | 'abort';
39
+ /** Retry policy applied around each `auditFn` call. Pass `null` to disable retry. */
40
+ retryPolicy?: RetryPolicy | null;
41
+ /** When true, skip targets that already have a non-failed result on the job (used by `bad jobs resume`). */
42
+ resume?: boolean;
43
+ }
44
+ export declare function runJob(job: Job, opts: RunJobOptions): Promise<Job>;
45
+ //# sourceMappingURL=queue.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queue.d.ts","sourceRoot":"","sources":["../../src/jobs/queue.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,KAAK,EAAE,GAAG,EAAkB,SAAS,EAAE,MAAM,YAAY,CAAA;AAChE,OAAO,EAAmC,KAAK,WAAW,EAAE,MAAM,YAAY,CAAA;AAE9E,MAAM,WAAW,OAAO;IACtB,CAAC,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;QACvD,KAAK,EAAE,MAAM,CAAA;QACb,UAAU,EAAE,MAAM,CAAA;QAClB,WAAW,CAAC,EAAE,MAAM,CAAA;QACpB,QAAQ,CAAC,EAAE,MAAM,CAAA;QACjB,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,UAAU,CAAC,EAAE,MAAM,CAAA;QACnB;;;;WAIG;QACH,aAAa,CAAC,EAAE,MAAM,CAAA;KACvB,CAAC,CAAA;CACH;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,OAAO,CAAA;IAChB,wCAAwC;IACxC,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,oEAAoE;IACpE,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,qFAAqF;IACrF,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,KAAK,KAAK,UAAU,GAAG,OAAO,CAAA;IACnE,qFAAqF;IACrF,WAAW,CAAC,EAAE,WAAW,GAAG,IAAI,CAAA;IAChC,4GAA4G;IAC5G,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB;AAID,wBAAsB,MAAM,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,GAAG,CAAC,CAwCxE"}
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Job runner — bounded-concurrency fan-out over the audit pipeline.
3
+ *
4
+ * Synchronous in the sense that `runJob()` returns when every target has been
5
+ * processed (ok, failed, or skipped). Crash safety: every individual result
6
+ * is persisted to disk as soon as it lands, so killing the process leaves a
7
+ * resumable record (resume is a future addition; today the partial state
8
+ * is just observable).
9
+ *
10
+ * `auditFn` is injected so tests can run the queue without touching
11
+ * Playwright/LLMs.
12
+ */
13
+ import { saveJob, appendIndexEntry } from './store.js';
14
+ import { withRetry, DEFAULT_RETRY_POLICY } from './retry.js';
15
+ const DEFAULT_CONCURRENCY = 2;
16
+ export async function runJob(job, opts) {
17
+ const concurrency = opts.concurrency ?? job.spec.concurrency ?? DEFAULT_CONCURRENCY;
18
+ job.status = 'running';
19
+ job.startedAt = job.startedAt ?? new Date().toISOString();
20
+ saveJob(job, opts.dir);
21
+ appendIndexEntry(job, opts.dir);
22
+ // Resume support: skip targets that already have a non-failed result.
23
+ const completed = new Set();
24
+ if (opts.resume) {
25
+ for (const r of job.results) {
26
+ if (r.status === 'ok' || r.status === 'skipped')
27
+ completed.add(targetKey(r));
28
+ }
29
+ }
30
+ const queue = job.targets.filter(t => !completed.has(targetKey(t)));
31
+ let aborted = false;
32
+ async function worker() {
33
+ while (queue.length > 0 && !aborted) {
34
+ const target = queue.shift();
35
+ if (!target)
36
+ break;
37
+ const entry = await runOne(target, job.spec.audit, opts.auditFn, opts.retryPolicy, opts.onError);
38
+ if (entry === 'abort') {
39
+ aborted = true;
40
+ return;
41
+ }
42
+ job.results.push(entry);
43
+ if (entry.costUSD)
44
+ job.totalCostUSD = round2(job.totalCostUSD + entry.costUSD);
45
+ saveJob(job, opts.dir);
46
+ }
47
+ }
48
+ const workers = Array.from({ length: concurrency }, () => worker());
49
+ await Promise.all(workers);
50
+ job.completedAt = new Date().toISOString();
51
+ job.status = aborted ? 'cancelled' : finalStatus(job);
52
+ saveJob(job, opts.dir);
53
+ appendIndexEntry(job, opts.dir);
54
+ return job;
55
+ }
56
+ async function runOne(target, audit, auditFn, retryPolicy, onError) {
57
+ try {
58
+ const policy = retryPolicy === null ? undefined : (retryPolicy ?? DEFAULT_RETRY_POLICY);
59
+ const call = () => auditFn(target, audit);
60
+ const out = policy ? await withRetry(call, policy) : await call();
61
+ if (out.blockedReason) {
62
+ return {
63
+ ...target,
64
+ status: 'skipped',
65
+ runId: out.runId,
66
+ resultPath: out.resultPath,
67
+ error: out.blockedReason,
68
+ costUSD: out.costUSD,
69
+ };
70
+ }
71
+ return {
72
+ ...target,
73
+ status: 'ok',
74
+ runId: out.runId,
75
+ resultPath: out.resultPath,
76
+ rollupScore: out.rollupScore,
77
+ pageType: out.pageType,
78
+ costUSD: out.costUSD,
79
+ tokensPath: out.tokensPath,
80
+ };
81
+ }
82
+ catch (err) {
83
+ const error = err;
84
+ const decision = onError?.(target, error) ?? 'continue';
85
+ if (decision === 'abort')
86
+ return 'abort';
87
+ return {
88
+ ...target,
89
+ status: 'failed',
90
+ error: error.message,
91
+ };
92
+ }
93
+ }
94
+ function targetKey(t) {
95
+ return t.snapshotUrl ?? t.url;
96
+ }
97
+ function finalStatus(job) {
98
+ const total = job.results.length;
99
+ if (total === 0)
100
+ return 'failed';
101
+ const ok = job.results.filter(r => r.status === 'ok').length;
102
+ const failed = job.results.filter(r => r.status === 'failed').length;
103
+ // 'skipped' is a deterministic non-failure (e.g. anti-bot block detected).
104
+ // Treat it as a clean outcome — we recorded the reason and moved on.
105
+ if (ok === 0 && failed > 0)
106
+ return 'failed';
107
+ if (failed > 0 || ok < total)
108
+ return 'partial';
109
+ return 'completed';
110
+ }
111
+ function round2(n) {
112
+ return Math.round(n * 100) / 100;
113
+ }
114
+ //# sourceMappingURL=queue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queue.js","sourceRoot":"","sources":["../../src/jobs/queue.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Retry-with-backoff helper used by the jobs queue. Deliberately not an LLM
3
+ * decision — exponential backoff with jitter is a protocol, not a judgment
4
+ * call. Wrapping it in prose adds latency without adding intelligence.
5
+ *
6
+ * Default policy:
7
+ * - 3 attempts total (one initial + two retries)
8
+ * - Base delay 500ms, doubles each attempt, capped at 5s
9
+ * - Adds ±20% jitter so concurrent workers don't synchronize their retries
10
+ * - Retries network errors, HTTP 429, HTTP 5xx, and timeouts
11
+ * - Does NOT retry HTTP 4xx, anti-bot blocks, or schema-validation errors
12
+ */
13
+ export interface RetryPolicy {
14
+ maxAttempts: number;
15
+ baseDelayMs: number;
16
+ maxDelayMs: number;
17
+ /** Returns true if the error is retryable. */
18
+ isRetryable: (err: Error) => boolean;
19
+ /** Override default backoff (for tests). */
20
+ backoffFn?: (attempt: number, base: number, cap: number) => number;
21
+ /** Hook fired before each retry — useful for telemetry. */
22
+ onRetry?: (attempt: number, err: Error, delayMs: number) => void;
23
+ }
24
+ /**
25
+ * Whitelist-only: an error is retryable iff it matches one of the patterns
26
+ * above. Anything else (4xx, anti-bot, schema-validation, unknown errors) is
27
+ * deterministic and we don't waste round-trips retrying it.
28
+ */
29
+ export declare function isRetryableDefault(err: Error): boolean;
30
+ export declare const DEFAULT_RETRY_POLICY: RetryPolicy;
31
+ /**
32
+ * Run `fn` with retry-on-retryable-failure. Throws the last error after
33
+ * `maxAttempts` exhausted, or immediately on a non-retryable error.
34
+ */
35
+ export declare function withRetry<T>(fn: () => Promise<T>, policy?: RetryPolicy): Promise<T>;
36
+ //# sourceMappingURL=retry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"retry.d.ts","sourceRoot":"","sources":["../../src/jobs/retry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,MAAM,WAAW,WAAW;IAC1B,WAAW,EAAE,MAAM,CAAA;IACnB,WAAW,EAAE,MAAM,CAAA;IACnB,UAAU,EAAE,MAAM,CAAA;IAClB,8CAA8C;IAC9C,WAAW,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,OAAO,CAAA;IACpC,4CAA4C;IAC5C,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,KAAK,MAAM,CAAA;IAClE,2DAA2D;IAC3D,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;CACjE;AAiBD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,KAAK,GAAG,OAAO,CAEtD;AAED,eAAO,MAAM,oBAAoB,EAAE,WAKlC,CAAA;AAQD;;;GAGG;AACH,wBAAsB,SAAS,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EAAE,MAAM,GAAE,WAAkC,GAAG,OAAO,CAAC,CAAC,CAAC,CAmB/G"}
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Retry-with-backoff helper used by the jobs queue. Deliberately not an LLM
3
+ * decision — exponential backoff with jitter is a protocol, not a judgment
4
+ * call. Wrapping it in prose adds latency without adding intelligence.
5
+ *
6
+ * Default policy:
7
+ * - 3 attempts total (one initial + two retries)
8
+ * - Base delay 500ms, doubles each attempt, capped at 5s
9
+ * - Adds ±20% jitter so concurrent workers don't synchronize their retries
10
+ * - Retries network errors, HTTP 429, HTTP 5xx, and timeouts
11
+ * - Does NOT retry HTTP 4xx, anti-bot blocks, or schema-validation errors
12
+ */
13
+ const RETRYABLE_PATTERNS = [
14
+ /\b429\b/, // rate limit
15
+ /\b5\d\d\b/, // 5xx
16
+ /timeout/i,
17
+ /timed out/i,
18
+ /etimedout/i,
19
+ /econnreset/i,
20
+ /econnrefused/i,
21
+ /enotfound/i,
22
+ /socket hang up/i,
23
+ /network/i,
24
+ /fetch failed/i,
25
+ /CDX returned 5\d\d/i,
26
+ ];
27
+ /**
28
+ * Whitelist-only: an error is retryable iff it matches one of the patterns
29
+ * above. Anything else (4xx, anti-bot, schema-validation, unknown errors) is
30
+ * deterministic and we don't waste round-trips retrying it.
31
+ */
32
+ export function isRetryableDefault(err) {
33
+ return RETRYABLE_PATTERNS.some(re => re.test(err.message));
34
+ }
35
+ export const DEFAULT_RETRY_POLICY = {
36
+ maxAttempts: 3,
37
+ baseDelayMs: 500,
38
+ maxDelayMs: 5000,
39
+ isRetryable: isRetryableDefault,
40
+ };
41
+ function defaultBackoff(attempt, base, cap) {
42
+ const exp = Math.min(base * 2 ** attempt, cap);
43
+ const jitter = exp * (0.8 + Math.random() * 0.4); // ±20%
44
+ return Math.round(jitter);
45
+ }
46
+ /**
47
+ * Run `fn` with retry-on-retryable-failure. Throws the last error after
48
+ * `maxAttempts` exhausted, or immediately on a non-retryable error.
49
+ */
50
+ export async function withRetry(fn, policy = DEFAULT_RETRY_POLICY) {
51
+ const max = Math.max(1, policy.maxAttempts);
52
+ const backoff = policy.backoffFn ?? defaultBackoff;
53
+ let lastErr;
54
+ for (let attempt = 0; attempt < max; attempt++) {
55
+ try {
56
+ return await fn();
57
+ }
58
+ catch (err) {
59
+ lastErr = err;
60
+ if (!policy.isRetryable(lastErr))
61
+ throw lastErr;
62
+ if (attempt === max - 1)
63
+ break;
64
+ const delay = backoff(attempt, policy.baseDelayMs, policy.maxDelayMs);
65
+ policy.onRetry?.(attempt + 1, lastErr, delay);
66
+ await sleep(delay);
67
+ }
68
+ }
69
+ // Tag the final error so callers can tell retried-and-failed apart from never-retried.
70
+ if (lastErr)
71
+ lastErr.message = `[after ${max} attempts] ${lastErr.message}`;
72
+ throw lastErr ?? new Error('withRetry: no error captured but no result returned');
73
+ }
74
+ function sleep(ms) {
75
+ return new Promise(resolve => setTimeout(resolve, ms));
76
+ }
77
+ //# sourceMappingURL=retry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"retry.js","sourceRoot":"","sources":["../../src/jobs/retry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAcH,MAAM,kBAAkB,GAAG;IACzB,SAAS,EAAE,aAAa;IACxB,WAAW,EAAE,MAAM;IACnB,UAAU;IACV,YAAY;IACZ,YAAY;IACZ,aAAa;IACb,eAAe;IACf,YAAY;IACZ,iBAAiB;IACjB,UAAU;IACV,eAAe;IACf,qBAAqB;CACtB,CAAA;AAED;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAAC,GAAU;IAC3C,OAAO,kBAAkB,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAA;AAC5D,CAAC;AAED,MAAM,CAAC,MAAM,oBAAoB,GAAgB;IAC/C,WAAW,EAAE,CAAC;IACd,WAAW,EAAE,GAAG;IAChB,UAAU,EAAE,IAAI;IAChB,WAAW,EAAE,kBAAkB;CAChC,CAAA;AAED,SAAS,cAAc,CAAC,OAAe,EAAE,IAAY,EAAE,GAAW;IAChE,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,IAAI,OAAO,EAAE,GAAG,CAAC,CAAA;IAC9C,MAAM,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,GAAG,IAAI,CAAC,MAAM,EAAE,GAAG,GAAG,CAAC,CAAA,CAAC,OAAO;IACxD,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAA;AAC3B,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAAI,EAAoB,EAAE,SAAsB,oBAAoB;IACjG,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,WAAW,CAAC,CAAA;IAC3C,MAAM,OAAO,GAAG,MAAM,CAAC,SAAS,IAAI,cAAc,CAAA;IAClD,IAAI,OAA0B,CAAA;IAC9B,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,GAAG,GAAG,EAAE,OAAO,EAAE,EAAE,CAAC;QAC/C,IAAI,CAAC;YACH,OAAO,MAAM,EAAE,EAAE,CAAA;QACnB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,GAAG,GAAY,CAAA;YACtB,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,OAAO,CAAC;gBAAE,MAAM,OAAO,CAAA;YAC/C,IAAI,OAAO,KAAK,GAAG,GAAG,CAAC;gBAAE,MAAK;YAC9B,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,EAAE,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,UAAU,CAAC,CAAA;YACrE,MAAM,CAAC,OAAO,EAAE,CAAC,OAAO,GAAG,CAAC,EAAE,OAAO,EAAE,KAAK,CAAC,CAAA;YAC7C,MAAM,KAAK,CAAC,KAAK,CAAC,CAAA;QACpB,CAAC;IACH,CAAC;IACD,uFAAuF;IACvF,IAAI,OAAO;QAAE,OAAO,CAAC,OAAO,GAAG,UAAU,GAAG,cAAc,OAAO,CAAC,OAAO,EAAE,CAAA;IAC3E,MAAM,OAAO,IAAI,IAAI,KAAK,CAAC,qDAAqD,CAAC,CAAA;AACnF,CAAC;AAED,SAAS,KAAK,CAAC,EAAU;IACvB,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAA;AACxD,CAAC"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Job store — whole-file JSON per job + an append-only index for listing.
3
+ *
4
+ * Layout:
5
+ * ~/.bad/jobs/<jobId>.json — Job record (atomic write via tmp + rename)
6
+ * ~/.bad/jobs/index.jsonl — append-only one line per job ({jobId, status, createdAt, label})
7
+ *
8
+ * Whole-file rewrite is intentional: a job is a single coherent record with
9
+ * monotonically-extending `results[]`. Append-only at the entry level would
10
+ * duplicate target rows; append-only at the file level would force readers
11
+ * to fold N updates into one logical record. Atomic rename gives us crash
12
+ * safety without that complexity.
13
+ */
14
+ import type { Job, JobStatus } from './types.js';
15
+ export declare function jobsDir(override?: string): string;
16
+ export declare function jobPath(jobId: string, dir?: string): string;
17
+ export declare function newJobId(now?: Date): string;
18
+ export declare function saveJob(job: Job, dir?: string): void;
19
+ export declare function loadJob(jobId: string, dir?: string): Job | null;
20
+ export declare function appendIndexEntry(job: Job, dir?: string): void;
21
+ export interface JobIndexEntry {
22
+ jobId: string;
23
+ status: JobStatus;
24
+ createdAt: string;
25
+ label: string | null;
26
+ targetCount: number;
27
+ }
28
+ /** Reads the index file. Later entries for the same jobId override earlier ones. */
29
+ export declare function listJobs(dir?: string): JobIndexEntry[];
30
+ export declare function updateJobStatus(jobId: string, status: JobStatus, dir?: string): Job | null;
31
+ //# sourceMappingURL=store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/jobs/store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAKH,OAAO,KAAK,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAIhD,wBAAgB,OAAO,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAEjD;AAMD,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAE3D;AAMD,wBAAgB,QAAQ,CAAC,GAAG,GAAE,IAAiB,GAAG,MAAM,CAIvD;AAED,wBAAgB,OAAO,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAMpD;AAED,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,GAAG,GAAG,IAAI,CAI/D;AAED,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAW7D;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,SAAS,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,WAAW,EAAE,MAAM,CAAA;CACpB;AAED,oFAAoF;AACpF,wBAAgB,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,aAAa,EAAE,CActD;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,GAAG,GAAG,IAAI,CAW1F"}