@getrift/rift 0.1.0-beta.2 → 0.1.0-beta.21

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 (390) hide show
  1. package/README.md +35 -9
  2. package/dist/src/auth/keychain.d.ts +9 -0
  3. package/dist/src/auth/keychain.d.ts.map +1 -1
  4. package/dist/src/auth/keychain.js +37 -0
  5. package/dist/src/auth/keychain.js.map +1 -1
  6. package/dist/src/capture/auto-capture.d.ts +7 -0
  7. package/dist/src/capture/auto-capture.d.ts.map +1 -1
  8. package/dist/src/capture/auto-capture.js +82 -15
  9. package/dist/src/capture/auto-capture.js.map +1 -1
  10. package/dist/src/capture/auto-repair.d.ts +110 -0
  11. package/dist/src/capture/auto-repair.d.ts.map +1 -0
  12. package/dist/src/capture/auto-repair.js +269 -0
  13. package/dist/src/capture/auto-repair.js.map +1 -0
  14. package/dist/src/capture/codex-cli-triage-provider.d.ts.map +1 -1
  15. package/dist/src/capture/codex-cli-triage-provider.js +4 -3
  16. package/dist/src/capture/codex-cli-triage-provider.js.map +1 -1
  17. package/dist/src/capture/observability.d.ts +42 -0
  18. package/dist/src/capture/observability.d.ts.map +1 -1
  19. package/dist/src/capture/observability.js +45 -4
  20. package/dist/src/capture/observability.js.map +1 -1
  21. package/dist/src/capture/recover-quarantine.d.ts +260 -0
  22. package/dist/src/capture/recover-quarantine.d.ts.map +1 -0
  23. package/dist/src/capture/recover-quarantine.js +522 -0
  24. package/dist/src/capture/recover-quarantine.js.map +1 -0
  25. package/dist/src/cli/commands/backfill.d.ts.map +1 -1
  26. package/dist/src/cli/commands/backfill.js +5 -2
  27. package/dist/src/cli/commands/backfill.js.map +1 -1
  28. package/dist/src/cli/commands/capture-recover.d.ts +40 -0
  29. package/dist/src/cli/commands/capture-recover.d.ts.map +1 -0
  30. package/dist/src/cli/commands/capture-recover.js +184 -0
  31. package/dist/src/cli/commands/capture-recover.js.map +1 -0
  32. package/dist/src/cli/commands/capture.d.ts.map +1 -1
  33. package/dist/src/cli/commands/capture.js +96 -5
  34. package/dist/src/cli/commands/capture.js.map +1 -1
  35. package/dist/src/cli/commands/doctor.d.ts +6 -0
  36. package/dist/src/cli/commands/doctor.d.ts.map +1 -0
  37. package/dist/src/cli/commands/doctor.js +242 -0
  38. package/dist/src/cli/commands/doctor.js.map +1 -0
  39. package/dist/src/cli/commands/feedback.d.ts +12 -0
  40. package/dist/src/cli/commands/feedback.d.ts.map +1 -1
  41. package/dist/src/cli/commands/feedback.js +93 -4
  42. package/dist/src/cli/commands/feedback.js.map +1 -1
  43. package/dist/src/cli/commands/mcp-install.js +5 -2
  44. package/dist/src/cli/commands/mcp-install.js.map +1 -1
  45. package/dist/src/cli/commands/menubar.d.ts +80 -0
  46. package/dist/src/cli/commands/menubar.d.ts.map +1 -0
  47. package/dist/src/cli/commands/menubar.js +388 -0
  48. package/dist/src/cli/commands/menubar.js.map +1 -0
  49. package/dist/src/cli/commands/onboard.d.ts +143 -5
  50. package/dist/src/cli/commands/onboard.d.ts.map +1 -1
  51. package/dist/src/cli/commands/onboard.js +844 -188
  52. package/dist/src/cli/commands/onboard.js.map +1 -1
  53. package/dist/src/cli/commands/rebuild.d.ts.map +1 -1
  54. package/dist/src/cli/commands/rebuild.js +6 -3
  55. package/dist/src/cli/commands/rebuild.js.map +1 -1
  56. package/dist/src/cli/commands/reconcile.d.ts.map +1 -1
  57. package/dist/src/cli/commands/reconcile.js +12 -0
  58. package/dist/src/cli/commands/reconcile.js.map +1 -1
  59. package/dist/src/cli/commands/review.d.ts.map +1 -1
  60. package/dist/src/cli/commands/review.js +22 -7
  61. package/dist/src/cli/commands/review.js.map +1 -1
  62. package/dist/src/cli/commands/search.d.ts +2 -0
  63. package/dist/src/cli/commands/search.d.ts.map +1 -1
  64. package/dist/src/cli/commands/search.js +34 -4
  65. package/dist/src/cli/commands/search.js.map +1 -1
  66. package/dist/src/cli/commands/status.d.ts +9 -7
  67. package/dist/src/cli/commands/status.d.ts.map +1 -1
  68. package/dist/src/cli/commands/status.js +117 -12
  69. package/dist/src/cli/commands/status.js.map +1 -1
  70. package/dist/src/cli/commands/token-issue.d.ts.map +1 -1
  71. package/dist/src/cli/commands/token-issue.js +9 -1
  72. package/dist/src/cli/commands/token-issue.js.map +1 -1
  73. package/dist/src/cli/commands/triage.d.ts.map +1 -1
  74. package/dist/src/cli/commands/triage.js +7 -5
  75. package/dist/src/cli/commands/triage.js.map +1 -1
  76. package/dist/src/cli/commands/update.d.ts +80 -0
  77. package/dist/src/cli/commands/update.d.ts.map +1 -0
  78. package/dist/src/cli/commands/update.js +390 -0
  79. package/dist/src/cli/commands/update.js.map +1 -0
  80. package/dist/src/cli/default-config-path.d.ts +15 -0
  81. package/dist/src/cli/default-config-path.d.ts.map +1 -0
  82. package/dist/src/cli/default-config-path.js +27 -0
  83. package/dist/src/cli/default-config-path.js.map +1 -0
  84. package/dist/src/cli/feedback/feedback-config.d.ts +46 -0
  85. package/dist/src/cli/feedback/feedback-config.d.ts.map +1 -1
  86. package/dist/src/cli/feedback/feedback-config.js +130 -4
  87. package/dist/src/cli/feedback/feedback-config.js.map +1 -1
  88. package/dist/src/cli/feedback/feedback-history.d.ts +7 -0
  89. package/dist/src/cli/feedback/feedback-history.d.ts.map +1 -1
  90. package/dist/src/cli/feedback/feedback-history.js +39 -9
  91. package/dist/src/cli/feedback/feedback-history.js.map +1 -1
  92. package/dist/src/cli/feedback/feedback-payload.d.ts +22 -1
  93. package/dist/src/cli/feedback/feedback-payload.d.ts.map +1 -1
  94. package/dist/src/cli/feedback/feedback-payload.js.map +1 -1
  95. package/dist/src/cli/feedback/feedback-relay.d.ts +2 -2
  96. package/dist/src/cli/feedback/feedback-relay.d.ts.map +1 -1
  97. package/dist/src/cli/feedback/feedback-relay.js.map +1 -1
  98. package/dist/src/cli/feedback/invite.d.ts +17 -0
  99. package/dist/src/cli/feedback/invite.d.ts.map +1 -0
  100. package/dist/src/cli/feedback/invite.js +67 -0
  101. package/dist/src/cli/feedback/invite.js.map +1 -0
  102. package/dist/src/cli/feedback/relay-secret-store.d.ts +32 -0
  103. package/dist/src/cli/feedback/relay-secret-store.d.ts.map +1 -0
  104. package/dist/src/cli/feedback/relay-secret-store.js +137 -0
  105. package/dist/src/cli/feedback/relay-secret-store.js.map +1 -0
  106. package/dist/src/cli/http-client.d.ts +93 -1
  107. package/dist/src/cli/http-client.d.ts.map +1 -1
  108. package/dist/src/cli/http-client.js +254 -6
  109. package/dist/src/cli/http-client.js.map +1 -1
  110. package/dist/src/cli/index.d.ts.map +1 -1
  111. package/dist/src/cli/index.js +29 -6
  112. package/dist/src/cli/index.js.map +1 -1
  113. package/dist/src/cli/postinstall-menubar.d.ts +22 -0
  114. package/dist/src/cli/postinstall-menubar.d.ts.map +1 -0
  115. package/dist/src/cli/postinstall-menubar.js +53 -0
  116. package/dist/src/cli/postinstall-menubar.js.map +1 -0
  117. package/dist/src/cli/status/friend-header.d.ts +16 -1
  118. package/dist/src/cli/status/friend-header.d.ts.map +1 -1
  119. package/dist/src/cli/status/friend-header.js +354 -26
  120. package/dist/src/cli/status/friend-header.js.map +1 -1
  121. package/dist/src/cli/status/local-signals.d.ts +18 -0
  122. package/dist/src/cli/status/local-signals.d.ts.map +1 -1
  123. package/dist/src/cli/status/local-signals.js +29 -0
  124. package/dist/src/cli/status/local-signals.js.map +1 -1
  125. package/dist/src/cli/ui.d.ts +47 -0
  126. package/dist/src/cli/ui.d.ts.map +1 -0
  127. package/dist/src/cli/ui.js +166 -0
  128. package/dist/src/cli/ui.js.map +1 -0
  129. package/dist/src/config/schema.d.ts +79 -0
  130. package/dist/src/config/schema.d.ts.map +1 -1
  131. package/dist/src/config/schema.js +44 -0
  132. package/dist/src/config/schema.js.map +1 -1
  133. package/dist/src/diagnostics/codex-preflight.d.ts +33 -0
  134. package/dist/src/diagnostics/codex-preflight.d.ts.map +1 -0
  135. package/dist/src/diagnostics/codex-preflight.js +75 -0
  136. package/dist/src/diagnostics/codex-preflight.js.map +1 -0
  137. package/dist/src/diagnostics/doctor.d.ts +114 -0
  138. package/dist/src/diagnostics/doctor.d.ts.map +1 -0
  139. package/dist/src/diagnostics/doctor.js +352 -0
  140. package/dist/src/diagnostics/doctor.js.map +1 -0
  141. package/dist/src/diagnostics/notify.d.ts +90 -0
  142. package/dist/src/diagnostics/notify.d.ts.map +1 -0
  143. package/dist/src/diagnostics/notify.js +177 -0
  144. package/dist/src/diagnostics/notify.js.map +1 -0
  145. package/dist/src/diagnostics/repair-prompt.d.ts +49 -0
  146. package/dist/src/diagnostics/repair-prompt.d.ts.map +1 -0
  147. package/dist/src/diagnostics/repair-prompt.js +223 -0
  148. package/dist/src/diagnostics/repair-prompt.js.map +1 -0
  149. package/dist/src/ingestion/inbox-core/conversation-fingerprint.d.ts +2 -0
  150. package/dist/src/ingestion/inbox-core/conversation-fingerprint.d.ts.map +1 -0
  151. package/dist/src/ingestion/inbox-core/conversation-fingerprint.js +27 -0
  152. package/dist/src/ingestion/inbox-core/conversation-fingerprint.js.map +1 -0
  153. package/dist/src/ingestion/inbox-core/conversation-key.d.ts +2 -0
  154. package/dist/src/ingestion/inbox-core/conversation-key.d.ts.map +1 -0
  155. package/dist/src/ingestion/inbox-core/conversation-key.js +31 -0
  156. package/dist/src/ingestion/inbox-core/conversation-key.js.map +1 -0
  157. package/dist/src/ingestion/inbox-core/extensions.d.ts +3 -0
  158. package/dist/src/ingestion/inbox-core/extensions.d.ts.map +1 -0
  159. package/dist/src/ingestion/inbox-core/extensions.js +16 -0
  160. package/dist/src/ingestion/inbox-core/extensions.js.map +1 -0
  161. package/dist/src/ingestion/inbox-core/idempotency.d.ts +2 -0
  162. package/dist/src/ingestion/inbox-core/idempotency.d.ts.map +1 -0
  163. package/dist/src/ingestion/inbox-core/idempotency.js +22 -0
  164. package/dist/src/ingestion/inbox-core/idempotency.js.map +1 -0
  165. package/dist/src/ingestion/inbox-core/index.d.ts +20 -0
  166. package/dist/src/ingestion/inbox-core/index.d.ts.map +1 -0
  167. package/dist/src/ingestion/inbox-core/index.js +20 -0
  168. package/dist/src/ingestion/inbox-core/index.js.map +1 -0
  169. package/dist/src/ingestion/inbox-core/source-detection.d.ts +2 -0
  170. package/dist/src/ingestion/inbox-core/source-detection.d.ts.map +1 -0
  171. package/dist/src/ingestion/inbox-core/source-detection.js +23 -0
  172. package/dist/src/ingestion/inbox-core/source-detection.js.map +1 -0
  173. package/dist/src/ingestion/inbox-core/source-sniffer.d.ts +11 -0
  174. package/dist/src/ingestion/inbox-core/source-sniffer.d.ts.map +1 -0
  175. package/dist/src/ingestion/inbox-core/source-sniffer.js +69 -0
  176. package/dist/src/ingestion/inbox-core/source-sniffer.js.map +1 -0
  177. package/dist/src/ingestion/inbox-core/zip-sniffer.d.ts +70 -0
  178. package/dist/src/ingestion/inbox-core/zip-sniffer.d.ts.map +1 -0
  179. package/dist/src/ingestion/inbox-core/zip-sniffer.js +161 -0
  180. package/dist/src/ingestion/inbox-core/zip-sniffer.js.map +1 -0
  181. package/dist/src/ingestion/inbox-watcher.d.ts.map +1 -1
  182. package/dist/src/ingestion/inbox-watcher.js +34 -50
  183. package/dist/src/ingestion/inbox-watcher.js.map +1 -1
  184. package/dist/src/ingestion/indexer.d.ts +7 -0
  185. package/dist/src/ingestion/indexer.d.ts.map +1 -1
  186. package/dist/src/ingestion/indexer.js +36 -2
  187. package/dist/src/ingestion/indexer.js.map +1 -1
  188. package/dist/src/ingestion/metadata-extraction.d.ts +8 -5
  189. package/dist/src/ingestion/metadata-extraction.d.ts.map +1 -1
  190. package/dist/src/ingestion/metadata-extraction.js +24 -5
  191. package/dist/src/ingestion/metadata-extraction.js.map +1 -1
  192. package/dist/src/ingestion/skip-quarantine.d.ts +10 -0
  193. package/dist/src/ingestion/skip-quarantine.d.ts.map +1 -0
  194. package/dist/src/ingestion/skip-quarantine.js +35 -0
  195. package/dist/src/ingestion/skip-quarantine.js.map +1 -0
  196. package/dist/src/jobs/handlers/compact.d.ts.map +1 -1
  197. package/dist/src/jobs/handlers/compact.js +30 -4
  198. package/dist/src/jobs/handlers/compact.js.map +1 -1
  199. package/dist/src/jobs/handlers/dedupe-conversations.d.ts +134 -0
  200. package/dist/src/jobs/handlers/dedupe-conversations.d.ts.map +1 -0
  201. package/dist/src/jobs/handlers/dedupe-conversations.js +371 -0
  202. package/dist/src/jobs/handlers/dedupe-conversations.js.map +1 -0
  203. package/dist/src/jobs/handlers/ingest.d.ts.map +1 -1
  204. package/dist/src/jobs/handlers/ingest.js +295 -41
  205. package/dist/src/jobs/handlers/ingest.js.map +1 -1
  206. package/dist/src/jobs/handlers/reconcile.d.ts +28 -0
  207. package/dist/src/jobs/handlers/reconcile.d.ts.map +1 -1
  208. package/dist/src/jobs/handlers/reconcile.js +145 -19
  209. package/dist/src/jobs/handlers/reconcile.js.map +1 -1
  210. package/dist/src/jobs/handlers/reindex.d.ts.map +1 -1
  211. package/dist/src/jobs/handlers/reindex.js +13 -2
  212. package/dist/src/jobs/handlers/reindex.js.map +1 -1
  213. package/dist/src/jobs/handlers/save.d.ts.map +1 -1
  214. package/dist/src/jobs/handlers/save.js +57 -3
  215. package/dist/src/jobs/handlers/save.js.map +1 -1
  216. package/dist/src/jobs/queue.d.ts +51 -1
  217. package/dist/src/jobs/queue.d.ts.map +1 -1
  218. package/dist/src/jobs/queue.js +466 -26
  219. package/dist/src/jobs/queue.js.map +1 -1
  220. package/dist/src/jobs/worker-entry.d.ts.map +1 -1
  221. package/dist/src/jobs/worker-entry.js +35 -7
  222. package/dist/src/jobs/worker-entry.js.map +1 -1
  223. package/dist/src/jobs/worker-process.d.ts +11 -0
  224. package/dist/src/jobs/worker-process.d.ts.map +1 -1
  225. package/dist/src/jobs/worker-process.js +37 -4
  226. package/dist/src/jobs/worker-process.js.map +1 -1
  227. package/dist/src/main.js +199 -46
  228. package/dist/src/main.js.map +1 -1
  229. package/dist/src/mcp/errors.d.ts.map +1 -1
  230. package/dist/src/mcp/errors.js +20 -1
  231. package/dist/src/mcp/errors.js.map +1 -1
  232. package/dist/src/mcp/server.d.ts.map +1 -1
  233. package/dist/src/mcp/server.js +43 -3
  234. package/dist/src/mcp/server.js.map +1 -1
  235. package/dist/src/mcp/tools/context-pack.d.ts.map +1 -1
  236. package/dist/src/mcp/tools/context-pack.js +164 -23
  237. package/dist/src/mcp/tools/context-pack.js.map +1 -1
  238. package/dist/src/mcp/tools/search.d.ts +6 -2
  239. package/dist/src/mcp/tools/search.d.ts.map +1 -1
  240. package/dist/src/mcp/tools/search.js +35 -4
  241. package/dist/src/mcp/tools/search.js.map +1 -1
  242. package/dist/src/observability/embedding-events.d.ts +52 -0
  243. package/dist/src/observability/embedding-events.d.ts.map +1 -0
  244. package/dist/src/observability/embedding-events.js +149 -0
  245. package/dist/src/observability/embedding-events.js.map +1 -0
  246. package/dist/src/observability/index-events.d.ts +70 -0
  247. package/dist/src/observability/index-events.d.ts.map +1 -0
  248. package/dist/src/observability/index-events.js +148 -0
  249. package/dist/src/observability/index-events.js.map +1 -0
  250. package/dist/src/observability/onboarding-metric.d.ts +131 -0
  251. package/dist/src/observability/onboarding-metric.d.ts.map +1 -0
  252. package/dist/src/observability/onboarding-metric.js +351 -0
  253. package/dist/src/observability/onboarding-metric.js.map +1 -0
  254. package/dist/src/observability/tool-usage-stats.d.ts +77 -4
  255. package/dist/src/observability/tool-usage-stats.d.ts.map +1 -1
  256. package/dist/src/observability/tool-usage-stats.js +112 -32
  257. package/dist/src/observability/tool-usage-stats.js.map +1 -1
  258. package/dist/src/observability/tool-usage.d.ts +100 -7
  259. package/dist/src/observability/tool-usage.d.ts.map +1 -1
  260. package/dist/src/observability/tool-usage.js +196 -33
  261. package/dist/src/observability/tool-usage.js.map +1 -1
  262. package/dist/src/observability/version-check.d.ts +71 -0
  263. package/dist/src/observability/version-check.d.ts.map +1 -0
  264. package/dist/src/observability/version-check.js +198 -0
  265. package/dist/src/observability/version-check.js.map +1 -0
  266. package/dist/src/providers/basic-metadata-extraction.d.ts +60 -0
  267. package/dist/src/providers/basic-metadata-extraction.d.ts.map +1 -0
  268. package/dist/src/providers/basic-metadata-extraction.js +114 -0
  269. package/dist/src/providers/basic-metadata-extraction.js.map +1 -0
  270. package/dist/src/providers/codex-cli-metadata-extraction.d.ts +1 -0
  271. package/dist/src/providers/codex-cli-metadata-extraction.d.ts.map +1 -1
  272. package/dist/src/providers/codex-cli-metadata-extraction.js +6 -2
  273. package/dist/src/providers/codex-cli-metadata-extraction.js.map +1 -1
  274. package/dist/src/providers/codex-cli-model.d.ts +61 -0
  275. package/dist/src/providers/codex-cli-model.d.ts.map +1 -0
  276. package/dist/src/providers/codex-cli-model.js +194 -0
  277. package/dist/src/providers/codex-cli-model.js.map +1 -0
  278. package/dist/src/providers/codex-cli-runner.d.ts +39 -0
  279. package/dist/src/providers/codex-cli-runner.d.ts.map +1 -1
  280. package/dist/src/providers/codex-cli-runner.js +234 -48
  281. package/dist/src/providers/codex-cli-runner.js.map +1 -1
  282. package/dist/src/providers/conversation-generation.d.ts.map +1 -1
  283. package/dist/src/providers/conversation-generation.js +43 -6
  284. package/dist/src/providers/conversation-generation.js.map +1 -1
  285. package/dist/src/providers/ollama-embed.d.ts +2 -1
  286. package/dist/src/providers/ollama-embed.d.ts.map +1 -1
  287. package/dist/src/providers/ollama-embed.js +1 -0
  288. package/dist/src/providers/ollama-embed.js.map +1 -1
  289. package/dist/src/providers/openai-metadata-extraction.d.ts +3 -3
  290. package/dist/src/providers/openai-metadata-extraction.d.ts.map +1 -1
  291. package/dist/src/providers/openai-metadata-extraction.js +18 -3
  292. package/dist/src/providers/openai-metadata-extraction.js.map +1 -1
  293. package/dist/src/providers/placeholder-embed.d.ts +56 -0
  294. package/dist/src/providers/placeholder-embed.d.ts.map +1 -0
  295. package/dist/src/providers/placeholder-embed.js +64 -0
  296. package/dist/src/providers/placeholder-embed.js.map +1 -0
  297. package/dist/src/providers/stub.d.ts +2 -0
  298. package/dist/src/providers/stub.d.ts.map +1 -1
  299. package/dist/src/providers/stub.js +2 -0
  300. package/dist/src/providers/stub.js.map +1 -1
  301. package/dist/src/providers/types.d.ts +11 -0
  302. package/dist/src/providers/types.d.ts.map +1 -1
  303. package/dist/src/providers/voyage.d.ts +2 -1
  304. package/dist/src/providers/voyage.d.ts.map +1 -1
  305. package/dist/src/providers/voyage.js +1 -0
  306. package/dist/src/providers/voyage.js.map +1 -1
  307. package/dist/src/retrieval/compact.d.ts +116 -2
  308. package/dist/src/retrieval/compact.d.ts.map +1 -1
  309. package/dist/src/retrieval/compact.js +158 -5
  310. package/dist/src/retrieval/compact.js.map +1 -1
  311. package/dist/src/retrieval/context-pack.d.ts +114 -0
  312. package/dist/src/retrieval/context-pack.d.ts.map +1 -1
  313. package/dist/src/retrieval/context-pack.js +292 -8
  314. package/dist/src/retrieval/context-pack.js.map +1 -1
  315. package/dist/src/retrieval/current-truth.d.ts +360 -0
  316. package/dist/src/retrieval/current-truth.d.ts.map +1 -0
  317. package/dist/src/retrieval/current-truth.js +766 -0
  318. package/dist/src/retrieval/current-truth.js.map +1 -0
  319. package/dist/src/retrieval/git-state.d.ts +53 -0
  320. package/dist/src/retrieval/git-state.d.ts.map +1 -0
  321. package/dist/src/retrieval/git-state.js +174 -0
  322. package/dist/src/retrieval/git-state.js.map +1 -0
  323. package/dist/src/retrieval/lexical.d.ts.map +1 -1
  324. package/dist/src/retrieval/lexical.js +19 -3
  325. package/dist/src/retrieval/lexical.js.map +1 -1
  326. package/dist/src/retrieval/locator-boost.d.ts +37 -0
  327. package/dist/src/retrieval/locator-boost.d.ts.map +1 -0
  328. package/dist/src/retrieval/locator-boost.js +129 -0
  329. package/dist/src/retrieval/locator-boost.js.map +1 -0
  330. package/dist/src/retrieval/report-demotion.d.ts +46 -0
  331. package/dist/src/retrieval/report-demotion.d.ts.map +1 -0
  332. package/dist/src/retrieval/report-demotion.js +169 -0
  333. package/dist/src/retrieval/report-demotion.js.map +1 -0
  334. package/dist/src/retrieval/vector.d.ts.map +1 -1
  335. package/dist/src/retrieval/vector.js +11 -2
  336. package/dist/src/retrieval/vector.js.map +1 -1
  337. package/dist/src/server/app.d.ts.map +1 -1
  338. package/dist/src/server/app.js +92 -11
  339. package/dist/src/server/app.js.map +1 -1
  340. package/dist/src/server/routes/compact.d.ts.map +1 -1
  341. package/dist/src/server/routes/compact.js +4 -1
  342. package/dist/src/server/routes/compact.js.map +1 -1
  343. package/dist/src/server/routes/context.d.ts +1 -1
  344. package/dist/src/server/routes/context.d.ts.map +1 -1
  345. package/dist/src/server/routes/context.js +2 -1
  346. package/dist/src/server/routes/context.js.map +1 -1
  347. package/dist/src/server/routes/conversations-search.d.ts.map +1 -1
  348. package/dist/src/server/routes/conversations-search.js +28 -3
  349. package/dist/src/server/routes/conversations-search.js.map +1 -1
  350. package/dist/src/server/routes/enqueue.d.ts +11 -0
  351. package/dist/src/server/routes/enqueue.d.ts.map +1 -0
  352. package/dist/src/server/routes/enqueue.js +17 -0
  353. package/dist/src/server/routes/enqueue.js.map +1 -0
  354. package/dist/src/server/routes/friend-status.d.ts +339 -3
  355. package/dist/src/server/routes/friend-status.d.ts.map +1 -1
  356. package/dist/src/server/routes/friend-status.js +447 -13
  357. package/dist/src/server/routes/friend-status.js.map +1 -1
  358. package/dist/src/server/routes/ingest.d.ts.map +1 -1
  359. package/dist/src/server/routes/ingest.js +5 -2
  360. package/dist/src/server/routes/ingest.js.map +1 -1
  361. package/dist/src/server/routes/mcp-usage.d.ts +5 -4
  362. package/dist/src/server/routes/mcp-usage.d.ts.map +1 -1
  363. package/dist/src/server/routes/mcp-usage.js.map +1 -1
  364. package/dist/src/server/routes/reconcile.d.ts.map +1 -1
  365. package/dist/src/server/routes/reconcile.js +20 -1
  366. package/dist/src/server/routes/reconcile.js.map +1 -1
  367. package/dist/src/server/routes/reindex.d.ts.map +1 -1
  368. package/dist/src/server/routes/reindex.js +4 -1
  369. package/dist/src/server/routes/reindex.js.map +1 -1
  370. package/dist/src/server/routes/save.d.ts.map +1 -1
  371. package/dist/src/server/routes/save.js +4 -1
  372. package/dist/src/server/routes/save.js.map +1 -1
  373. package/dist/src/server/routes/search.d.ts +1 -1
  374. package/dist/src/server/routes/search.d.ts.map +1 -1
  375. package/dist/src/server/routes/search.js +253 -29
  376. package/dist/src/server/routes/search.js.map +1 -1
  377. package/dist/src/server/routes/triage.d.ts.map +1 -1
  378. package/dist/src/server/routes/triage.js +4 -1
  379. package/dist/src/server/routes/triage.js.map +1 -1
  380. package/dist/src/storage/rebuild.d.ts +35 -1
  381. package/dist/src/storage/rebuild.d.ts.map +1 -1
  382. package/dist/src/storage/rebuild.js +288 -64
  383. package/dist/src/storage/rebuild.js.map +1 -1
  384. package/dist/src/storage/tables.d.ts +29 -0
  385. package/dist/src/storage/tables.d.ts.map +1 -1
  386. package/dist/src/storage/tables.js +32 -1
  387. package/dist/src/storage/tables.js.map +1 -1
  388. package/operator/swiftbar/render-menu.py +524 -0
  389. package/operator/swiftbar/rift.10s.sh +176 -0
  390. package/package.json +9 -3
