@rulemetric/cli 0.6.9 → 0.7.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 (288) hide show
  1. package/dist/base-command.js +4 -3
  2. package/dist/{chunk-SZ7VDCD6.js → chunk-2HK7RGZB.js} +22 -3
  3. package/dist/chunk-2HK7RGZB.js.map +7 -0
  4. package/dist/{chunk-RUCSRN7F.js → chunk-2ORJWTNZ.js} +2 -2
  5. package/dist/{chunk-SLL3OBKN.js → chunk-37LN5O7M.js} +2 -2
  6. package/dist/chunk-3S7223UR.js +81 -0
  7. package/dist/chunk-3S7223UR.js.map +7 -0
  8. package/dist/{chunk-DAJNO4OI.js → chunk-4LYCDS2N.js} +28 -16
  9. package/dist/chunk-4LYCDS2N.js.map +7 -0
  10. package/dist/{chunk-UPXIO7CG.js → chunk-4XAWK6S2.js} +8 -8
  11. package/dist/{chunk-QD6X4L4L.js → chunk-5BAOGNUH.js} +4 -4
  12. package/dist/chunk-5EDMGK3W.js +39 -0
  13. package/dist/chunk-5EDMGK3W.js.map +7 -0
  14. package/dist/{chunk-5ADHO5QF.js → chunk-6NBS5XFS.js} +6 -6
  15. package/dist/{chunk-BBLTN35F.js → chunk-7DULPSFU.js} +49 -20
  16. package/dist/chunk-7DULPSFU.js.map +7 -0
  17. package/dist/chunk-7LVVKQMZ.js +98 -0
  18. package/dist/chunk-7LVVKQMZ.js.map +7 -0
  19. package/dist/{chunk-PXVQLD7S.js → chunk-7NNFYWKK.js} +48 -1
  20. package/dist/chunk-7NNFYWKK.js.map +7 -0
  21. package/dist/{chunk-DTFXCHHI.js → chunk-DNKSVWMD.js} +4 -4
  22. package/dist/{chunk-CNMWYYSV.js → chunk-DUJSRBLP.js} +2 -2
  23. package/dist/chunk-EARWG4EV.js +22 -0
  24. package/dist/chunk-EARWG4EV.js.map +7 -0
  25. package/dist/{chunk-6EMZI4Y3.js → chunk-EI5BHWXA.js} +437 -401
  26. package/dist/chunk-EI5BHWXA.js.map +7 -0
  27. package/dist/{chunk-HNZLEEG2.js → chunk-EJ3YAFDP.js} +4 -4
  28. package/dist/{chunk-HNZLEEG2.js.map → chunk-EJ3YAFDP.js.map} +2 -2
  29. package/dist/{chunk-YSEQWCN4.js → chunk-EYPAAGN5.js} +2 -2
  30. package/dist/{chunk-JEJ3J5J6.js → chunk-GIUBARWS.js} +2 -2
  31. package/dist/chunk-GQNJO2P2.js +102 -0
  32. package/dist/chunk-GQNJO2P2.js.map +7 -0
  33. package/dist/chunk-ILYDELUE.js +46 -0
  34. package/dist/chunk-ILYDELUE.js.map +7 -0
  35. package/dist/chunk-J6QFW4LD.js +55 -0
  36. package/dist/chunk-J6QFW4LD.js.map +7 -0
  37. package/dist/{chunk-OTOWLUOJ.js → chunk-JULOLTZS.js} +2 -2
  38. package/dist/{chunk-DR5WLIOU.js → chunk-KJKBDQ4D.js} +2 -2
  39. package/dist/{chunk-XN23W5HL.js → chunk-LXKPNATC.js} +93 -7
  40. package/dist/chunk-LXKPNATC.js.map +7 -0
  41. package/dist/{chunk-N5A2IQAK.js → chunk-OK3I555A.js} +2 -2
  42. package/dist/{chunk-XNNOXGP6.js → chunk-OKWCKCPE.js} +38 -19
  43. package/dist/chunk-OKWCKCPE.js.map +7 -0
  44. package/dist/{chunk-AHDRN6NE.js → chunk-OO7JDFS4.js} +5 -5
  45. package/dist/{chunk-N2X35ITW.js → chunk-OQBBVTDG.js} +11 -1
  46. package/dist/{chunk-N2X35ITW.js.map → chunk-OQBBVTDG.js.map} +2 -2
  47. package/dist/{chunk-3QH73QOQ.js → chunk-PED7L5GX.js} +2 -2
  48. package/dist/{chunk-VBGUNFKY.js → chunk-PMI56ED5.js} +5 -5
  49. package/dist/{chunk-RCMXTLQF.js → chunk-RMTT4KDY.js} +2 -2
  50. package/dist/{chunk-W4MZXKZ2.js → chunk-UKMLICWS.js} +2 -2
  51. package/dist/{chunk-3POYC5NA.js → chunk-XK42ZBDE.js} +3 -3
  52. package/dist/{chunk-ZSGKRCKD.js → chunk-XUV6DDBF.js} +2 -2
  53. package/dist/{chunk-LAKZUE35.js → chunk-Y3WD5CSW.js} +2 -2
  54. package/dist/{chunk-3QVMIJUL.js → chunk-YOBA3NAM.js} +5 -5
  55. package/dist/{chunk-ZRLYHBPN.js → chunk-ZUJBM6HE.js} +20 -2
  56. package/dist/chunk-ZUJBM6HE.js.map +7 -0
  57. package/dist/commands/antigravity/backfill.js +5 -4
  58. package/dist/commands/antigravity/backfill.js.map +1 -1
  59. package/dist/commands/auth/create-key.js +4 -3
  60. package/dist/commands/auth/create-key.js.map +1 -1
  61. package/dist/commands/auth/login.js +13 -9
  62. package/dist/commands/auth/login.js.map +2 -2
  63. package/dist/commands/auth/logout.js +4 -3
  64. package/dist/commands/auth/logout.js.map +1 -1
  65. package/dist/commands/auth/revoke-key.js +4 -3
  66. package/dist/commands/auth/revoke-key.js.map +1 -1
  67. package/dist/commands/auth/signup.js +13 -9
  68. package/dist/commands/auth/signup.js.map +2 -2
  69. package/dist/commands/auth/status.js +4 -3
  70. package/dist/commands/auth/status.js.map +1 -1
  71. package/dist/commands/catalog/index.js +4 -3
  72. package/dist/commands/catalog/index.js.map +1 -1
  73. package/dist/commands/catalog/recommend.js +4 -3
  74. package/dist/commands/catalog/recommend.js.map +1 -1
  75. package/dist/commands/convert.js +4 -3
  76. package/dist/commands/convert.js.map +1 -1
  77. package/dist/commands/converters.js +4 -3
  78. package/dist/commands/converters.js.map +1 -1
  79. package/dist/commands/crons/list.js +4 -3
  80. package/dist/commands/crons/list.js.map +1 -1
  81. package/dist/commands/crons/run.js +4 -3
  82. package/dist/commands/crons/run.js.map +1 -1
  83. package/dist/commands/dashboard.js +4 -3
  84. package/dist/commands/dashboard.js.map +1 -1
  85. package/dist/commands/diagnostics.js +117 -0
  86. package/dist/commands/diagnostics.js.map +7 -0
  87. package/dist/commands/eval-targets/create.js +4 -3
  88. package/dist/commands/eval-targets/create.js.map +1 -1
  89. package/dist/commands/eval-targets/list.js +4 -3
  90. package/dist/commands/eval-targets/list.js.map +1 -1
  91. package/dist/commands/evals/agent.js +41 -20
  92. package/dist/commands/evals/agent.js.map +2 -2
  93. package/dist/commands/evals/benchmark.js +5 -4
  94. package/dist/commands/evals/benchmark.js.map +1 -1
  95. package/dist/commands/evals/compare.js +4 -3
  96. package/dist/commands/evals/compare.js.map +1 -1
  97. package/dist/commands/evals/create.js +4 -3
  98. package/dist/commands/evals/create.js.map +1 -1
  99. package/dist/commands/evals/optimize-description.js +4 -3
  100. package/dist/commands/evals/optimize-description.js.map +1 -1
  101. package/dist/commands/evals/optimize.js +4 -3
  102. package/dist/commands/evals/optimize.js.map +1 -1
  103. package/dist/commands/evals/run.js +4 -3
  104. package/dist/commands/evals/run.js.map +1 -1
  105. package/dist/commands/gateway/ensure.js +75 -5
  106. package/dist/commands/gateway/ensure.js.map +2 -2
  107. package/dist/commands/hooks/install.js +76 -23
  108. package/dist/commands/hooks/install.js.map +2 -2
  109. package/dist/commands/hooks/run.js +4 -3
  110. package/dist/commands/hooks/run.js.map +1 -1
  111. package/dist/commands/hooks/uninstall.js +31 -40
  112. package/dist/commands/hooks/uninstall.js.map +2 -2
  113. package/dist/commands/import.js +4 -3
  114. package/dist/commands/import.js.map +1 -1
  115. package/dist/commands/instructions/create.js +4 -3
  116. package/dist/commands/instructions/create.js.map +1 -1
  117. package/dist/commands/instructions/delete.js +4 -3
  118. package/dist/commands/instructions/delete.js.map +1 -1
  119. package/dist/commands/instructions/fork.js +4 -3
  120. package/dist/commands/instructions/fork.js.map +1 -1
  121. package/dist/commands/instructions/get.js +4 -3
  122. package/dist/commands/instructions/get.js.map +1 -1
  123. package/dist/commands/instructions/list.js +4 -3
  124. package/dist/commands/instructions/list.js.map +1 -1
  125. package/dist/commands/instructions/promote.js +4 -3
  126. package/dist/commands/instructions/promote.js.map +1 -1
  127. package/dist/commands/instructions/pull.js +4 -3
  128. package/dist/commands/instructions/pull.js.map +1 -1
  129. package/dist/commands/instructions/upstream.js +4 -3
  130. package/dist/commands/instructions/upstream.js.map +1 -1
  131. package/dist/commands/instructions/versions.js +4 -3
  132. package/dist/commands/instructions/versions.js.map +1 -1
  133. package/dist/commands/launcher.js +4 -3
  134. package/dist/commands/launcher.js.map +1 -1
  135. package/dist/commands/org/current.js +4 -3
  136. package/dist/commands/org/current.js.map +1 -1
  137. package/dist/commands/org/list.js +4 -3
  138. package/dist/commands/org/list.js.map +1 -1
  139. package/dist/commands/org/switch.js +4 -3
  140. package/dist/commands/org/switch.js.map +1 -1
  141. package/dist/commands/packs/add-instruction.js +4 -3
  142. package/dist/commands/packs/add-instruction.js.map +1 -1
  143. package/dist/commands/packs/apply.js +4 -3
  144. package/dist/commands/packs/apply.js.map +1 -1
  145. package/dist/commands/packs/create.js +4 -3
  146. package/dist/commands/packs/create.js.map +1 -1
  147. package/dist/commands/packs/fork.js +4 -3
  148. package/dist/commands/packs/fork.js.map +1 -1
  149. package/dist/commands/packs/get.js +4 -3
  150. package/dist/commands/packs/get.js.map +1 -1
  151. package/dist/commands/packs/list.js +4 -3
  152. package/dist/commands/packs/list.js.map +1 -1
  153. package/dist/commands/packs/remove-instruction.js +4 -3
  154. package/dist/commands/packs/remove-instruction.js.map +1 -1
  155. package/dist/commands/proxy/env.js +7 -4
  156. package/dist/commands/proxy/env.js.map +1 -1
  157. package/dist/commands/proxy/logs.js +4 -3
  158. package/dist/commands/proxy/logs.js.map +1 -1
  159. package/dist/commands/proxy/restart.js +4 -3
  160. package/dist/commands/proxy/restart.js.map +1 -1
  161. package/dist/commands/proxy/setup.js +20 -14
  162. package/dist/commands/proxy/setup.js.map +2 -2
  163. package/dist/commands/proxy/start.js +29 -15
  164. package/dist/commands/proxy/start.js.map +2 -2
  165. package/dist/commands/proxy/status.js +16 -13
  166. package/dist/commands/proxy/status.js.map +2 -2
  167. package/dist/commands/proxy/stop.js +21 -12
  168. package/dist/commands/proxy/stop.js.map +2 -2
  169. package/dist/commands/research/backfill.js +5 -4
  170. package/dist/commands/research/backfill.js.map +1 -1
  171. package/dist/commands/research/tail-transcript.js +5 -4
  172. package/dist/commands/research/tail-transcript.js.map +1 -1
  173. package/dist/commands/service/install.js +6 -6
  174. package/dist/commands/service/status.js +4 -3
  175. package/dist/commands/service/status.js.map +1 -1
  176. package/dist/commands/service/uninstall.js +4 -3
  177. package/dist/commands/service/uninstall.js.map +1 -1
  178. package/dist/commands/sessions/analyze.js +4 -3
  179. package/dist/commands/sessions/analyze.js.map +1 -1
  180. package/dist/commands/sessions/end.js +4 -3
  181. package/dist/commands/sessions/end.js.map +1 -1
  182. package/dist/commands/sessions/import.js +4 -3
  183. package/dist/commands/sessions/import.js.map +1 -1
  184. package/dist/commands/sessions/list.js +4 -3
  185. package/dist/commands/sessions/list.js.map +1 -1
  186. package/dist/commands/sessions/start.js +4 -3
  187. package/dist/commands/sessions/start.js.map +1 -1
  188. package/dist/commands/setup.js +10 -6
  189. package/dist/commands/setup.js.map +1 -1
  190. package/dist/commands/skills/export-approved.js +4 -3
  191. package/dist/commands/skills/export-approved.js.map +1 -1
  192. package/dist/commands/skills/install.js +4 -3
  193. package/dist/commands/skills/install.js.map +1 -1
  194. package/dist/commands/skills/list.js +4 -3
  195. package/dist/commands/skills/list.js.map +1 -1
  196. package/dist/commands/skills/publish.js +4 -3
  197. package/dist/commands/skills/publish.js.map +1 -1
  198. package/dist/commands/skills/search.js +4 -3
  199. package/dist/commands/skills/search.js.map +1 -1
  200. package/dist/commands/skills/update.js +4 -3
  201. package/dist/commands/skills/update.js.map +1 -1
  202. package/dist/commands/suggestions/accept.js +4 -3
  203. package/dist/commands/suggestions/accept.js.map +1 -1
  204. package/dist/commands/suggestions/dismiss.js +4 -3
  205. package/dist/commands/suggestions/dismiss.js.map +1 -1
  206. package/dist/commands/suggestions/list.js +4 -3
  207. package/dist/commands/suggestions/list.js.map +1 -1
  208. package/dist/commands/suggestions/refresh.js +4 -3
  209. package/dist/commands/suggestions/refresh.js.map +1 -1
  210. package/dist/dashboard/Dashboard.js +16 -16
  211. package/dist/dashboard/data.js +2 -2
  212. package/dist/dashboard/launcher/ActiveJobsList.js +2 -2
  213. package/dist/dashboard/launcher/LauncherOverlay.js +7 -7
  214. package/dist/dashboard/launcher/NewLaunchForm.js +2 -2
  215. package/dist/dashboard/launcher/api.js +2 -2
  216. package/dist/dashboard/launcher/useLaunchJobs.js +3 -3
  217. package/dist/lib/active-org-refresh.js +2 -2
  218. package/dist/lib/agent-loop.js +19 -17
  219. package/dist/lib/antigravity-transcript.js +2 -2
  220. package/dist/lib/api-client.js +1 -1
  221. package/dist/lib/capture-hosts.js +8 -0
  222. package/dist/lib/capture-hosts.js.map +7 -0
  223. package/dist/lib/diagnostics.js +12 -0
  224. package/dist/lib/diagnostics.js.map +7 -0
  225. package/dist/lib/editor-proxy-pins.js +14 -0
  226. package/dist/lib/editor-proxy-pins.js.map +7 -0
  227. package/dist/lib/ensure-api-key.js +3 -3
  228. package/dist/lib/eval-benchmark.js +2 -2
  229. package/dist/lib/eval-conversation-handler.js +2 -2
  230. package/dist/lib/gateway-entry.js +39 -1
  231. package/dist/lib/gateway-entry.js.map +2 -2
  232. package/dist/lib/gateway-identity.js +16 -0
  233. package/dist/lib/gateway-identity.js.map +7 -0
  234. package/dist/lib/gateway-lifecycle.js +9 -3
  235. package/dist/lib/gateway.js +3 -1
  236. package/dist/lib/handlers/cron-suggest-instructions.js +3 -3
  237. package/dist/lib/handlers/process-announcement.js +3 -3
  238. package/dist/lib/handlers/process-changelog.js +2 -2
  239. package/dist/lib/handlers/process-conversation.js +3 -3
  240. package/dist/lib/handlers/process-eval.js +2 -2
  241. package/dist/lib/handlers/process-insights.js +2 -2
  242. package/dist/lib/handlers/process-launch.js +2 -2
  243. package/dist/lib/handlers/process-run-cleanup.js +2 -2
  244. package/dist/lib/handlers/process-session-goal.js +2 -2
  245. package/dist/lib/hooks-config.js +18 -1
  246. package/dist/lib/manual-tasks.js +5 -5
  247. package/dist/lib/pid-identity.js +14 -0
  248. package/dist/lib/pid-identity.js.map +7 -0
  249. package/dist/lib/proxy-respawn.js +12 -0
  250. package/dist/lib/proxy-respawn.js.map +7 -0
  251. package/dist/lib/research-client.js +2 -2
  252. package/dist/lib/setup-steps.js +7 -4
  253. package/dist/lib/statusline-shim.js +3 -1
  254. package/dist/lib/telemetry.js +4 -2
  255. package/dist/lib/terminal-target-heartbeat.js +2 -2
  256. package/oclif.manifest.json +34 -1
  257. package/package.json +4 -4
  258. package/dist/chunk-6EMZI4Y3.js.map +0 -7
  259. package/dist/chunk-BBLTN35F.js.map +0 -7
  260. package/dist/chunk-DAJNO4OI.js.map +0 -7
  261. package/dist/chunk-PXVQLD7S.js.map +0 -7
  262. package/dist/chunk-SZ7VDCD6.js.map +0 -7
  263. package/dist/chunk-TX2HKJLA.js +0 -55
  264. package/dist/chunk-TX2HKJLA.js.map +0 -7
  265. package/dist/chunk-XN23W5HL.js.map +0 -7
  266. package/dist/chunk-XNNOXGP6.js.map +0 -7
  267. package/dist/chunk-ZRLYHBPN.js.map +0 -7
  268. /package/dist/{chunk-RUCSRN7F.js.map → chunk-2ORJWTNZ.js.map} +0 -0
  269. /package/dist/{chunk-SLL3OBKN.js.map → chunk-37LN5O7M.js.map} +0 -0
  270. /package/dist/{chunk-UPXIO7CG.js.map → chunk-4XAWK6S2.js.map} +0 -0
  271. /package/dist/{chunk-QD6X4L4L.js.map → chunk-5BAOGNUH.js.map} +0 -0
  272. /package/dist/{chunk-5ADHO5QF.js.map → chunk-6NBS5XFS.js.map} +0 -0
  273. /package/dist/{chunk-DTFXCHHI.js.map → chunk-DNKSVWMD.js.map} +0 -0
  274. /package/dist/{chunk-CNMWYYSV.js.map → chunk-DUJSRBLP.js.map} +0 -0
  275. /package/dist/{chunk-YSEQWCN4.js.map → chunk-EYPAAGN5.js.map} +0 -0
  276. /package/dist/{chunk-JEJ3J5J6.js.map → chunk-GIUBARWS.js.map} +0 -0
  277. /package/dist/{chunk-OTOWLUOJ.js.map → chunk-JULOLTZS.js.map} +0 -0
  278. /package/dist/{chunk-DR5WLIOU.js.map → chunk-KJKBDQ4D.js.map} +0 -0
  279. /package/dist/{chunk-N5A2IQAK.js.map → chunk-OK3I555A.js.map} +0 -0
  280. /package/dist/{chunk-AHDRN6NE.js.map → chunk-OO7JDFS4.js.map} +0 -0
  281. /package/dist/{chunk-3QH73QOQ.js.map → chunk-PED7L5GX.js.map} +0 -0
  282. /package/dist/{chunk-VBGUNFKY.js.map → chunk-PMI56ED5.js.map} +0 -0
  283. /package/dist/{chunk-RCMXTLQF.js.map → chunk-RMTT4KDY.js.map} +0 -0
  284. /package/dist/{chunk-W4MZXKZ2.js.map → chunk-UKMLICWS.js.map} +0 -0
  285. /package/dist/{chunk-3POYC5NA.js.map → chunk-XK42ZBDE.js.map} +0 -0
  286. /package/dist/{chunk-ZSGKRCKD.js.map → chunk-XUV6DDBF.js.map} +0 -0
  287. /package/dist/{chunk-LAKZUE35.js.map → chunk-Y3WD5CSW.js.map} +0 -0
  288. /package/dist/{chunk-3QVMIJUL.js.map → chunk-YOBA3NAM.js.map} +0 -0
