agy-worker-mcp 0.3.1

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 (227) hide show
  1. package/CHANGELOG.md +141 -0
  2. package/LICENSE +21 -0
  3. package/README.md +309 -0
  4. package/commands/agy-ceiling.md +31 -0
  5. package/dist/broker/blockers.d.ts +86 -0
  6. package/dist/broker/blockers.d.ts.map +1 -0
  7. package/dist/broker/blockers.js +390 -0
  8. package/dist/broker/blockers.js.map +1 -0
  9. package/dist/broker/outcome.d.ts +85 -0
  10. package/dist/broker/outcome.d.ts.map +1 -0
  11. package/dist/broker/outcome.js +147 -0
  12. package/dist/broker/outcome.js.map +1 -0
  13. package/dist/broker/reconcile.d.ts +74 -0
  14. package/dist/broker/reconcile.d.ts.map +1 -0
  15. package/dist/broker/reconcile.js +415 -0
  16. package/dist/broker/reconcile.js.map +1 -0
  17. package/dist/broker/result.d.ts +88 -0
  18. package/dist/broker/result.d.ts.map +1 -0
  19. package/dist/broker/result.js +234 -0
  20. package/dist/broker/result.js.map +1 -0
  21. package/dist/broker/verify.d.ts +52 -0
  22. package/dist/broker/verify.d.ts.map +1 -0
  23. package/dist/broker/verify.js +174 -0
  24. package/dist/broker/verify.js.map +1 -0
  25. package/dist/contract/errors.d.ts +119 -0
  26. package/dist/contract/errors.d.ts.map +1 -0
  27. package/dist/contract/errors.js +99 -0
  28. package/dist/contract/errors.js.map +1 -0
  29. package/dist/contract/index.d.ts +10 -0
  30. package/dist/contract/index.d.ts.map +1 -0
  31. package/dist/contract/index.js +10 -0
  32. package/dist/contract/index.js.map +1 -0
  33. package/dist/contract/paths.d.ts +139 -0
  34. package/dist/contract/paths.d.ts.map +1 -0
  35. package/dist/contract/paths.js +359 -0
  36. package/dist/contract/paths.js.map +1 -0
  37. package/dist/contract/schema.sql +64 -0
  38. package/dist/contract/types.d.ts +1113 -0
  39. package/dist/contract/types.d.ts.map +1 -0
  40. package/dist/contract/types.js +114 -0
  41. package/dist/contract/types.js.map +1 -0
  42. package/dist/events/cursor.d.ts +65 -0
  43. package/dist/events/cursor.d.ts.map +1 -0
  44. package/dist/events/cursor.js +286 -0
  45. package/dist/events/cursor.js.map +1 -0
  46. package/dist/events/detect.d.ts +58 -0
  47. package/dist/events/detect.d.ts.map +1 -0
  48. package/dist/events/detect.js +183 -0
  49. package/dist/events/detect.js.map +1 -0
  50. package/dist/events/normalize.d.ts +30 -0
  51. package/dist/events/normalize.d.ts.map +1 -0
  52. package/dist/events/normalize.js +158 -0
  53. package/dist/events/normalize.js.map +1 -0
  54. package/dist/events/parse.d.ts +41 -0
  55. package/dist/events/parse.d.ts.map +1 -0
  56. package/dist/events/parse.js +89 -0
  57. package/dist/events/parse.js.map +1 -0
  58. package/dist/gate/bind.d.ts +28 -0
  59. package/dist/gate/bind.d.ts.map +1 -0
  60. package/dist/gate/bind.js +154 -0
  61. package/dist/gate/bind.js.map +1 -0
  62. package/dist/gate/gate.d.ts +139 -0
  63. package/dist/gate/gate.d.ts.map +1 -0
  64. package/dist/gate/gate.js +488 -0
  65. package/dist/gate/gate.js.map +1 -0
  66. package/dist/gate/hooks-file.d.ts +55 -0
  67. package/dist/gate/hooks-file.d.ts.map +1 -0
  68. package/dist/gate/hooks-file.js +104 -0
  69. package/dist/gate/hooks-file.js.map +1 -0
  70. package/dist/gate.d.ts +3 -0
  71. package/dist/gate.d.ts.map +1 -0
  72. package/dist/gate.js +20 -0
  73. package/dist/gate.js.map +1 -0
  74. package/dist/policy/ceiling-review.d.ts +22 -0
  75. package/dist/policy/ceiling-review.d.ts.map +1 -0
  76. package/dist/policy/ceiling-review.js +170 -0
  77. package/dist/policy/ceiling-review.js.map +1 -0
  78. package/dist/policy/ceiling.d.ts +128 -0
  79. package/dist/policy/ceiling.d.ts.map +1 -0
  80. package/dist/policy/ceiling.js +273 -0
  81. package/dist/policy/ceiling.js.map +1 -0
  82. package/dist/policy/containment.d.ts +100 -0
  83. package/dist/policy/containment.d.ts.map +1 -0
  84. package/dist/policy/containment.js +1071 -0
  85. package/dist/policy/containment.js.map +1 -0
  86. package/dist/policy/hard-deny.d.ts +28 -0
  87. package/dist/policy/hard-deny.d.ts.map +1 -0
  88. package/dist/policy/hard-deny.js +78 -0
  89. package/dist/policy/hard-deny.js.map +1 -0
  90. package/dist/policy/profiles.d.ts +82 -0
  91. package/dist/policy/profiles.d.ts.map +1 -0
  92. package/dist/policy/profiles.js +317 -0
  93. package/dist/policy/profiles.js.map +1 -0
  94. package/dist/policy/rules.d.ts +138 -0
  95. package/dist/policy/rules.d.ts.map +1 -0
  96. package/dist/policy/rules.js +1180 -0
  97. package/dist/policy/rules.js.map +1 -0
  98. package/dist/policy/seatbelt.d.ts +32 -0
  99. package/dist/policy/seatbelt.d.ts.map +1 -0
  100. package/dist/policy/seatbelt.js +59 -0
  101. package/dist/policy/seatbelt.js.map +1 -0
  102. package/dist/policy/tools.d.ts +36 -0
  103. package/dist/policy/tools.d.ts.map +1 -0
  104. package/dist/policy/tools.js +94 -0
  105. package/dist/policy/tools.js.map +1 -0
  106. package/dist/runner/gate-watchdog.d.ts +93 -0
  107. package/dist/runner/gate-watchdog.d.ts.map +1 -0
  108. package/dist/runner/gate-watchdog.js +153 -0
  109. package/dist/runner/gate-watchdog.js.map +1 -0
  110. package/dist/runner/idle.d.ts +51 -0
  111. package/dist/runner/idle.d.ts.map +1 -0
  112. package/dist/runner/idle.js +81 -0
  113. package/dist/runner/idle.js.map +1 -0
  114. package/dist/runner/inbox.d.ts +56 -0
  115. package/dist/runner/inbox.d.ts.map +1 -0
  116. package/dist/runner/inbox.js +134 -0
  117. package/dist/runner/inbox.js.map +1 -0
  118. package/dist/runner/reap.d.ts +65 -0
  119. package/dist/runner/reap.d.ts.map +1 -0
  120. package/dist/runner/reap.js +168 -0
  121. package/dist/runner/reap.js.map +1 -0
  122. package/dist/runner/runner.d.ts +51 -0
  123. package/dist/runner/runner.d.ts.map +1 -0
  124. package/dist/runner/runner.js +289 -0
  125. package/dist/runner/runner.js.map +1 -0
  126. package/dist/runner/spawn.d.ts +98 -0
  127. package/dist/runner/spawn.d.ts.map +1 -0
  128. package/dist/runner/spawn.js +230 -0
  129. package/dist/runner/spawn.js.map +1 -0
  130. package/dist/runner/verify.d.ts +24 -0
  131. package/dist/runner/verify.d.ts.map +1 -0
  132. package/dist/runner/verify.js +108 -0
  133. package/dist/runner/verify.js.map +1 -0
  134. package/dist/runner.d.ts +3 -0
  135. package/dist/runner.d.ts.map +1 -0
  136. package/dist/runner.js +15 -0
  137. package/dist/runner.js.map +1 -0
  138. package/dist/server/context.d.ts +41 -0
  139. package/dist/server/context.d.ts.map +1 -0
  140. package/dist/server/context.js +59 -0
  141. package/dist/server/context.js.map +1 -0
  142. package/dist/server/instructions.d.ts +13 -0
  143. package/dist/server/instructions.d.ts.map +1 -0
  144. package/dist/server/instructions.js +121 -0
  145. package/dist/server/instructions.js.map +1 -0
  146. package/dist/server/server.d.ts +20 -0
  147. package/dist/server/server.d.ts.map +1 -0
  148. package/dist/server/server.js +55 -0
  149. package/dist/server/server.js.map +1 -0
  150. package/dist/server/tools/cancel.d.ts +24 -0
  151. package/dist/server/tools/cancel.d.ts.map +1 -0
  152. package/dist/server/tools/cancel.js +59 -0
  153. package/dist/server/tools/cancel.js.map +1 -0
  154. package/dist/server/tools/capabilities.d.ts +36 -0
  155. package/dist/server/tools/capabilities.d.ts.map +1 -0
  156. package/dist/server/tools/capabilities.js +116 -0
  157. package/dist/server/tools/capabilities.js.map +1 -0
  158. package/dist/server/tools/ceiling.d.ts +31 -0
  159. package/dist/server/tools/ceiling.d.ts.map +1 -0
  160. package/dist/server/tools/ceiling.js +121 -0
  161. package/dist/server/tools/ceiling.js.map +1 -0
  162. package/dist/server/tools/index.d.ts +38 -0
  163. package/dist/server/tools/index.d.ts.map +1 -0
  164. package/dist/server/tools/index.js +115 -0
  165. package/dist/server/tools/index.js.map +1 -0
  166. package/dist/server/tools/listJobs.d.ts +30 -0
  167. package/dist/server/tools/listJobs.d.ts.map +1 -0
  168. package/dist/server/tools/listJobs.js +37 -0
  169. package/dist/server/tools/listJobs.js.map +1 -0
  170. package/dist/server/tools/logs.d.ts +31 -0
  171. package/dist/server/tools/logs.d.ts.map +1 -0
  172. package/dist/server/tools/logs.js +96 -0
  173. package/dist/server/tools/logs.js.map +1 -0
  174. package/dist/server/tools/result.d.ts +32 -0
  175. package/dist/server/tools/result.d.ts.map +1 -0
  176. package/dist/server/tools/result.js +95 -0
  177. package/dist/server/tools/result.js.map +1 -0
  178. package/dist/server/tools/send.d.ts +37 -0
  179. package/dist/server/tools/send.d.ts.map +1 -0
  180. package/dist/server/tools/send.js +71 -0
  181. package/dist/server/tools/send.js.map +1 -0
  182. package/dist/server/tools/sessions.d.ts +28 -0
  183. package/dist/server/tools/sessions.d.ts.map +1 -0
  184. package/dist/server/tools/sessions.js +54 -0
  185. package/dist/server/tools/sessions.js.map +1 -0
  186. package/dist/server/tools/start.d.ts +107 -0
  187. package/dist/server/tools/start.d.ts.map +1 -0
  188. package/dist/server/tools/start.js +482 -0
  189. package/dist/server/tools/start.js.map +1 -0
  190. package/dist/server/tools/wait.d.ts +29 -0
  191. package/dist/server/tools/wait.d.ts.map +1 -0
  192. package/dist/server/tools/wait.js +120 -0
  193. package/dist/server/tools/wait.js.map +1 -0
  194. package/dist/server.d.ts +3 -0
  195. package/dist/server.d.ts.map +1 -0
  196. package/dist/server.js +17 -0
  197. package/dist/server.js.map +1 -0
  198. package/dist/setup/install.d.ts +52 -0
  199. package/dist/setup/install.d.ts.map +1 -0
  200. package/dist/setup/install.js +108 -0
  201. package/dist/setup/install.js.map +1 -0
  202. package/dist/setup.d.ts +3 -0
  203. package/dist/setup.d.ts.map +1 -0
  204. package/dist/setup.js +24 -0
  205. package/dist/setup.js.map +1 -0
  206. package/dist/store/db.d.ts +49 -0
  207. package/dist/store/db.d.ts.map +1 -0
  208. package/dist/store/db.js +101 -0
  209. package/dist/store/db.js.map +1 -0
  210. package/dist/store/jobs.d.ts +45 -0
  211. package/dist/store/jobs.d.ts.map +1 -0
  212. package/dist/store/jobs.js +150 -0
  213. package/dist/store/jobs.js.map +1 -0
  214. package/dist/store/locks.d.ts +60 -0
  215. package/dist/store/locks.d.ts.map +1 -0
  216. package/dist/store/locks.js +172 -0
  217. package/dist/store/locks.js.map +1 -0
  218. package/dist/store/sessions.d.ts +37 -0
  219. package/dist/store/sessions.d.ts.map +1 -0
  220. package/dist/store/sessions.js +117 -0
  221. package/dist/store/sessions.js.map +1 -0
  222. package/dist/trace/digest.d.ts +77 -0
  223. package/dist/trace/digest.d.ts.map +1 -0
  224. package/dist/trace/digest.js +481 -0
  225. package/dist/trace/digest.js.map +1 -0
  226. package/package.json +68 -0
  227. package/skills/agy-ceiling/SKILL.md +72 -0