@@ -10,9 +10,13 @@ import type { Job, JobType, CreateJobResult, JobHandler } from "./types.js";
10
10
  export declare class JobQueue {
11
11
  private queuePath;
12
12
  private jobs;
13
+ private payloadRefs;
13
14
  private handlers;
14
15
  private processing;
15
16
  private held;
17
+ private persistRetryTimer;
18
+ private startRetryTimer;
19
+ private startRetryDelayMs;
16
20
  constructor(queuePath: string);
17
21
  /**
18
22
  * Hold processing — registerHandler and create won't auto-start queued
@@ -55,6 +59,17 @@ export declare class JobQueue {
55
59
  }>;
56
60
  /** Get a job by ID, or undefined. */
57
61
  get(id: string): Job | undefined;
62
+ /**
63
+ * Return all jobs whose `idempotency_key` starts with `prefix`. Used
64
+ * by the friend-status projection to surface inbox freshness — jobs
65
+ * created from the inbox watcher all use keys of the form
66
+ * `inbox:<hex>`, so a prefix scan answers "what's the inbox lane
67
+ * doing" without leaking queue internals to the route layer.
68
+ *
69
+ * Read-only iteration of the in-memory map; safe to call on a hot
70
+ * queue. Returns the snapshot in insertion order.
71
+ */
72
+ listByIdempotencyPrefix(prefix: string): Job[];
58
73
  /**
59
74
  * Check whether an idempotency key matches an existing active job.
60
75
  * Returns the job if found in queued/running/completed state, undefined otherwise.
@@ -70,8 +85,35 @@ export declare class JobQueue {
70
85
  cancel(id: string): Promise<Job | {
71
86
  conflict: true;
72
87
  } | undefined>;
73
- /** Atomic write: temp file + rename. */
88
+ /**
89
+ * Keep the persisted ledger bounded so `JSON.stringify` can never exceed
90
+ * V8's max string length (the failure mode that turned every save into a
91
+ * 500). Two cheap, safe reductions on terminal jobs only:
92
+ * 1. Drop `payload` — only active jobs need it.
93
+ * 2. Cap how many terminal jobs we retain (oldest dropped first).
94
+ * Non-terminal jobs (queued/running/interrupted) are never touched.
95
+ * Returns true if anything changed.
96
+ */
97
+ private compactLedger;
98
+ /** Atomic write: temp file + rename. Ledger is compacted first so the
99
+ * serialized form stays well under V8's max string length. */
74
100
  private persist;
101
+ private createLedgerTooLargeError;
102
+ private snapshotState;
103
+ private restoreSnapshot;
104
+ private payloadDir;
105
+ private payloadPath;
106
+ private payloadRefPath;
107
+ private payloadAbsPath;
108
+ private isPayloadRef;
109
+ private hydrateJob;
110
+ private readPayloadRef;
111
+ private serializeJobForLedger;
112
+ private writePayloadFile;
113
+ private prunePayloadRefCache;
114
+ private deletePayloadFilesForJob;
115
+ private cleanupPayloadFiles;
116
+ private deletePayloadPath;
75
117
  /**
76
118
  * Find the active job for a given idempotency key.
77
119
  * Skip superseded jobs (those with `retried_by` set) so we always
@@ -102,6 +144,14 @@ export declare class JobQueue {
102
144
  private createRetryJob;
103
145
  /** Process the next queued job if no job is currently running. */
104
146
  private processNext;
147
+ private applyPersistedMutation;
148
+ private applyTerminalMutation;
149
+ private markPersistDirty;
150
+ private schedulePersistRetry;
151
+ private scheduleStartRetry;
152
+ private clearStartRetry;
153
+ private clearPersistRetry;
154
+ private retryDirtyPersist;
105
155
  private findNextQueued;
106
156
  }