@@ -1,10 +1,11 @@
1
1
  import {
2
2
  BaseCommand
3
- } from "./chunk-SZ7VDCD6.js";
3
+ } from "./chunk-2HK7RGZB.js";
4
4
  import "./chunk-R56PH7YE.js";
5
- import "./chunk-SLL3OBKN.js";
5
+ import "./chunk-GQNJO2P2.js";
6
+ import "./chunk-37LN5O7M.js";
6
7
  import "./chunk-EKJ2CABV.js";
7
- import "./chunk-ZRLYHBPN.js";
8
+ import "./chunk-ZUJBM6HE.js";
8
9
  import "./chunk-W2YERO7E.js";
9
10
  import "./chunk-NSBPE2FW.js";
10
11
  export {
@@ -2,18 +2,29 @@ import {
2
2
  getUpgradeBanner,
3
3
  refreshVersionCacheInBackground
4
4
  } from "./chunk-R56PH7YE.js";
5
+ import {
6
+ emitErrorBeacon
7
+ } from "./chunk-GQNJO2P2.js";
5
8
  import {
6
9
  maybeRefreshActiveOrg
7
- } from "./chunk-SLL3OBKN.js";
10
+ } from "./chunk-37LN5O7M.js";
8
11
  import {
9
12
  ApiError
10
- } from "./chunk-ZRLYHBPN.js";
13
+ } from "./chunk-ZUJBM6HE.js";
11
14
  import {
12
15
  isAuthenticated
13
16
  } from "./chunk-W2YERO7E.js";