@@ -0,0 +1,60 @@
1
+ import type { LockRow, LockScope } from '../contract/types.js';
2
+ import type { Store } from './db.js';
3
+ /** Default per-project concurrency ceiling. */
4
+ export declare const DEFAULT_MAX_RUNNING = 3;
5
+ /** Everything one job needs, acquired together or not at all. */
6
+ export interface JobLockRequest {
7
+ jobId: string;
8
+ /** Canonical cwd. Only taken when `writeMode` is true — readers run in parallel. */
9
+ cwd: string;
10
+ writeMode: boolean;
11
+ /** Session id. Enforces one running turn per agy conversation. */
12
+ sessionId?: string | null;
13
+ /** Ceiling on `lifecycle IN ('starting','running')` jobs. */
14
+ maxRunning?: number;
15
+ }
16
+ export interface AcquiredLocks {
17
+ jobId: string;
18
+ rows: LockRow[];
19
+ /** Stale rows reclaimed on the way in, for the audit trail. */
20
+ reclaimed: LockRow[];
21
+ }
22
+ /**
23
+ * Is the job holding this lock still the process we recorded?
24
+ *
25
+ * True while the holder is still in its start grace window (pid not published
26
+ * yet, but young enough that it plausibly will be). False once the pid is
27
+ * gone, once the grace window has elapsed with still no pid, or when the pid
28
+ * is alive but `proc_start_time` differs, which means the number was reused by
29
+ * an unrelated process.
30
+ */
31
+ export declare function isHolderAlive(store: Store, holderJobId: string): boolean;
32
+ /**
33
+ * Acquire every lock the job needs inside one `BEGIN IMMEDIATE` transaction.
34
+ *
35
+ * The procedure is fixed and must not be split across
36
+ * transactions — a check in one and an insert in another is a TOCTOU race:
37
+ *
38
+ * 1. select the `(scope, key)` rows
39
+ * 2. for each existing row, look up the holder job and decide staleness with
40
+ * **both** pid liveness and `proc_start_time` equality. pid alone is wrong:
41
+ * a recycled pid reads as a live holder and the lock never frees.
42
+ * 3. count `starting|running` jobs against `maxRunning`
43
+ * 4. `INSERT INTO locks` — a primary-key collision *is* the lost race
44
+ * 5. `COMMIT`
45
+ *
46
+ * @throws {import('../contract/errors.js').LockConflictError} when a live holder
47
+ * or the ceiling blocks it. Losing must be an error, never quiet serialization,
48
+ * and the error carries the holder so the caller can wait on
49
+ * it or cancel it.
50
+ */
51
+ export declare function acquireJobLocks(store: Store, req: JobLockRequest): AcquiredLocks;
52
+ /** Release every lock held by a job. Idempotent; returns how many rows went away. */
53
+ export declare function releaseJobLocks(store: Store, jobId: string): number;
54
+ export declare function listLocks(store: Store, scope?: LockScope): LockRow[];
55
+ /**
56
+ * Drop rows whose holder is provably gone. Called from `reconcile`; also runs
57
+ * inline during {@link acquireJobLocks} so a dead holder never deadlocks anyone.
58
+ */
59
+ export declare function reapStaleLocks(store: Store): LockRow[];
60
+ //# sourceMappingURL=locks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"locks.d.ts","sourceRoot":"","sources":["../../src/store/locks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAA;AAK9D,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAA;AAGpC,+CAA+C;AAC/C,eAAO,MAAM,mBAAmB,IAAI,CAAA;AAEpC,iEAAiE;AACjE,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAA;IACb,oFAAoF;IACpF,GAAG,EAAE,MAAM,CAAA;IACX,SAAS,EAAE,OAAO,CAAA;IAClB,kEAAkE;IAClE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,6DAA6D;IAC7D,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,OAAO,EAAE,CAAA;IACf,+DAA+D;IAC/D,SAAS,EAAE,OAAO,EAAE,CAAA;CACrB;AAiCD;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAWxE;AAsBD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,cAAc,GAAG,aAAa,CAmEhF;AAED,qFAAqF;AACrF,wBAAgB,eAAe,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAGnE;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,CAAC,EAAE,SAAS,GAAG,OAAO,EAAE,CAKpE;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,KAAK,GAAG,OAAO,EAAE,CAYtD"}
@@ -0,0 +1,172 @@
1
+ import { LockConflictError } from '../contract/errors.js';
2
+ import { canonicalize } from '../contract/paths.js';
3
+ import { isPidAlive, isSameProcess } from '../runner/reap.js';
4
+ import { transaction, now } from './db.js';
5
+ import { countLiveJobs, tryGetJob, LIVE_LIFECYCLES } from './jobs.js';
6
+ /** Default per-project concurrency ceiling. */
7
+ export const DEFAULT_MAX_RUNNING = 3;
8
+ function toLockRow(raw) {
9
+ return raw;
10
+ }
11
+ function getLockRow(store, scope, key) {
12
+ const row = store.db
13
+ .prepare('SELECT * FROM locks WHERE scope = ? AND key = ?')
14
+ .get(scope, key);
15
+ return row ? toLockRow(row) : null;
16
+ }
17
+ function deleteLockRow(store, scope, key) {
18
+ store.db.prepare('DELETE FROM locks WHERE scope = ? AND key = ?').run(scope, key);
19
+ }
20
+ /**
21
+ * How long a holder with no pid yet is still treated as alive: `agy_start`
22
+ * acquires locks, creates the rows, writes job files and only then spawns the
23
+ * detached runner — tens to hundreds of ms during which `job.pid` is
24
+ * legitimately still null. Without this grace window every lock acquired in
25
+ * that window reads as "dead holder" and is stolen (finding 2).
26
+ */
27
+ const START_GRACE_MS = 30_000;
28
+ /**
29
+ * Is the job holding this lock still the process we recorded?
30
+ *
31
+ * True while the holder is still in its start grace window (pid not published
32
+ * yet, but young enough that it plausibly will be). False once the pid is
33
+ * gone, once the grace window has elapsed with still no pid, or when the pid
34
+ * is alive but `proc_start_time` differs, which means the number was reused by
35
+ * an unrelated process.
36
+ */
37
+ export function isHolderAlive(store, holderJobId) {
38
+ const job = tryGetJob(store, holderJobId);
39
+ if (!job)
40
+ return false;
41
+ if (job.pid === null) {
42
+ if (job.lifecycle === 'queued' || job.lifecycle === 'starting') {
43
+ return now() - job.created_at < START_GRACE_MS;
44
+ }
45
+ return false;
46
+ }
47
+ if (!isPidAlive(job.pid))
48
+ return false;
49
+ return isSameProcess(job.pid, job.proc_start_time);
50
+ }
51
+ /**
52
+ * Resolve one existing lock row: alive → the row itself (caller must treat as a
53
+ * conflict), stale → deleted and returned via `reclaimed`, absent → null both ways.
54
+ */
55
+ function resolveExisting(store, scope, key, reclaimed) {
56
+ const existing = getLockRow(store, scope, key);
57
+ if (!existing)
58
+ return null;
59
+ if (isHolderAlive(store, existing.holder_job_id)) {
60
+ return existing;
61
+ }
62
+ deleteLockRow(store, scope, key);
63
+ reclaimed.push(existing);
64
+ return null;
65
+ }
66
+ /**
67
+ * Acquire every lock the job needs inside one `BEGIN IMMEDIATE` transaction.
68
+ *
69
+ * The procedure is fixed and must not be split across
70
+ * transactions — a check in one and an insert in another is a TOCTOU race:
71
+ *
72
+ * 1. select the `(scope, key)` rows
73
+ * 2. for each existing row, look up the holder job and decide staleness with
74
+ * **both** pid liveness and `proc_start_time` equality. pid alone is wrong:
75
+ * a recycled pid reads as a live holder and the lock never frees.
76
+ * 3. count `starting|running` jobs against `maxRunning`
77
+ * 4. `INSERT INTO locks` — a primary-key collision *is* the lost race
78
+ * 5. `COMMIT`
79
+ *
80
+ * @throws {import('../contract/errors.js').LockConflictError} when a live holder
81
+ * or the ceiling blocks it. Losing must be an error, never quiet serialization,
82
+ * and the error carries the holder so the caller can wait on
83
+ * it or cancel it.
84
+ */
85
+ export function acquireJobLocks(store, req) {
86
+ return transaction(store.db, () => {
87
+ const reclaimed = [];
88
+ const wanted = [];
89
+ if (req.writeMode) {
90
+ wanted.push({ scope: 'cwd_write', key: canonicalize(req.cwd) });
91
+ }
92
+ if (req.sessionId) {
93
+ wanted.push({ scope: 'session', key: req.sessionId });
94
+ }
95
+ for (const w of wanted) {
96
+ const held = resolveExisting(store, w.scope, w.key, reclaimed);
97
+ if (held) {
98
+ const holder = tryGetJob(store, held.holder_job_id);
99
+ const detail = {
100
+ scope: w.scope,
101
+ key: w.key,
102
+ reason: 'held',
103
+ holder_job_id: held.holder_job_id,
104
+ holder_pid: holder?.pid ?? null,
105
+ holder_started_at: holder?.started_at ?? null,
106
+ acquired_at: held.acquired_at,
107
+ running_job_ids: [],
108
+ limit: null,
109
+ };
110
+ throw new LockConflictError(detail);
111
+ }
112
+ }
113
+ const maxRunning = req.maxRunning ?? DEFAULT_MAX_RUNNING;
114
+ const liveCount = countLiveJobs(store);
115
+ if (liveCount >= maxRunning) {
116
+ const runningJobIds = store.db
117
+ .prepare(`SELECT job_id FROM jobs WHERE lifecycle IN (${LIVE_LIFECYCLES.map(() => '?').join(', ')})`)
118
+ .all(...LIVE_LIFECYCLES)
119
+ .map((r) => r.job_id);
120
+ const detail = {
121
+ scope: 'running_limit',
122
+ key: '*',
123
+ reason: 'limit',
124
+ holder_job_id: null,
125
+ holder_pid: null,
126
+ holder_started_at: null,
127
+ acquired_at: null,
128
+ running_job_ids: runningJobIds,
129
+ limit: maxRunning,
130
+ };
131
+ throw new LockConflictError(detail);
132
+ }
133
+ const acquiredAt = now();
134
+ const rows = [];
135
+ for (const w of wanted) {
136
+ store.db
137
+ .prepare('INSERT INTO locks (scope, key, holder_job_id, acquired_at) VALUES (?, ?, ?, ?)')
138
+ .run(w.scope, w.key, req.jobId, acquiredAt);
139
+ rows.push({ scope: w.scope, key: w.key, holder_job_id: req.jobId, acquired_at: acquiredAt });
140
+ }
141
+ return { jobId: req.jobId, rows, reclaimed };
142
+ });
143
+ }
144
+ /** Release every lock held by a job. Idempotent; returns how many rows went away. */
145
+ export function releaseJobLocks(store, jobId) {
146
+ const result = store.db.prepare('DELETE FROM locks WHERE holder_job_id = ?').run(jobId);
147
+ return Number(result.changes);
148
+ }
149
+ export function listLocks(store, scope) {
150
+ const rows = scope
151
+ ? store.db.prepare('SELECT * FROM locks WHERE scope = ?').all(scope)
152
+ : store.db.prepare('SELECT * FROM locks').all();
153
+ return rows.map(toLockRow);
154
+ }
155
+ /**
156
+ * Drop rows whose holder is provably gone. Called from `reconcile`; also runs
157
+ * inline during {@link acquireJobLocks} so a dead holder never deadlocks anyone.
158
+ */
159
+ export function reapStaleLocks(store) {
160
+ return transaction(store.db, () => {
161
+ const all = listLocks(store);
162
+ const reclaimed = [];
163
+ for (const row of all) {
164
+ if (!isHolderAlive(store, row.holder_job_id)) {
165
+ deleteLockRow(store, row.scope, row.key);
166
+ reclaimed.push(row);
167
+ }
168
+ }
169
+ return reclaimed;
170
+ });
171
+ }
172
+ //# sourceMappingURL=locks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"locks.js","sourceRoot":"","sources":["../../src/store/locks.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAA2B,MAAM,uBAAuB,CAAA;AAClF,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAA;AACnD,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AAC7D,OAAO,EAAE,WAAW,EAAE,GAAG,EAAE,MAAM,SAAS,CAAA;AAE1C,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,WAAW,CAAA;AAErE,+CAA+C;AAC/C,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,CAAA;AA4BpC,SAAS,SAAS,CAAC,GAAe;IAChC,OAAO,GAAyB,CAAA;AAClC,CAAC;AAED,SAAS,UAAU,CAAC,KAAY,EAAE,KAAgB,EAAE,GAAW;IAC7D,MAAM,GAAG,GAAG,KAAK,CAAC,EAAE;SACjB,OAAO,CAAC,iDAAiD,CAAC;SAC1D,GAAG,CAAC,KAAK,EAAE,GAAG,CAA2B,CAAA;IAC5C,OAAO,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;AACpC,CAAC;AAED,SAAS,aAAa,CAAC,KAAY,EAAE,KAAgB,EAAE,GAAW;IAChE,KAAK,CAAC,EAAE,CAAC,OAAO,CAAC,+CAA+C,CAAC,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,CAAA;AACnF,CAAC;AAED;;;;;;GAMG;AACH,MAAM,cAAc,GAAG,MAAM,CAAA;AAE7B;;;;;;;;GAQG;AACH,MAAM,UAAU,aAAa,CAAC,KAAY,EAAE,WAAmB;IAC7D,MAAM,GAAG,GAAG,SAAS,CAAC,KAAK,EAAE,WAAW,CAAC,CAAA;IACzC,IAAI,CAAC,GAAG;QAAE,OAAO,KAAK,CAAA;IACtB,IAAI,GAAG,CAAC,GAAG,KAAK,IAAI,EAAE,CAAC;QACrB,IAAI,GAAG,CAAC,SAAS,KAAK,QAAQ,IAAI,GAAG,CAAC,SAAS,KAAK,UAAU,EAAE,CAAC;YAC/D,OAAO,GAAG,EAAE,GAAG,GAAG,CAAC,UAAU,GAAG,cAAc,CAAA;QAChD,CAAC;QACD,OAAO,KAAK,CAAA;IACd,CAAC;IACD,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC;QAAE,OAAO,KAAK,CAAA;IACtC,OAAO,aAAa,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,eAAe,CAAC,CAAA;AACpD,CAAC;AAED;;;GAGG;AACH,SAAS,eAAe,CACtB,KAAY,EACZ,KAAgB,EAChB,GAAW,EACX,SAAoB;IAEpB,MAAM,QAAQ,GAAG,UAAU,CAAC,KAAK,EAAE,KAAK,EAAE,GAAG,CAAC,CAAA;IAC9C,IAAI,CAAC,QAAQ;QAAE,OAAO,IAAI,CAAA;IAC1B,IAAI,aAAa,CAAC,KAAK,EAAE,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC;QACjD,OAAO,QAAQ,CAAA;IACjB,CAAC;IACD,aAAa,CAAC,KAAK,EAAE,KAAK,EAAE,GAAG,CAAC,CAAA;IAChC,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;IACxB,OAAO,IAAI,CAAA;AACb,CAAC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,eAAe,CAAC,KAAY,EAAE,GAAmB;IAC/D,OAAO,WAAW,CAAC,KAAK,CAAC,EAAE,EAAE,GAAG,EAAE;QAChC,MAAM,SAAS,GAAc,EAAE,CAAA;QAC/B,MAAM,MAAM,GAA6C,EAAE,CAAA;QAE3D,IAAI,GAAG,CAAC,SAAS,EAAE,CAAC;YAClB,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,GAAG,EAAE,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;QACjE,CAAC;QACD,IAAI,GAAG,CAAC,SAAS,EAAE,CAAC;YAClB,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,EAAE,GAAG,CAAC,SAAS,EAAE,CAAC,CAAA;QACvD,CAAC;QAED,KAAK,MAAM,CAAC,IAAI,MAAM,EAAE,CAAC;YACvB,MAAM,IAAI,GAAG,eAAe,CAAC,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,GAAG,EAAE,SAAS,CAAC,CAAA;YAC9D,IAAI,IAAI,EAAE,CAAC;gBACT,MAAM,MAAM,GAAG,SAAS,CAAC,KAAK,EAAE,IAAI,CAAC,aAAa,CAAC,CAAA;gBACnD,MAAM,MAAM,GAAuB;oBACjC,KAAK,EAAE,CAAC,CAAC,KAAK;oBACd,GAAG,EAAE,CAAC,CAAC,GAAG;oBACV,MAAM,EAAE,MAAM;oBACd,aAAa,EAAE,IAAI,CAAC,aAAa;oBACjC,UAAU,EAAE,MAAM,EAAE,GAAG,IAAI,IAAI;oBAC/B,iBAAiB,EAAE,MAAM,EAAE,UAAU,IAAI,IAAI;oBAC7C,WAAW,EAAE,IAAI,CAAC,WAAW;oBAC7B,eAAe,EAAE,EAAE;oBACnB,KAAK,EAAE,IAAI;iBACZ,CAAA;gBACD,MAAM,IAAI,iBAAiB,CAAC,MAAM,CAAC,CAAA;YACrC,CAAC;QACH,CAAC;QAED,MAAM,UAAU,GAAG,GAAG,CAAC,UAAU,IAAI,mBAAmB,CAAA;QACxD,MAAM,SAAS,GAAG,aAAa,CAAC,KAAK,CAAC,CAAA;QACtC,IAAI,SAAS,IAAI,UAAU,EAAE,CAAC;YAC5B,MAAM,aAAa,GAAG,KAAK,CAAC,EAAE;iBAC3B,OAAO,CACN,+CAA+C,eAAe,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAC5F;iBACA,GAAG,CAAC,GAAG,eAAe,CAAC;iBACvB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAE,CAAwB,CAAC,MAAM,CAAC,CAAA;YAC/C,MAAM,MAAM,GAAuB;gBACjC,KAAK,EAAE,eAAe;gBACtB,GAAG,EAAE,GAAG;gBACR,MAAM,EAAE,OAAO;gBACf,aAAa,EAAE,IAAI;gBACnB,UAAU,EAAE,IAAI;gBAChB,iBAAiB,EAAE,IAAI;gBACvB,WAAW,EAAE,IAAI;gBACjB,eAAe,EAAE,aAAa;gBAC9B,KAAK,EAAE,UAAU;aAClB,CAAA;YACD,MAAM,IAAI,iBAAiB,CAAC,MAAM,CAAC,CAAA;QACrC,CAAC;QAED,MAAM,UAAU,GAAG,GAAG,EAAE,CAAA;QACxB,MAAM,IAAI,GAAc,EAAE,CAAA;QAC1B,KAAK,MAAM,CAAC,IAAI,MAAM,EAAE,CAAC;YACvB,KAAK,CAAC,EAAE;iBACL,OAAO,CACN,gFAAgF,CACjF;iBACA,GAAG,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,GAAG,EAAE,GAAG,CAAC,KAAK,EAAE,UAAU,CAAC,CAAA;YAC7C,IAAI,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,EAAE,aAAa,EAAE,GAAG,CAAC,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,CAAC,CAAA;QAC9F,CAAC;QAED,OAAO,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,CAAA;IAC9C,CAAC,CAAC,CAAA;AACJ,CAAC;AAED,qFAAqF;AACrF,MAAM,UAAU,eAAe,CAAC,KAAY,EAAE,KAAa;IACzD,MAAM,MAAM,GAAG,KAAK,CAAC,EAAE,CAAC,OAAO,CAAC,2CAA2C,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;IACvF,OAAO,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;AAC/B,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,KAAY,EAAE,KAAiB;IACvD,MAAM,IAAI,GAAG,KAAK;QAChB,CAAC,CAAE,KAAK,CAAC,EAAE,CAAC,OAAO,CAAC,qCAAqC,CAAC,CAAC,GAAG,CAAC,KAAK,CAA6B;QACjG,CAAC,CAAE,KAAK,CAAC,EAAE,CAAC,OAAO,CAAC,qBAAqB,CAAC,CAAC,GAAG,EAA8B,CAAA;IAC9E,OAAO,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,CAAA;AAC5B,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,KAAY;IACzC,OAAO,WAAW,CAAC,KAAK,CAAC,EAAE,EAAE,GAAG,EAAE;QAChC,MAAM,GAAG,GAAG,SAAS,CAAC,KAAK,CAAC,CAAA;QAC5B,MAAM,SAAS,GAAc,EAAE,CAAA;QAC/B,KAAK,MAAM,GAAG,IAAI,GAAG,EAAE,CAAC;YACtB,IAAI,CAAC,aAAa,CAAC,KAAK,EAAE,GAAG,CAAC,aAAa,CAAC,EAAE,CAAC;gBAC7C,aAAa,CAAC,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,GAAG,CAAC,CAAA;gBACxC,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;YACrB,CAAC;QACH,CAAC;QACD,OAAO,SAAS,CAAA;IAClB,CAAC,CAAC,CAAA;AACJ,CAAC"}
@@ -0,0 +1,37 @@
1
+ import type { Profile, SessionRow, SessionState } from '../contract/types.js';
2
+ import type { Store } from './db.js';
3
+ export interface NewSession {
4
+ /** Omit to mint one. */
5
+ sessionId?: string;
6
+ cwd: string;
7
+ model?: string | null;
8
+ effort?: string | null;
9
+ profile?: Profile | null;
10
+ }
11
+ export interface SessionFilter {
12
+ state?: SessionState;
13
+ cwd?: string;
14
+ limit?: number;
15
+ }
16
+ export declare function createSession(store: Store, input: NewSession): SessionRow;
17
+ export declare function getSession(store: Store, sessionId: string): SessionRow | null;
18
+ /** Reverse lookup used by the gate after it binds a conversation to a job. */
19
+ export declare function findSessionByConversation(store: Store, conversationId: string): SessionRow | null;
20
+ /**
21
+ * Attach the conversation id captured from the first `init` event.
22
+ *
23
+ * Bound lazily: at `agy_start` time we do not have one yet (agy mints it), so the
24
+ * column stays NULL until the events file has its first line.
25
+ */
26
+ export declare function bindConversationId(store: Store, sessionId: string, conversationId: string): SessionRow;
27
+ /** Update `last_used_at`, `last_job_id`, and any supplied columns. */
28
+ export declare function touchSession(store: Store, sessionId: string, patch?: Partial<Pick<SessionRow, 'last_job_id' | 'model' | 'effort' | 'profile' | 'turn_count'>>): SessionRow;
29
+ /** Advance `turn_count` by the number of `result` events a finished job produced. */
30
+ export declare function addTurns(store: Store, sessionId: string, n: number): SessionRow;
31
+ export declare function listSessions(store: Store, filter?: SessionFilter): SessionRow[];
32
+ /**
33
+ * Mark a session closed. The conversation itself is not destroyed — agy resume is
34
+ * lossless, so a closed session is a bookkeeping state, not a deletion.
35
+ */
36
+ export declare function closeSession(store: Store, sessionId: string): SessionRow;
37
+ //# sourceMappingURL=sessions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sessions.d.ts","sourceRoot":"","sources":["../../src/store/sessions.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAA;AAE7E,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAA;AAKpC,MAAM,WAAW,UAAU;IACzB,wBAAwB;IACxB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,OAAO,CAAC,EAAE,OAAO,GAAG,IAAI,CAAA;CACzB;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,CAAC,EAAE,YAAY,CAAA;IACpB,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AA4BD,wBAAgB,aAAa,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,UAAU,GAAG,UAAU,CAuBzE;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAK7E;AAED,8EAA8E;AAC9E,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,KAAK,EAAE,cAAc,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAKjG;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,UAAU,CAQtG;AAED,sEAAsE;AACtE,wBAAgB,YAAY,CAC1B,KAAK,EAAE,KAAK,EACZ,SAAS,EAAE,MAAM,EACjB,KAAK,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,UAAU,EAAE,aAAa,GAAG,OAAO,GAAG,QAAQ,GAAG,SAAS,GAAG,YAAY,CAAC,CAAC,GAC/F,UAAU,CAaZ;AAED,qFAAqF;AACrF,wBAAgB,QAAQ,CAAC,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,UAAU,CAU/E;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,EAAE,aAAa,GAAG,UAAU,EAAE,CAiB/E;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,GAAG,UAAU,CAQxE"}
@@ -0,0 +1,117 @@
1
+ import { newSessionId } from '../contract/paths.js';
2
+ import { now } from './db.js';
3
+ function toSessionRow(raw) {
4
+ return raw;
5
+ }
6
+ function requireSession(store, sessionId) {
7
+ const row = getSession(store, sessionId);
8
+ if (!row) {
9
+ throw new Error(`no session "${sessionId}" in project ${store.root}`);
10
+ }
11
+ return row;
12
+ }
13
+ export function createSession(store, input) {
14
+ const sessionId = input.sessionId || newSessionId();
15
+ const ts = now();
16
+ store.db
17
+ .prepare(`INSERT INTO sessions (
18
+ session_id, conversation_id, cwd, model, effort, profile,
19
+ turn_count, last_job_id, created_at, last_used_at, state
20
+ ) VALUES (
21
+ @session_id, NULL, @cwd, @model, @effort, @profile,
22
+ 0, NULL, @created_at, @last_used_at, 'active'
23
+ )`)
24
+ .run({
25
+ session_id: sessionId,
26
+ cwd: input.cwd,
27
+ model: input.model ?? null,
28
+ effort: input.effort ?? null,
29
+ profile: input.profile ?? null,
30
+ created_at: ts,
31
+ last_used_at: ts,
32
+ });
33
+ return requireSession(store, sessionId);
34
+ }
35
+ export function getSession(store, sessionId) {
36
+ const row = store.db.prepare('SELECT * FROM sessions WHERE session_id = ?').get(sessionId);
37
+ return row ? toSessionRow(row) : null;
38
+ }
39
+ /** Reverse lookup used by the gate after it binds a conversation to a job. */
40
+ export function findSessionByConversation(store, conversationId) {
41
+ const row = store.db
42
+ .prepare('SELECT * FROM sessions WHERE conversation_id = ?')
43
+ .get(conversationId);
44
+ return row ? toSessionRow(row) : null;
45
+ }
46
+ /**
47
+ * Attach the conversation id captured from the first `init` event.
48
+ *
49
+ * Bound lazily: at `agy_start` time we do not have one yet (agy mints it), so the
50
+ * column stays NULL until the events file has its first line.
51
+ */
52
+ export function bindConversationId(store, sessionId, conversationId) {
53
+ const result = store.db
54
+ .prepare('UPDATE sessions SET conversation_id = ? WHERE session_id = ?')
55
+ .run(conversationId, sessionId);
56
+ if (result.changes === 0) {
57
+ throw new Error(`no session "${sessionId}" in project ${store.root}`);
58
+ }
59
+ return requireSession(store, sessionId);
60
+ }
61
+ /** Update `last_used_at`, `last_job_id`, and any supplied columns. */
62
+ export function touchSession(store, sessionId, patch) {
63
+ const entries = Object.entries(patch ?? {});
64
+ const setClauses = ['last_used_at = @last_used_at', ...entries.map(([k]) => `${k} = @${k}`)];
65
+ const params = { session_id: sessionId, last_used_at: now() };
66
+ for (const [k, v] of entries)
67
+ params[k] = v;
68
+ const result = store.db
69
+ .prepare(`UPDATE sessions SET ${setClauses.join(', ')} WHERE session_id = @session_id`)
70
+ .run(params);
71
+ if (result.changes === 0) {
72
+ throw new Error(`no session "${sessionId}" in project ${store.root}`);
73
+ }
74
+ return requireSession(store, sessionId);
75
+ }
76
+ /** Advance `turn_count` by the number of `result` events a finished job produced. */
77
+ export function addTurns(store, sessionId, n) {
78
+ const result = store.db
79
+ .prepare('UPDATE sessions SET turn_count = turn_count + @n, last_used_at = @last_used_at WHERE session_id = @session_id')
80
+ .run({ session_id: sessionId, n, last_used_at: now() });
81
+ if (result.changes === 0) {
82
+ throw new Error(`no session "${sessionId}" in project ${store.root}`);
83
+ }
84
+ return requireSession(store, sessionId);
85
+ }
86
+ export function listSessions(store, filter) {
87
+ const clauses = [];
88
+ const params = {};
89
+ if (filter?.state) {
90
+ clauses.push('state = @state');
91
+ params.state = filter.state;
92
+ }
93
+ if (filter?.cwd) {
94
+ clauses.push('cwd = @cwd');
95
+ params.cwd = filter.cwd;
96
+ }
97
+ const where = clauses.length ? `WHERE ${clauses.join(' AND ')}` : '';
98
+ const limit = filter?.limit && filter.limit > 0 ? Math.floor(filter.limit) : 100;
99
+ const rows = store.db
100
+ .prepare(`SELECT * FROM sessions ${where} ORDER BY last_used_at DESC LIMIT ${limit}`)
101
+ .all(params);
102
+ return rows.map(toSessionRow);
103
+ }
104
+ /**
105
+ * Mark a session closed. The conversation itself is not destroyed — agy resume is
106
+ * lossless, so a closed session is a bookkeeping state, not a deletion.
107
+ */
108
+ export function closeSession(store, sessionId) {
109
+ const result = store.db
110
+ .prepare("UPDATE sessions SET state = 'closed', last_used_at = @last_used_at WHERE session_id = @session_id")
111
+ .run({ session_id: sessionId, last_used_at: now() });
112
+ if (result.changes === 0) {
113
+ throw new Error(`no session "${sessionId}" in project ${store.root}`);
114
+ }
115
+ return requireSession(store, sessionId);
116
+ }
117
+ //# sourceMappingURL=sessions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sessions.js","sourceRoot":"","sources":["../../src/store/sessions.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Execution trace digest.
3
+ *
4
+ * Distills raw job logs (gate-log.jsonl, events.ndjson, broker-result.json)
5
+ * into a compact, human-readable summary of commands, files touched,
6
+ * gate denials, and tool errors. Pure functions, no I/O except the explicit
7
+ * loader `loadJobDigest`.
8
+ */
9
+ export interface DigestCommand {
10
+ command: string;
11
+ count: number;
12
+ denied: number;
13
+ }
14
+ export interface DigestFiles {
15
+ read: string[];
16
+ edited: string[];
17
+ read_count: number;
18
+ edited_count: number;
19
+ }
20
+ export interface DigestDenial {
21
+ tool: string;
22
+ command: string | null;
23
+ stage: string;
24
+ required_rule: string | null;
25
+ count: number;
26
+ }
27
+ export interface DigestToolError {
28
+ step_idx: number | null;
29
+ tool: string;
30
+ message: string;
31
+ }
32
+ export interface JobDigest {
33
+ job_id: string;
34
+ outcome: string | null;
35
+ exit_code: number | null;
36
+ duration_ms: number | null;
37
+ turns: number | null;
38
+ commands: DigestCommand[];
39
+ files: DigestFiles;
40
+ denials: DigestDenial[];
41
+ tool_errors: DigestToolError[];
42
+ trace: 'full' | 'no_gate_log' | 'empty';
43
+ }
44
+ export interface ProjectDigest {
45
+ jobs: number;
46
+ outcomes: Record<string, number>;
47
+ denied_rules: Array<{
48
+ required_rule: string;
49
+ count: number;
50
+ last_job_id: string;
51
+ last_ts: number;
52
+ }>;
53
+ top_commands: DigestCommand[];
54
+ }
55
+ /**
56
+ * Pure digest computation over in-memory string captures.
57
+ */
58
+ export declare function digestJob(input: {
59
+ job_id: string;
60
+ workspace: string | null;
61
+ gateLog: string | null;
62
+ events: string | null;
63
+ brokerResult: unknown | null;
64
+ }): JobDigest;
65
+ /**
66
+ * Cross-job roll-up across multiple job digests and their gate logs.
67
+ */
68
+ export declare function digestProject(digests: JobDigest[], gateLogsById: Record<string, string | null>): ProjectDigest;
69
+ /**
70
+ * Load a job's files from its job directory and compute its digest.
71
+ */
72
+ export declare function loadJobDigest(jobDir: string, workspace: string | null): JobDigest;
73
+ /**
74
+ * Render a JobDigest into human-readable text layout under ~40 lines.
75
+ */
76
+ export declare function renderDigest(d: JobDigest): string;
77
+ //# sourceMappingURL=digest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"digest.d.ts","sourceRoot":"","sources":["../../src/trace/digest.ts"],"names":[],"mappings":"AAMA;;;;;;;GAOG;AAEH,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,EAAE,CAAA;IACd,MAAM,EAAE,MAAM,EAAE,CAAA;IAChB,UAAU,EAAE,MAAM,CAAA;IAClB,YAAY,EAAE,MAAM,CAAA;CACrB;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,KAAK,EAAE,MAAM,CAAA;IACb,aAAa,EAAE,MAAM,GAAG,IAAI,CAAA;IAC5B,KAAK,EAAE,MAAM,CAAA;CACd;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,QAAQ,EAAE,aAAa,EAAE,CAAA;IACzB,KAAK,EAAE,WAAW,CAAA;IAClB,OAAO,EAAE,YAAY,EAAE,CAAA;IACvB,WAAW,EAAE,eAAe,EAAE,CAAA;IAC9B,KAAK,EAAE,MAAM,GAAG,aAAa,GAAG,OAAO,CAAA;CACxC;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAChC,YAAY,EAAE,KAAK,CAAC;QAAE,aAAa,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IACnG,YAAY,EAAE,aAAa,EAAE,CAAA;CAC9B;AA8CD;;GAEG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE;IAC/B,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,YAAY,EAAE,OAAO,GAAG,IAAI,CAAA;CAC7B,GAAG,SAAS,CA+OZ;AAED;;GAEG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,SAAS,EAAE,EACpB,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,GAC1C,aAAa,CA2Ef;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAuDjF;AA2BD;;GAEG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAAE,SAAS,GAAG,MAAM,CAwDjD"}