107
157
  //# sourceMappingURL=queue.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"queue.d.ts","sourceRoot":"","sources":["../../../src/jobs/queue.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,GAAG,EAAE,OAAO,EAAa,eAAe,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAGvF;;;;;;;GAOG;AACH,qBAAa,QAAQ;IAMP,OAAO,CAAC,SAAS;IAL7B,OAAO,CAAC,IAAI,CAA0B;IACtC,OAAO,CAAC,QAAQ,CAAkC;IAClD,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,IAAI,CAAS;gBAED,SAAS,EAAE,MAAM;IAErC;;;;OAIG;IACH,IAAI,IAAI,IAAI;IAIZ,6DAA6D;IAC7D,WAAW,IAAI,IAAI;IAKnB,+EAA+E;IAC/E,eAAe,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,UAAU,GAAG,IAAI;IAKzD,uDAAuD;IACvD,UAAU,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO;IAIlC;;;;;;;OAOG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IA8C3B;;;;;;;;;;;OAWG;IACG,MAAM,CACV,IAAI,EAAE,OAAO,EACb,IAAI,CAAC,EAAE;QAAE,eAAe,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,OAAO,CAAA;KAAE,GACrD,OAAO,CAAC,eAAe,GAAG;QAAE,QAAQ,EAAE,IAAI,CAAA;KAAE,CAAC;IA0ChD,qCAAqC;IACrC,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,GAAG,GAAG,SAAS;IAIhC;;;;OAIG;IACH,aAAa,CAAC,cAAc,EAAE,MAAM,GAAG,GAAG,GAAG,SAAS;IAatD;;;;;OAKG;IACG,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,GAAG;QAAE,QAAQ,EAAE,IAAI,CAAA;KAAE,GAAG,SAAS,CAAC;IAkBvE,wCAAwC;YAC1B,OAAO;IAOrB;;;;OAIG;IACH,OAAO,CAAC,oBAAoB;IAO5B;;;;;;;;;OASG;IACG,YAAY,CAChB,IAAI,EAAE,OAAO,EACb,IAAI,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,OAAO,CAAA;KAAE,GAC3B,OAAO,CAAC,eAAe,CAAC;IAwB3B,sDAAsD;IACtD,qBAAqB,IAAI,OAAO;IAYhC;;;;OAIG;IACH,sBAAsB,IAAI,GAAG,GAAG,IAAI;YAStB,cAAc;IA6B5B,kEAAkE;IAClE,OAAO,CAAC,WAAW;IAgCnB,OAAO,CAAC,cAAc;CAMvB"}