14
17
 
15
18
  // src/base-command.ts
16
19
  import { Command, Flags } from "@oclif/core";
20
+ function isAvailabilityError(err) {
21
+ if (err instanceof ApiError) {
22
+ return err.statusCode >= 500 || err.statusCode === 408 || err.statusCode === 0;
23
+ }
24
+ return /fetch failed|ECONNREFUSED|ECONNRESET|ETIMEDOUT|EAI_AGAIN|bad gateway|timed out/i.test(
25
+ err.message ?? ""
26
+ );
27
+ }
17
28
  var BaseCommand = class extends Command {
18
29
  static baseFlags = {
19
30
  "api-url": Flags.string({
@@ -44,6 +55,14 @@ ${banner}
44
55
  void maybeRefreshActiveOrg();
45
56
  }
46
57
  async catch(err) {
58
+ if (isAvailabilityError(err)) {
59
+ await emitErrorBeacon({
60
+ command: this.id,
61
+ statusCode: err instanceof ApiError ? err.statusCode : void 0,
62
+ message: err.message,
63
+ cliVersion: this.config.version
64
+ });
65
+ }
47
66
  if (err instanceof ApiError) {
48
67
  if (err.statusCode === 401) {
49
68
  this.error("Session expired. Run `rulemetric auth login` to re-authenticate.");
@@ -60,4 +79,4 @@ ${banner}
60
79
  export {
61
80
  BaseCommand
62
81
  };
63
- //# sourceMappingURL=chunk-SZ7VDCD6.js.map
82
+ //# sourceMappingURL=chunk-2HK7RGZB.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/base-command.ts"],
4
+ "sourcesContent": ["import { Command, Flags } from '@oclif/core';\nimport { isAuthenticated } from './lib/auth.js';\nimport { ApiError } from './lib/api-client.js';\nimport { maybeRefreshActiveOrg } from './lib/active-org-refresh.js';\nimport { getUpgradeBanner, refreshVersionCacheInBackground } from './lib/version-check.js';\nimport { emitErrorBeacon } from './lib/telemetry.js';\n\n/**\n * Is this error an API AVAILABILITY failure (server/edge/proxy unreachable), as\n * opposed to a normal 4xx business error (401/404/409)? These are the failures\n * the unauthenticated error beacon exists to surface \u2014 a 502 from a broken local\n * gateway, a 5xx from the edge, a client timeout (408, added in api-client), or\n * a raw connection failure \u2014 precisely the class that authenticated telemetry\n * can never report because it happens before/instead of auth.\n */\nfunction isAvailabilityError(err: Error): boolean {\n if (err instanceof ApiError) {\n return err.statusCode >= 500 || err.statusCode === 408 || err.statusCode === 0;\n }\n return /fetch failed|ECONNREFUSED|ECONNRESET|ETIMEDOUT|EAI_AGAIN|bad gateway|timed out/i.test(\n err.message ?? '',\n );\n}\n\nexport abstract class BaseCommand extends Command {\n static override baseFlags = {\n 'api-url': Flags.string({\n description: 'Override API base URL',\n env: 'RULEMETRIC_API_URL',\n hidden: true,\n }),\n };\n\n protected override async init(): Promise<void> {\n await super.init();\n const { flags } = await this.parse(this.constructor as typeof BaseCommand);\n if (flags['api-url']) {\n process.env.RULEMETRIC_API_URL = flags['api-url'];\n }\n // Print the upgrade banner from the cache (populated by the previous\n // command). To stderr so it doesn't pollute scriptable stdout. Then\n // kick off a background refresh for the *next* invocation. Suppressed\n // on commands meant to be machine-readable (`hooks run`, etc.) since\n // a banner there would corrupt JSON output piped to other tools.\n const banner = getUpgradeBanner(this.config.version);\n if (banner && !this.argv.includes('--quiet') && !process.env.RULEMETRIC_QUIET) {\n process.stderr.write(`\\n${banner}\\n\\n`);\n }\n refreshVersionCacheInBackground();\n }\n\n protected requireAuth(): void {\n if (!isAuthenticated()) {\n this.error('Not logged in. Run `rulemetric auth login` to authenticate.');\n }\n // Fire-and-forget refresh of the local active-org cache. Doesn't block\n // the command \u2014 its only side effect is rewriting the cache file.\n void maybeRefreshActiveOrg();\n }\n\n protected override async catch(err: Error & { exitCode?: number }): Promise<void> {\n // Surface availability failures (5xx / 502 / timeout / connection refused) to\n // the unauthenticated, proxy-bypassing error beacon FIRST \u2014 this is the only\n // path that makes a pre-auth / broken-gateway onboarding failure visible\n // server-side. Best-effort + bounded internally; awaited so it lands before\n // this.error() exits the process.\n if (isAvailabilityError(err)) {\n await emitErrorBeacon({\n command: this.id,\n statusCode: err instanceof ApiError ? err.statusCode : undefined,\n message: err.message,\n cliVersion: this.config.version,\n });\n }\n\n if (err instanceof ApiError) {\n if (err.statusCode === 401) {\n this.error('Session expired. Run `rulemetric auth login` to re-authenticate.');\n }\n\n this.error(err.message);\n }\n\n if (err.message?.includes('fetch failed') || err.message?.includes('ECONNREFUSED')) {\n this.error('Could not connect to the API server. Is it running?');\n }\n\n throw err;\n }\n}\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;AAAA,SAAS,SAAS,aAAa;AAe/B,SAAS,oBAAoB,KAAqB;AAChD,MAAI,eAAe,UAAU;AAC3B,WAAO,IAAI,cAAc,OAAO,IAAI,eAAe,OAAO,IAAI,eAAe;AAAA,EAC/E;AACA,SAAO,kFAAkF;AAAA,IACvF,IAAI,WAAW;AAAA,EACjB;AACF;AAEO,IAAe,cAAf,cAAmC,QAAQ;AAAA,EAChD,OAAgB,YAAY;AAAA,IAC1B,WAAW,MAAM,OAAO;AAAA,MACtB,aAAa;AAAA,MACb,KAAK;AAAA,MACL,QAAQ;AAAA,IACV,CAAC;AAAA,EACH;AAAA,EAEA,MAAyB,OAAsB;AAC7C,UAAM,MAAM,KAAK;AACjB,UAAM,EAAE,MAAM,IAAI,MAAM,KAAK,MAAM,KAAK,WAAiC;AACzE,QAAI,MAAM,SAAS,GAAG;AACpB,cAAQ,IAAI,qBAAqB,MAAM,SAAS;AAAA,IAClD;AAMA,UAAM,SAAS,iBAAiB,KAAK,OAAO,OAAO;AACnD,QAAI,UAAU,CAAC,KAAK,KAAK,SAAS,SAAS,KAAK,CAAC,QAAQ,IAAI,kBAAkB;AAC7E,cAAQ,OAAO,MAAM;AAAA,EAAK,MAAM;AAAA;AAAA,CAAM;AAAA,IACxC;AACA,oCAAgC;AAAA,EAClC;AAAA,EAEU,cAAoB;AAC5B,QAAI,CAAC,gBAAgB,GAAG;AACtB,WAAK,MAAM,6DAA6D;AAAA,IAC1E;AAGA,SAAK,sBAAsB;AAAA,EAC7B;AAAA,EAEA,MAAyB,MAAM,KAAmD;AAMhF,QAAI,oBAAoB,GAAG,GAAG;AAC5B,YAAM,gBAAgB;AAAA,QACpB,SAAS,KAAK;AAAA,QACd,YAAY,eAAe,WAAW,IAAI,aAAa;AAAA,QACvD,SAAS,IAAI;AAAA,QACb,YAAY,KAAK,OAAO;AAAA,MAC1B,CAAC;AAAA,IACH;AAEA,QAAI,eAAe,UAAU;AAC3B,UAAI,IAAI,eAAe,KAAK;AAC1B,aAAK,MAAM,kEAAkE;AAAA,MAC/E;AAEA,WAAK,MAAM,IAAI,OAAO;AAAA,IACxB;AAEA,QAAI,IAAI,SAAS,SAAS,cAAc,KAAK,IAAI,SAAS,SAAS,cAAc,GAAG;AAClF,WAAK,MAAM,qDAAqD;AAAA,IAClE;AAEA,UAAM;AAAA,EACR;AACF;",
6
+ "names": []
7
+ }
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  ApiError,
3
3
  api
4
- } from "./chunk-ZRLYHBPN.js";
4
+ } from "./chunk-ZUJBM6HE.js";
5
5
 
6
6
  // src/lib/antigravity-transcript.ts
7
7
  import { existsSync, mkdirSync, readFileSync, renameSync, rmdirSync, writeFileSync } from "node:fs";
@@ -304,4 +304,4 @@ export {
304
304
  tailAntigravityTranscript,
305
305
  stateApi
306
306
  };
307
- //# sourceMappingURL=chunk-RUCSRN7F.js.map
307
+ //# sourceMappingURL=chunk-2ORJWTNZ.js.map
@@ -6,7 +6,7 @@ import {
6
6
  } from "./chunk-EKJ2CABV.js";
7
7
  import {
8
8
  apiGet
9
- } from "./chunk-ZRLYHBPN.js";
9
+ } from "./chunk-ZUJBM6HE.js";
10
10
 
11
11
  // src/lib/active-org-refresh.ts
12
12
  var attempted = false;
@@ -56,4 +56,4 @@ export {
56
56
  bootstrapActiveOrg,
57
57
  _resetRefreshAttempt
58
58
  };
59
- //# sourceMappingURL=chunk-SLL3OBKN.js.map
59
+ //# sourceMappingURL=chunk-37LN5O7M.js.map
@@ -0,0 +1,81 @@
1
+ import {
2
+ getCursorUserSettingsPath,
3
+ getVSCodeUserSettingsPath
4
+ } from "./chunk-LXKPNATC.js";
5
+
6
+ // src/lib/editor-proxy-pins.ts
7
+ import { existsSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
8
+ import { homedir } from "node:os";
9
+ import { join } from "node:path";
10
+ function defaultEditorPinPaths() {
11
+ return {
12
+ vscode: getVSCodeUserSettingsPath(),
13
+ cursor: getCursorUserSettingsPath(),
14
+ marker: join(homedir(), ".config", "rulemetric", "editor-proxy-unpinned.json")
15
+ };
16
+ }
17
+ function isOurProxyValue(value, gatewayPort) {
18
+ return value === `http://localhost:${gatewayPort}` || value === `http://127.0.0.1:${gatewayPort}`;
19
+ }
20
+ function readSettings(path) {
21
+ if (!existsSync(path)) return null;
22
+ try {
23
+ return JSON.parse(readFileSync(path, "utf-8"));
24
+ } catch {
25
+ return null;
26
+ }
27
+ }
28
+ function unpinEditorProxies(gatewayPort, paths = defaultEditorPinPaths()) {
29
+ const unpinned = [];
30
+ for (const editor of ["vscode", "cursor"]) {
31
+ const path = paths[editor];
32
+ const settings = readSettings(path);
33
+ if (!settings || !isOurProxyValue(settings["http.proxy"], gatewayPort)) continue;
34
+ delete settings["http.proxy"];
35
+ if (settings["http.proxyStrictSSL"] === false) {
36
+ delete settings["http.proxyStrictSSL"];
37
+ }
38
+ try {
39
+ writeFileSync(path, JSON.stringify(settings, null, 2) + "\n");
40
+ unpinned.push(editor);
41
+ } catch {
42
+ }
43
+ }
44
+ if (unpinned.length > 0) {
45
+ const prior = readSettings(paths.marker);
46
+ const editors = [.../* @__PURE__ */ new Set([...prior?.editors ?? [], ...unpinned])];
47
+ try {
48
+ writeFileSync(
49
+ paths.marker,
50
+ JSON.stringify({ editors, gatewayPort, unpinnedAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2) + "\n"
51
+ );
52
+ } catch {
53
+ }
54
+ }
55
+ return unpinned;
56
+ }
57
+ function repinEditorProxies(writePin, paths = defaultEditorPinPaths()) {
58
+ const marker = readSettings(paths.marker);
59
+ if (!marker || !Array.isArray(marker.editors)) return [];
60
+ const repinned = [];
61
+ for (const editor of marker.editors) {
62
+ if (editor !== "vscode" && editor !== "cursor") continue;
63
+ try {
64
+ writePin(editor);
65
+ repinned.push(editor);
66
+ } catch {
67
+ }
68
+ }
69
+ try {
70
+ unlinkSync(paths.marker);
71
+ } catch {
72
+ }
73
+ return repinned;
74
+ }
75
+
76
+ export {
77
+ defaultEditorPinPaths,
78
+ unpinEditorProxies,
79
+ repinEditorProxies
80
+ };
81
+ //# sourceMappingURL=chunk-3S7223UR.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/lib/editor-proxy-pins.ts"],
4
+ "sourcesContent": ["import { existsSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { getVSCodeUserSettingsPath, getCursorUserSettingsPath } from './hooks-config.js';\n\n/**\n * Editor proxy pin lifecycle for a DEAD/unownable gateway.\n *\n * `hooks install` pins `http.proxy` (+ `http.proxyStrictSSL: false`) into\n * VS Code and Cursor USER-level settings so Copilot/Cursor traffic routes\n * through the capture gateway. Those pins outlive the gateway: after a crash\n * that can't self-heal \u2014 most importantly a FOREIGN process owning :8787 \u2014\n * the editors keep routing HTTP at a port we don't control, with strictSSL\n * off. `gateway ensure` (run on every session start) calls `unpin` in that\n * state and `repin` once the gateway is verifiably ours again.\n *\n * Ownership predicate is strict: `http.proxy` is only ever removed when it\n * points at OUR gateway port on localhost \u2014 a user-owned corporate proxy is\n * never touched (same discipline as removeRulemetricProxyEnv). What was\n * unpinned is recorded in a marker file so repin restores exactly that set,\n * and never manufactures a pin the user removed themselves.\n */\n\nexport interface EditorPinPaths {\n vscode: string;\n cursor: string;\n marker: string;\n}\n\nexport function defaultEditorPinPaths(): EditorPinPaths {\n return {\n vscode: getVSCodeUserSettingsPath(),\n cursor: getCursorUserSettingsPath(),\n marker: join(homedir(), '.config', 'rulemetric', 'editor-proxy-unpinned.json'),\n };\n}\n\nfunction isOurProxyValue(value: unknown, gatewayPort: number): boolean {\n return (\n value === `http://localhost:${gatewayPort}` ||\n value === `http://127.0.0.1:${gatewayPort}`\n );\n}\n\nfunction readSettings(path: string): Record<string, unknown> | null {\n if (!existsSync(path)) return null;\n try {\n return JSON.parse(readFileSync(path, 'utf-8')) as Record<string, unknown>;\n } catch {\n return null; // JSONC/corrupt \u2014 never risk clobbering a user's editor config\n }\n}\n\n/**\n * Remove our gateway pin from each editor's user settings. Returns the list\n * of editors actually unpinned and records them in the marker (merging with\n * a prior marker so repeated unpins don't lose state).\n */\nexport function unpinEditorProxies(\n gatewayPort: number,\n paths: EditorPinPaths = defaultEditorPinPaths(),\n): Array<'vscode' | 'cursor'> {\n const unpinned: Array<'vscode' | 'cursor'> = [];\n for (const editor of ['vscode', 'cursor'] as const) {\n const path = paths[editor];\n const settings = readSettings(path);\n if (!settings || !isOurProxyValue(settings['http.proxy'], gatewayPort)) continue;\n delete settings['http.proxy'];\n // strictSSL rides along with our pin; only strip it when the pin was ours\n // (checked above) AND it holds the value we wrote.\n if (settings['http.proxyStrictSSL'] === false) {\n delete settings['http.proxyStrictSSL'];\n }\n try {\n writeFileSync(path, JSON.stringify(settings, null, 2) + '\\n');\n unpinned.push(editor);\n } catch {\n /* editor settings not writable \u2014 leave as-is */\n }\n }\n if (unpinned.length > 0) {\n const prior = readSettings(paths.marker) as { editors?: string[] } | null;\n const editors = [...new Set([...(prior?.editors ?? []), ...unpinned])];\n try {\n writeFileSync(\n paths.marker,\n JSON.stringify({ editors, gatewayPort, unpinnedAt: new Date().toISOString() }, null, 2) + '\\n',\n );\n } catch {\n /* marker write failure only costs a future auto-repin */\n }\n }\n return unpinned;\n}\n\n/**\n * Restore pins for exactly the editors a previous unpin removed (per the\n * marker), then clear the marker. `writePin` is injected by the caller\n * (writeVSCodeUserProxyConfig / writeCursorUserProxyConfig) \u2014 those writers\n * already refuse to overwrite a user-owned non-localhost proxy.\n */\nexport function repinEditorProxies(\n writePin: (editor: 'vscode' | 'cursor') => void,\n paths: EditorPinPaths = defaultEditorPinPaths(),\n): Array<'vscode' | 'cursor'> {\n const marker = readSettings(paths.marker) as { editors?: unknown } | null;\n if (!marker || !Array.isArray(marker.editors)) return [];\n const repinned: Array<'vscode' | 'cursor'> = [];\n for (const editor of marker.editors) {\n if (editor !== 'vscode' && editor !== 'cursor') continue;\n try {\n writePin(editor);\n repinned.push(editor);\n } catch {\n /* best-effort \u2014 a failed repin keeps the marker below */\n }\n }\n try {\n unlinkSync(paths.marker);\n } catch {\n /* already gone */\n }\n return repinned;\n}\n"],
5
+ "mappings": ";;;;;;AAAA,SAAS,YAAY,cAAc,YAAY,qBAAqB;AACpE,SAAS,eAAe;AACxB,SAAS,YAAY;AA2Bd,SAAS,wBAAwC;AACtD,SAAO;AAAA,IACL,QAAQ,0BAA0B;AAAA,IAClC,QAAQ,0BAA0B;AAAA,IAClC,QAAQ,KAAK,QAAQ,GAAG,WAAW,cAAc,4BAA4B;AAAA,EAC/E;AACF;AAEA,SAAS,gBAAgB,OAAgB,aAA8B;AACrE,SACE,UAAU,oBAAoB,WAAW,MACzC,UAAU,oBAAoB,WAAW;AAE7C;AAEA,SAAS,aAAa,MAA8C;AAClE,MAAI,CAAC,WAAW,IAAI,EAAG,QAAO;AAC9B,MAAI;AACF,WAAO,KAAK,MAAM,aAAa,MAAM,OAAO,CAAC;AAAA,EAC/C,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAOO,SAAS,mBACd,aACA,QAAwB,sBAAsB,GAClB;AAC5B,QAAM,WAAuC,CAAC;AAC9C,aAAW,UAAU,CAAC,UAAU,QAAQ,GAAY;AAClD,UAAM,OAAO,MAAM,MAAM;AACzB,UAAM,WAAW,aAAa,IAAI;AAClC,QAAI,CAAC,YAAY,CAAC,gBAAgB,SAAS,YAAY,GAAG,WAAW,EAAG;AACxE,WAAO,SAAS,YAAY;AAG5B,QAAI,SAAS,qBAAqB,MAAM,OAAO;AAC7C,aAAO,SAAS,qBAAqB;AAAA,IACvC;AACA,QAAI;AACF,oBAAc,MAAM,KAAK,UAAU,UAAU,MAAM,CAAC,IAAI,IAAI;AAC5D,eAAS,KAAK,MAAM;AAAA,IACtB,QAAQ;AAAA,IAER;AAAA,EACF;AACA,MAAI,SAAS,SAAS,GAAG;AACvB,UAAM,QAAQ,aAAa,MAAM,MAAM;AACvC,UAAM,UAAU,CAAC,GAAG,oBAAI,IAAI,CAAC,GAAI,OAAO,WAAW,CAAC,GAAI,GAAG,QAAQ,CAAC,CAAC;AACrE,QAAI;AACF;AAAA,QACE,MAAM;AAAA,QACN,KAAK,UAAU,EAAE,SAAS,aAAa,aAAY,oBAAI,KAAK,GAAE,YAAY,EAAE,GAAG,MAAM,CAAC,IAAI;AAAA,MAC5F;AAAA,IACF,QAAQ;AAAA,IAER;AAAA,EACF;AACA,SAAO;AACT;AAQO,SAAS,mBACd,UACA,QAAwB,sBAAsB,GAClB;AAC5B,QAAM,SAAS,aAAa,MAAM,MAAM;AACxC,MAAI,CAAC,UAAU,CAAC,MAAM,QAAQ,OAAO,OAAO,EAAG,QAAO,CAAC;AACvD,QAAM,WAAuC,CAAC;AAC9C,aAAW,UAAU,OAAO,SAAS;AACnC,QAAI,WAAW,YAAY,WAAW,SAAU;AAChD,QAAI;AACF,eAAS,MAAM;AACf,eAAS,KAAK,MAAM;AAAA,IACtB,QAAQ;AAAA,IAER;AAAA,EACF;AACA,MAAI;AACF,eAAW,MAAM,MAAM;AAAA,EACzB,QAAQ;AAAA,EAER;AACA,SAAO;AACT;",
6
+ "names": []
7
+ }
@@ -1,3 +1,12 @@
1
+ import {
2
+ probeGatewayIdentity,
3
+ waitForGatewayIdentity
4
+ } from "./chunk-7LVVKQMZ.js";
5
+ import {
6
+ GATEWAY_ENTRY_COMMAND_PATTERN,
7
+ isPidLiveAndMatching
8
+ } from "./chunk-5EDMGK3W.js";
9
+
1
10
  // src/lib/gateway-lifecycle.ts
2
11
  import { spawn } from "node:child_process";
3
12
  import { createConnection } from "node:net";
@@ -32,17 +41,16 @@ async function waitForGatewayListening(port = GATEWAY_PORT, { timeoutMs = 3e3, i
32
41
  await new Promise((resolve2) => setTimeout(resolve2, intervalMs));
33
42
  }
34
43
  }
35
- function isGatewayRunning() {
36
- if (!existsSync(GATEWAY_PID_FILE)) return false;
37
- const content = readFileSync(GATEWAY_PID_FILE, "utf-8").trim();
38
- const pid = parseInt(content, 10);
39
- if (isNaN(pid)) return false;
40
- try {
41
- process.kill(pid, 0);
42
- return true;
43
- } catch {
44
- return false;
45
- }
44
+ function isRulemetricGatewayListening(port = GATEWAY_PORT, timeoutMs = 1500) {
45
+ return probeGatewayIdentity(port, timeoutMs);
46
+ }
47
+ function waitForRulemetricGateway(port = GATEWAY_PORT, opts = {}) {
48
+ return waitForGatewayIdentity(port, opts);
49
+ }
50
+ function isGatewayRunning(pidOpts = {}) {
51
+ const pid = getGatewayPid();
52
+ if (pid === null) return false;
53
+ return isPidLiveAndMatching(pid, GATEWAY_ENTRY_COMMAND_PATTERN, pidOpts);
46
54
  }
47
55
  function getGatewayPid() {
48
56
  if (!existsSync(GATEWAY_PID_FILE)) return null;
@@ -98,12 +106,14 @@ function spawnGateway(cliVersion) {
98
106
  writeFileSync(GATEWAY_PID_FILE, String(child.pid));
99
107
  return child.pid;
100
108
  }
101
- function stopGateway() {
109
+ function stopGateway(pidOpts = {}) {
102
110
  const pid = getGatewayPid();
103
111
  if (pid === null) return false;
104
- try {
105
- process.kill(pid, "SIGTERM");
106
- } catch {
112
+ if (isPidLiveAndMatching(pid, GATEWAY_ENTRY_COMMAND_PATTERN, pidOpts)) {
113
+ try {
114
+ process.kill(pid, "SIGTERM");
115
+ } catch {
116
+ }
107
117
  }
108
118
  try {
109
119
  unlinkSync(GATEWAY_PID_FILE);
@@ -119,9 +129,11 @@ export {
119
129
  MITMPROXY_PORT,
120
130
  isGatewayListening,
121
131
  waitForGatewayListening,
132
+ isRulemetricGatewayListening,
133
+ waitForRulemetricGateway,
122
134
  isGatewayRunning,
123
135
  getGatewayPid,
124
136
  spawnGateway,
125
137
  stopGateway
126
138
  };
127
- //# sourceMappingURL=chunk-DAJNO4OI.js.map
139
+ //# sourceMappingURL=chunk-4LYCDS2N.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/lib/gateway-lifecycle.ts"],
4
+ "sourcesContent": ["/**\n * Gateway process lifecycle helpers \u2014 spawn, stop, check status.\n * Used by hooks install/uninstall, session-start hook, and proxy status.\n */\nimport { spawn } from 'node:child_process';\nimport { createConnection } from 'node:net';\nimport { existsSync, mkdirSync, openSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { dirname, join, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport {\n probeGatewayIdentity,\n waitForGatewayIdentity,\n type GatewayIdentity,\n} from './gateway-identity.js';\nimport {\n isPidLiveAndMatching,\n GATEWAY_ENTRY_COMMAND_PATTERN,\n type PidIdentityOptions,\n} from './pid-identity.js';\n\n// `import.meta.dirname` only exists on Node >= 20.11 \u2014 on Node 18.x/\u226420.10 it is\n// `undefined`, and `resolve(undefined, \u2026)` throws, surfacing as a spurious\n// \"Could not find gateway-entry.js\" on fresh npm installs (the launchd gateway\n// masks it in dev). Derive the dir portably instead. See onboarding field report\n// 2026-07-07 F1.\nconst MODULE_DIR = dirname(fileURLToPath(import.meta.url));\n\nconst CONFIG_DIR = join(homedir(), '.config', 'rulemetric');\nexport const GATEWAY_PID_FILE = join(CONFIG_DIR, 'gateway.pid');\nexport const GATEWAY_LOG_FILE = join(CONFIG_DIR, 'gateway.log');\nexport const GATEWAY_PORT = 8787;\nexport const MITMPROXY_PORT = 8788;\n\n/**\n * TCP liveness probe: does something actually ACCEPT a connection on the port?\n * Distinct from isGatewayRunning() (which only checks the PID is alive) \u2014 a\n * process can be \"running\" per its pidfile while :8787 accepts nothing. This is\n * the check that must gate writing HTTPS_PROXY: never pin Claude Code's proxy\n * at a port with nothing behind it (the fresh-laptop ConnectionRefused footgun).\n */\nexport function isGatewayListening(port: number = GATEWAY_PORT, timeoutMs = 1500): Promise<boolean> {\n return new Promise((resolveProbe) => {\n const socket = createConnection({ host: '127.0.0.1', port });\n const finish = (result: boolean) => {\n socket.destroy();\n resolveProbe(result);\n };\n socket.setTimeout(timeoutMs);\n socket.once('connect', () => finish(true));\n socket.once('timeout', () => finish(false));\n socket.once('error', () => finish(false));\n });\n}\n\n/**\n * Poll isGatewayListening() until it's true or the window elapses. spawnGateway()\n * returns as soon as it writes the PID file \u2014 the detached child hasn't bound\n * :8787 yet (Node bootstrap + module load). A one-shot probe microseconds later\n * fails-closed on every fresh install, so the proxy/deep-capture layer silently\n * never turns on. This tolerates a normal cold-start bind delay.\n */\nexport async function waitForGatewayListening(\n port: number = GATEWAY_PORT,\n { timeoutMs = 3000, intervalMs = 150 }: { timeoutMs?: number; intervalMs?: number } = {},\n): Promise<boolean> {\n const deadline = Date.now() + timeoutMs;\n for (;;) {\n if (await isGatewayListening(port, Math.min(1500, timeoutMs))) return true;\n if (Date.now() >= deadline) return false;\n await new Promise((resolve) => setTimeout(resolve, intervalMs));\n }\n}\n\n/**\n * Identity-aware liveness probe on :GATEWAY_PORT \u2014 connects, requests the\n * identity marker, and distinguishes ours-live / foreign / dead. This (not\n * the raw TCP probe above) is what HTTPS_PROXY-pinning decisions must use: a\n * foreign process accepting on :8787 passes isGatewayListening() but would\n * brick every HTTPS request Claude Code makes.\n */\nexport function isRulemetricGatewayListening(\n port: number = GATEWAY_PORT,\n timeoutMs = 1500,\n): Promise<GatewayIdentity> {\n return probeGatewayIdentity(port, timeoutMs);\n}\n\n/**\n * Poll the identity probe through a cold-start bind delay. 'dead' retries\n * until the window elapses; 'ours-live' and 'foreign' return immediately\n * (waiting can't change what owns the port).\n */\nexport function waitForRulemetricGateway(\n port: number = GATEWAY_PORT,\n opts: { timeoutMs?: number; intervalMs?: number } = {},\n): Promise<GatewayIdentity> {\n return waitForGatewayIdentity(port, opts);\n}\n\n/**\n * PID-file liveness WITH identity validation. PID files survive reboots, and\n * `kill -0` on a reused PID reports \"alive\" for an unrelated process \u2014 which\n * permanently defeats the session-start self-heal. The recorded PID only\n * counts as \"running\" when its command line is actually gateway-entry.\n */\nexport function isGatewayRunning(pidOpts: PidIdentityOptions = {}): boolean {\n const pid = getGatewayPid();\n if (pid === null) return false;\n return isPidLiveAndMatching(pid, GATEWAY_ENTRY_COMMAND_PATTERN, pidOpts);\n}\n\nexport function getGatewayPid(): number | null {\n if (!existsSync(GATEWAY_PID_FILE)) return null;\n const content = readFileSync(GATEWAY_PID_FILE, 'utf-8').trim();\n const pid = parseInt(content, 10);\n return isNaN(pid) ? null : pid;\n}\n\nfunction findGatewayEntry(): string {\n // Resolve the compiled gateway-entry.js across BOTH layouts:\n // - unbundled (dev/tsx, or tsc output): this module sits in dist/lib, so\n // gateway-entry.js is a sibling (`./gateway-entry.js`).\n // - BUNDLED (published build): esbuild flattens this module into\n // dist/chunk-*.js at the dist ROOT, so MODULE_DIR is dist/ and the entry\n // (kept as a separate build entry) is at dist/lib/gateway-entry.js\n // (`./lib/gateway-entry.js`). Without this candidate the gateway never\n // spawns on a clean npm install \u2014 masked in dev by the launchd gateway\n // short-circuiting before this runs. See onboarding field report F1.\n const candidates = [\n resolve(MODULE_DIR, './gateway-entry.js'), // sibling (unbundled)\n resolve(MODULE_DIR, './lib/gateway-entry.js'), // bundled chunk at dist root\n resolve(MODULE_DIR, '../lib/gateway-entry.js'), // nested command dir \u2192 dist/lib\n resolve(MODULE_DIR, '../../lib/gateway-entry.js'),\n ];\n for (const p of candidates) {\n if (existsSync(p)) return p;\n }\n throw new Error(\n 'Could not find gateway-entry.js. Is the CLI built?\\n' +\n 'Searched:\\n' + candidates.map(c => ` ${c}`).join('\\n'),\n );\n}\n\nexport function spawnGateway(cliVersion?: string): number {\n if (isGatewayRunning()) {\n const pid = getGatewayPid();\n if (pid) return pid;\n }\n\n mkdirSync(CONFIG_DIR, { recursive: true });\n\n const entryPath = findGatewayEntry();\n const logFd = openSync(GATEWAY_LOG_FILE, 'a');\n\n const child = spawn(process.execPath, [entryPath], {\n detached: true,\n stdio: ['ignore', logFd, logFd],\n // The gateway inherits the spawning CLI's env. RULEMETRIC_CLI_VERSION\n // identifies the CLI version that started this gateway \u2014 used to\n // detect drift when a newer CLI starts mitmproxy with a different\n // version. RULEMETRIC_CLI_ENTRY is the path to the oclif entry point\n // (process.argv[1]) so the gateway can re-invoke the CLI for\n // `proxy restart` without needing the binary in PATH. Falls back to\n // \"unknown\" if the caller didn't pass them.\n env: {\n ...process.env,\n RULEMETRIC_CLI_VERSION: cliVersion ?? 'unknown',\n RULEMETRIC_CLI_ENTRY: process.argv[1] ?? '',\n },\n });\n\n child.unref();\n\n if (!child.pid) {\n throw new Error('Failed to spawn gateway process');\n }\n\n writeFileSync(GATEWAY_PID_FILE, String(child.pid));\n return child.pid;\n}\n\nexport function stopGateway(pidOpts: PidIdentityOptions = {}): boolean {\n const pid = getGatewayPid();\n if (pid === null) return false;\n\n // Only signal a PID whose command line is actually gateway-entry \u2014 a\n // reused PID belongs to an unrelated process we must never SIGTERM. Either\n // way the PID file is stale after this and gets removed.\n if (isPidLiveAndMatching(pid, GATEWAY_ENTRY_COMMAND_PATTERN, pidOpts)) {\n try {\n process.kill(pid, 'SIGTERM');\n } catch {\n // Process already gone\n }\n }\n\n try { unlinkSync(GATEWAY_PID_FILE); } catch { /* already gone */ }\n return true;\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["resolve"]
7
+ }
@@ -1,20 +1,20 @@
1
1
  import {
2
2
  ActiveJobsList
3
- } from "./chunk-QD6X4L4L.js";
3
+ } from "./chunk-5BAOGNUH.js";
4
4
  import {
5
5
  NewLaunchForm
6
- } from "./chunk-DTFXCHHI.js";
6
+ } from "./chunk-DNKSVWMD.js";
7
+ import {
8
+ ORANGE
9
+ } from "./chunk-QJQS6TUN.js";
7
10
  import {
8
11
  useLaunchJobs
9
- } from "./chunk-YSEQWCN4.js";
12
+ } from "./chunk-EYPAAGN5.js";
10
13
  import {
11
14
  cancelLaunchJob,
12
15
  createLaunchJob,
13
16
  listLaunchers
14
- } from "./chunk-W4MZXKZ2.js";
15
- import {
16
- ORANGE
17
- } from "./chunk-QJQS6TUN.js";
17
+ } from "./chunk-UKMLICWS.js";
18
18
 
19
19
  // src/dashboard/launcher/LauncherOverlay.tsx
20
20
  import { useEffect, useState } from "react";
@@ -158,4 +158,4 @@ function LauncherOverlay({
158
158
  export {
159
159
  LauncherOverlay
160
160
  };
161
- //# sourceMappingURL=chunk-UPXIO7CG.js.map
161
+ //# sourceMappingURL=chunk-4XAWK6S2.js.map
@@ -1,12 +1,12 @@
1
- import {
2
- toolLabel
3
- } from "./chunk-RH7NHVSK.js";
4
1
  import {
5
2
  DIM,
6
3
  GREEN,
7
4
  ORANGE,
8
5
  RED
9
6
  } from "./chunk-QJQS6TUN.js";
7
+ import {
8
+ toolLabel
9
+ } from "./chunk-RH7NHVSK.js";
10
10
 
11
11
  // src/dashboard/launcher/ActiveJobsList.tsx
12
12
  import { Box, Text } from "ink";
@@ -76,4 +76,4 @@ function ActiveJobsList({
76
76
  export {
77
77
  ActiveJobsList
78
78
  };
79
- //# sourceMappingURL=chunk-QD6X4L4L.js.map
79
+ //# sourceMappingURL=chunk-5BAOGNUH.js.map
@@ -0,0 +1,39 @@
1
+ // src/lib/pid-identity.ts
2
+ import { execFileSync } from "node:child_process";
3
+ var GATEWAY_ENTRY_COMMAND_PATTERN = /gateway-entry\.[cm]?js/;
4
+ var MITMPROXY_COMMAND_PATTERN = /mitm(dump|web)/;
5
+ function pidAlive(pid) {
6
+ try {
7
+ process.kill(pid, 0);
8
+ return true;
9
+ } catch (e) {
10
+ return e.code === "EPERM";
11
+ }
12
+ }
13
+ function readCommandViaPs(pid) {
14
+ try {
15
+ return execFileSync("ps", ["-p", String(pid), "-o", "command="], {
16
+ encoding: "utf-8",
17
+ stdio: ["ignore", "pipe", "ignore"]
18
+ });
19
+ } catch {
20
+ return null;
21
+ }
22
+ }
23
+ function isPidLiveAndMatching(pid, commandPattern, opts = {}) {
24
+ if (!Number.isInteger(pid) || pid <= 0) return false;
25
+ const isAlive = opts.isAlive ?? pidAlive;
26
+ if (!isAlive(pid)) return false;
27
+ const readCommand = opts.readCommand ?? readCommandViaPs;
28
+ const command = readCommand(pid);
29
+ if (command === null) return true;
30
+ return commandPattern.test(command);
31
+ }
32
+
33
+ export {
34
+ GATEWAY_ENTRY_COMMAND_PATTERN,
35
+ MITMPROXY_COMMAND_PATTERN,
36
+ pidAlive,
37
+ isPidLiveAndMatching
38
+ };
39
+ //# sourceMappingURL=chunk-5EDMGK3W.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/lib/pid-identity.ts"],
4
+ "sourcesContent": ["/**\n * PID-file identity validation \u2014 the shared defense against PID reuse.\n *\n * PID files survive reboots and SIGKILLs, and the OS may later hand the\n * recorded PID to an unrelated process. `kill -0` alone then reports \"alive\"\n * forever: self-heal is defeated and restarts are blocked (\"already running\").\n * Before trusting a PID file, confirm the process's command line actually\n * matches what the file claims to track (same pattern as the evals-agent\n * single-instance lock).\n */\nimport { execFileSync } from 'node:child_process';\n\n/** The gateway is always spawned as `node \u2026/gateway-entry.js`. */\nexport const GATEWAY_ENTRY_COMMAND_PATTERN = /gateway-entry\\.[cm]?js/;\n/** mitmproxy runs as mitmdump or mitmweb (pipx/brew both keep the name in argv). */\nexport const MITMPROXY_COMMAND_PATTERN = /mitm(dump|web)/;\n\nexport interface PidIdentityOptions {\n /** Override the liveness check (`process.kill(pid, 0)`). Used in tests. */\n isAlive?: (pid: number) => boolean;\n /**\n * Override `ps -p <pid> -o command=`. Return the command line, or null when\n * ps itself is unavailable/failed. Used in tests.\n */\n readCommand?: (pid: number) => string | null;\n}\n\n/** `kill -0` liveness. EPERM means the process exists but is owned by another user. */\nexport function pidAlive(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch (e) {\n return (e as NodeJS.ErrnoException).code === 'EPERM';\n }\n}\n\nfunction readCommandViaPs(pid: number): string | null {\n try {\n return execFileSync('ps', ['-p', String(pid), '-o', 'command='], {\n encoding: 'utf-8',\n stdio: ['ignore', 'pipe', 'ignore'],\n });\n } catch {\n // ps unavailable (e.g. Windows) or failed \u2014 identity is unverifiable.\n return null;\n }\n}\n\n/**\n * True iff `pid` is alive AND its command line matches `commandPattern`.\n * A live-but-mismatched PID is a reused PID: callers must treat the PID file\n * as stale (reclaim/overwrite), and must never signal that process.\n * When the command line can't be read at all, falls back to liveness only\n * (conservative: a live PID stays \"held\", matching the evals-agent lock).\n */\nexport function isPidLiveAndMatching(\n pid: number,\n commandPattern: RegExp,\n opts: PidIdentityOptions = {},\n): boolean {\n if (!Number.isInteger(pid) || pid <= 0) return false;\n const isAlive = opts.isAlive ?? pidAlive;\n if (!isAlive(pid)) return false;\n const readCommand = opts.readCommand ?? readCommandViaPs;\n const command = readCommand(pid);\n if (command === null) return true;\n return commandPattern.test(command);\n}\n"],
5
+ "mappings": ";AAUA,SAAS,oBAAoB;AAGtB,IAAM,gCAAgC;AAEtC,IAAM,4BAA4B;AAalC,SAAS,SAAS,KAAsB;AAC7C,MAAI;AACF,YAAQ,KAAK,KAAK,CAAC;AACnB,WAAO;AAAA,EACT,SAAS,GAAG;AACV,WAAQ,EAA4B,SAAS;AAAA,EAC/C;AACF;AAEA,SAAS,iBAAiB,KAA4B;AACpD,MAAI;AACF,WAAO,aAAa,MAAM,CAAC,MAAM,OAAO,GAAG,GAAG,MAAM,UAAU,GAAG;AAAA,MAC/D,UAAU;AAAA,MACV,OAAO,CAAC,UAAU,QAAQ,QAAQ;AAAA,IACpC,CAAC;AAAA,EACH,QAAQ;AAEN,WAAO;AAAA,EACT;AACF;AASO,SAAS,qBACd,KACA,gBACA,OAA2B,CAAC,GACnB;AACT,MAAI,CAAC,OAAO,UAAU,GAAG,KAAK,OAAO,EAAG,QAAO;AAC/C,QAAM,UAAU,KAAK,WAAW;AAChC,MAAI,CAAC,QAAQ,GAAG,EAAG,QAAO;AAC1B,QAAM,cAAc,KAAK,eAAe;AACxC,QAAM,UAAU,YAAY,GAAG;AAC/B,MAAI,YAAY,KAAM,QAAO;AAC7B,SAAO,eAAe,KAAK,OAAO;AACpC;",
6
+ "names": []
7
+ }
@@ -1,14 +1,14 @@
1
+ import {
2
+ agentRuns,
3
+ getDb,
4
+ sessions
5
+ } from "./chunk-EI5BHWXA.js";
1
6
  import {
2
7
  removeWorktree
3
8
  } from "./chunk-Y4BJXXYV.js";
4
9
  import {
5
10
  PermanentJobError
6
11
  } from "./chunk-DGHWRQXL.js";
7
- import {
8
- agentRuns,
9
- getDb,
10
- sessions
11
- } from "./chunk-6EMZI4Y3.js";
12
12
 
13
13
  // src/lib/handlers/process-run-cleanup.ts
14
14
  import { and, eq, isNotNull } from "drizzle-orm";
@@ -96,4 +96,4 @@ async function processRunCleanup(payload, deps = {}) {
96
96
  export {
97
97
  processRunCleanup
98
98
  };
99
- //# sourceMappingURL=chunk-5ADHO5QF.js.map
99
+ //# sourceMappingURL=chunk-6NBS5XFS.js.map
@@ -1,44 +1,44 @@
1
1
  import {
2
2
  startTerminalTargetHeartbeat
3
- } from "./chunk-JEJ3J5J6.js";
4
- import {
5
- processEval
6
- } from "./chunk-RCMXTLQF.js";
7
- import {
8
- processInsights
9
- } from "./chunk-N5A2IQAK.js";
10
- import {
11
- processLaunch
12
- } from "./chunk-OTOWLUOJ.js";
3
+ } from "./chunk-GIUBARWS.js";
13
4
  import {
14
5
  processRunCleanup
15
- } from "./chunk-5ADHO5QF.js";
6
+ } from "./chunk-6NBS5XFS.js";
16
7
  import {
17
8
  processSendMessage
18
9
  } from "./chunk-W53GKIZQ.js";
19
10
  import {
20
11
  processSessionGoal
21
- } from "./chunk-3QVMIJUL.js";
12
+ } from "./chunk-YOBA3NAM.js";
22
13
  import {
23
14
  cronRefreshSkills
24
15
  } from "./chunk-RQ2TMLKG.js";
25
16
  import {
26
17
  cronSuggestInstructions
27
- } from "./chunk-VBGUNFKY.js";
18
+ } from "./chunk-PMI56ED5.js";
28
19
  import {
29
20
  processAnnouncement
30
- } from "./chunk-AHDRN6NE.js";
21
+ } from "./chunk-OO7JDFS4.js";
22
+ import {
23
+ processConversation
24
+ } from "./chunk-XK42ZBDE.js";
25
+ import {
26
+ processEval
27
+ } from "./chunk-RMTT4KDY.js";
28
+ import {
29
+ processInsights
30
+ } from "./chunk-OK3I555A.js";
31
+ import {
32
+ processLaunch
33
+ } from "./chunk-JULOLTZS.js";
31
34
  import {
32
35
  isPermanentJobError
33
36
  } from "./chunk-DGHWRQXL.js";
34
- import {
35
- processConversation
36
- } from "./chunk-3POYC5NA.js";
37
37
  import {
38
38
  ApiError,
39
39
  apiGet,
40
40
  apiPost
41
- } from "./chunk-ZRLYHBPN.js";
41
+ } from "./chunk-ZUJBM6HE.js";
42
42
 
43
43
  // src/lib/agent-loop.ts
44
44
  var HANDLERS = {
@@ -65,6 +65,15 @@ var HANDLERS = {
65
65
  cron_refresh_skills: cronRefreshSkills
66
66
  };
67
67
  var CAPABILITIES = Object.keys(HANDLERS);
68
+ var DB_REQUIRING_KINDS = /* @__PURE__ */ new Set([
69
+ "process_session_goal",
70
+ "process_announcement",
71
+ "process_run_cleanup"
72
+ ]);
73
+ function availableCapabilities(env = process.env) {
74
+ if (env.DATABASE_URL) return CAPABILITIES;
75
+ return CAPABILITIES.filter((kind) => !DB_REQUIRING_KINDS.has(kind));
76
+ }
68
77
  var DEFAULT_POLL_WAIT_MS = 3e4;
69
78
  var DEFAULT_HEARTBEAT_MS = 3e4;
70
79
  var PRESENCE_HEARTBEAT_MS = 3e4;
@@ -217,7 +226,7 @@ async function runAgentLoop(opts) {
217
226
  const log = opts.log ?? (() => void 0);
218
227
  const pollWaitMs = opts.pollWaitMs ?? DEFAULT_POLL_WAIT_MS;
219
228
  const heartbeatIntervalMs = opts.heartbeatIntervalMs ?? DEFAULT_HEARTBEAT_MS;
220
- const capabilities = opts.capabilities ?? CAPABILITIES;
229
+ const capabilities = opts.capabilities ?? availableCapabilities();
221
230
  let backoffMs = BACKOFF_INITIAL_MS;
222
231
  structuredLog(log, {
223
232
  event: "agent_start",
@@ -232,7 +241,26 @@ async function runAgentLoop(opts) {
232
241
  error: err instanceof Error ? err.message : String(err)
233
242
  });
234
243
  });
244
+ const VERSION_CHECK_INTERVAL_MS = 5 * 6e4;
245
+ let lastVersionCheck = 0;
246
+ const versionDrifted = () => {
247
+ if (!opts.readInstalledVersion || !opts.version) return false;
248
+ const now = Date.now();
249
+ if (now - lastVersionCheck < VERSION_CHECK_INTERVAL_MS) return false;
250
+ lastVersionCheck = now;
251
+ const onDisk = opts.readInstalledVersion();
252
+ return Boolean(onDisk && onDisk !== opts.version);
253
+ };
235
254
  while (!opts.signal.aborted) {
255
+ if (versionDrifted()) {
256
+ structuredLog(log, {
257
+ event: "agent_version_drift",
258
+ bootVersion: opts.version,
259
+ installedVersion: opts.readInstalledVersion?.() ?? null
260
+ });
261
+ opts.onVersionDrift?.();
262
+ break;
263
+ }
236
264
  let job = null;
237
265
  try {
238
266
  job = await pollForWork(capabilities, pollWaitMs);
@@ -264,7 +292,8 @@ async function runAgentLoop(opts) {
264
292
  export {
265
293
  HANDLERS,
266
294
  CAPABILITIES,
295
+ availableCapabilities,
267
296
  pollForWork,
268
297
  runAgentLoop
269
298
  };
270
- //# sourceMappingURL=chunk-BBLTN35F.js.map
299
+ //# sourceMappingURL=chunk-7DULPSFU.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/lib/agent-loop.ts"],
4
+ "sourcesContent": ["// Phase 4b thin-agent migration: the parallel-run worker loop that replaces\n// graphile-worker's LISTEN/NOTIFY pickup with HTTP long-polling against the\n// Phase 4a /api/work endpoints. See docs/plans/2026-05-25-thin-agent-migration.md.\n//\n// This file is invoked exclusively by `rulemetric evals agent` \u2014 the only\n// worker mode since Phase 4c deleted the graphile path.\n//\n// Lifecycle for one cycle:\n// 1. Long-poll GET /api/work/next?capabilities=...&waitMs=...\n// 2. If 204 (returned as \"\" by api-client because no JSON body), increment\n// a small backoff capped at 5s and re-poll.\n// 3. If 200, dispatch by `kind` to the matching handler from HANDLERS.\n// 4. Start a heartbeat timer that POSTs /api/work/:id/heartbeat at\n// heartbeatIntervalMs while the handler runs.\n// 5. On success: POST /api/work/:id/complete with { result, leaseToken }.\n// On PermanentJobError: POST /fail with terminal=true (server will not\n// requeue regardless of attempts). On any other error: POST /fail with\n// terminal=false (server requeues if attempts < max).\n//\n// Abort behavior: an AbortSignal short-circuits the next long-poll and any\n// backoff sleep. An in-flight handler can't be cancelled (it has already\n// spawned `claude` subprocesses), so we await it to natural completion and\n// then exit. This matches listen.ts's 10s shutdown grace pattern, except we\n// don't need an outer hard-kill timer here \u2014 the caller (the oclif command)\n// owns the process.exit.\n\nimport type { JobHelpers } from './job-helpers.js';\nimport { apiGet, apiPost, ApiError } from './api-client.js';\nimport { isPermanentJobError } from './permanent-job-error.js';\nimport { processEval } from './handlers/process-eval.js';\nimport { processInsights } from './handlers/process-insights.js';\nimport { processLaunch } from './handlers/process-launch.js';\nimport { processConversation } from './handlers/process-conversation.js';\nimport { processSendMessage } from './handlers/process-send-message.js';\nimport { processSessionGoal } from './handlers/process-session-goal.js';\nimport { processAnnouncement } from './handlers/process-announcement.js';\nimport { processRunCleanup } from './handlers/process-run-cleanup.js';\nimport { cronSuggestInstructions } from './handlers/cron-suggest-instructions.js';\nimport { cronRefreshSkills } from './handlers/cron-refresh-skills.js';\nimport { startTerminalTargetHeartbeat } from './terminal-target-heartbeat.js';\n\n// Handler dispatch table. Keys MUST match the API's CAPABILITY_TO_TABLE keys\n// in apps/api/src/routes/work.ts; mismatches would cause the API to silently\n// drop the capability and starve this kind of work.\ntype Handler = (\n payload: Record<string, unknown>,\n helpers: JobHelpers,\n) => Promise<Record<string, unknown> | undefined | void>;\n\nexport const HANDLERS: Record<string, Handler> = {\n process_eval: processEval as unknown as Handler,\n process_insights: processInsights as unknown as Handler,\n process_launch: processLaunch as unknown as Handler,\n process_send_message: processSendMessage as unknown as Handler,\n process_conversation: processConversation as unknown as Handler,\n // agent_jobs kinds (Phase 4c Stage 3b, migration 00083). processSessionGoal\n // and processAnnouncement take an optional deps object in the helpers\n // position \u2014 every key is optional, so JobHelpers passing through is\n // harmless and they fall back to their defaults.\n process_session_goal: processSessionGoal as unknown as Handler,\n process_announcement: processAnnouncement as unknown as Handler,\n // Multi-session orchestration (\"runs\") worktree cleanup (Task 5.2). agent_jobs\n // kind \u2014 no mirror table. Enqueued by POST /api/runs/:id/cleanup; prunes the\n // worktrees of a run's ended sessions, guarding against uncommitted work.\n process_run_cleanup: processRunCleanup as unknown as Handler,\n cron_suggest_instructions: cronSuggestInstructions as unknown as Handler,\n // Dropped by accident in the Phase 4c graphile\u2192agent_jobs migration: the\n // handler survived but lost its registration + schedule, so the skills\n // registry froze (last refresh 2026-05-21) and the suggest cron marked\n // every suggestion stale on each run. Re-scheduled by migration 00086.\n cron_refresh_skills: cronRefreshSkills as unknown as Handler,\n};\n\nexport const CAPABILITIES: readonly string[] = Object.keys(HANDLERS);\n\n// Kinds whose handlers open a DIRECT Postgres connection (getDb \u2192 requires\n// DATABASE_URL): process-session-goal, process-announcement, process-run-cleanup.\n// A worker-only npm install (the onboarding default) has API auth but no DB\n// creds \u2014 if it advertises these kinds it claims jobs it can only throw on, and\n// process_session_goal has MAX_ATTEMPTS=1, so a single claim PERMANENTLY fails\n// the job (goalSummary dead for every npm user). Advertise them only when a\n// direct DB connection is actually configured; otherwise the rows stay pending\n// for a repo-checkout worker instead of being burned.\nconst DB_REQUIRING_KINDS: ReadonlySet<string> = new Set([\n 'process_session_goal',\n 'process_announcement',\n 'process_run_cleanup',\n]);\n\n/** The capability list this process can actually serve (see DB_REQUIRING_KINDS). */\nexport function availableCapabilities(\n env: NodeJS.ProcessEnv = process.env,\n): readonly string[] {\n if (env.DATABASE_URL) return CAPABILITIES;\n return CAPABILITIES.filter((kind) => !DB_REQUIRING_KINDS.has(kind));\n}\n\ninterface NextWorkResponse {\n id: string;\n kind: string;\n payload: Record<string, unknown>;\n attempts: number;\n leaseToken: string;\n leaseDurationSeconds: number;\n table: string;\n}\n\nexport interface RunAgentLoopOptions {\n signal: AbortSignal;\n log?: (msg: string) => void;\n /** Long-poll wait passed to GET /api/work/next?waitMs= */\n pollWaitMs?: number;\n /** How often to POST /api/work/:id/heartbeat while a handler runs */\n heartbeatIntervalMs?: number;\n /** Capability list to advertise. Defaults to all known handlers. */\n capabilities?: readonly string[];\n /** Running CLI version, reported as `clientVersion` in the presence heartbeat\n * so the dashboard can detect an outdated CLI and nudge an upgrade. */\n version?: string;\n /** Reads the CURRENTLY INSTALLED CLI version from disk (vs `version`, which\n * is frozen at boot). When they diverge \u2014 `npm i -g` upgraded the CLI under\n * a running worker \u2014 the loop stops between jobs and calls `onVersionDrift`\n * so the supervisor can respawn onto the new code. */\n readInstalledVersion?: () => string | null;\n /** Invoked (idle, never mid-job) when version drift is detected. */\n onVersionDrift?: () => void;\n}\n\nconst DEFAULT_POLL_WAIT_MS = 30_000;\nconst DEFAULT_HEARTBEAT_MS = 30_000;\nconst PRESENCE_HEARTBEAT_MS = 30_000;\nconst BACKOFF_INITIAL_MS = 250;\nconst BACKOFF_MAX_MS = 5_000;\n\n// Stable per-process identifier so worker_connections groups the same agent\n// across heartbeats (the unique key is (userId, workerType, clientName)).\nconst PRESENCE_CLIENT_NAME = `thin-agent-${process.pid}`;\n\n/**\n * Background presence heartbeat. Posts to /api/workers/heartbeat every 30s so\n * worker_connections has a fresh row \u2014 that's what the dashboard's\n * useWorkerStatus reads to decide \"Worker online\" for live-send gating.\n * Distinct from the per-job heartbeat in runOne, which only extends a\n * specific job's lease. The graphile-worker runner had this since day one;\n * the thin agent was missing it, so the dashboard showed \"Worker offline\"\n * even when the agent was actively processing jobs.\n */\nfunction startPresenceHeartbeat(\n capabilities: readonly string[],\n signal: AbortSignal,\n log: (msg: string) => void,\n version: string | undefined,\n): void {\n const send = (status: 'alive' | 'shutting_down') => {\n void apiPost('/api/workers/heartbeat', {\n workerType: 'cli-worker',\n clientName: PRESENCE_CLIENT_NAME,\n clientVersion: version,\n workerVersion: '0.1.0',\n currentJobId: null,\n inFlightCount: 0,\n lastError: null,\n status,\n metadata: { capabilities, mode: 'thin-agent' },\n }).catch((err) => {\n structuredLog(log, {\n event: 'presence_heartbeat_error',\n error: err instanceof Error ? err.message : String(err),\n });\n });\n };\n send('alive');\n const timer = setInterval(() => send('alive'), PRESENCE_HEARTBEAT_MS);\n signal.addEventListener('abort', () => {\n clearInterval(timer);\n send('shutting_down');\n }, { once: true });\n}\n\nfunction structuredLog(\n log: (msg: string) => void,\n event: Record<string, unknown>,\n): void {\n try {\n log(JSON.stringify({ ts: new Date().toISOString(), ...event }));\n } catch {\n // JSON.stringify can throw on a circular structure; fall back to a\n // best-effort string so a log failure never takes down the loop.\n log(`[agent-loop] log_error event=${event['event']}`);\n }\n}\n\n/** Sleep that resolves early when the abort signal fires. Returns true if\n * the full duration elapsed, false if aborted. */\nfunction abortableSleep(ms: number, signal: AbortSignal): Promise<boolean> {\n if (signal.aborted) return Promise.resolve(false);\n if (ms <= 0) return Promise.resolve(true);\n return new Promise<boolean>((resolve) => {\n const timer = setTimeout(() => {\n signal.removeEventListener('abort', onAbort);\n resolve(true);\n }, ms);\n const onAbort = () => {\n clearTimeout(timer);\n signal.removeEventListener('abort', onAbort);\n resolve(false);\n };\n signal.addEventListener('abort', onAbort, { once: true });\n });\n}\n\n/** Poll once for work. Returns the parsed response or null on 204. api-client\n * returns \"\" for an empty response body (Content-Type is not JSON on 204), so\n * we use that as the 204 sentinel. */\nexport async function pollForWork(\n capabilities: readonly string[],\n waitMs: number,\n): Promise<NextWorkResponse | null> {\n const qs = `capabilities=${encodeURIComponent(capabilities.join(','))}&waitMs=${waitMs}`;\n const result = await apiGet<NextWorkResponse | string>(`/api/work/next?${qs}`);\n if (result == null || typeof result === 'string') return null;\n return result;\n}\n\n/** Construct a minimal JobHelpers shim. Handlers only touch\n * helpers.logger.{info,warn,error}(msg) and helpers.job.attempts \u2014 verified\n * by grep against apps/cli/src/lib/handlers/. We cast through `unknown` so\n * TS accepts the structurally smaller object as the full JobHelpers type;\n * pulling graphile-worker into this hot path would defeat the Phase 4c\n * cleanup that removes the dep entirely. */\nfunction makeHelpers(attempts: number, log: (msg: string) => void): JobHelpers {\n return {\n logger: {\n info: (msg: string) => log(msg),\n warn: (msg: string) => log(msg),\n error: (msg: string) => log(msg),\n },\n job: { attempts },\n };\n}\n\nasync function runOne(\n job: NextWorkResponse,\n heartbeatIntervalMs: number,\n log: (msg: string) => void,\n): Promise<void> {\n const handler = HANDLERS[job.kind];\n const startedAt = Date.now();\n structuredLog(log, {\n event: 'claim',\n jobId: job.id,\n kind: job.kind,\n attempts: job.attempts,\n });\n\n // Defensive: if the API returned a kind the agent doesn't know about\n // (capability filter mismatch, schema drift), fail terminally so the row\n // doesn't bounce forever. The /api/work/next filter should already prevent\n // this, but failing closed at the dispatcher means a single new kind on\n // the API side can't wedge an older agent.\n if (!handler) {\n const msg = `unknown kind: ${job.kind}`;\n structuredLog(log, {\n event: 'fail',\n jobId: job.id,\n kind: job.kind,\n terminal: true,\n error: msg,\n });\n await apiPost(`/api/work/${job.id}/fail`, {\n leaseToken: job.leaseToken,\n error: msg,\n terminal: true,\n }).catch(() => {\n // best-effort: the lease will expire and the reconciler reclaims it\n });\n return;\n }\n\n // Heartbeat timer. Starts after the first heartbeatIntervalMs elapses so\n // we don't spam an extra request for sub-second jobs. Errors are swallowed\n // \u2014 a single failed heartbeat doesn't abort the run; the server's\n // 5-minute lease gives us up to ten chances at 30s intervals before we\n // lose the claim.\n const heartbeatTimer = setInterval(() => {\n structuredLog(log, { event: 'heartbeat', jobId: job.id });\n apiPost(`/api/work/${job.id}/heartbeat`, {\n leaseToken: job.leaseToken,\n }).catch((err) => {\n structuredLog(log, {\n event: 'heartbeat_error',\n jobId: job.id,\n error: err instanceof Error ? err.message : String(err),\n });\n });\n }, heartbeatIntervalMs);\n\n try {\n const helpers = makeHelpers(job.attempts, log);\n const result = (await handler(job.payload, helpers)) ?? {};\n clearInterval(heartbeatTimer);\n structuredLog(log, {\n event: 'complete',\n jobId: job.id,\n kind: job.kind,\n durationMs: Date.now() - startedAt,\n });\n await apiPost(`/api/work/${job.id}/complete`, {\n leaseToken: job.leaseToken,\n result,\n });\n } catch (err) {\n clearInterval(heartbeatTimer);\n const terminal = isPermanentJobError(err);\n const message = err instanceof Error ? err.message : String(err);\n structuredLog(log, {\n event: 'fail',\n jobId: job.id,\n kind: job.kind,\n durationMs: Date.now() - startedAt,\n terminal,\n error: message,\n });\n await apiPost(`/api/work/${job.id}/fail`, {\n leaseToken: job.leaseToken,\n error: message,\n terminal,\n }).catch((postErr) => {\n structuredLog(log, {\n event: 'fail_post_error',\n jobId: job.id,\n error: postErr instanceof Error ? postErr.message : String(postErr),\n });\n });\n }\n}\n\nexport async function runAgentLoop(opts: RunAgentLoopOptions): Promise<void> {\n const log = opts.log ?? (() => undefined);\n const pollWaitMs = opts.pollWaitMs ?? DEFAULT_POLL_WAIT_MS;\n const heartbeatIntervalMs = opts.heartbeatIntervalMs ?? DEFAULT_HEARTBEAT_MS;\n const capabilities = opts.capabilities ?? availableCapabilities();\n\n let backoffMs = BACKOFF_INITIAL_MS;\n structuredLog(log, {\n event: 'agent_start',\n capabilities,\n pollWaitMs,\n heartbeatIntervalMs,\n });\n\n // Background presence heartbeat \u2014 writes to worker_connections so the\n // dashboard's \"Worker online\" indicator turns on. Fire-and-forget; failures\n // are logged but don't block the work poll.\n startPresenceHeartbeat(capabilities, opts.signal, log, opts.version);\n\n // Fire-and-forget the terminal-target liveness heartbeat. It runs on its\n // own interval (default 60s) and shuts down on the same abort signal.\n // Errors are caught internally \u2014 we don't await it so a slow tmux probe\n // can't delay the work poll.\n void startTerminalTargetHeartbeat({ signal: opts.signal, log }).catch((err) => {\n structuredLog(log, {\n event: 'tt_heartbeat_fatal',\n error: err instanceof Error ? err.message : String(err),\n });\n });\n\n // Version-drift self-restart: after `npm i -g` upgrades the CLI, this\n // long-lived process keeps executing the OLD code forever \u2014 launchd bounces\n // on dist changes only in the repo-checkout install, and the Linux systemd\n // unit has no WatchPaths equivalent at all. Between jobs (never mid-job),\n // compare the on-disk installed version against the one this process booted\n // with; on drift, hand control to the caller (which exits non-zero so BOTH\n // supervisors respawn onto the new code: systemd is Restart=on-failure,\n // launchd KeepAlive restarts regardless). Throttled \u2014 a disk read per idle\n // poll would be wasteful.\n const VERSION_CHECK_INTERVAL_MS = 5 * 60_000;\n let lastVersionCheck = 0;\n const versionDrifted = (): boolean => {\n if (!opts.readInstalledVersion || !opts.version) return false;\n const now = Date.now();\n if (now - lastVersionCheck < VERSION_CHECK_INTERVAL_MS) return false;\n lastVersionCheck = now;\n const onDisk = opts.readInstalledVersion();\n return Boolean(onDisk && onDisk !== opts.version);\n };\n\n while (!opts.signal.aborted) {\n if (versionDrifted()) {\n structuredLog(log, {\n event: 'agent_version_drift',\n bootVersion: opts.version,\n installedVersion: opts.readInstalledVersion?.() ?? null,\n });\n opts.onVersionDrift?.();\n break;\n }\n let job: NextWorkResponse | null = null;\n try {\n job = await pollForWork(capabilities, pollWaitMs);\n } catch (err) {\n // Network/API errors during poll: log + backoff. The API may be\n // restarting, the laptop may have just woken, etc. Don't crash the\n // loop \u2014 that's the whole point of this migration.\n const status = err instanceof ApiError ? err.statusCode : undefined;\n structuredLog(log, {\n event: 'poll_error',\n status,\n error: err instanceof Error ? err.message : String(err),\n });\n const completed = await abortableSleep(backoffMs, opts.signal);\n if (!completed) break;\n backoffMs = Math.min(backoffMs * 2, BACKOFF_MAX_MS);\n continue;\n }\n\n if (!job) {\n structuredLog(log, { event: 'poll_204' });\n const completed = await abortableSleep(backoffMs, opts.signal);\n if (!completed) break;\n backoffMs = Math.min(backoffMs * 2, BACKOFF_MAX_MS);\n continue;\n }\n\n // Got work \u2014 reset the backoff and run it. An in-flight handler is\n // intentionally not abort-cancellable; we await its natural completion\n // even if the signal fires mid-run.\n backoffMs = BACKOFF_INITIAL_MS;\n await runOne(job, heartbeatIntervalMs, log);\n }\n\n structuredLog(log, { event: 'agent_stop' });\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }