@krapht/ork 1.0.0-alpha.1 → 1.0.0-alpha.10

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 (41) hide show
  1. package/README.md +3 -4
  2. package/dist/{__build_info__-D9lSlMFy.mjs → __build_info__-DFF18K8y.mjs} +72 -3
  3. package/dist/bin.mjs +579 -379
  4. package/dist/{clean-CA_Oi2Wl.mjs → clean-DVaEH9e7.mjs} +5 -2
  5. package/dist/{completion-DBGfL-gN.mjs → completion-Dv5kf598.mjs} +6 -6
  6. package/dist/context-CEaKOVmG.mjs +683 -0
  7. package/dist/{decide-CoIJh34e.mjs → decide-3l9q8UtX.mjs} +3 -3
  8. package/dist/{find-rBBk7Jui.mjs → find-DEjjOlTG.mjs} +1 -1
  9. package/dist/format-Cb5_m-Qd.mjs +191 -0
  10. package/dist/{in-a-run-DdAakpRR.mjs → in-a-run-BS9Nc-JW.mjs} +1 -1
  11. package/dist/{limits-BPM9wUlV.mjs → limits-BSwEjQST.mjs} +1 -1
  12. package/dist/{list-Bv5Lxu7z.mjs → list-Qxr6rwsY.mjs} +11 -4
  13. package/dist/{mcp-DM30MFBw.mjs → mcp-CslnG5wf.mjs} +6 -6
  14. package/dist/output-Bqljtmp_.mjs +40 -0
  15. package/dist/{pause-C7lJkSma.mjs → pause-D7OVB5Ac.mjs} +4 -4
  16. package/dist/{relay-BipJYszF.mjs → relay-EceRIebO.mjs} +2 -2
  17. package/dist/{remove-BDs9isB6.mjs → remove-0IQZVWmI.mjs} +14 -7
  18. package/dist/{send-nbkjf3sc.mjs → send-C0uwfUYi.mjs} +3 -3
  19. package/dist/{src-hrj2h0IZ.mjs → src-Df6KOIDC.mjs} +5678 -3245
  20. package/dist/{src-BxUJPGa5.mjs → src-cmmXKHn1.mjs} +265 -203
  21. package/dist/{src-B0TT7DBi.mjs → src-i5k2Y-kf.mjs} +35 -86
  22. package/dist/src-oaJYIddp.mjs +2255 -0
  23. package/dist/ui/THIRD_PARTY_LICENSES.txt +594 -0
  24. package/dist/ui/assets/app-C8uhHVYp.css +1 -0
  25. package/dist/ui/assets/app-qs8Lofv6.js +75 -0
  26. package/dist/ui/assets/{index-CPx74lFf.js → index-DV1ZaOk-.js} +3 -3
  27. package/dist/ui/assets/theme-CdXvqTQ6.js +1 -0
  28. package/dist/ui/index.html +2 -2
  29. package/dist/{ui-B98FSboa.mjs → ui-CBkHJdqk.mjs} +4 -4
  30. package/dist/{usage-EW7DI6Z7.mjs → usage-Cd8pEYyK.mjs} +5 -5
  31. package/dist/{watch-BAkSGaOp.mjs → watch-DW5R2OPO.mjs} +6 -6
  32. package/dist/{workflows-vsEohqa7.mjs → workflows-BT1gDRIV.mjs} +4 -3
  33. package/dist/{worktrees-Ahj9rdWK.mjs → worktrees-g1MQMGie.mjs} +78 -19
  34. package/package.json +2 -4
  35. package/dist/context-6x3kXREO.mjs +0 -274
  36. package/dist/format-zMmhqyLE.mjs +0 -427
  37. package/dist/output-TV_j7Ni0.mjs +0 -950
  38. package/dist/src-B9v0lDc6.mjs +0 -1204
  39. package/dist/ui/assets/app-BNT0SuIz.css +0 -1
  40. package/dist/ui/assets/app-CmkynZyW.js +0 -67
  41. package/dist/ui/assets/theme-3xH5jxKs.js +0 -1