1
+ {"version":3,"file":"queue.d.ts","sourceRoot":"","sources":["../../../src/jobs/queue.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,GAAG,EAAE,OAAO,EAAa,eAAe,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAgEvF;;;;;;;GAOG;AACH,qBAAa,QAAQ;IAUP,OAAO,CAAC,SAAS;IAT7B,OAAO,CAAC,IAAI,CAA0B;IACtC,OAAO,CAAC,WAAW,CAAwC;IAC3D,OAAO,CAAC,QAAQ,CAAkC;IAClD,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,IAAI,CAAS;IACrB,OAAO,CAAC,iBAAiB,CAA4C;IACrE,OAAO,CAAC,eAAe,CAA4C;IACnE,OAAO,CAAC,iBAAiB,CAAgC;gBAErC,SAAS,EAAE,MAAM;IAErC;;;;OAIG;IACH,IAAI,IAAI,IAAI;IAIZ,6DAA6D;IAC7D,WAAW,IAAI,IAAI;IAKnB,+EAA+E;IAC/E,eAAe,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,UAAU,GAAG,IAAI;IAKzD,uDAAuD;IACvD,UAAU,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO;IAIlC;;;;;;;OAOG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAmD3B;;;;;;;;;;;OAWG;IACG,MAAM,CACV,IAAI,EAAE,OAAO,EACb,IAAI,CAAC,EAAE;QAAE,eAAe,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,OAAO,CAAA;KAAE,GACrD,OAAO,CAAC,eAAe,GAAG;QAAE,QAAQ,EAAE,IAAI,CAAA;KAAE,CAAC;IAiDhD,qCAAqC;IACrC,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,GAAG,GAAG,SAAS;IAIhC;;;;;;;;;OASG;IACH,uBAAuB,CAAC,MAAM,EAAE,MAAM,GAAG,GAAG,EAAE;IAQ9C;;;;OAIG;IACH,aAAa,CAAC,cAAc,EAAE,MAAM,GAAG,GAAG,GAAG,SAAS;IAatD;;;;;OAKG;IACG,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,GAAG;QAAE,QAAQ,EAAE,IAAI,CAAA;KAAE,GAAG,SAAS,CAAC;IAwBvE;;;;;;;;OAQG;IACH,OAAO,CAAC,aAAa;IAiErB;kEAC8D;YAChD,OAAO;IAkCrB,OAAO,CAAC,yBAAyB;IAWjC,OAAO,CAAC,aAAa;IAcrB,OAAO,CAAC,eAAe;IAKvB,OAAO,CAAC,UAAU;IAIlB,OAAO,CAAC,WAAW;IAInB,OAAO,CAAC,cAAc;IAItB,OAAO,CAAC,cAAc;IAMtB,OAAO,CAAC,YAAY;IAWpB,OAAO,CAAC,UAAU;IAalB,OAAO,CAAC,cAAc;IAWtB,OAAO,CAAC,qBAAqB;IAgD7B,OAAO,CAAC,gBAAgB;IAOxB,OAAO,CAAC,oBAAoB;IAQ5B,OAAO,CAAC,wBAAwB;IAmBhC,OAAO,CAAC,mBAAmB;IAqB3B,OAAO,CAAC,iBAAiB;IAYzB;;;;OAIG;IACH,OAAO,CAAC,oBAAoB;IAO5B;;;;;;;;;OASG;IACG,YAAY,CAChB,IAAI,EAAE,OAAO,EACb,IAAI,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,OAAO,CAAA;KAAE,GAC3B,OAAO,CAAC,eAAe,CAAC;IA+B3B,sDAAsD;IACtD,qBAAqB,IAAI,OAAO;IAYhC;;;;OAIG;IACH,sBAAsB,IAAI,GAAG,GAAG,IAAI;YAStB,cAAc;IAoC5B,kEAAkE;IAClE,OAAO,CAAC,WAAW;YA8CL,sBAAsB;YAWtB,qBAAqB;IAYnC,OAAO,CAAC,gBAAgB;IAOxB,OAAO,CAAC,oBAAoB;IAS5B,OAAO,CAAC,kBAAkB;IAc1B,OAAO,CAAC,eAAe;IAQvB,OAAO,CAAC,iBAAiB;YAMX,iBAAiB;IAW/B,OAAO,CAAC,cAAc;CAMvB"}
@@ -1,6 +1,46 @@
1
1
  import crypto from "node:crypto";
2
2
  import fs from "node:fs";
3
+ import path from "node:path";
3
4
  import { EXCLUSIVE_JOBS } from "./types.js";
5
+ /**
6
+ * Terminal statuses: the job is done and its `payload` (full
7
+ * conversation / export text) is dead weight — the durable record lives
8
+ * in raw files + the vector index, not the queue ledger. Only
9
+ * queued/running/interrupted jobs still need their payload.
10
+ */
11
+ const TERMINAL_STATUSES = new Set([
12
+ "completed",
13
+ "failed",
14
+ "cancelled",
15
+ ]);
16
+ /**
17
+ * Terminal statuses whose `payload` is safe to drop. A `failed` job is
18
+ * still retryable — `create()` with the same idempotency_key turns it into
19
+ * a retry whose chain root (the failed job) must still carry the canonical
20
+ * source/raw payload (save.ts resolveChainRoot). The failure path persists
21
+ * (and thus compacts) the job the instant it fails, before any retry exists,
22
+ * so stripping `failed` payloads here would silently break changed-source
23
+ * retries. Failed jobs are few (cap still bounds them); keep their payload.
24
+ */
25
+ const STRIPPABLE_STATUSES = new Set([
26
+ "completed",
27
+ "cancelled",
28
+ ]);
29
+ /**
30
+ * Cap on retained terminal-job records. Metadata is tiny (~200 B/job),
31
+ * but unbounded retention eventually blows `JSON.stringify`'s max string
32
+ * length (V8 ~512 MB) — the failure that turned every save into a 500.
33
+ * We keep the most recent N; idempotency dedup and the friend-status
34
+ * projection only need recent terminal jobs, and older sessions are
35
+ * re-guarded by capture-state.json + content-hash dedup.
36
+ */
37
+ const MAX_TERMINAL_JOBS = 2000;
38
+ const PAYLOAD_OFFLOAD_THRESHOLD_BYTES = 64 * 1024;
39
+ const MAX_LEDGER_BYTES = 64 * 1024 * 1024;
40
+ const PAYLOAD_REF_MARKER = "__rift_queue_payload_ref";
41
+ const PERSIST_RETRY_DELAY_MS = 100;
42
+ const START_RETRY_INITIAL_DELAY_MS = 100;
43
+ const START_RETRY_MAX_DELAY_MS = 5000;
4
44
  /**
5
45
  * In-process job queue with:
6
46
  * - Persistence to `queue.json` (atomic write: temp + rename)
@@ -12,9 +52,13 @@ import { EXCLUSIVE_JOBS } from "./types.js";
12
52
  export class JobQueue {
13
53
  queuePath;
14
54
  jobs = new Map();
55
+ payloadRefs = new Map();
15
56
  handlers = new Map();
16
57
  processing = false;
17
58
  held = false;
59
+ persistRetryTimer;
60
+ startRetryTimer;
61
+ startRetryDelayMs = START_RETRY_INITIAL_DELAY_MS;
18
62
  constructor(queuePath) {
19
63
  this.queuePath = queuePath;
20
64
  }
@@ -53,9 +97,11 @@ export class JobQueue {
53
97
  const raw = fs.readFileSync(this.queuePath, "utf-8");
54
98
  const entries = JSON.parse(raw);
55
99
  this.jobs.clear();
100
+ this.payloadRefs.clear();
56
101
  // Collect jobs that need recovery (were running at crash time).
57
102
  const toRequeue = [];
58
- for (const job of entries) {
103
+ for (const entry of entries) {
104
+ const job = this.hydrateJob(entry);
59
105
  if (job.status === "running") {
60
106
  job.status = "interrupted";
61
107
  job.updated_at = new Date().toISOString();
@@ -79,7 +125,10 @@ export class JobQueue {
79
125
  job.retried_by = requeuedId;
80
126
  this.jobs.set(requeuedId, requeued);
81
127
  }
82
- if (toRequeue.length > 0) {
128
+ // Heal a ledger that bloated before this fix shipped: strip terminal
129
+ // payloads / cap retention on load, even when nothing needs re-queuing.
130
+ const compacted = this.compactLedger();
131
+ if (toRequeue.length > 0 || compacted) {
83
132
  await this.persist();
84
133
  }
85
134
  }
@@ -134,8 +183,16 @@ export class JobQueue {
134
183
  ...(key !== undefined ? { idempotency_key: key } : {}),
135
184
  ...(opts?.payload !== undefined ? { payload: opts.payload } : {}),
136
185
  };
186
+ const snapshot = this.snapshotState();
137
187
  this.jobs.set(job.id, job);
138
- await this.persist();
188
+ try {
189
+ await this.persist();
190
+ }
191
+ catch (err) {
192
+ this.restoreSnapshot(snapshot);
193
+ this.deletePayloadFilesForJob(job.id);
194
+ throw err;
195
+ }
139
196
  this.processNext();
140
197
  return { job, duplicate: false };
141
198
  }
@@ -143,6 +200,24 @@ export class JobQueue {
143
200
  get(id) {
144
201
  return this.jobs.get(id);
145
202
  }
203
+ /**
204
+ * Return all jobs whose `idempotency_key` starts with `prefix`. Used
205
+ * by the friend-status projection to surface inbox freshness — jobs
206
+ * created from the inbox watcher all use keys of the form
207
+ * `inbox:<hex>`, so a prefix scan answers "what's the inbox lane
208
+ * doing" without leaking queue internals to the route layer.
209
+ *
210
+ * Read-only iteration of the in-memory map; safe to call on a hot
211
+ * queue. Returns the snapshot in insertion order.
212
+ */
213
+ listByIdempotencyPrefix(prefix) {
214
+ const out = [];
215
+ for (const job of this.jobs.values()) {
216
+ if (job.idempotency_key?.startsWith(prefix))
217
+ out.push(job);
218
+ }
219
+ return out;
220
+ }
146
221
  /**
147
222
  * Check whether an idempotency key matches an existing active job.
148
223
  * Returns the job if found in queued/running/completed state, undefined otherwise.
@@ -169,9 +244,16 @@ export class JobQueue {
169
244
  if (!job)
170
245
  return undefined;
171
246
  if (job.status === "queued" || job.status === "interrupted") {
247
+ const snapshot = this.snapshotState();
172
248
  job.status = "cancelled";
173
249
  job.updated_at = new Date().toISOString();
174
- await this.persist();
250
+ try {
251
+ await this.persist();
252
+ }
253
+ catch (err) {
254
+ this.restoreSnapshot(snapshot);
255
+ throw err;
256
+ }
175
257
  return job;
176
258
  }
177
259
  if (job.status === "running") {
@@ -179,12 +261,274 @@ export class JobQueue {
179
261
  }
180
262
  return undefined;
181
263
  }
182
- /** Atomic write: temp file + rename. */
264
+ /**
265
+ * Keep the persisted ledger bounded so `JSON.stringify` can never exceed
266
+ * V8's max string length (the failure mode that turned every save into a
267
+ * 500). Two cheap, safe reductions on terminal jobs only:
268
+ * 1. Drop `payload` — only active jobs need it.
269
+ * 2. Cap how many terminal jobs we retain (oldest dropped first).
270
+ * Non-terminal jobs (queued/running/interrupted) are never touched.
271
+ * Returns true if anything changed.
272
+ */
273
+ compactLedger() {
274
+ let changed = false;
275
+ // Protect every job reachable by walking `retry_of` from a non-terminal
276
+ // job. The save/ingest handlers call resolveChainRoot() to recover the
277
+ // canonical row id + source from the chain root. If an ancestor is
278
+ // dropped (payload stripped OR row deleted) while a retry is still
279
+ // queued/running, resolveChainRoot stops short (save.ts:55), the handler
280
+ // falls back to the retry's own id/source, and a duplicate row is
281
+ // created. So these ancestors are exempt from BOTH reductions below.
282
+ const protectedIds = new Set();
283
+ for (const job of this.jobs.values()) {
284
+ if (TERMINAL_STATUSES.has(job.status))
285
+ continue;
286
+ let cursor = job;
287
+ let depth = 0;
288
+ while (cursor?.retry_of && depth < 50) {
289
+ const parent = this.jobs.get(cursor.retry_of);
290
+ if (!parent)
291
+ break;
292
+ protectedIds.add(parent.id);
293
+ cursor = parent;
294
+ depth++;
295
+ }
296
+ }
297
+ const terminal = [];
298
+ for (const job of this.jobs.values()) {
299
+ if (TERMINAL_STATUSES.has(job.status)) {
300
+ // Keep payload on retry-chain ancestors: the save handler walks
301
+ // `retry_of` to the root job and reads its payload to resolve the
302
+ // canonical source/raw artifact (src/jobs/handlers/save.ts). A job
303
+ // with `retried_by` set is such an ancestor, as is any ancestor of
304
+ // a still-active retry (protectedIds). Standalone/leaf terminal jobs
305
+ // no longer need their payload.
306
+ if (job.payload !== undefined &&
307
+ STRIPPABLE_STATUSES.has(job.status) &&
308
+ job.retried_by === undefined &&
309
+ !protectedIds.has(job.id)) {
310
+ delete job.payload;
311
+ changed = true;
312
+ }
313
+ terminal.push(job);
314
+ }
315
+ }
316
+ if (terminal.length > MAX_TERMINAL_JOBS) {
317
+ // Drop the oldest terminal jobs beyond the cap (oldest by updated_at),
318
+ // but never an ancestor of a still-active retry chain. If protected
319
+ // jobs prevent reaching the cap, the ledger stays slightly above it —
320
+ // correctness over a hard bound (the overflow is bounded by in-flight
321
+ // work, which is small).
322
+ terminal.sort((a, b) => a.updated_at.localeCompare(b.updated_at));
323
+ const overflow = terminal.length - MAX_TERMINAL_JOBS;
324
+ let dropped = 0;
325
+ for (const job of terminal) {
326
+ if (dropped >= overflow)
327
+ break;
328
+ if (protectedIds.has(job.id))
329
+ continue;
330
+ this.jobs.delete(job.id);
331
+ dropped++;
332
+ changed = true;
333
+ }
334
+ }
335
+ return changed;
336
+ }
337
+ /** Atomic write: temp file + rename. Ledger is compacted first so the
338
+ * serialized form stays well under V8's max string length. */
183
339
  async persist() {
184
- const data = JSON.stringify([...this.jobs.values()], null, 2);
340
+ this.compactLedger();
341
+ this.prunePayloadRefCache();
342
+ const activePayloadRefs = new Set();
343
+ const entries = [...this.jobs.values()].map((job) => this.serializeJobForLedger(job, activePayloadRefs));
344
+ let data;
345
+ try {
346
+ data = JSON.stringify(entries, null, 2);
347
+ if (Buffer.byteLength(data, "utf-8") > MAX_LEDGER_BYTES) {
348
+ throw this.createLedgerTooLargeError(`Job ledger exceeds ${MAX_LEDGER_BYTES} bytes after payload offload.`);
349
+ }
350
+ }
351
+ catch (err) {
352
+ // Defense in depth: even after compaction, a pathological volume of
353
+ // active payloads could exceed V8's max string length. Surface a
354
+ // typed, actionable error instead of a bare 500 — and never silently
355
+ // corrupt active jobs by dropping their payloads.
356
+ if (err instanceof RangeError) {
357
+ throw this.createLedgerTooLargeError("Job ledger too large to serialize (exceeds V8 max string length).");
358
+ }
359
+ throw err;
360
+ }
185
361
  const tmp = this.queuePath + `.tmp.${process.pid}`;
186
362
  fs.writeFileSync(tmp, data, "utf-8");
187
363
  fs.renameSync(tmp, this.queuePath);
364
+ this.cleanupPayloadFiles(activePayloadRefs);
365
+ this.clearPersistRetry();
366
+ }
367
+ createLedgerTooLargeError(message) {
368
+ const e = new Error(`${message} Payloads above ${PAYLOAD_OFFLOAD_THRESHOLD_BYTES} bytes are ` +
369
+ "offloaded automatically, so this means there are too many queued jobs " +
370
+ "or a malformed payload reference. Retry after current jobs drain, or " +
371
+ "see reports/2026-05-25-capture-500-queue-bloat.md for manual recovery.");
372
+ e.code = "queue_ledger_too_large";
373
+ return e;
374
+ }
375
+ snapshotState() {
376
+ return {
377
+ jobs: new Map([...this.jobs.entries()].map(([id, job]) => [id, { ...job }])),
378
+ payloadRefs: new Map([...this.payloadRefs.entries()].map(([id, entry]) => [
379
+ id,
380
+ { ref: { ...entry.ref }, value: entry.value },
381
+ ])),
382
+ };
383
+ }
384
+ restoreSnapshot(snapshot) {
385
+ this.jobs = snapshot.jobs;
386
+ this.payloadRefs = snapshot.payloadRefs;
387
+ }
388
+ payloadDir() {
389
+ return path.join(path.dirname(this.queuePath), "payloads");
390
+ }
391
+ payloadPath(jobId, sha256) {
392
+ return path.join(this.payloadDir(), `${jobId}.${sha256}.json`);
393
+ }
394
+ payloadRefPath(jobId, sha256) {
395
+ return `payloads/${jobId}.${sha256}.json`;
396
+ }
397
+ payloadAbsPath(ref) {
398
+ return path.isAbsolute(ref.path)
399
+ ? ref.path
400
+ : path.join(path.dirname(this.queuePath), ref.path);
401
+ }
402
+ isPayloadRef(payload) {
403
+ return (typeof payload === "object" &&
404
+ payload !== null &&
405
+ payload[PAYLOAD_REF_MARKER] === true &&
406
+ typeof payload["path"] === "string" &&
407
+ typeof payload["byte_length"] === "number" &&
408
+ typeof payload["sha256"] === "string");
409
+ }
410
+ hydrateJob(entry) {
411
+ const job = { ...entry };
412
+ if (this.isPayloadRef(entry.payload)) {
413
+ const payload = this.readPayloadRef(entry.payload);
414
+ job.payload = payload;
415
+ this.payloadRefs.set(job.id, {
416
+ ref: { ...entry.payload },
417
+ value: payload,
418
+ });
419
+ }
420
+ return job;
421
+ }
422
+ readPayloadRef(ref) {
423
+ const refPath = this.payloadAbsPath(ref);
424
+ const raw = fs.readFileSync(refPath, "utf-8");
425
+ const bytes = Buffer.byteLength(raw, "utf-8");
426
+ const sha256 = crypto.createHash("sha256").update(raw).digest("hex");
427
+ if (bytes !== ref.byte_length || sha256 !== ref.sha256) {
428
+ throw new Error(`Job payload ref failed integrity check: ${ref.path}`);
429
+ }
430
+ return JSON.parse(raw);
431
+ }
432
+ serializeJobForLedger(job, activePayloadRefs) {
433
+ const persisted = { ...job };
434
+ if (job.payload === undefined) {
435
+ delete persisted.payload;
436
+ this.payloadRefs.delete(job.id);
437
+ return persisted;
438
+ }
439
+ const cached = this.payloadRefs.get(job.id);
440
+ if (cached &&
441
+ cached.value === job.payload &&
442
+ fs.existsSync(this.payloadAbsPath(cached.ref))) {
443
+ activePayloadRefs.add(this.payloadAbsPath(cached.ref));
444
+ persisted.payload = cached.ref;
445
+ return persisted;
446
+ }
447
+ const payloadJson = JSON.stringify(job.payload);
448
+ if (payloadJson === undefined) {
449
+ throw new Error(`Job ${job.id} payload is not JSON-serializable`);
450
+ }
451
+ const byteLength = Buffer.byteLength(payloadJson, "utf-8");
452
+ if (byteLength <= PAYLOAD_OFFLOAD_THRESHOLD_BYTES) {
453
+ this.payloadRefs.delete(job.id);
454
+ persisted.payload = job.payload;
455
+ return persisted;
456
+ }
457
+ const sha256 = crypto.createHash("sha256").update(payloadJson).digest("hex");
458
+ const refPath = this.payloadRefPath(job.id, sha256);
459
+ const ref = {
460
+ [PAYLOAD_REF_MARKER]: true,
461
+ path: refPath,
462
+ byte_length: byteLength,
463
+ sha256,
464
+ };
465
+ this.writePayloadFile(this.payloadPath(job.id, sha256), payloadJson);
466
+ this.payloadRefs.set(job.id, { ref, value: job.payload });
467
+ activePayloadRefs.add(this.payloadAbsPath(ref));
468
+ persisted.payload = ref;
469
+ return persisted;
470
+ }
471
+ writePayloadFile(filePath, data) {
472
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
473
+ const tmp = `${filePath}.tmp.${process.pid}`;
474
+ fs.writeFileSync(tmp, data, "utf-8");
475
+ fs.renameSync(tmp, filePath);
476
+ }
477
+ prunePayloadRefCache() {
478
+ for (const jobId of this.payloadRefs.keys()) {
479
+ if (!this.jobs.has(jobId)) {
480
+ this.payloadRefs.delete(jobId);
481
+ }
482
+ }
483
+ }
484
+ deletePayloadFilesForJob(jobId) {
485
+ const dir = this.payloadDir();
486
+ let files;
487
+ try {
488
+ if (!fs.existsSync(dir))
489
+ return;
490
+ files = fs.readdirSync(dir);
491
+ }
492
+ catch (err) {
493
+ process.stderr.write(`queue: warning: failed to scan payload files for ${jobId}: ${err instanceof Error ? err.message : String(err)}\n`);
494
+ return;
495
+ }
496
+ for (const file of files) {
497
+ if (file === `${jobId}.json` || file.startsWith(`${jobId}.`)) {
498
+ this.deletePayloadPath(path.join(dir, file), jobId);
499
+ }
500
+ }
501
+ }
502
+ cleanupPayloadFiles(activePayloadRefs) {
503
+ const dir = this.payloadDir();
504
+ let files;
505
+ try {
506
+ if (!fs.existsSync(dir))
507
+ return;
508
+ files = fs.readdirSync(dir);
509
+ }
510
+ catch (err) {
511
+ process.stderr.write(`queue: warning: failed to scan payload directory for cleanup: ${err instanceof Error ? err.message : String(err)}\n`);
512
+ return;
513
+ }
514
+ for (const file of files) {
515
+ if (!file.endsWith(".json"))
516
+ continue;
517
+ const filePath = path.join(dir, file);
518
+ if (!activePayloadRefs.has(filePath)) {
519
+ this.deletePayloadPath(filePath, file);
520
+ }
521
+ }
522
+ }
523
+ deletePayloadPath(filePath, label) {
524
+ try {
525
+ fs.unlinkSync(filePath);
526
+ }
527
+ catch (err) {
528
+ if (err.code !== "ENOENT") {
529
+ process.stderr.write(`queue: warning: failed to remove payload file ${label}: ${err instanceof Error ? err.message : String(err)}\n`);
530
+ }
531
+ }
188
532
  }
189
533
  /**
190
534
  * Find the active job for a given idempotency key.
@@ -219,13 +563,21 @@ export class JobQueue {
219
563
  };
220
564
  // Rebuild the Map with this job first so findNextQueued() picks it up
221
565
  // before any previously queued work.
566
+ const snapshot = this.snapshotState();
222
567
  const entries = [...this.jobs.entries()];
223
568
  this.jobs.clear();
224
569
  this.jobs.set(job.id, job);
225
570
  for (const [id, j] of entries) {
226
571
  this.jobs.set(id, j);
227
572
  }
228
- await this.persist();
573
+ try {
574
+ await this.persist();
575
+ }
576
+ catch (err) {
577
+ this.restoreSnapshot(snapshot);
578
+ this.deletePayloadFilesForJob(job.id);
579
+ throw err;
580
+ }
229
581
  this.processNext();
230
582
  return { job, duplicate: false };
231
583
  }
@@ -268,10 +620,18 @@ export class JobQueue {
268
620
  retry_of: failed.id,
269
621
  ...(opts?.payload !== undefined ? { payload: opts.payload } : {}),
270
622
  };
623
+ const snapshot = this.snapshotState();
271
624
  failed.retried_by = retryId;
272
625
  failed.updated_at = new Date().toISOString();
273
626
  this.jobs.set(retryId, retry);
274
- await this.persist();
627
+ try {
628
+ await this.persist();
629
+ }
630
+ catch (err) {
631
+ this.restoreSnapshot(snapshot);
632
+ this.deletePayloadFilesForJob(retryId);
633
+ throw err;
634
+ }
275
635
  this.processNext();
276
636
  return { job: retry, duplicate: false };
277
637
  }
@@ -288,25 +648,105 @@ export class JobQueue {
288
648
  if (!handler)
289
649
  return; // No handler registered — leave queued.
290
650
  this.processing = true;
291
- next.status = "running";
292
- next.updated_at = new Date().toISOString();
293
- this.persist().then(() => {
294
- handler(next)
295
- .then(() => {
296
- next.status = "completed";
297
- next.updated_at = new Date().toISOString();
298
- })
299
- .catch((err) => {
300
- next.status = "failed";
301
- next.error =
302
- err instanceof Error ? err.message : String(err);
303
- next.updated_at = new Date().toISOString();
304
- })
305
- .finally(() => {
651
+ void (async () => {
652
+ try {
653
+ await this.applyPersistedMutation(() => {
654
+ next.status = "running";
655
+ next.updated_at = new Date().toISOString();
656
+ });
657
+ }
658
+ catch (err) {
306
659
  this.processing = false;
307
- this.persist().then(() => this.processNext());
308
- });
309
- });
660
+ process.stderr.write(`queue: failed to persist start of job ${next.id}: ${err instanceof Error ? err.message : String(err)}\n`);
661
+ this.scheduleStartRetry();
662
+ return;
663
+ }
664
+ this.clearStartRetry();
665
+ try {
666
+ await handler(next);
667
+ await this.applyTerminalMutation(`completion of job ${next.id}`, () => {
668
+ next.status = "completed";
669
+ next.updated_at = new Date().toISOString();
670
+ });
671
+ }
672
+ catch (err) {
673
+ await this.applyTerminalMutation(`failure of job ${next.id}`, () => {
674
+ next.status = "failed";
675
+ next.error = err instanceof Error ? err.message : String(err);
676
+ next.updated_at = new Date().toISOString();
677
+ });
678
+ }
679
+ finally {
680
+ this.processing = false;
681
+ this.processNext();
682
+ }
683
+ })();
684
+ }
685
+ async applyPersistedMutation(mutator) {
686
+ const snapshot = this.snapshotState();
687
+ mutator();
688
+ try {
689
+ await this.persist();
690
+ }
691
+ catch (err) {
692
+ this.restoreSnapshot(snapshot);
693
+ throw err;
694
+ }
695
+ }
696
+ async applyTerminalMutation(label, mutator) {
697
+ mutator();
698
+ try {
699
+ await this.persist();
700
+ }
701
+ catch (err) {
702
+ this.markPersistDirty(label, err);
703
+ }
704
+ }
705
+ markPersistDirty(label, err) {
706
+ process.stderr.write(`queue: failed to persist ${label}: ${err instanceof Error ? err.message : String(err)}; will retry\n`);
707
+ this.schedulePersistRetry();
708
+ }
709
+ schedulePersistRetry() {
710
+ if (this.persistRetryTimer)
711
+ return;
712
+ this.persistRetryTimer = setTimeout(() => {
713
+ this.persistRetryTimer = undefined;
714
+ void this.retryDirtyPersist();
715
+ }, PERSIST_RETRY_DELAY_MS);
716
+ this.persistRetryTimer.unref?.();
717
+ }
718
+ scheduleStartRetry() {
719
+ if (this.startRetryTimer)
720
+ return;
721
+ const delayMs = this.startRetryDelayMs;
722
+ this.startRetryDelayMs = Math.min(this.startRetryDelayMs * 2, START_RETRY_MAX_DELAY_MS);
723
+ this.startRetryTimer = setTimeout(() => {
724
+ this.startRetryTimer = undefined;
725
+ this.processNext();
726
+ }, delayMs);
727
+ this.startRetryTimer.unref?.();
728
+ }
729
+ clearStartRetry() {
730
+ if (this.startRetryTimer) {
731
+ clearTimeout(this.startRetryTimer);
732
+ this.startRetryTimer = undefined;
733
+ }
734
+ this.startRetryDelayMs = START_RETRY_INITIAL_DELAY_MS;
735
+ }
736
+ clearPersistRetry() {
737
+ if (!this.persistRetryTimer)
738
+ return;
739
+ clearTimeout(this.persistRetryTimer);
740
+ this.persistRetryTimer = undefined;
741
+ }
742
+ async retryDirtyPersist() {
743
+ try {
744
+ await this.persist();
745
+ }
746
+ catch (err) {
747
+ process.stderr.write(`queue: failed to retry dirty ledger persist: ${err instanceof Error ? err.message : String(err)}\n`);
748
+ this.schedulePersistRetry();
749
+ }
310
750
  }
311
751
  findNextQueued() {
312
752
  for (const job of this.jobs.values()) {