@@ -1,950 +0,0 @@
1
- import { $ as text, Bt as FEEDBACK_TARGETS, Q as inTransaction, Rt as FEEDBACK_KINDS, X as usageQueries, Xt as maskReplacer, Y as backfillUsage, Z as eventOf, Zt as maskSecrets, gn as PROJECT_MODES, yt as ownProcessStart, zt as FEEDBACK_SEVERITIES } from "./src-hrj2h0IZ.mjs";
2
- import path from "node:path";
3
- import { mkdirSync } from "node:fs";
4
- import { homedir } from "node:os";
5
- import { DatabaseSync } from "node:sqlite";
6
- import { createInterface } from "node:readline/promises";
7
- import { stripVTControlCharacters, styleText } from "node:util";
8
- // ../core/src/store/drafts.ts
9
- const rowOf = (row) => {
10
- const baseHash = text(row, "base_hash");
11
- return {
12
- project: text(row, "project"),
13
- workflow: text(row, "workflow"),
14
- text: text(row, "text"),
15
- baseHash: baseHash === "" ? null : baseHash,
16
- at: text(row, "at")
17
- };
18
- };
19
- const draftQueries = (db) => {
20
- const put = db.prepare("INSERT OR REPLACE INTO workflow_drafts (project, workflow, text, base_hash, at) VALUES (?, ?, ?, ?, ?)");
21
- const one = db.prepare("SELECT project, workflow, text, base_hash, at FROM workflow_drafts WHERE project = ? AND workflow = ?");
22
- const all = db.prepare("SELECT project, workflow, text, base_hash, at FROM workflow_drafts WHERE project = ? ORDER BY workflow");
23
- const drop = db.prepare("DELETE FROM workflow_drafts WHERE project = ? AND workflow = ?");
24
- return {
25
- putDraft: (draft) => {
26
- put.run(draft.project, draft.workflow, draft.text, draft.baseHash, draft.at);
27
- },
28
- draft: (project, workflow) => {
29
- const row = one.get(project, workflow);
30
- return row === void 0 ? void 0 : rowOf(row);
31
- },
32
- drafts: (project) => all.all(project).map((row) => rowOf(row)),
33
- dropDraft: (project, workflow) => {
34
- drop.run(project, workflow);
35
- }
36
- };
37
- };
38
- // ../core/src/store/decisions.ts
39
- const choicesOf = (json) => {
40
- const parsed = JSON.parse(json);
41
- if (!Array.isArray(parsed)) return [];
42
- return parsed.flatMap((item) => {
43
- if (item === null || typeof item !== "object" || !("id" in item)) return [];
44
- const { id } = item;
45
- if (typeof id !== "string") return [];
46
- const choice = { id };
47
- if ("label" in item && typeof item.label === "string") choice.label = item.label;
48
- if ("description" in item && typeof item.description === "string") choice.description = item.description;
49
- return [choice];
50
- });
51
- };
52
- /** A note as stored: masked, or null when blank. */
53
- const noteOf = (note) => {
54
- const trimmed = note?.trim() ?? "";
55
- return trimmed === "" ? null : maskSecrets(trimmed);
56
- };
57
- const kindOf = (value) => value === "permission" ? "permission" : "question";
58
- const statusOf$1 = (value) => value === "answered" || value === "withdrawn" ? value : "open";
59
- const decisionOf = (row) => {
60
- const record = {
61
- id: text(row, "id"),
62
- runId: text(row, "run_id"),
63
- node: text(row, "node"),
64
- kind: kindOf(text(row, "kind")),
65
- question: text(row, "question"),
66
- options: choicesOf(text(row, "options") || "[]"),
67
- openedAt: text(row, "opened_at"),
68
- status: statusOf$1(text(row, "status"))
69
- };
70
- const optional = {
71
- recommended: text(row, "recommended"),
72
- context: text(row, "context"),
73
- answer: text(row, "answer"),
74
- answeredAt: text(row, "answered_at"),
75
- note: text(row, "note"),
76
- owner: text(row, "owner")
77
- };
78
- for (const [key, value] of Object.entries(optional)) if (value !== "") Object.assign(record, { [key]: value });
79
- const by = text(row, "answered_by");
80
- if (by === "terminal" || by === "decide" || by === "ui" || by === "owner") record.answeredBy = by;
81
- return record;
82
- };
83
- const decisionQueries = (db) => {
84
- const insert = db.prepare(`INSERT INTO decisions (id, run_id, node, kind, question, options, recommended, context, owner, opened_at, status)
85
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'open')`);
86
- const answer = db.prepare(`UPDATE decisions SET status = 'answered', answer = ?, answered_by = ?, answered_at = ?, note = ?
87
- WHERE id = ? AND status = 'open'`);
88
- const withdraw = db.prepare("UPDATE decisions SET status = 'withdrawn', answered_at = ? WHERE id = ? AND status = 'open'");
89
- const byId = db.prepare("SELECT * FROM decisions WHERE id = ?");
90
- const filtered = db.prepare(`SELECT d.* FROM decisions d JOIN runs r ON r.id = d.run_id
91
- WHERE (?1 IS NULL OR d.run_id = ?1)
92
- AND (?2 IS NULL OR r.project = ?2)
93
- AND (?3 IS NULL OR d.status = ?3)
94
- ORDER BY d.opened_at, d.rowid`);
95
- const addCommand = db.prepare("INSERT INTO commands (run_id, kind, node, body, sender, at) VALUES (?, ?, ?, ?, ?, ?)");
96
- const pending = db.prepare("SELECT id, kind, node, body, sender FROM commands WHERE run_id = ? AND handled_at IS NULL ORDER BY id");
97
- const handled = db.prepare("UPDATE commands SET handled_at = ? WHERE id = ?");
98
- const scriptsSent = db.prepare("SELECT COUNT(*) AS sent FROM commands WHERE run_id = ? AND kind = 'script' AND sender = ?");
99
- return {
100
- openDecision: (decision) => {
101
- const openedAt = (/* @__PURE__ */ new Date()).toISOString();
102
- const masked = {
103
- ...decision,
104
- question: maskSecrets(decision.question),
105
- ...decision.context === void 0 ? {} : { context: maskSecrets(decision.context) }
106
- };
107
- insert.run(masked.id, masked.runId, masked.node, masked.kind, masked.question, JSON.stringify(masked.options), masked.recommended ?? null, masked.context ?? null, masked.owner ?? null, openedAt);
108
- return {
109
- ...masked,
110
- openedAt,
111
- status: "open"
112
- };
113
- },
114
- answerDecision: (id, choice, by, note) => Number(answer.run(choice, by, (/* @__PURE__ */ new Date()).toISOString(), noteOf(note), id).changes) === 1,
115
- withdrawDecision: (id) => {
116
- withdraw.run((/* @__PURE__ */ new Date()).toISOString(), id);
117
- },
118
- decision: (id) => {
119
- const row = byId.get(id);
120
- return row === void 0 ? void 0 : decisionOf(row);
121
- },
122
- decisions: (filter) => filtered.all(filter.runId ?? null, filter.project ?? null, filter.status ?? null).map((row) => decisionOf(row)),
123
- command: (runId, command) => {
124
- const at = (/* @__PURE__ */ new Date()).toISOString();
125
- if ("body" in command) addCommand.run(runId, command.kind, command.node, maskSecrets(command.body), command.kind === "relay" || command.kind === "script" ? command.from : null, at);
126
- else addCommand.run(runId, command.kind, command.node ?? null, null, null, at);
127
- },
128
- scriptCommand: (runId, command, atMost) => inTransaction(db, () => {
129
- const row = scriptsSent.get(runId, command.from);
130
- if (Number(row?.["sent"] ?? 0) >= atMost) return false;
131
- addCommand.run(runId, command.kind, command.node, maskSecrets(command.body), command.from, (/* @__PURE__ */ new Date()).toISOString());
132
- return true;
133
- }),
134
- takeCommands: (runId) => {
135
- const at = (/* @__PURE__ */ new Date()).toISOString();
136
- return pending.all(runId).flatMap((row) => {
137
- handled.run(at, Number(row["id"]));
138
- const kind = text(row, "kind");
139
- if (kind === "stop" || kind === "pause" || kind === "continue") {
140
- const node = row["node"];
141
- return [typeof node === "string" ? {
142
- kind,
143
- node
144
- } : { kind }];
145
- }
146
- if (kind === "message" || kind === "notice") return [{
147
- kind,
148
- node: text(row, "node"),
149
- body: text(row, "body")
150
- }];
151
- if (kind === "relay" || kind === "script") return [{
152
- kind,
153
- node: text(row, "node"),
154
- from: text(row, "sender"),
155
- body: text(row, "body")
156
- }];
157
- return [];
158
- });
159
- }
160
- };
161
- };
162
- // ../core/src/store/feedback.ts
163
- const STATUSES$1 = [
164
- "new",
165
- "triaged",
166
- "accepted",
167
- "dismissed",
168
- "published",
169
- "resolved"
170
- ];
171
- const SOURCES = [
172
- "system",
173
- "agent",
174
- "human"
175
- ];
176
- /** `value` if it is one of `values`, else `fallback`. */
177
- const oneOf = (values, value, fallback) => values.find((v) => v === value) ?? fallback;
178
- /** A JSON array column of strings, without nulls. */
179
- const strings = (row, key) => {
180
- const parsed = JSON.parse(text(row, key) || "[]");
181
- return Array.isArray(parsed) ? parsed.filter((v) => typeof v === "string" && v !== "") : [];
182
- };
183
- const ORIGIN_COLUMNS = [
184
- ["workflow", "workflow"],
185
- ["workflowHash", "workflow_hash"],
186
- ["nodeType", "node_type"],
187
- ["model", "model"],
188
- ["driver", "driver"],
189
- ["version", "version"]
190
- ];
191
- const recordOf$3 = (row) => {
192
- const origin = {};
193
- for (const [key, column] of ORIGIN_COLUMNS) {
194
- const value = text(row, column);
195
- if (value !== "") origin[key] = value;
196
- }
197
- const feedback = {
198
- source: oneOf(SOURCES, text(row, "source"), "system"),
199
- kind: oneOf(FEEDBACK_KINDS, text(row, "kind"), "idea"),
200
- target: oneOf(FEEDBACK_TARGETS, text(row, "target"), "workflow"),
201
- severity: oneOf(FEEDBACK_SEVERITIES, text(row, "severity"), "medium"),
202
- title: text(row, "title"),
203
- body: text(row, "body"),
204
- evidence: strings(row, "evidence"),
205
- fingerprint: text(row, "fingerprint"),
206
- origin
207
- };
208
- const code = text(row, "code");
209
- if (code !== "") feedback.code = code;
210
- const record = {
211
- id: Number(row?.["id"]),
212
- runId: text(row, "run_id"),
213
- seq: Number(row?.["seq"]),
214
- at: text(row, "at"),
215
- feedback
216
- };
217
- const node = text(row, "node");
218
- if (node !== "") record.node = node;
219
- return record;
220
- };
221
- const itemOf = (row) => {
222
- const severities = strings(row, "severities");
223
- const id = text(row, "fingerprint");
224
- return {
225
- id,
226
- merged: strings(row, "fingerprints").filter((f) => f !== id),
227
- status: oneOf(STATUSES$1, text(row, "status"), "new"),
228
- title: text(row, "title"),
229
- target: oneOf(FEEDBACK_TARGETS, text(row, "target"), "workflow"),
230
- kind: oneOf(FEEDBACK_KINDS, text(row, "kind"), "idea"),
231
- severity: FEEDBACK_SEVERITIES.toReversed().find((s) => severities.includes(s)) ?? "medium",
232
- sources: strings(row, "sources").flatMap((s) => SOURCES.filter((known) => known === s)),
233
- count: Number(row?.["count"]),
234
- firstSeen: text(row, "first_seen"),
235
- lastSeen: text(row, "last_seen"),
236
- runs: strings(row, "runs"),
237
- workflows: strings(row, "workflows"),
238
- models: strings(row, "models")
239
- };
240
- };
241
- /** Each record with the item it lists under: its own, or the one it merged into. */
242
- const ITEMS = `SELECT r.fingerprint, r.status,
243
- (SELECT title FROM feedback WHERE fingerprint = r.fingerprint ORDER BY id LIMIT 1) AS title,
244
- MIN(f.target) AS target, MIN(f.kind) AS kind,
245
- json_group_array(DISTINCT f.severity) AS severities,
246
- json_group_array(DISTINCT f.source) AS sources,
247
- COUNT(*) AS count, MIN(f.at) AS first_seen, MAX(f.at) AS last_seen,
248
- json_group_array(DISTINCT f.run_id) AS runs,
249
- json_group_array(DISTINCT f.workflow) AS workflows,
250
- json_group_array(DISTINCT f.model) AS models,
251
- json_group_array(DISTINCT f.fingerprint) AS fingerprints
252
- FROM feedback f
253
- JOIN feedback_items i ON i.fingerprint = f.fingerprint
254
- JOIN feedback_items r ON r.fingerprint = COALESCE(i.merged_into, i.fingerprint)`;
255
- const feedbackQueries = (db) => {
256
- const insert = db.prepare(`INSERT INTO feedback (run_id, seq, at, node, source, kind, target, severity,
257
- title, body, evidence, code, fingerprint, workflow, workflow_hash,
258
- node_type, model, driver, version, feedback_version)
259
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
260
- const insertItem = db.prepare(`INSERT INTO feedback_items (fingerprint, status, updated_at)
261
- VALUES (?, 'new', ?) ON CONFLICT (fingerprint) DO NOTHING`);
262
- const shown = db.prepare(`${ITEMS} WHERE r.status != 'dismissed' GROUP BY r.fingerprint ORDER BY last_seen DESC`);
263
- const every = db.prepare(`${ITEMS} GROUP BY r.fingerprint ORDER BY last_seen DESC`);
264
- const byPrefix = db.prepare(`${ITEMS} WHERE r.fingerprint IN (
265
- SELECT COALESCE(merged_into, fingerprint) FROM feedback_items
266
- WHERE substr(fingerprint, 1, ?) = ?
267
- ) GROUP BY r.fingerprint ORDER BY last_seen DESC`);
268
- const records = db.prepare(`SELECT * FROM feedback
269
- WHERE fingerprint = ? OR fingerprint IN (
270
- SELECT fingerprint FROM feedback_items WHERE merged_into = ?
271
- ) ORDER BY id`);
272
- const rootOf = db.prepare("SELECT COALESCE(merged_into, fingerprint) AS root FROM feedback_items WHERE fingerprint = ?");
273
- const mergeInto = db.prepare(`UPDATE feedback_items SET merged_into = ?, updated_at = ?
274
- WHERE (fingerprint = ? OR merged_into = ?) AND fingerprint != ?`);
275
- const exported = db.prepare(`SELECT f.* FROM feedback f
276
- LEFT JOIN feedback_items i ON i.fingerprint = f.fingerprint
277
- LEFT JOIN feedback_items r ON r.fingerprint = COALESCE(i.merged_into, f.fingerprint)
278
- WHERE ? = 1 OR COALESCE(r.status, 'new') != 'dismissed' ORDER BY f.id`);
279
- const known = db.prepare("SELECT 1 FROM feedback WHERE run_id = ? AND seq = ?");
280
- const split = db.prepare("UPDATE feedback_items SET merged_into = NULL, updated_at = ? WHERE merged_into = ?");
281
- const setStatus = db.prepare("UPDATE feedback_items SET status = ?, updated_at = ? WHERE fingerprint = ?");
282
- /** Writes a record, its secrets masked, and its item when it's new. */
283
- const insertRecord = (runId, seq, at, node, feedback) => {
284
- const { origin } = feedback;
285
- insert.run(runId, seq, at, node ?? null, feedback.source, feedback.kind, feedback.target, feedback.severity, maskSecrets(feedback.title), maskSecrets(feedback.body), JSON.stringify(feedback.evidence.map((e) => maskSecrets(e))), feedback.code ?? null, feedback.fingerprint, origin.workflow ?? null, origin.workflowHash ?? null, origin.nodeType ?? null, origin.model ?? null, origin.driver ?? null, origin.version ?? null, 1);
286
- insertItem.run(feedback.fingerprint, at);
287
- };
288
- return {
289
- insertFeedback: insertRecord,
290
- feedbackItems: (filter = {}) => (filter.all === true ? every : shown).all().map((r) => itemOf(r)),
291
- findFeedback: (prefix) => byPrefix.all(prefix.length, prefix).map((r) => itemOf(r)),
292
- feedbackRecords: (fingerprint) => records.all(fingerprint, fingerprint).map((r) => recordOf$3(r)),
293
- setFeedbackStatus: (fingerprint, status) => Number(setStatus.run(status, (/* @__PURE__ */ new Date()).toISOString(), fingerprint).changes) > 0,
294
- mergeFeedback: (into, from) => {
295
- const root = text(rootOf.get(into), "root");
296
- if (root === "") return 0;
297
- const at = (/* @__PURE__ */ new Date()).toISOString();
298
- return inTransaction(db, () => from.filter((fingerprint) => fingerprint !== root).reduce((moved, fingerprint) => moved + Number(mergeInto.run(root, at, fingerprint, fingerprint, root).changes), 0));
299
- },
300
- splitFeedback: (fingerprint) => Number(split.run((/* @__PURE__ */ new Date()).toISOString(), fingerprint).changes),
301
- exportFeedback: (filter = {}) => exported.all(filter.all === true ? 1 : 0).map((r) => recordOf$3(r)),
302
- importFeedback: (record) => {
303
- if (known.get(record.runId, record.seq) !== void 0) return false;
304
- insertRecord(record.runId, record.seq, record.at, record.node, record.feedback);
305
- return true;
306
- }
307
- };
308
- };
309
- // ../core/src/store/links.ts
310
- const linkOf = (row) => {
311
- const ended = text(row, "ended_at");
312
- return {
313
- id: Number(row?.["id"]),
314
- a: {
315
- run: text(row, "a_run"),
316
- node: text(row, "a_node")
317
- },
318
- b: {
319
- run: text(row, "b_run"),
320
- node: text(row, "b_node")
321
- },
322
- messages: Number(row?.["messages"]),
323
- maxMessages: Number(row?.["max_messages"]),
324
- createdAt: text(row, "created_at"),
325
- ...ended === "" ? {} : { endedAt: ended }
326
- };
327
- };
328
- const linkQueries = (db) => {
329
- const insert = db.prepare(`INSERT INTO links (a_run, a_node, b_run, b_node, max_messages, created_at)
330
- VALUES (?, ?, ?, ?, ?, ?)`);
331
- const byId = db.prepare("SELECT * FROM links WHERE id = ?");
332
- const filtered = db.prepare(`SELECT * FROM links
333
- WHERE (?1 IS NULL OR a_run = ?1 OR b_run = ?1)
334
- AND (?2 = 0 OR ended_at IS NULL)
335
- ORDER BY id`);
336
- const count = db.prepare("UPDATE links SET messages = messages + 1 WHERE id = ?");
337
- const extend = db.prepare("UPDATE links SET max_messages = max_messages + ? WHERE id = ?");
338
- const end = db.prepare("UPDATE links SET ended_at = ? WHERE id = ? AND ended_at IS NULL");
339
- const link = (id) => {
340
- const row = byId.get(id);
341
- return row === void 0 ? void 0 : linkOf(row);
342
- };
343
- return {
344
- addLink: (a, b, maxMessages) => {
345
- const { lastInsertRowid } = insert.run(a.run, a.node, b.run, b.node, maxMessages, (/* @__PURE__ */ new Date()).toISOString());
346
- return linkOf(byId.get(lastInsertRowid));
347
- },
348
- link,
349
- links: (filter = {}) => filtered.all(filter.run ?? null, filter.live === true ? 1 : 0).map((row) => linkOf(row)),
350
- countMessage: (id) => {
351
- count.run(id);
352
- },
353
- extendLink: (id, by) => {
354
- extend.run(by, id);
355
- },
356
- endLink: (id) => end.run((/* @__PURE__ */ new Date()).toISOString(), id).changes > 0
357
- };
358
- };
359
- // ../core/src/store/plan-limits.ts
360
- const planLimitQueries = (db) => {
361
- const put = db.prepare("INSERT OR REPLACE INTO plan_limits (name, status, resets_at, at) VALUES (?, ?, ?, ?)");
362
- const all = db.prepare("SELECT name, status, resets_at, at FROM plan_limits ORDER BY name");
363
- const clear = db.prepare("DELETE FROM plan_limits");
364
- return {
365
- clearPlanLimits: () => {
366
- clear.run();
367
- },
368
- recordPlanLimit: ({ window, status, resetsAt }, at) => {
369
- put.run(window, status, resetsAt ?? null, at);
370
- },
371
- planLimits: () => all.all().flatMap((r) => {
372
- const status = text(r, "status");
373
- const resetsAt = text(r, "resets_at");
374
- return status === "allowed" || status === "allowed_warning" || status === "rejected" ? [{
375
- window: text(r, "name"),
376
- status,
377
- at: text(r, "at"),
378
- ...resetsAt === "" ? {} : { resetsAt }
379
- }] : [];
380
- })
381
- };
382
- };
383
- // ../core/src/store/projects.ts
384
- /**
385
- * @file The store's registered projects (ADR-035): which repositories Ork
386
- * knows, and where it keeps each one's files (in the repository, or in
387
- * `~/.ork/projects/<id>/` when attached), and moving it from one to the other
388
- * (`ork migrate`) with its runs' workflow files.
389
- */
390
- const recordOf$2 = (row) => ({
391
- id: text(row, "id"),
392
- root: text(row, "root"),
393
- mode: PROJECT_MODES.find((m) => m === text(row, "mode")) ?? "in-repo",
394
- dir: text(row, "dir"),
395
- registeredAt: text(row, "registered_at")
396
- });
397
- const projectQueries = (db) => {
398
- const all = db.prepare("SELECT * FROM projects ORDER BY id");
399
- const insert = db.prepare(`INSERT INTO projects (id, root, mode, dir, registered_at)
400
- VALUES (?, ?, ?, ?, ?)`);
401
- const remove = db.prepare("DELETE FROM projects WHERE id = ?");
402
- const removeDrafts = db.prepare("DELETE FROM workflow_drafts WHERE project = ?");
403
- const removeTriggers = db.prepare("DELETE FROM triggers WHERE project = ?");
404
- const removeSeen = db.prepare("DELETE FROM trigger_seen WHERE project = ?");
405
- const move = db.prepare("UPDATE projects SET root = ?, mode = ?, dir = ? WHERE id = ?");
406
- const rehome = db.prepare("UPDATE runs SET project = ? WHERE project = ?");
407
- const rehomeUsage = db.prepare("UPDATE usage SET project = ? WHERE project = ?");
408
- const refile = db.prepare(`UPDATE runs SET file = ? || substr(file, ?)
409
- WHERE project = ? AND substr(file, 1, ?) = ?`);
410
- return {
411
- projects: () => all.all().map((r) => recordOf$2(r)),
412
- addProject: (project) => {
413
- const record = {
414
- ...project,
415
- registeredAt: (/* @__PURE__ */ new Date()).toISOString()
416
- };
417
- insert.run(record.id, record.root, record.mode, record.dir, record.registeredAt);
418
- return record;
419
- },
420
- forgetProject: (id) => {
421
- removeDrafts.run(id);
422
- removeTriggers.run(id);
423
- removeSeen.run(id);
424
- return Number(remove.run(id).changes) > 0;
425
- },
426
- moveProject: (id, place) => {
427
- const before = all.all().map((r) => recordOf$2(r)).find((p) => p.id === id);
428
- if (before === void 0) return false;
429
- const from = `${before.dir}${path.sep}`;
430
- const root = place.root ?? before.root;
431
- db.exec("BEGIN");
432
- try {
433
- move.run(root, place.mode, place.dir, id);
434
- rehome.run(root, before.root);
435
- rehomeUsage.run(root, before.root);
436
- refile.run(`${place.dir}${path.sep}`, from.length + 1, root, from.length, from);
437
- db.exec("COMMIT");
438
- } catch (error) {
439
- db.exec("ROLLBACK");
440
- throw error;
441
- }
442
- return true;
443
- }
444
- };
445
- };
446
- // ../core/src/store/triggers.ts
447
- const recordOf$1 = (row) => {
448
- const acceptedHash = text(row, "accepted_hash");
449
- const lookedAt = text(row, "looked_at");
450
- const error = text(row, "error");
451
- const held = text(row, "held");
452
- return {
453
- project: text(row, "project"),
454
- trigger: text(row, "trigger"),
455
- baselined: Number(row["baselined"]) === 1,
456
- ...acceptedHash === "" ? {} : { acceptedHash },
457
- ...lookedAt === "" ? {} : { lookedAt },
458
- ...error === "" ? {} : { error },
459
- ...held === "" ? {} : { held }
460
- };
461
- };
462
- const holderOf = (row) => row === void 0 ? void 0 : {
463
- pid: Number(row["pid"]),
464
- at: text(row, "at")
465
- };
466
- const triggerQueries = (db) => {
467
- const all = db.prepare("SELECT * FROM triggers ORDER BY project, trigger");
468
- const ofProject = db.prepare("SELECT * FROM triggers WHERE project = ? ORDER BY trigger");
469
- const accept = db.prepare(`INSERT INTO triggers (project, trigger, accepted_hash, baselined)
470
- VALUES (?, ?, ?, 0)
471
- ON CONFLICT (project, trigger) DO UPDATE SET
472
- accepted_hash = excluded.accepted_hash, baselined = 0,
473
- error = NULL, held = NULL`);
474
- const look = db.prepare(`UPDATE triggers SET looked_at = ?, error = ?, held = ?
475
- WHERE project = ? AND trigger = ?`);
476
- const setBaselined = db.prepare("UPDATE triggers SET baselined = 1 WHERE project = ? AND trigger = ?");
477
- const putSeen = db.prepare(`INSERT OR IGNORE INTO trigger_seen (project, trigger, key, run_id, at)
478
- VALUES (?, ?, ?, ?, ?)`);
479
- const isSeen = db.prepare("SELECT 1 FROM trigger_seen WHERE project = ? AND trigger = ? AND key = ?");
480
- const dropSeen = db.prepare("DELETE FROM trigger_seen WHERE project = ? AND trigger = ? AND key = ?");
481
- const runsSince = db.prepare(`SELECT run_id FROM trigger_seen
482
- WHERE project = ? AND trigger = ? AND run_id IS NOT NULL AND at >= ?
483
- ORDER BY at`);
484
- const holder = db.prepare("SELECT pid, at FROM watch_lock");
485
- const hold = db.prepare(`INSERT INTO watch_lock (id, pid, at) VALUES (1, ?, ?)
486
- ON CONFLICT (id) DO UPDATE SET pid = excluded.pid, at = excluded.at`);
487
- const letGo = db.prepare("DELETE FROM watch_lock WHERE pid = ?");
488
- return {
489
- triggerRecords: (project) => (project === void 0 ? all.all() : ofProject.all(project)).map((row) => recordOf$1(row)),
490
- acceptTrigger: (project, trigger, hash) => {
491
- accept.run(project, trigger, hash);
492
- },
493
- recordLook: (project, trigger, { at, error, held }) => {
494
- look.run(at, error ?? null, held ?? null, project, trigger);
495
- },
496
- baselineTrigger: (project, trigger, keys, at) => {
497
- for (const key of keys) putSeen.run(project, trigger, key, null, at);
498
- setBaselined.run(project, trigger);
499
- },
500
- seenKeys: (project, trigger, keys) => new Set(keys.filter((key) => isSeen.get(project, trigger, key) !== void 0)),
501
- markStarted: (project, trigger, key, runId, at) => Number(putSeen.run(project, trigger, key, runId, at).changes) > 0,
502
- giveUpOn: (project, trigger, key, at) => {
503
- putSeen.run(project, trigger, key, null, at);
504
- },
505
- unmarkStarted: (project, trigger, key) => {
506
- dropSeen.run(project, trigger, key);
507
- },
508
- triggerRuns: (project, trigger, since) => runsSince.all(project, trigger, since).map((row) => text(row, "run_id")),
509
- watchHolder: () => holderOf(holder.get()),
510
- takeWatchHolder: (me, holds) => inTransaction(db, () => {
511
- const current = holderOf(holder.get());
512
- if (current !== void 0 && holds(current)) return current;
513
- hold.run(me.pid, me.at);
514
- return null;
515
- }),
516
- releaseWatchHolder: (pid) => {
517
- letGo.run(pid);
518
- }
519
- };
520
- };
521
- // ../core/src/store/queries.ts
522
- /**
523
- * Every query set on `db`; `insertFeedback` and `recordUsage` are the store's
524
- * own.
525
- */
526
- const storeQueries = (db) => ({
527
- ...decisionQueries(db),
528
- ...linkQueries(db),
529
- ...feedbackQueries(db),
530
- ...projectQueries(db),
531
- ...usageQueries(db),
532
- ...planLimitQueries(db),
533
- ...triggerQueries(db),
534
- ...draftQueries(db)
535
- });
536
- // ../core/src/store/schema.ts
537
- const MIGRATIONS = [
538
- `CREATE TABLE runs (
539
- id TEXT PRIMARY KEY,
540
- workflow TEXT NOT NULL,
541
- file TEXT NOT NULL,
542
- project TEXT NOT NULL,
543
- status TEXT NOT NULL,
544
- pid INTEGER NOT NULL,
545
- started_at TEXT NOT NULL,
546
- finished_at TEXT,
547
- cost_usd REAL NOT NULL DEFAULT 0
548
- );
549
- CREATE INDEX runs_by_project ON runs (project, started_at);
550
- CREATE TABLE events (
551
- run_id TEXT NOT NULL REFERENCES runs (id),
552
- seq INTEGER NOT NULL,
553
- at TEXT NOT NULL,
554
- type TEXT NOT NULL,
555
- node TEXT,
556
- data TEXT NOT NULL,
557
- PRIMARY KEY (run_id, seq)
558
- ) WITHOUT ROWID;`,
559
- `CREATE TABLE decisions (
560
- id TEXT PRIMARY KEY,
561
- run_id TEXT NOT NULL REFERENCES runs (id),
562
- node TEXT NOT NULL,
563
- kind TEXT NOT NULL,
564
- question TEXT NOT NULL,
565
- options TEXT NOT NULL,
566
- recommended TEXT,
567
- context TEXT,
568
- opened_at TEXT NOT NULL,
569
- status TEXT NOT NULL,
570
- answer TEXT,
571
- answered_by TEXT,
572
- answered_at TEXT
573
- );
574
- CREATE INDEX decisions_by_status ON decisions (status, run_id);
575
- CREATE TABLE commands (
576
- id INTEGER PRIMARY KEY AUTOINCREMENT,
577
- run_id TEXT NOT NULL REFERENCES runs (id),
578
- kind TEXT NOT NULL,
579
- at TEXT NOT NULL,
580
- handled_at TEXT
581
- );`,
582
- `ALTER TABLE commands ADD COLUMN node TEXT;
583
- ALTER TABLE commands ADD COLUMN body TEXT;`,
584
- `CREATE TABLE feedback (
585
- id INTEGER PRIMARY KEY AUTOINCREMENT,
586
- run_id TEXT NOT NULL REFERENCES runs (id),
587
- seq INTEGER NOT NULL,
588
- at TEXT NOT NULL,
589
- node TEXT,
590
- source TEXT NOT NULL,
591
- kind TEXT NOT NULL,
592
- target TEXT NOT NULL,
593
- severity TEXT NOT NULL,
594
- title TEXT NOT NULL,
595
- body TEXT NOT NULL,
596
- evidence TEXT NOT NULL,
597
- code TEXT,
598
- fingerprint TEXT NOT NULL,
599
- workflow TEXT,
600
- workflow_hash TEXT,
601
- node_type TEXT,
602
- model TEXT,
603
- driver TEXT,
604
- version TEXT,
605
- feedback_version INTEGER NOT NULL
606
- );
607
- CREATE INDEX feedback_by_fingerprint ON feedback (fingerprint, at);
608
- CREATE TABLE feedback_items (
609
- fingerprint TEXT PRIMARY KEY,
610
- status TEXT NOT NULL,
611
- updated_at TEXT NOT NULL
612
- );`,
613
- `CREATE TABLE projects (
614
- id TEXT PRIMARY KEY,
615
- root TEXT NOT NULL UNIQUE,
616
- mode TEXT NOT NULL,
617
- dir TEXT NOT NULL,
618
- registered_at TEXT NOT NULL
619
- );`,
620
- `CREATE TABLE links (
621
- id INTEGER PRIMARY KEY AUTOINCREMENT,
622
- a_run TEXT NOT NULL REFERENCES runs (id),
623
- a_node TEXT NOT NULL,
624
- b_run TEXT NOT NULL REFERENCES runs (id),
625
- b_node TEXT NOT NULL,
626
- max_messages INTEGER NOT NULL,
627
- messages INTEGER NOT NULL DEFAULT 0,
628
- created_at TEXT NOT NULL,
629
- ended_at TEXT
630
- );
631
- CREATE INDEX links_by_a ON links (a_run);
632
- CREATE INDEX links_by_b ON links (b_run);
633
- ALTER TABLE commands ADD COLUMN sender TEXT;`,
634
- `ALTER TABLE runs ADD COLUMN parent TEXT;
635
- ALTER TABLE runs ADD COLUMN depth INTEGER NOT NULL DEFAULT 0;`,
636
- (db) => {
637
- db.exec(`CREATE TABLE usage (
638
- run_id TEXT NOT NULL,
639
- seq INTEGER NOT NULL,
640
- at TEXT NOT NULL,
641
- project TEXT NOT NULL,
642
- workflow TEXT NOT NULL,
643
- node TEXT NOT NULL,
644
- template TEXT,
645
- model TEXT,
646
- driver TEXT,
647
- kind TEXT NOT NULL,
648
- round INTEGER NOT NULL,
649
- failed INTEGER NOT NULL,
650
- cost_usd REAL NOT NULL,
651
- input_tokens INTEGER NOT NULL,
652
- output_tokens INTEGER NOT NULL,
653
- cache_read_tokens INTEGER NOT NULL,
654
- cache_write_tokens INTEGER NOT NULL,
655
- turns INTEGER NOT NULL,
656
- duration_ms INTEGER NOT NULL,
657
- PRIMARY KEY (run_id, seq)
658
- ) WITHOUT ROWID;
659
- CREATE INDEX usage_by_time ON usage (at);
660
- CREATE INDEX usage_by_project ON usage (project, at);
661
- ALTER TABLE feedback RENAME TO feedback_old;
662
- DROP INDEX feedback_by_fingerprint;
663
- CREATE TABLE feedback (
664
- id INTEGER PRIMARY KEY AUTOINCREMENT,
665
- run_id TEXT NOT NULL,
666
- seq INTEGER NOT NULL,
667
- at TEXT NOT NULL,
668
- node TEXT,
669
- source TEXT NOT NULL,
670
- kind TEXT NOT NULL,
671
- target TEXT NOT NULL,
672
- severity TEXT NOT NULL,
673
- title TEXT NOT NULL,
674
- body TEXT NOT NULL,
675
- evidence TEXT NOT NULL,
676
- code TEXT,
677
- fingerprint TEXT NOT NULL,
678
- workflow TEXT,
679
- workflow_hash TEXT,
680
- node_type TEXT,
681
- model TEXT,
682
- driver TEXT,
683
- version TEXT,
684
- feedback_version INTEGER NOT NULL
685
- );
686
- INSERT INTO feedback SELECT * FROM feedback_old;
687
- DROP TABLE feedback_old;
688
- CREATE INDEX feedback_by_fingerprint ON feedback (fingerprint, at);`);
689
- backfillUsage(db);
690
- },
691
- `CREATE TABLE plan_limits (
692
- name TEXT PRIMARY KEY,
693
- status TEXT NOT NULL,
694
- resets_at TEXT,
695
- at TEXT NOT NULL
696
- );`,
697
- `CREATE TABLE workflow_drafts (
698
- project TEXT NOT NULL,
699
- workflow TEXT NOT NULL,
700
- text TEXT NOT NULL,
701
- base_hash TEXT,
702
- at TEXT NOT NULL,
703
- PRIMARY KEY (project, workflow)
704
- );`,
705
- `CREATE TABLE triggers (
706
- project TEXT NOT NULL,
707
- trigger TEXT NOT NULL,
708
- accepted_hash TEXT,
709
- baselined INTEGER NOT NULL DEFAULT 0,
710
- looked_at TEXT,
711
- error TEXT,
712
- held TEXT,
713
- PRIMARY KEY (project, trigger)
714
- ) WITHOUT ROWID;
715
- CREATE TABLE trigger_seen (
716
- project TEXT NOT NULL,
717
- trigger TEXT NOT NULL,
718
- key TEXT NOT NULL,
719
- run_id TEXT,
720
- at TEXT NOT NULL,
721
- PRIMARY KEY (project, trigger, key)
722
- ) WITHOUT ROWID;`,
723
- `CREATE TABLE watch_lock (
724
- id INTEGER PRIMARY KEY CHECK (id = 1),
725
- pid INTEGER NOT NULL,
726
- at TEXT NOT NULL
727
- );`,
728
- `ALTER TABLE feedback_items ADD COLUMN merged_into TEXT;
729
- CREATE INDEX feedback_items_merged ON feedback_items (merged_into);`,
730
- `ALTER TABLE runs ADD COLUMN process_start TEXT;`,
731
- `ALTER TABLE runs ADD COLUMN skipped INTEGER;
732
- ALTER TABLE runs ADD COLUMN failures_handled INTEGER;`,
733
- `ALTER TABLE decisions ADD COLUMN note TEXT;`,
734
- `ALTER TABLE decisions ADD COLUMN owner TEXT;`
735
- ];
736
- const versionOf = (db) => Number(db.prepare("PRAGMA user_version").get()?.["user_version"] ?? 0);
737
- /**
738
- * Refuses a database that is not at this Ork's schema version, newer or older,
739
- * for a reader that may not migrate it (a read-only open).
740
- */
741
- const requireCurrent = (db, migrations = MIGRATIONS) => {
742
- const version = versionOf(db);
743
- if (version !== migrations.length) throw new Error(`The store is at schema version ${String(version)}; this Ork reads ${String(migrations.length)}.`);
744
- };
745
- /** Applies the migrations the database has not had yet. */
746
- const migrate = (db, migrations = MIGRATIONS) => {
747
- for (const [index, step] of migrations.entries()) {
748
- if (index < versionOf(db)) continue;
749
- inTransaction(db, () => {
750
- if (index < versionOf(db)) return;
751
- if (typeof step === "string") db.exec(step);
752
- else step(db);
753
- db.exec(`PRAGMA user_version = ${String(index + 1)}`);
754
- });
755
- }
756
- };
757
- // ../core/src/store/sqlite.ts
758
- /**
759
- * @file The event store on `node:sqlite` (ADR-024): one database for every
760
- * project, `~/.ork/state.db`, in WAL mode so other `ork` processes read while
761
- * a run writes. Events are stored with secrets masked, feedback records
762
- * beside them, and the registered projects; the schema is versioned with
763
- * `user_version`. Tab completion opens it read-only: no store created, no
764
- * migration, a store at another version refused, and the database never
765
- * changed (SQLite may still add the `-wal` and `-shm` files beside it).
766
- */
767
- /** `~/.ork/state.db`; `home` is `~` (tests pass a folder). */
768
- const storePath = (home = homedir()) => path.join(home, ".ork", "state.db");
769
- const STATUSES = [
770
- "running",
771
- "succeeded",
772
- "failed",
773
- "budget_exhausted",
774
- "quota_exhausted",
775
- "timed_out",
776
- "interrupted"
777
- ];
778
- const statusOf = (value) => STATUSES.find((s) => s === value) ?? "failed";
779
- const recordOf = (row) => {
780
- const record = {
781
- id: text(row, "id"),
782
- workflow: text(row, "workflow"),
783
- file: text(row, "file"),
784
- project: text(row, "project"),
785
- status: statusOf(text(row, "status")),
786
- pid: Number(row?.["pid"]),
787
- startedAt: text(row, "started_at"),
788
- costUsd: Number(row?.["cost_usd"]),
789
- depth: Number(row?.["depth"] ?? 0)
790
- };
791
- const processStart = text(row, "process_start");
792
- if (processStart !== "") record.processStart = processStart;
793
- const parent = text(row, "parent");
794
- if (parent !== "") record.parent = parent;
795
- const finished = text(row, "finished_at");
796
- if (finished !== "") record.finishedAt = finished;
797
- const skipped = row?.["skipped"];
798
- if (typeof skipped === "number") record.skipped = skipped;
799
- const handled = row?.["failures_handled"];
800
- if (typeof handled === "number") record.failuresHandled = handled;
801
- return record;
802
- };
803
- /** The node an event is about, for filtering. */
804
- const nodeOf = (event) => {
805
- if ("id" in event && typeof event.id === "string") return event.id;
806
- if ("node" in event && typeof event.node === "string") return event.node;
807
- return null;
808
- };
809
- /** A database opened for reading only, refused unless it is current. */
810
- const openReadOnly = (file) => {
811
- const db = new DatabaseSync(file, { readOnly: true });
812
- try {
813
- db.exec("PRAGMA busy_timeout = 5000");
814
- requireCurrent(db);
815
- return db;
816
- } catch (error) {
817
- db.close();
818
- throw error;
819
- }
820
- };
821
- /** A database opened for writing: created, in WAL mode, migrated. */
822
- const openWritable = (file) => {
823
- if (file !== ":memory:") mkdirSync(path.dirname(file), { recursive: true });
824
- const db = new DatabaseSync(file);
825
- db.exec("PRAGMA busy_timeout = 5000");
826
- db.exec("PRAGMA journal_mode = WAL");
827
- db.exec("PRAGMA synchronous = NORMAL");
828
- db.exec("PRAGMA foreign_keys = ON");
829
- migrate(db);
830
- return db;
831
- };
832
- /**
833
- * Opens (and creates) the store at `file`; `:memory:` for tests. With
834
- * `readOnly`, it neither creates nor migrates, and throws when it can't read.
835
- */
836
- const openStore = (file = storePath(), options = {}) => {
837
- const ownStart = options.processStart ?? ownProcessStart;
838
- const db = options.readOnly === true ? openReadOnly(file) : openWritable(file);
839
- const insertRun = db.prepare(`INSERT INTO runs (id, workflow, file, project, status, pid, process_start, started_at, parent, depth)
840
- VALUES (?, ?, ?, ?, 'running', ?, ?, ?, ?, ?)`);
841
- const lastSeq = db.prepare("SELECT COALESCE(MAX(seq), 0) AS seq FROM events WHERE run_id = ?");
842
- const insertEvent = db.prepare("INSERT INTO events (run_id, seq, at, type, node, data) VALUES (?, ?, ?, ?, ?, ?)");
843
- const finishRun = db.prepare("UPDATE runs SET status = ?, finished_at = ?, cost_usd = ?, skipped = ?, failures_handled = ? WHERE id = ?");
844
- const reopenRun = db.prepare("UPDATE runs SET status = 'running', pid = ?, process_start = ?, finished_at = NULL, skipped = NULL, failures_handled = NULL WHERE id = ?");
845
- const addCost = db.prepare("UPDATE runs SET cost_usd = cost_usd + ? WHERE id = ?");
846
- const byId = db.prepare("SELECT * FROM runs WHERE id = ?");
847
- const byPrefix = db.prepare("SELECT * FROM runs WHERE substr(id, 1, ?) = ? ORDER BY started_at DESC, rowid DESC");
848
- const allRuns = db.prepare("SELECT * FROM runs ORDER BY started_at DESC, rowid DESC LIMIT ?");
849
- const projectRuns = db.prepare("SELECT * FROM runs WHERE project = ? ORDER BY started_at DESC, rowid DESC LIMIT ?");
850
- const eventsAfter = db.prepare("SELECT seq, at, data FROM events WHERE run_id = ? AND seq > ? ORDER BY seq");
851
- const runNodes = db.prepare("SELECT node FROM events WHERE run_id = ? AND node IS NOT NULL GROUP BY node ORDER BY MIN(seq)");
852
- const { insertFeedback, recordUsage, recordPlanLimit, ...queries } = storeQueries(db);
853
- return {
854
- ...queries,
855
- begin: (run) => {
856
- const startedAt = (/* @__PURE__ */ new Date()).toISOString();
857
- const depth = run.parent === void 0 ? 0 : Number(byId.get(run.parent)?.["depth"] ?? 0) + 1;
858
- const processStart = ownStart();
859
- insertRun.run(run.id, run.workflow, run.file, run.project, process.pid, processStart ?? null, startedAt, run.parent ?? null, depth);
860
- return {
861
- ...run,
862
- depth,
863
- status: "running",
864
- pid: process.pid,
865
- ...processStart === void 0 ? {} : { processStart },
866
- startedAt,
867
- costUsd: 0
868
- };
869
- },
870
- reopen: (runId) => {
871
- reopenRun.run(process.pid, ownStart() ?? null, runId);
872
- },
873
- append: (runId, event) => inTransaction(db, () => {
874
- const at = (/* @__PURE__ */ new Date()).toISOString();
875
- const seq = Number(lastSeq.get(runId)?.["seq"] ?? 0) + 1;
876
- insertEvent.run(runId, seq, at, event.type, nodeOf(event), JSON.stringify(event, maskReplacer));
877
- if (event.type === "node.finished" && event.usage !== void 0) addCost.run(event.usage.costUsd, runId);
878
- if (event.type === "node.event" && event.event.type === "session.finished") recordUsage(runId);
879
- if (event.type === "node.event" && event.event.type === "plan.limit") recordPlanLimit(event.event, at);
880
- if (event.type === "feedback.recorded") insertFeedback(runId, seq, at, event.node, event.feedback);
881
- if (event.type === "run.finished") {
882
- const { status, costUsd, skipped, failuresHandled } = event.result;
883
- finishRun.run(status, at, costUsd, skipped ?? null, failuresHandled ?? null, runId);
884
- }
885
- return {
886
- runId,
887
- seq,
888
- at,
889
- event
890
- };
891
- }),
892
- runs: (filter = {}) => {
893
- const limit = filter.limit ?? -1;
894
- return (filter.project === void 0 ? allRuns.all(limit) : projectRuns.all(filter.project, limit)).map((r) => recordOf(r));
895
- },
896
- lookup: (prefix) => {
897
- const exact = byId.get(prefix);
898
- if (exact !== void 0) return [recordOf(exact)];
899
- return byPrefix.all(prefix.length, prefix).map((r) => recordOf(r));
900
- },
901
- events: (runId, after = 0) => eventsAfter.all(runId, after).map((r) => ({
902
- runId,
903
- seq: Number(r["seq"]),
904
- at: text(r, "at"),
905
- event: eventOf(text(r, "data"))
906
- })),
907
- nodes: (runId) => runNodes.all(runId).map((r) => text(r, "node")),
908
- close: () => {
909
- db.close();
910
- }
911
- };
912
- };
913
- // src/output.ts
914
- /**
915
- * @file Terminal output helpers: styled text that turns plain when stdout is
916
- * not a TTY or `NO_COLOR` is set, and a yes-or-no question on the terminal.
917
- */
918
- /** Styles text for stdout; plain when stdout is not a TTY or NO_COLOR is set. */
919
- const style = (format, text) => styleText(format, text, { stream: process.stdout });
920
- /**
921
- * Text from outside, fit for a terminal: escape sequences could hide a line on
922
- * the very screen a person judges from, and a line break could forge one.
923
- */
924
- const plain = (text) => stripVTControlCharacters(text).replaceAll(/[\p{Cc}\p{Bidi_Control}]/gu, " ");
925
- const write = (text) => {
926
- process.stdout.write(`${text}\n`);
927
- };
928
- const writeErr = (text) => {
929
- process.stderr.write(`${text}\n`);
930
- };
931
- /** What a run spent: `$0.0312`, or `$0.00` for nothing. */
932
- const cost = (usd) => usd === 0 ? "$0.00" : `$${usd.toFixed(4)}`;
933
- /** Asks a yes-or-no question on the terminal; anything but yes is no. */
934
- const confirm = async (question) => {
935
- const rl = createInterface({
936
- input: process.stdin,
937
- output: process.stderr
938
- });
939
- const { promise: ended, resolve: end } = Promise.withResolvers();
940
- rl.once("close", () => {
941
- end("");
942
- });
943
- try {
944
- const answer = await Promise.race([rl.question(question), ended]);
945
- return ["y", "yes"].includes(answer.trim().toLowerCase());
946
- } finally {
947
- rl.close();
948
- }
949
- };
950
- export { write as a, openStore as c, style as i, storePath as l, cost as n, writeErr as o, plain as r, nodeOf as s, confirm as t };