@isaacriehm/cairn 0.1.0 โ†’ 0.1.3

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 (533) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/index.d.ts +0 -1
  3. package/dist/index.js +0 -1
  4. package/dist/index.js.map +1 -1
  5. package/package.json +4 -4
  6. package/dist/backprop/id.d.ts +0 -14
  7. package/dist/backprop/id.js +0 -40
  8. package/dist/backprop/id.js.map +0 -1
  9. package/dist/backprop/index.d.ts +0 -23
  10. package/dist/backprop/index.js +0 -21
  11. package/dist/backprop/index.js.map +0 -1
  12. package/dist/backprop/prompt.d.ts +0 -16
  13. package/dist/backprop/prompt.js +0 -101
  14. package/dist/backprop/prompt.js.map +0 -1
  15. package/dist/backprop/runner.d.ts +0 -18
  16. package/dist/backprop/runner.js +0 -95
  17. package/dist/backprop/runner.js.map +0 -1
  18. package/dist/backprop/schema.d.ts +0 -61
  19. package/dist/backprop/schema.js +0 -55
  20. package/dist/backprop/schema.js.map +0 -1
  21. package/dist/backprop/types.d.ts +0 -101
  22. package/dist/backprop/types.js +0 -24
  23. package/dist/backprop/types.js.map +0 -1
  24. package/dist/backprop/writer.d.ts +0 -27
  25. package/dist/backprop/writer.js +0 -301
  26. package/dist/backprop/writer.js.map +0 -1
  27. package/dist/claude/error.d.ts +0 -33
  28. package/dist/claude/error.js +0 -58
  29. package/dist/claude/error.js.map +0 -1
  30. package/dist/claude/index.d.ts +0 -3
  31. package/dist/claude/index.js +0 -3
  32. package/dist/claude/index.js.map +0 -1
  33. package/dist/claude/runner.d.ts +0 -11
  34. package/dist/claude/runner.js +0 -132
  35. package/dist/claude/runner.js.map +0 -1
  36. package/dist/claude/types.d.ts +0 -52
  37. package/dist/claude/types.js +0 -14
  38. package/dist/claude/types.js.map +0 -1
  39. package/dist/cli/daemon.d.ts +0 -54
  40. package/dist/cli/daemon.js +0 -351
  41. package/dist/cli/daemon.js.map +0 -1
  42. package/dist/cli/install.d.ts +0 -52
  43. package/dist/cli/install.js +0 -308
  44. package/dist/cli/install.js.map +0 -1
  45. package/dist/cli/mirror.d.ts +0 -1
  46. package/dist/cli/mirror.js +0 -97
  47. package/dist/cli/mirror.js.map +0 -1
  48. package/dist/cli/run.d.ts +0 -1
  49. package/dist/cli/run.js +0 -174
  50. package/dist/cli/run.js.map +0 -1
  51. package/dist/cli/task.d.ts +0 -18
  52. package/dist/cli/task.js +0 -137
  53. package/dist/cli/task.js.map +0 -1
  54. package/dist/cli/watch.d.ts +0 -1
  55. package/dist/cli/watch.js +0 -73
  56. package/dist/cli/watch.js.map +0 -1
  57. package/dist/decision-capture/capture.d.ts +0 -57
  58. package/dist/decision-capture/capture.js +0 -186
  59. package/dist/decision-capture/capture.js.map +0 -1
  60. package/dist/decision-capture/extractor.d.ts +0 -20
  61. package/dist/decision-capture/extractor.js +0 -103
  62. package/dist/decision-capture/extractor.js.map +0 -1
  63. package/dist/decision-capture/id.d.ts +0 -14
  64. package/dist/decision-capture/id.js +0 -44
  65. package/dist/decision-capture/id.js.map +0 -1
  66. package/dist/decision-capture/index.d.ts +0 -25
  67. package/dist/decision-capture/index.js +0 -21
  68. package/dist/decision-capture/index.js.map +0 -1
  69. package/dist/decision-capture/prompt.d.ts +0 -15
  70. package/dist/decision-capture/prompt.js +0 -68
  71. package/dist/decision-capture/prompt.js.map +0 -1
  72. package/dist/decision-capture/refinement-prompt.d.ts +0 -25
  73. package/dist/decision-capture/refinement-prompt.js +0 -146
  74. package/dist/decision-capture/refinement-prompt.js.map +0 -1
  75. package/dist/decision-capture/refinement-schema.d.ts +0 -52
  76. package/dist/decision-capture/refinement-schema.js +0 -61
  77. package/dist/decision-capture/refinement-schema.js.map +0 -1
  78. package/dist/decision-capture/refinement.d.ts +0 -60
  79. package/dist/decision-capture/refinement.js +0 -439
  80. package/dist/decision-capture/refinement.js.map +0 -1
  81. package/dist/decision-capture/schema.d.ts +0 -70
  82. package/dist/decision-capture/schema.js +0 -71
  83. package/dist/decision-capture/schema.js.map +0 -1
  84. package/dist/decision-capture/types.d.ts +0 -201
  85. package/dist/decision-capture/types.js +0 -20
  86. package/dist/decision-capture/types.js.map +0 -1
  87. package/dist/decision-capture/writer.d.ts +0 -90
  88. package/dist/decision-capture/writer.js +0 -267
  89. package/dist/decision-capture/writer.js.map +0 -1
  90. package/dist/frontend/discord/acl.d.ts +0 -6
  91. package/dist/frontend/discord/acl.js +0 -19
  92. package/dist/frontend/discord/acl.js.map +0 -1
  93. package/dist/frontend/discord/channels.d.ts +0 -29
  94. package/dist/frontend/discord/channels.js +0 -58
  95. package/dist/frontend/discord/channels.js.map +0 -1
  96. package/dist/frontend/discord/classifier.d.ts +0 -16
  97. package/dist/frontend/discord/classifier.js +0 -29
  98. package/dist/frontend/discord/classifier.js.map +0 -1
  99. package/dist/frontend/discord/index.d.ts +0 -118
  100. package/dist/frontend/discord/index.js +0 -1104
  101. package/dist/frontend/discord/index.js.map +0 -1
  102. package/dist/frontend/discord/slash.d.ts +0 -18
  103. package/dist/frontend/discord/slash.js +0 -90
  104. package/dist/frontend/discord/slash.js.map +0 -1
  105. package/dist/frontend/inbox.d.ts +0 -17
  106. package/dist/frontend/inbox.js +0 -30
  107. package/dist/frontend/inbox.js.map +0 -1
  108. package/dist/frontend/index.d.ts +0 -8
  109. package/dist/frontend/index.js +0 -6
  110. package/dist/frontend/index.js.map +0 -1
  111. package/dist/frontend/stub/index.d.ts +0 -58
  112. package/dist/frontend/stub/index.js +0 -144
  113. package/dist/frontend/stub/index.js.map +0 -1
  114. package/dist/frontend/types.d.ts +0 -247
  115. package/dist/frontend/types.js +0 -15
  116. package/dist/frontend/types.js.map +0 -1
  117. package/dist/gc/apply.d.ts +0 -26
  118. package/dist/gc/apply.js +0 -48
  119. package/dist/gc/apply.js.map +0 -1
  120. package/dist/gc/canary.d.ts +0 -42
  121. package/dist/gc/canary.js +0 -134
  122. package/dist/gc/canary.js.map +0 -1
  123. package/dist/gc/classify.d.ts +0 -25
  124. package/dist/gc/classify.js +0 -89
  125. package/dist/gc/classify.js.map +0 -1
  126. package/dist/gc/doc-gardening.d.ts +0 -29
  127. package/dist/gc/doc-gardening.js +0 -146
  128. package/dist/gc/doc-gardening.js.map +0 -1
  129. package/dist/gc/frontmatter.d.ts +0 -35
  130. package/dist/gc/frontmatter.js +0 -111
  131. package/dist/gc/frontmatter.js.map +0 -1
  132. package/dist/gc/generator-drift.d.ts +0 -28
  133. package/dist/gc/generator-drift.js +0 -53
  134. package/dist/gc/generator-drift.js.map +0 -1
  135. package/dist/gc/index.d.ts +0 -35
  136. package/dist/gc/index.js +0 -26
  137. package/dist/gc/index.js.map +0 -1
  138. package/dist/gc/quality-update.d.ts +0 -23
  139. package/dist/gc/quality-update.js +0 -69
  140. package/dist/gc/quality-update.js.map +0 -1
  141. package/dist/gc/stub-hits.d.ts +0 -31
  142. package/dist/gc/stub-hits.js +0 -125
  143. package/dist/gc/stub-hits.js.map +0 -1
  144. package/dist/gc/sweep.d.ts +0 -56
  145. package/dist/gc/sweep.js +0 -178
  146. package/dist/gc/sweep.js.map +0 -1
  147. package/dist/gc/types.d.ts +0 -129
  148. package/dist/gc/types.js +0 -26
  149. package/dist/gc/types.js.map +0 -1
  150. package/dist/ground/drift.d.ts +0 -8
  151. package/dist/ground/drift.js +0 -23
  152. package/dist/ground/drift.js.map +0 -1
  153. package/dist/ground/frontmatter.d.ts +0 -20
  154. package/dist/ground/frontmatter.js +0 -49
  155. package/dist/ground/frontmatter.js.map +0 -1
  156. package/dist/ground/glob.d.ts +0 -10
  157. package/dist/ground/glob.js +0 -46
  158. package/dist/ground/glob.js.map +0 -1
  159. package/dist/ground/index.d.ts +0 -14
  160. package/dist/ground/index.js +0 -10
  161. package/dist/ground/index.js.map +0 -1
  162. package/dist/ground/ledgers.d.ts +0 -18
  163. package/dist/ground/ledgers.js +0 -103
  164. package/dist/ground/ledgers.js.map +0 -1
  165. package/dist/ground/manifest.d.ts +0 -10
  166. package/dist/ground/manifest.js +0 -88
  167. package/dist/ground/manifest.js.map +0 -1
  168. package/dist/ground/paths.d.ts +0 -20
  169. package/dist/ground/paths.js +0 -61
  170. package/dist/ground/paths.js.map +0 -1
  171. package/dist/ground/quality-grades.d.ts +0 -11
  172. package/dist/ground/quality-grades.js +0 -98
  173. package/dist/ground/quality-grades.js.map +0 -1
  174. package/dist/ground/schemas.d.ts +0 -306
  175. package/dist/ground/schemas.js +0 -188
  176. package/dist/ground/schemas.js.map +0 -1
  177. package/dist/ground/walk.d.ts +0 -7
  178. package/dist/ground/walk.js +0 -53
  179. package/dist/ground/walk.js.map +0 -1
  180. package/dist/init/detect.d.ts +0 -25
  181. package/dist/init/detect.js +0 -336
  182. package/dist/init/detect.js.map +0 -1
  183. package/dist/init/index.d.ts +0 -14
  184. package/dist/init/index.js +0 -9
  185. package/dist/init/index.js.map +0 -1
  186. package/dist/init/init.d.ts +0 -68
  187. package/dist/init/init.js +0 -673
  188. package/dist/init/init.js.map +0 -1
  189. package/dist/init/mapper.d.ts +0 -160
  190. package/dist/init/mapper.js +0 -248
  191. package/dist/init/mapper.js.map +0 -1
  192. package/dist/init/prompts.d.ts +0 -70
  193. package/dist/init/prompts.js +0 -80
  194. package/dist/init/prompts.js.map +0 -1
  195. package/dist/init/secrets.d.ts +0 -18
  196. package/dist/init/secrets.js +0 -76
  197. package/dist/init/secrets.js.map +0 -1
  198. package/dist/init/seed.d.ts +0 -21
  199. package/dist/init/seed.js +0 -75
  200. package/dist/init/seed.js.map +0 -1
  201. package/dist/init/setup-runners.d.ts +0 -17
  202. package/dist/init/setup-runners.js +0 -70
  203. package/dist/init/setup-runners.js.map +0 -1
  204. package/dist/init/types.d.ts +0 -59
  205. package/dist/init/types.js +0 -10
  206. package/dist/init/types.js.map +0 -1
  207. package/dist/init/walker.d.ts +0 -53
  208. package/dist/init/walker.js +0 -460
  209. package/dist/init/walker.js.map +0 -1
  210. package/dist/init/workflow-block.d.ts +0 -34
  211. package/dist/init/workflow-block.js +0 -110
  212. package/dist/init/workflow-block.js.map +0 -1
  213. package/dist/logger.d.ts +0 -3
  214. package/dist/logger.js +0 -23
  215. package/dist/logger.js.map +0 -1
  216. package/dist/mcp/context.d.ts +0 -16
  217. package/dist/mcp/context.js +0 -8
  218. package/dist/mcp/context.js.map +0 -1
  219. package/dist/mcp/errors.d.ts +0 -17
  220. package/dist/mcp/errors.js +0 -23
  221. package/dist/mcp/errors.js.map +0 -1
  222. package/dist/mcp/index.d.ts +0 -10
  223. package/dist/mcp/index.js +0 -7
  224. package/dist/mcp/index.js.map +0 -1
  225. package/dist/mcp/path-allowlist.d.ts +0 -25
  226. package/dist/mcp/path-allowlist.js +0 -66
  227. package/dist/mcp/path-allowlist.js.map +0 -1
  228. package/dist/mcp/result.d.ts +0 -8
  229. package/dist/mcp/result.js +0 -18
  230. package/dist/mcp/result.js.map +0 -1
  231. package/dist/mcp/schemas.d.ts +0 -153
  232. package/dist/mcp/schemas.js +0 -135
  233. package/dist/mcp/schemas.js.map +0 -1
  234. package/dist/mcp/server.d.ts +0 -11
  235. package/dist/mcp/server.js +0 -58
  236. package/dist/mcp/server.js.map +0 -1
  237. package/dist/mcp/telemetry.d.ts +0 -15
  238. package/dist/mcp/telemetry.js +0 -13
  239. package/dist/mcp/telemetry.js.map +0 -1
  240. package/dist/mcp/tools/append.d.ts +0 -8
  241. package/dist/mcp/tools/append.js +0 -33
  242. package/dist/mcp/tools/append.js.map +0 -1
  243. package/dist/mcp/tools/archive.d.ts +0 -8
  244. package/dist/mcp/tools/archive.js +0 -49
  245. package/dist/mcp/tools/archive.js.map +0 -1
  246. package/dist/mcp/tools/ask-operator.d.ts +0 -34
  247. package/dist/mcp/tools/ask-operator.js +0 -93
  248. package/dist/mcp/tools/ask-operator.js.map +0 -1
  249. package/dist/mcp/tools/canonical-for-topic.d.ts +0 -6
  250. package/dist/mcp/tools/canonical-for-topic.js +0 -40
  251. package/dist/mcp/tools/canonical-for-topic.js.map +0 -1
  252. package/dist/mcp/tools/decision-get.d.ts +0 -6
  253. package/dist/mcp/tools/decision-get.js +0 -49
  254. package/dist/mcp/tools/decision-get.js.map +0 -1
  255. package/dist/mcp/tools/decisions-for-symbol.d.ts +0 -7
  256. package/dist/mcp/tools/decisions-for-symbol.js +0 -42
  257. package/dist/mcp/tools/decisions-for-symbol.js.map +0 -1
  258. package/dist/mcp/tools/decisions-in-scope.d.ts +0 -7
  259. package/dist/mcp/tools/decisions-in-scope.js +0 -47
  260. package/dist/mcp/tools/decisions-in-scope.js.map +0 -1
  261. package/dist/mcp/tools/drop-task.d.ts +0 -12
  262. package/dist/mcp/tools/drop-task.js +0 -47
  263. package/dist/mcp/tools/drop-task.js.map +0 -1
  264. package/dist/mcp/tools/get-full.d.ts +0 -7
  265. package/dist/mcp/tools/get-full.js +0 -46
  266. package/dist/mcp/tools/get-full.js.map +0 -1
  267. package/dist/mcp/tools/ground-get.d.ts +0 -7
  268. package/dist/mcp/tools/ground-get.js +0 -80
  269. package/dist/mcp/tools/ground-get.js.map +0 -1
  270. package/dist/mcp/tools/index.d.ts +0 -3
  271. package/dist/mcp/tools/index.js +0 -44
  272. package/dist/mcp/tools/index.js.map +0 -1
  273. package/dist/mcp/tools/invariant-get.d.ts +0 -6
  274. package/dist/mcp/tools/invariant-get.js +0 -49
  275. package/dist/mcp/tools/invariant-get.js.map +0 -1
  276. package/dist/mcp/tools/invariants-in-scope.d.ts +0 -7
  277. package/dist/mcp/tools/invariants-in-scope.js +0 -65
  278. package/dist/mcp/tools/invariants-in-scope.js.map +0 -1
  279. package/dist/mcp/tools/query-history.d.ts +0 -9
  280. package/dist/mcp/tools/query-history.js +0 -33
  281. package/dist/mcp/tools/query-history.js.map +0 -1
  282. package/dist/mcp/tools/record-decision.d.ts +0 -14
  283. package/dist/mcp/tools/record-decision.js +0 -101
  284. package/dist/mcp/tools/record-decision.js.map +0 -1
  285. package/dist/mcp/tools/record-run-event.d.ts +0 -10
  286. package/dist/mcp/tools/record-run-event.js +0 -28
  287. package/dist/mcp/tools/record-run-event.js.map +0 -1
  288. package/dist/mcp/tools/search.d.ts +0 -9
  289. package/dist/mcp/tools/search.js +0 -165
  290. package/dist/mcp/tools/search.js.map +0 -1
  291. package/dist/mcp/tools/supersedes-chain.d.ts +0 -6
  292. package/dist/mcp/tools/supersedes-chain.js +0 -66
  293. package/dist/mcp/tools/supersedes-chain.js.map +0 -1
  294. package/dist/mcp/tools/timeline.d.ts +0 -9
  295. package/dist/mcp/tools/timeline.js +0 -65
  296. package/dist/mcp/tools/timeline.js.map +0 -1
  297. package/dist/mcp/tools/types.d.ts +0 -9
  298. package/dist/mcp/tools/types.js +0 -2
  299. package/dist/mcp/tools/types.js.map +0 -1
  300. package/dist/mirror/clone.d.ts +0 -6
  301. package/dist/mirror/clone.js +0 -48
  302. package/dist/mirror/clone.js.map +0 -1
  303. package/dist/mirror/dirty-overlap.d.ts +0 -13
  304. package/dist/mirror/dirty-overlap.js +0 -77
  305. package/dist/mirror/dirty-overlap.js.map +0 -1
  306. package/dist/mirror/index.d.ts +0 -7
  307. package/dist/mirror/index.js +0 -7
  308. package/dist/mirror/index.js.map +0 -1
  309. package/dist/mirror/paths.d.ts +0 -18
  310. package/dist/mirror/paths.js +0 -45
  311. package/dist/mirror/paths.js.map +0 -1
  312. package/dist/mirror/push.d.ts +0 -9
  313. package/dist/mirror/push.js +0 -27
  314. package/dist/mirror/push.js.map +0 -1
  315. package/dist/mirror/state.d.ts +0 -4
  316. package/dist/mirror/state.js +0 -36
  317. package/dist/mirror/state.js.map +0 -1
  318. package/dist/mirror/sync.d.ts +0 -9
  319. package/dist/mirror/sync.js +0 -33
  320. package/dist/mirror/sync.js.map +0 -1
  321. package/dist/mirror/types.d.ts +0 -77
  322. package/dist/mirror/types.js +0 -2
  323. package/dist/mirror/types.js.map +0 -1
  324. package/dist/orchestrator/activity-summarizer.d.ts +0 -33
  325. package/dist/orchestrator/activity-summarizer.js +0 -120
  326. package/dist/orchestrator/activity-summarizer.js.map +0 -1
  327. package/dist/orchestrator/inbox.d.ts +0 -78
  328. package/dist/orchestrator/inbox.js +0 -115
  329. package/dist/orchestrator/inbox.js.map +0 -1
  330. package/dist/orchestrator/index.d.ts +0 -9
  331. package/dist/orchestrator/index.js +0 -7
  332. package/dist/orchestrator/index.js.map +0 -1
  333. package/dist/orchestrator/orchestrator.d.ts +0 -154
  334. package/dist/orchestrator/orchestrator.js +0 -2437
  335. package/dist/orchestrator/orchestrator.js.map +0 -1
  336. package/dist/orchestrator/prompt.d.ts +0 -19
  337. package/dist/orchestrator/prompt.js +0 -50
  338. package/dist/orchestrator/prompt.js.map +0 -1
  339. package/dist/orchestrator/queue.d.ts +0 -21
  340. package/dist/orchestrator/queue.js +0 -80
  341. package/dist/orchestrator/queue.js.map +0 -1
  342. package/dist/orchestrator/run-log.d.ts +0 -53
  343. package/dist/orchestrator/run-log.js +0 -92
  344. package/dist/orchestrator/run-log.js.map +0 -1
  345. package/dist/orchestrator/runner.d.ts +0 -56
  346. package/dist/orchestrator/runner.js +0 -172
  347. package/dist/orchestrator/runner.js.map +0 -1
  348. package/dist/orchestrator/tool-digest.d.ts +0 -35
  349. package/dist/orchestrator/tool-digest.js +0 -116
  350. package/dist/orchestrator/tool-digest.js.map +0 -1
  351. package/dist/orchestrator/types.d.ts +0 -263
  352. package/dist/orchestrator/types.js +0 -2
  353. package/dist/orchestrator/types.js.map +0 -1
  354. package/dist/orchestrator/workspace.d.ts +0 -21
  355. package/dist/orchestrator/workspace.js +0 -31
  356. package/dist/orchestrator/workspace.js.map +0 -1
  357. package/dist/profiles/index.d.ts +0 -3
  358. package/dist/profiles/index.js +0 -3
  359. package/dist/profiles/index.js.map +0 -1
  360. package/dist/profiles/registry.d.ts +0 -5
  361. package/dist/profiles/registry.js +0 -31
  362. package/dist/profiles/registry.js.map +0 -1
  363. package/dist/profiles/types.d.ts +0 -48
  364. package/dist/profiles/types.js +0 -11
  365. package/dist/profiles/types.js.map +0 -1
  366. package/dist/profiles/unknown.d.ts +0 -9
  367. package/dist/profiles/unknown.js +0 -17
  368. package/dist/profiles/unknown.js.map +0 -1
  369. package/dist/reviewer/index.d.ts +0 -6
  370. package/dist/reviewer/index.js +0 -5
  371. package/dist/reviewer/index.js.map +0 -1
  372. package/dist/reviewer/prompt.d.ts +0 -11
  373. package/dist/reviewer/prompt.js +0 -132
  374. package/dist/reviewer/prompt.js.map +0 -1
  375. package/dist/reviewer/remediation.d.ts +0 -15
  376. package/dist/reviewer/remediation.js +0 -61
  377. package/dist/reviewer/remediation.js.map +0 -1
  378. package/dist/reviewer/reviewer.d.ts +0 -9
  379. package/dist/reviewer/reviewer.js +0 -89
  380. package/dist/reviewer/reviewer.js.map +0 -1
  381. package/dist/reviewer/schema.d.ts +0 -45
  382. package/dist/reviewer/schema.js +0 -43
  383. package/dist/reviewer/schema.js.map +0 -1
  384. package/dist/reviewer/types.d.ts +0 -74
  385. package/dist/reviewer/types.js +0 -14
  386. package/dist/reviewer/types.js.map +0 -1
  387. package/dist/sensors/attestation.d.ts +0 -44
  388. package/dist/sensors/attestation.js +0 -262
  389. package/dist/sensors/attestation.js.map +0 -1
  390. package/dist/sensors/catalog.d.ts +0 -41
  391. package/dist/sensors/catalog.js +0 -123
  392. package/dist/sensors/catalog.js.map +0 -1
  393. package/dist/sensors/decisions.d.ts +0 -30
  394. package/dist/sensors/decisions.js +0 -393
  395. package/dist/sensors/decisions.js.map +0 -1
  396. package/dist/sensors/diff.d.ts +0 -27
  397. package/dist/sensors/diff.js +0 -148
  398. package/dist/sensors/diff.js.map +0 -1
  399. package/dist/sensors/index.d.ts +0 -13
  400. package/dist/sensors/index.js +0 -9
  401. package/dist/sensors/index.js.map +0 -1
  402. package/dist/sensors/remediation.d.ts +0 -20
  403. package/dist/sensors/remediation.js +0 -65
  404. package/dist/sensors/remediation.js.map +0 -1
  405. package/dist/sensors/runner.d.ts +0 -44
  406. package/dist/sensors/runner.js +0 -95
  407. package/dist/sensors/runner.js.map +0 -1
  408. package/dist/sensors/structural.d.ts +0 -30
  409. package/dist/sensors/structural.js +0 -204
  410. package/dist/sensors/structural.js.map +0 -1
  411. package/dist/sensors/stub-catalog.d.ts +0 -39
  412. package/dist/sensors/stub-catalog.js +0 -115
  413. package/dist/sensors/stub-catalog.js.map +0 -1
  414. package/dist/sensors/types.d.ts +0 -135
  415. package/dist/sensors/types.js +0 -14
  416. package/dist/sensors/types.js.map +0 -1
  417. package/dist/tier0/classify.d.ts +0 -5
  418. package/dist/tier0/classify.js +0 -91
  419. package/dist/tier0/classify.js.map +0 -1
  420. package/dist/tier0/index.d.ts +0 -3
  421. package/dist/tier0/index.js +0 -3
  422. package/dist/tier0/index.js.map +0 -1
  423. package/dist/tier0/ollama.d.ts +0 -22
  424. package/dist/tier0/ollama.js +0 -63
  425. package/dist/tier0/ollama.js.map +0 -1
  426. package/dist/tier0/types.d.ts +0 -24
  427. package/dist/tier0/types.js +0 -7
  428. package/dist/tier0/types.js.map +0 -1
  429. package/dist/tightener/index.d.ts +0 -4
  430. package/dist/tightener/index.js +0 -4
  431. package/dist/tightener/index.js.map +0 -1
  432. package/dist/tightener/prompt.d.ts +0 -3
  433. package/dist/tightener/prompt.js +0 -67
  434. package/dist/tightener/prompt.js.map +0 -1
  435. package/dist/tightener/schema.d.ts +0 -68
  436. package/dist/tightener/schema.js +0 -44
  437. package/dist/tightener/schema.js.map +0 -1
  438. package/dist/tightener/tighten.d.ts +0 -2
  439. package/dist/tightener/tighten.js +0 -66
  440. package/dist/tightener/tighten.js.map +0 -1
  441. package/dist/tightener/types.d.ts +0 -74
  442. package/dist/tightener/types.js +0 -6
  443. package/dist/tightener/types.js.map +0 -1
  444. package/dist/uat/bundle.d.ts +0 -68
  445. package/dist/uat/bundle.js +0 -168
  446. package/dist/uat/bundle.js.map +0 -1
  447. package/dist/uat/index.d.ts +0 -15
  448. package/dist/uat/index.js +0 -10
  449. package/dist/uat/index.js.map +0 -1
  450. package/dist/uat/persistent.d.ts +0 -64
  451. package/dist/uat/persistent.js +0 -206
  452. package/dist/uat/persistent.js.map +0 -1
  453. package/dist/uat/probes/cli.d.ts +0 -11
  454. package/dist/uat/probes/cli.js +0 -107
  455. package/dist/uat/probes/cli.js.map +0 -1
  456. package/dist/uat/probes/http.d.ts +0 -12
  457. package/dist/uat/probes/http.js +0 -139
  458. package/dist/uat/probes/http.js.map +0 -1
  459. package/dist/uat/probes/index.d.ts +0 -21
  460. package/dist/uat/probes/index.js +0 -30
  461. package/dist/uat/probes/index.js.map +0 -1
  462. package/dist/uat/probes/integration.d.ts +0 -18
  463. package/dist/uat/probes/integration.js +0 -188
  464. package/dist/uat/probes/integration.js.map +0 -1
  465. package/dist/uat/probes/sql/config.d.ts +0 -14
  466. package/dist/uat/probes/sql/config.js +0 -57
  467. package/dist/uat/probes/sql/config.js.map +0 -1
  468. package/dist/uat/probes/sql/index.d.ts +0 -29
  469. package/dist/uat/probes/sql/index.js +0 -43
  470. package/dist/uat/probes/sql/index.js.map +0 -1
  471. package/dist/uat/probes/sql/mysql.d.ts +0 -12
  472. package/dist/uat/probes/sql/mysql.js +0 -96
  473. package/dist/uat/probes/sql/mysql.js.map +0 -1
  474. package/dist/uat/probes/sql/pg.d.ts +0 -20
  475. package/dist/uat/probes/sql/pg.js +0 -102
  476. package/dist/uat/probes/sql/pg.js.map +0 -1
  477. package/dist/uat/probes/sql/sqlite.d.ts +0 -9
  478. package/dist/uat/probes/sql/sqlite.js +0 -58
  479. package/dist/uat/probes/sql/sqlite.js.map +0 -1
  480. package/dist/uat/probes/sql/types.d.ts +0 -46
  481. package/dist/uat/probes/sql/types.js +0 -10
  482. package/dist/uat/probes/sql/types.js.map +0 -1
  483. package/dist/uat/probes/sql.d.ts +0 -9
  484. package/dist/uat/probes/sql.js +0 -119
  485. package/dist/uat/probes/sql.js.map +0 -1
  486. package/dist/uat/probes/ui.d.ts +0 -19
  487. package/dist/uat/probes/ui.js +0 -244
  488. package/dist/uat/probes/ui.js.map +0 -1
  489. package/dist/uat/prompt.d.ts +0 -10
  490. package/dist/uat/prompt.js +0 -85
  491. package/dist/uat/prompt.js.map +0 -1
  492. package/dist/uat/question.d.ts +0 -50
  493. package/dist/uat/question.js +0 -139
  494. package/dist/uat/question.js.map +0 -1
  495. package/dist/uat/rejection.d.ts +0 -58
  496. package/dist/uat/rejection.js +0 -163
  497. package/dist/uat/rejection.js.map +0 -1
  498. package/dist/uat/runner.d.ts +0 -6
  499. package/dist/uat/runner.js +0 -96
  500. package/dist/uat/runner.js.map +0 -1
  501. package/dist/uat/schema.d.ts +0 -322
  502. package/dist/uat/schema.js +0 -189
  503. package/dist/uat/schema.js.map +0 -1
  504. package/dist/uat/types.d.ts +0 -268
  505. package/dist/uat/types.js +0 -18
  506. package/dist/uat/types.js.map +0 -1
  507. package/dist/uat/uat.d.ts +0 -89
  508. package/dist/uat/uat.js +0 -256
  509. package/dist/uat/uat.js.map +0 -1
  510. package/dist/voice/index.d.ts +0 -4
  511. package/dist/voice/index.js +0 -4
  512. package/dist/voice/index.js.map +0 -1
  513. package/dist/voice/model.d.ts +0 -23
  514. package/dist/voice/model.js +0 -46
  515. package/dist/voice/model.js.map +0 -1
  516. package/dist/voice/pipe.d.ts +0 -9
  517. package/dist/voice/pipe.js +0 -47
  518. package/dist/voice/pipe.js.map +0 -1
  519. package/dist/voice/transcribe.d.ts +0 -3
  520. package/dist/voice/transcribe.js +0 -43
  521. package/dist/voice/transcribe.js.map +0 -1
  522. package/dist/voice/types.d.ts +0 -26
  523. package/dist/voice/types.js +0 -9
  524. package/dist/voice/types.js.map +0 -1
  525. package/dist/watch/daemon.d.ts +0 -21
  526. package/dist/watch/daemon.js +0 -143
  527. package/dist/watch/daemon.js.map +0 -1
  528. package/dist/watch/index.d.ts +0 -4
  529. package/dist/watch/index.js +0 -3
  530. package/dist/watch/index.js.map +0 -1
  531. package/dist/watch/regenerate.d.ts +0 -25
  532. package/dist/watch/regenerate.js +0 -51
  533. package/dist/watch/regenerate.js.map +0 -1
@@ -1,1104 +0,0 @@
1
- import { ActionRowBuilder, ButtonBuilder, ButtonStyle, Client, EmbedBuilder, Events, GatewayIntentBits, Partials, } from "discord.js";
2
- import { randomBytes } from "node:crypto";
3
- import { logger } from "../../logger.js";
4
- import { writeInboxRow } from "../inbox.js";
5
- import { classifyTier0 } from "../../tier0/index.js";
6
- import { transcribeUrl, whisperModelExists } from "../../voice/index.js";
7
- import { isOwner, parseOwnerIds } from "./acl.js";
8
- import { CATEGORY_NAMES, createTaskChannel, ensureCategories, moveChannelToCategory, slugifyForChannel, } from "./channels.js";
9
- import { registerSlashCommands, SLASH_COMMAND_NAMES } from "./slash.js";
10
- const log = logger("frontend.discord");
11
- const VOICE_MIME_PREFIXES = ["audio/", "video/ogg"];
12
- /**
13
- * Real Discord adapter. Implements `FrontendAdapter`. Phase 5 wires:
14
- * - slash command surface per `WORKFLOW_GUIDE.md` ยง3
15
- * - channel-per-task lifecycle in ๐Ÿ“‹/๐ŸŸข/๐Ÿ“ฆ categories
16
- * - ACL on `DISCORD_OWNER_USER_IDS`
17
- * - free-text โ†’ regex Tier-0 stub (real Ollama in Phase 6)
18
- * - voice attachments โ†’ inbox row only (Whisper transcription Phase 6)
19
- * - button interactions for approval / dialog round-trips
20
- *
21
- * Inbox rows are the only output Phase 5 produces โ€” the orchestrator (Phase
22
- * 8) is the consumer. No code dispatch lives here.
23
- */
24
- export class DiscordFrontendAdapter {
25
- name = "discord";
26
- opts;
27
- ownerIds;
28
- client;
29
- confidenceFloor;
30
- tier0Opts;
31
- taskHandler;
32
- voiceHandler;
33
- slashHandler;
34
- freeTextHandler;
35
- interactionHandler;
36
- pendingApprovals = new Map();
37
- pendingDialogs = new Map();
38
- /**
39
- * ยง3.4 win 1 โ€” bundleId โ†’ messageId for in-place dialog edit. When a
40
- * follow-up DialogSpec arrives with `replaceBundleId` set, the adapter
41
- * fetches that message and edits it (new prompt + new buttons) instead
42
- * of posting a fresh one. Single edited message replaces the N+1
43
- * scroll explosion.
44
- */
45
- dialogMessageIds = new Map();
46
- /**
47
- * In-memory map of taskId โ†’ live status messageId. The live status
48
- * message is one embed per task that gets edited in place on each
49
- * postTaskUpdate call (instead of spamming the channel with a new
50
- * message per phase). Restart loses the mapping; a fresh run posts a
51
- * new live status message.
52
- */
53
- liveStatusMessages = new Map();
54
- /**
55
- * Channels that have responded with Unknown Channel (Discord error
56
- * 10003). Once flagged dead, subsequent postTaskUpdate / sendTyping
57
- * / requestDialog calls bail without hitting Discord. This stops the
58
- * 8s typing heartbeat from spamming logs after the operator deletes
59
- * a per-task channel mid-run.
60
- */
61
- deadChannels = new Set();
62
- started = false;
63
- constructor(opts) {
64
- this.opts = opts;
65
- this.confidenceFloor = opts.confidenceFloor ?? 0.85;
66
- this.tier0Opts = opts.tier0 ?? {};
67
- this.ownerIds = parseOwnerIds(opts.ownerUserIdsEnv);
68
- if (this.ownerIds.size === 0) {
69
- log.warn("DISCORD_OWNER_USER_IDS empty โ€” no commands will be accepted; configure before live use");
70
- }
71
- this.client = new Client({
72
- intents: [
73
- GatewayIntentBits.Guilds,
74
- GatewayIntentBits.GuildMessages,
75
- GatewayIntentBits.MessageContent,
76
- GatewayIntentBits.GuildMessageReactions,
77
- GatewayIntentBits.DirectMessages,
78
- GatewayIntentBits.DirectMessageReactions,
79
- ],
80
- partials: [Partials.Channel, Partials.Message, Partials.Reaction],
81
- });
82
- }
83
- async start() {
84
- if (this.started)
85
- return;
86
- this.client.on(Events.InteractionCreate, (i) => {
87
- void this.handleInteraction(i);
88
- });
89
- this.client.on(Events.MessageCreate, (m) => {
90
- void this.handleMessage(m);
91
- });
92
- this.client.once(Events.ClientReady, (c) => {
93
- log.info({ user: c.user.tag, guildId: this.opts.guildId }, "discord client ready");
94
- });
95
- await this.client.login(this.opts.token);
96
- if (!this.opts.skipSlashRegistration) {
97
- const appId = this.opts.applicationId ?? this.client.application?.id;
98
- if (!appId) {
99
- await this.client.destroy();
100
- throw new Error("could not resolve application id for slash registration");
101
- }
102
- const registered = await registerSlashCommands({
103
- token: this.opts.token,
104
- appId,
105
- guildId: this.opts.guildId,
106
- });
107
- log.info({ count: registered.length }, "slash commands registered");
108
- }
109
- const guild = await this.requireGuild();
110
- await ensureCategories(guild);
111
- log.info({ categories: Object.values(CATEGORY_NAMES) }, "categories ensured");
112
- this.started = true;
113
- }
114
- async stop() {
115
- if (!this.started)
116
- return;
117
- for (const pending of this.pendingApprovals.values()) {
118
- clearTimeout(pending.timeoutHandle);
119
- pending.resolve({
120
- bundleId: pending.bundleId,
121
- decision: "ask",
122
- timedOut: true,
123
- reason: "adapter stopped",
124
- });
125
- }
126
- this.pendingApprovals.clear();
127
- for (const pending of this.pendingDialogs.values()) {
128
- clearTimeout(pending.timeoutHandle);
129
- pending.resolve({
130
- bundleId: pending.bundleId,
131
- choiceId: "e_other",
132
- timedOut: true,
133
- freeText: "(adapter stopped before reply)",
134
- });
135
- }
136
- this.pendingDialogs.clear();
137
- this.dialogMessageIds.clear();
138
- await this.client.destroy();
139
- this.started = false;
140
- log.info("discord adapter stopped");
141
- }
142
- onTask(handler) {
143
- this.taskHandler = handler;
144
- }
145
- onVoice(handler) {
146
- this.voiceHandler = handler;
147
- }
148
- onSlash(handler) {
149
- this.slashHandler = handler;
150
- }
151
- onFreeText(handler) {
152
- this.freeTextHandler = handler;
153
- }
154
- onInteraction(handler) {
155
- this.interactionHandler = handler;
156
- }
157
- async postTaskUpdate(update) {
158
- const channelId = update.channelId;
159
- if (!channelId) {
160
- log.warn({ taskId: update.taskId }, "postTaskUpdate without channelId; dropping");
161
- return;
162
- }
163
- if (this.isChannelDead(channelId))
164
- return;
165
- let channel;
166
- try {
167
- channel = await this.client.channels.fetch(channelId);
168
- }
169
- catch (err) {
170
- if (isUnknownChannelError(err)) {
171
- this.markChannelDead(channelId);
172
- return;
173
- }
174
- throw err;
175
- }
176
- if (!channel || !channel.isTextBased() || !("send" in channel)) {
177
- log.warn({ channelId }, "postTaskUpdate target channel not text-based");
178
- return;
179
- }
180
- const text = channel;
181
- const embed = buildPhaseEmbed(update);
182
- // ยง3.3 win 3 โ€” single edited live-status embed; no secondary content
183
- // post. body / activity / tools / recentEvents all render inside this
184
- // one embed via fields. Long bodies are truncated to 1024 chars + a
185
- // pointer to log.jsonl (operator can grep the full content there).
186
- const existingMsgId = this.liveStatusMessages.get(update.taskId);
187
- if (existingMsgId !== undefined) {
188
- try {
189
- const msg = await text.messages.fetch(existingMsgId);
190
- await msg.edit({ embeds: [embed] });
191
- return;
192
- }
193
- catch (err) {
194
- log.warn({ err: String(err), taskId: update.taskId }, "live status edit failed; recreating");
195
- this.liveStatusMessages.delete(update.taskId);
196
- }
197
- }
198
- try {
199
- const sent = await text.send({ embeds: [embed] });
200
- this.liveStatusMessages.set(update.taskId, sent.id);
201
- }
202
- catch (err) {
203
- log.warn({ err: String(err), taskId: update.taskId }, "live status send failed");
204
- }
205
- }
206
- async requestApproval(bundle) {
207
- const channelId = bundle.channelId;
208
- if (!channelId) {
209
- throw new Error("requestApproval requires bundle.channelId");
210
- }
211
- if (this.isChannelDead(channelId)) {
212
- // Same dead-channel sentinel as requestDialog โ€” orchestrator
213
- // treats timeout as ask/abandon.
214
- return { bundleId: bundle.bundleId, decision: "ask", timedOut: true };
215
- }
216
- let channel;
217
- try {
218
- channel = await this.client.channels.fetch(channelId);
219
- }
220
- catch (err) {
221
- if (isUnknownChannelError(err)) {
222
- this.markChannelDead(channelId);
223
- return { bundleId: bundle.bundleId, decision: "ask", timedOut: true };
224
- }
225
- throw err;
226
- }
227
- if (!channel || !("send" in channel) || !channel.isTextBased()) {
228
- throw new Error(`approval target channel not text-based: ${channelId}`);
229
- }
230
- const buttonIds = {
231
- approve: `harness:approve:${bundle.bundleId}`,
232
- reject: `harness:reject:${bundle.bundleId}`,
233
- ask: `harness:ask:${bundle.bundleId}`,
234
- };
235
- const row = new ActionRowBuilder().addComponents(new ButtonBuilder()
236
- .setCustomId(buttonIds.approve)
237
- .setStyle(ButtonStyle.Success)
238
- .setLabel("Approve & Push")
239
- .setEmoji("๐ŸŸข"), new ButtonBuilder()
240
- .setCustomId(buttonIds.reject)
241
- .setStyle(ButtonStyle.Danger)
242
- .setLabel("Reject + tell me why")
243
- .setEmoji("๐Ÿ”ด"), new ButtonBuilder()
244
- .setCustomId(buttonIds.ask)
245
- .setStyle(ButtonStyle.Secondary)
246
- .setLabel("Ask follow-up")
247
- .setEmoji("โ“"));
248
- const embed = new EmbedBuilder()
249
- .setTitle(`๐ŸŽฌ UAT โ€” ${bundle.runId}`)
250
- .setColor(0x1abc9c)
251
- .setDescription(bundle.goal)
252
- .setTimestamp(new Date());
253
- if (bundle.diffSummary) {
254
- embed.addFields({ name: "diff", value: bundle.diffSummary.slice(0, 1024) });
255
- }
256
- if (bundle.acceptance && bundle.acceptance.length > 0) {
257
- const acText = bundle.acceptance
258
- .map((ac) => {
259
- const mark = ac.status === "pass" ? "โœ“" : ac.status === "fail" ? "โœ—" : "โ—‹";
260
- return `${mark} ${ac.id}${ac.note ? ` โ€” ${ac.note}` : ""}`;
261
- })
262
- .join("\n");
263
- embed.addFields({ name: "acceptance", value: acText.slice(0, 1024) });
264
- }
265
- const sentApproval = await channel.send({
266
- embeds: [embed],
267
- components: [row],
268
- });
269
- try {
270
- await sentApproval.react("๐Ÿ‘€");
271
- }
272
- catch (err) {
273
- log.warn({ err: String(err) }, "approval ๐Ÿ‘€ react failed");
274
- }
275
- const timeoutMs = bundle.timeoutMs ?? 24 * 60 * 60 * 1000;
276
- return new Promise((resolve) => {
277
- const timeoutHandle = setTimeout(() => {
278
- this.pendingApprovals.delete(bundle.bundleId);
279
- resolve({ bundleId: bundle.bundleId, decision: "ask", timedOut: true });
280
- }, timeoutMs);
281
- this.pendingApprovals.set(bundle.bundleId, {
282
- bundleId: bundle.bundleId,
283
- resolve,
284
- timeoutHandle,
285
- });
286
- });
287
- }
288
- async requestDialog(spec) {
289
- const channelId = spec.channelId;
290
- if (!channelId) {
291
- throw new Error("requestDialog requires spec.channelId");
292
- }
293
- if (this.isChannelDead(channelId)) {
294
- // Operator can't see the prompt โ€” return a dead-channel sentinel
295
- // (timed-out so the orchestrator's existing timeout-handling
296
- // branch fires).
297
- return { bundleId: spec.bundleId, choiceId: "e_other", timedOut: true };
298
- }
299
- let channel;
300
- try {
301
- channel = await this.client.channels.fetch(channelId);
302
- }
303
- catch (err) {
304
- if (isUnknownChannelError(err)) {
305
- this.markChannelDead(channelId);
306
- return { bundleId: spec.bundleId, choiceId: "e_other", timedOut: true };
307
- }
308
- throw err;
309
- }
310
- if (!channel || !("send" in channel) || !channel.isTextBased()) {
311
- throw new Error(`dialog target channel not text-based: ${channelId}`);
312
- }
313
- const choiceMap = new Map();
314
- const choiceLabels = new Map();
315
- const builders = [];
316
- for (const choice of spec.choices.slice(0, 5)) {
317
- const buttonId = `harness:dialog:${spec.bundleId}:${choice.id}`;
318
- choiceMap.set(buttonId, choice.id);
319
- choiceLabels.set(choice.id, choice.label);
320
- builders.push(new ButtonBuilder()
321
- .setCustomId(buttonId)
322
- .setStyle(ButtonStyle.Secondary)
323
- .setLabel(choice.label.slice(0, 80)));
324
- }
325
- const row = new ActionRowBuilder().addComponents(...builders);
326
- // Plain content has a 2000-char cap, but operator-walked dialogs
327
- // can include 4 long ambiguity candidates that blow past it.
328
- // Discord embeds get 4096 chars in the description, so longer
329
- // prompts go there. Short prompts stay as content for the
330
- // visually-cleaner default rendering.
331
- const pingPrefix = spec.pingOperators === true && this.ownerIds.size > 0
332
- ? Array.from(this.ownerIds)
333
- .map((id) => `<@${id}>`)
334
- .join(" ") + " "
335
- : "";
336
- const sendOpts = { components: [row] };
337
- const useEmbed = spec.prompt.length > 1800;
338
- if (useEmbed) {
339
- sendOpts.embeds = [
340
- new EmbedBuilder()
341
- .setDescription(spec.prompt.slice(0, 4096))
342
- .setColor(0x3498db),
343
- ];
344
- // Pings only render in `content`, not embed description, so
345
- // when we use an embed for body, ping prefix lives on content.
346
- if (pingPrefix.length > 0)
347
- sendOpts.content = pingPrefix.trim();
348
- }
349
- else {
350
- sendOpts.content = pingPrefix + spec.prompt;
351
- }
352
- // ยง3.4 win 1 โ€” try to edit the previous walk step's message in place.
353
- let messageId;
354
- if (spec.replaceBundleId !== undefined) {
355
- const prevMsgId = this.dialogMessageIds.get(spec.replaceBundleId);
356
- if (prevMsgId !== undefined) {
357
- try {
358
- const prevMsg = await channel.messages.fetch(prevMsgId);
359
- await prevMsg.edit({
360
- content: sendOpts.content ?? "",
361
- embeds: sendOpts.embeds ?? [],
362
- components: sendOpts.components,
363
- });
364
- messageId = prevMsg.id;
365
- }
366
- catch (err) {
367
- log.warn({ err: String(err), prev: spec.replaceBundleId }, "dialog edit-in-place failed; falling back to fresh send");
368
- }
369
- }
370
- }
371
- if (messageId === undefined) {
372
- const sentMessage = await channel.send(sendOpts);
373
- messageId = sentMessage.id;
374
- try {
375
- await sentMessage.react("๐Ÿ‘€");
376
- }
377
- catch (err) {
378
- log.warn({ err: String(err), bundleId: spec.bundleId }, "dialog ๐Ÿ‘€ react failed");
379
- }
380
- }
381
- this.dialogMessageIds.set(spec.bundleId, messageId);
382
- if (spec.replaceBundleId !== undefined &&
383
- spec.replaceBundleId !== spec.bundleId) {
384
- this.dialogMessageIds.delete(spec.replaceBundleId);
385
- }
386
- const timeoutMs = spec.timeoutMs ?? 5 * 60 * 1000;
387
- const compactOnAnswer = spec.compactOnAnswer !== false;
388
- return new Promise((resolve) => {
389
- const timeoutHandle = setTimeout(() => {
390
- this.pendingDialogs.delete(spec.bundleId);
391
- this.dialogMessageIds.delete(spec.bundleId);
392
- resolve({ bundleId: spec.bundleId, choiceId: "e_other", timedOut: true });
393
- }, timeoutMs);
394
- this.pendingDialogs.set(spec.bundleId, {
395
- bundleId: spec.bundleId,
396
- resolve,
397
- choiceMap,
398
- choiceLabels,
399
- timeoutHandle,
400
- compactOnAnswer,
401
- });
402
- });
403
- }
404
- async isChannelAlive(channelId) {
405
- if (this.isChannelDead(channelId))
406
- return false;
407
- try {
408
- const ch = await this.client.channels.fetch(channelId);
409
- return ch !== null;
410
- }
411
- catch (err) {
412
- if (isUnknownChannelError(err)) {
413
- this.markChannelDead(channelId);
414
- }
415
- return false;
416
- }
417
- }
418
- startTyping(channelId) {
419
- let stopped = false;
420
- const ping = async () => {
421
- if (stopped)
422
- return;
423
- if (this.isChannelDead(channelId)) {
424
- stopped = true;
425
- return;
426
- }
427
- try {
428
- const channel = await this.client.channels.fetch(channelId);
429
- if (channel && channel.isTextBased() && "sendTyping" in channel) {
430
- await channel.sendTyping();
431
- }
432
- }
433
- catch (err) {
434
- if (isUnknownChannelError(err)) {
435
- this.markChannelDead(channelId);
436
- stopped = true;
437
- return;
438
- }
439
- log.warn({ err: String(err), channelId }, "sendTyping failed");
440
- }
441
- };
442
- void ping();
443
- // Discord's typing indicator decays after ~10s; refresh every 8s.
444
- const handle = setInterval(() => void ping(), 8_000);
445
- return () => {
446
- stopped = true;
447
- clearInterval(handle);
448
- };
449
- }
450
- async notify(level, message) {
451
- log[level === "warn" ? "warn" : level === "error" ? "error" : "info"]({ message }, "frontend notify");
452
- // No default channel for ad-hoc notifications. Discord-side surfacing
453
- // belongs to per-run channels (postTaskUpdate). This is a logging
454
- // fallback โ€” Phase 5 has no system channel concept.
455
- }
456
- // โ”€โ”€ private โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
457
- /**
458
- * Discord error 10003 = Unknown Channel. Operator deleted the
459
- * per-task channel after the harness recorded its id. Once flagged,
460
- * silently no-op on every subsequent send/edit/typing call so we
461
- * don't spam logs while the orchestrator winds the run down.
462
- */
463
- isChannelDead(channelId) {
464
- return this.deadChannels.has(channelId);
465
- }
466
- markChannelDead(channelId) {
467
- if (!this.deadChannels.has(channelId)) {
468
- this.deadChannels.add(channelId);
469
- log.warn({ channelId }, "channel marked dead โ€” operator likely deleted it; further posts/typing/dialogs will no-op");
470
- }
471
- }
472
- async requireGuild() {
473
- const guild = await this.client.guilds.fetch(this.opts.guildId);
474
- if (!guild) {
475
- throw new Error(`guild not accessible: ${this.opts.guildId}`);
476
- }
477
- return guild;
478
- }
479
- async handleInteraction(interaction) {
480
- try {
481
- if (interaction.isChatInputCommand()) {
482
- await this.handleSlash(interaction);
483
- return;
484
- }
485
- if (interaction.isButton()) {
486
- await this.handleButton(interaction);
487
- return;
488
- }
489
- }
490
- catch (err) {
491
- log.error({ err: String(err) }, "interaction handler threw");
492
- }
493
- }
494
- async handleSlash(interaction) {
495
- const command = interaction.commandName;
496
- const userId = interaction.user.id;
497
- if (!isOwner(this.ownerIds, userId)) {
498
- await interaction.reply({
499
- content: "Not authorized.",
500
- ephemeral: true,
501
- });
502
- return;
503
- }
504
- if (!SLASH_COMMAND_NAMES.includes(command)) {
505
- await interaction.reply({ content: `Unknown command: ${command}`, ephemeral: true });
506
- return;
507
- }
508
- const options = {};
509
- for (const opt of interaction.options.data) {
510
- if (opt.value !== undefined && opt.value !== null)
511
- options[opt.name] = opt.value;
512
- }
513
- const slashEvent = {
514
- source: this.name,
515
- command,
516
- options,
517
- authorId: userId,
518
- receivedAt: new Date().toISOString(),
519
- ...(interaction.channelId ? { channelId: interaction.channelId } : {}),
520
- ...(interaction.guildId ? { guildId: interaction.guildId } : {}),
521
- messageId: interaction.id,
522
- };
523
- if (command === "task") {
524
- await this.handleTaskCommand(interaction, slashEvent);
525
- return;
526
- }
527
- await writeInboxRow({
528
- repoRoot: this.opts.repoRoot,
529
- source: this.name,
530
- kind: "slash",
531
- payload: { slash: slashEvent },
532
- });
533
- if (this.slashHandler)
534
- await this.slashHandler(slashEvent);
535
- await interaction.reply({
536
- content: `Queued \`/${command}\` (run-id pending).`,
537
- ephemeral: true,
538
- });
539
- }
540
- async handleTaskCommand(interaction, slashEvent) {
541
- const body = typeof slashEvent.options["body"] === "string" ? slashEvent.options["body"] : "";
542
- const taskId = newTaskId();
543
- const bodySlug = slugifyForChannel(body);
544
- let createdChannelId;
545
- if (interaction.guild) {
546
- try {
547
- const categories = await ensureCategories(interaction.guild);
548
- const channel = await createTaskChannel({
549
- guild: interaction.guild,
550
- category: categories.active,
551
- taskId,
552
- bodySlug,
553
- });
554
- createdChannelId = channel.id;
555
- // ยง3.4 win โ€” no standalone "๐Ÿ†• Task" drop card. The orchestrator
556
- // surfaces the body inside the live-status embed (PostUpdate.taskBody
557
- // field) so the channel shows a single self-updating message per task
558
- // instead of two.
559
- }
560
- catch (err) {
561
- log.error({ err: String(err) }, "failed to create task channel");
562
- }
563
- }
564
- const task = {
565
- source: this.name,
566
- intent: "code_task",
567
- rawText: body,
568
- authorId: slashEvent.authorId,
569
- receivedAt: slashEvent.receivedAt,
570
- ...(createdChannelId ? { channelId: createdChannelId } : {}),
571
- ...(slashEvent.guildId ? { guildId: slashEvent.guildId } : {}),
572
- messageId: slashEvent.messageId ?? "",
573
- };
574
- await writeInboxRow({
575
- repoRoot: this.opts.repoRoot,
576
- source: this.name,
577
- kind: "task",
578
- payload: { task, slash: slashEvent, task_id: taskId },
579
- });
580
- if (this.taskHandler)
581
- await this.taskHandler(task);
582
- const replyLines = [`๐Ÿ“‹ Task ${taskId} queued.`];
583
- if (createdChannelId)
584
- replyLines.push(`Channel: <#${createdChannelId}>`);
585
- await interaction.reply({ content: replyLines.join("\n"), ephemeral: true });
586
- }
587
- async handleButton(interaction) {
588
- const userId = interaction.user.id;
589
- if (!isOwner(this.ownerIds, userId)) {
590
- await interaction.reply({ content: "Not authorized.", ephemeral: true });
591
- return;
592
- }
593
- const customId = interaction.customId;
594
- const parts = customId.split(":");
595
- const namespace = parts[0];
596
- const kind = parts[1];
597
- if (namespace !== "harness") {
598
- await interaction.reply({ content: "Unknown button.", ephemeral: true });
599
- return;
600
- }
601
- if (kind === "approve" || kind === "reject" || kind === "ask") {
602
- const bundleId = parts.slice(2).join(":");
603
- const pending = this.pendingApprovals.get(bundleId);
604
- if (pending) {
605
- clearTimeout(pending.timeoutHandle);
606
- this.pendingApprovals.delete(bundleId);
607
- pending.resolve({ bundleId, decision: kind });
608
- }
609
- const interactionEvent = {
610
- source: this.name,
611
- bundleId,
612
- choiceId: kind,
613
- authorId: userId,
614
- receivedAt: new Date().toISOString(),
615
- ...(interaction.channelId ? { channelId: interaction.channelId } : {}),
616
- ...(interaction.guildId ? { guildId: interaction.guildId } : {}),
617
- messageId: interaction.id,
618
- };
619
- await writeInboxRow({
620
- repoRoot: this.opts.repoRoot,
621
- source: this.name,
622
- kind: "interaction",
623
- payload: { interaction: interactionEvent },
624
- });
625
- if (this.interactionHandler)
626
- await this.interactionHandler(interactionEvent);
627
- await interaction.deferUpdate();
628
- await this.ackReact(interaction, kind);
629
- return;
630
- }
631
- if (kind === "voice-confirm") {
632
- const transcriptId = parts[2] ?? "";
633
- const choiceId = parts[3] ?? "";
634
- const interactionEvent = {
635
- source: this.name,
636
- bundleId: transcriptId,
637
- choiceId,
638
- authorId: userId,
639
- receivedAt: new Date().toISOString(),
640
- ...(interaction.channelId ? { channelId: interaction.channelId } : {}),
641
- ...(interaction.guildId ? { guildId: interaction.guildId } : {}),
642
- messageId: interaction.id,
643
- };
644
- await writeInboxRow({
645
- repoRoot: this.opts.repoRoot,
646
- source: this.name,
647
- kind: "interaction",
648
- payload: { interaction: interactionEvent, voice_transcript_id: transcriptId },
649
- });
650
- if (this.interactionHandler)
651
- await this.interactionHandler(interactionEvent);
652
- await interaction.deferUpdate();
653
- await this.ackReact(interaction, choiceId);
654
- return;
655
- }
656
- if (kind === "dialog") {
657
- // bundleId may contain colons (e.g. `TSK-XXX:Q1` from the
658
- // tightener per-question walk), so the choiceId is always the
659
- // LAST segment and bundleId is everything between `dialog:` and
660
- // the last `:`.
661
- const choiceId = parts[parts.length - 1] ?? "";
662
- const bundleId = parts.slice(2, -1).join(":");
663
- const pending = this.pendingDialogs.get(bundleId);
664
- const compact = pending?.compactOnAnswer ?? true;
665
- const choiceLabel = pending?.choiceLabels.get(choiceId) ?? choiceId.toUpperCase();
666
- if (pending) {
667
- clearTimeout(pending.timeoutHandle);
668
- this.pendingDialogs.delete(bundleId);
669
- // Don't drop the messageId here โ€” the next walk step's
670
- // requestDialog (with replaceBundleId=this) needs to fetch + edit
671
- // it. The next requestDialog deletes the old entry once it
672
- // claims the message.
673
- pending.resolve({ bundleId, choiceId });
674
- }
675
- const interactionEvent = {
676
- source: this.name,
677
- bundleId,
678
- choiceId,
679
- authorId: userId,
680
- receivedAt: new Date().toISOString(),
681
- ...(interaction.channelId ? { channelId: interaction.channelId } : {}),
682
- ...(interaction.guildId ? { guildId: interaction.guildId } : {}),
683
- messageId: interaction.id,
684
- };
685
- await writeInboxRow({
686
- repoRoot: this.opts.repoRoot,
687
- source: this.name,
688
- kind: "interaction",
689
- payload: { interaction: interactionEvent },
690
- });
691
- if (this.interactionHandler)
692
- await this.interactionHandler(interactionEvent);
693
- // Walk steps (compactOnAnswer=false) defer to the next dialog's
694
- // edit-in-place; touching the message here would race the next
695
- // step and either drop its content or its buttons. Only terminal
696
- // dialogs (confirm, UAT, ad-hoc operator question) compact.
697
- if (compact) {
698
- try {
699
- const original = interaction.message;
700
- const annotated = (original.content ?? "").trim() +
701
- `\n\n> **Answered:** ${choiceId.toUpperCase()} โ€” ${choiceLabel}`;
702
- await interaction.update({
703
- content: annotated.slice(0, 2000),
704
- components: [],
705
- });
706
- }
707
- catch (err) {
708
- log.warn({ err: String(err), bundleId, choiceId }, "dialog message compact-update failed");
709
- try {
710
- await interaction.deferUpdate();
711
- }
712
- catch {
713
- /* noop */
714
- }
715
- }
716
- }
717
- else {
718
- try {
719
- await interaction.deferUpdate();
720
- }
721
- catch (err) {
722
- log.warn({ err: String(err), bundleId, choiceId }, "dialog deferUpdate failed");
723
- }
724
- }
725
- await this.ackReact(interaction, choiceId);
726
- return;
727
- }
728
- await interaction.deferUpdate();
729
- }
730
- /**
731
- * Stamp the bot's processing on the message: success โ†’ โœ…, decline โ†’
732
- * โŒ, ask โ†’ โ“. Best-effort; reaction failures don't bubble.
733
- */
734
- async ackReact(interaction, decision) {
735
- let emoji = "โœ…";
736
- if (decision === "reject" ||
737
- decision === "cancel" ||
738
- decision === "no" ||
739
- decision === "edit") {
740
- emoji = "โŒ";
741
- }
742
- else if (decision === "ask" || decision === "ship_anyway") {
743
- emoji = "โšก";
744
- }
745
- try {
746
- await interaction.message.react(emoji);
747
- }
748
- catch (err) {
749
- log.warn({ err: String(err), choice: decision }, "ackReact failed");
750
- }
751
- }
752
- async handleMessage(message) {
753
- if (message.author.bot)
754
- return;
755
- const userId = message.author.id;
756
- if (!isOwner(this.ownerIds, userId))
757
- return;
758
- // Voice attachments take precedence โ€” bypass text classification path.
759
- const voiceAttachments = message.attachments.filter((a) => {
760
- const mime = a.contentType ?? "";
761
- return VOICE_MIME_PREFIXES.some((p) => mime.startsWith(p));
762
- });
763
- if (voiceAttachments.size > 0) {
764
- for (const attachment of voiceAttachments.values()) {
765
- await this.handleVoiceAttachment(message, userId, attachment);
766
- }
767
- return;
768
- }
769
- const text = message.content?.trim() ?? "";
770
- if (text.length === 0)
771
- return;
772
- const classification = await classifyTier0(text, this.tier0Opts);
773
- const event = {
774
- source: this.name,
775
- intent: classification.intent,
776
- rawText: text,
777
- authorId: userId,
778
- receivedAt: new Date().toISOString(),
779
- channelId: message.channelId,
780
- messageId: message.id,
781
- ...(message.guildId ? { guildId: message.guildId } : {}),
782
- };
783
- if (classification.intent === "code_task") {
784
- const taskId = newTaskId();
785
- const task = {
786
- source: this.name,
787
- intent: "code_task",
788
- rawText: text,
789
- authorId: userId,
790
- receivedAt: event.receivedAt,
791
- channelId: message.channelId,
792
- messageId: message.id,
793
- ...(message.guildId ? { guildId: message.guildId } : {}),
794
- };
795
- await writeInboxRow({
796
- repoRoot: this.opts.repoRoot,
797
- source: this.name,
798
- kind: "task",
799
- payload: { task, free_text: event, task_id: taskId, classification },
800
- });
801
- if (this.taskHandler)
802
- await this.taskHandler(task);
803
- try {
804
- await message.react("๐Ÿ“‹");
805
- }
806
- catch {
807
- // permission optional
808
- }
809
- return;
810
- }
811
- await writeInboxRow({
812
- repoRoot: this.opts.repoRoot,
813
- source: this.name,
814
- kind: "free_text",
815
- payload: { free_text: event, classification },
816
- });
817
- if (this.freeTextHandler)
818
- await this.freeTextHandler(event);
819
- try {
820
- await message.react(classification.intent === "unknown" ? "โ“" : "โœ…");
821
- }
822
- catch {
823
- // optional
824
- }
825
- }
826
- async handleVoiceAttachment(message, userId, attachment) {
827
- const transcriptId = `VTX-${Date.now().toString(36)}-${randomBytes(2).toString("hex")}`;
828
- let transcript = null;
829
- let transcribeError = null;
830
- if (!whisperModelExists()) {
831
- transcribeError = "whisper model not installed (Phase 16 init or manual download)";
832
- }
833
- else {
834
- try {
835
- transcript = await transcribeUrl(attachment.url, { language: "en" });
836
- }
837
- catch (err) {
838
- transcribeError = err instanceof Error ? err.message : String(err);
839
- }
840
- }
841
- let classification = null;
842
- if (transcript && transcript.text.length > 0) {
843
- try {
844
- classification = await classifyTier0(transcript.text, this.tier0Opts);
845
- }
846
- catch (err) {
847
- log.warn({ err: String(err) }, "tier0 classify on transcript failed");
848
- }
849
- }
850
- const voice = {
851
- source: this.name,
852
- attachmentUrl: attachment.url,
853
- authorId: userId,
854
- channelId: message.channelId,
855
- messageId: message.id,
856
- receivedAt: new Date().toISOString(),
857
- ...(attachment.contentType ? { mime: attachment.contentType } : {}),
858
- ...(message.guildId ? { guildId: message.guildId } : {}),
859
- };
860
- const belowFloor = transcript !== null && transcript.avgLogprob < this.confidenceFloor;
861
- const transcriptPayload = transcript
862
- ? {
863
- id: transcriptId,
864
- text: transcript.text,
865
- avg_logprob: transcript.avgLogprob,
866
- language: transcript.language,
867
- duration_ms: transcript.durationMs,
868
- segments: transcript.segments.length,
869
- confidence_floor: this.confidenceFloor,
870
- below_floor: belowFloor,
871
- }
872
- : null;
873
- await writeInboxRow({
874
- repoRoot: this.opts.repoRoot,
875
- source: this.name,
876
- kind: "voice",
877
- payload: {
878
- voice,
879
- transcript: transcriptPayload,
880
- transcribe_error: transcribeError,
881
- classification,
882
- },
883
- });
884
- if (this.voiceHandler)
885
- await this.voiceHandler(voice);
886
- if (transcribeError !== null) {
887
- try {
888
- await message.react("โš ๏ธ");
889
- }
890
- catch {
891
- // optional
892
- }
893
- try {
894
- await message.reply(`Transcription failed: \`${transcribeError.slice(0, 200)}\`. Inbox row dropped without transcript.`);
895
- }
896
- catch {
897
- // optional
898
- }
899
- return;
900
- }
901
- if (transcript && belowFloor) {
902
- try {
903
- const row = new ActionRowBuilder().addComponents(new ButtonBuilder()
904
- .setCustomId(`harness:voice-confirm:${transcriptId}:yes`)
905
- .setStyle(ButtonStyle.Success)
906
- .setLabel("Confirm")
907
- .setEmoji("๐ŸŸข"), new ButtonBuilder()
908
- .setCustomId(`harness:voice-confirm:${transcriptId}:no`)
909
- .setStyle(ButtonStyle.Danger)
910
- .setLabel("Re-record")
911
- .setEmoji("๐Ÿ”ด"));
912
- const pct = (transcript.avgLogprob * 100).toFixed(0);
913
- const summary = transcript.text.length > 220
914
- ? `${transcript.text.slice(0, 217)}...`
915
- : transcript.text;
916
- await message.reply({
917
- content: `Heard: "${summary}" (confidence ${pct}%) โ€” confirm?`,
918
- components: [row],
919
- });
920
- }
921
- catch (err) {
922
- log.warn({ err: String(err) }, "voice confirm prompt failed");
923
- }
924
- return;
925
- }
926
- try {
927
- await message.react("๐Ÿ‘‚");
928
- }
929
- catch {
930
- // optional
931
- }
932
- }
933
- }
934
- function newTaskId() {
935
- return `TSK-${Date.now().toString(36)}-${randomBytes(2).toString("hex")}`;
936
- }
937
- /**
938
- * True when the error is a discord.js DiscordAPIError with code 10003
939
- * (Unknown Channel). The harness flags the channel dead and silently
940
- * no-ops every subsequent post against it.
941
- */
942
- function isUnknownChannelError(err) {
943
- if (typeof err !== "object" || err === null)
944
- return false;
945
- const e = err;
946
- return e["code"] === 10003;
947
- }
948
- /**
949
- * Map orchestrator phase names โ†’ embed colors. Mirrors the harness
950
- * pipeline: tighten โ†’ prep โ†’ run โ†’ sense โ†’ review โ†’ uat โ†’ done.
951
- *
952
- * `blocked` is cyan (waiting-on-operator), distinct from failure
953
- * orange โ€” operator should read the color as "your move", not "broken".
954
- */
955
- const PHASE_COLOR = {
956
- queued: 0x607d8b,
957
- tightening: 0x3498db,
958
- blocked: 0x4a90e2,
959
- prepping: 0x95a5a6,
960
- running: 0xf1c40f,
961
- sensing: 0xe67e22,
962
- reviewing: 0x9b59b6,
963
- uat: 0x1abc9c,
964
- backpropping: 0x6f42c1,
965
- succeeded: 0x2ecc71,
966
- failed: 0xe74c3c,
967
- };
968
- const PHASE_EMOJI = {
969
- queued: "๐ŸŸฆ",
970
- tightening: "๐Ÿช›",
971
- blocked: "โณ",
972
- prepping: "๐Ÿงฐ",
973
- running: "๐ŸŸก",
974
- sensing: "๐Ÿ”Ž",
975
- reviewing: "๐Ÿ”ฌ",
976
- uat: "๐Ÿงช",
977
- backpropping: "๐Ÿ“",
978
- succeeded: "๐ŸŸข",
979
- failed: "๐Ÿ”ด",
980
- };
981
- const PHASE_LABEL = {
982
- queued: "queued",
983
- tightening: "tightening spec",
984
- blocked: "awaiting you",
985
- prepping: "prepping workspace",
986
- running: "agent running",
987
- sensing: "sensors sweeping",
988
- reviewing: "reviewer checking",
989
- uat: "UAT in flight",
990
- backpropping: "backprop",
991
- succeeded: "shipped",
992
- failed: "failed",
993
- };
994
- /** ยง3.4 win 3 โ€” class-specific failure color so operator routes per class. */
995
- const FAILURE_COLOR = {
996
- sensor: 0xff8c00, // orange โ€” mechanical / structural fail; usually fixable
997
- reviewer: 0x9b59b6, // purple โ€” inferential reject; needs spec or code thinking
998
- uat: 0x3498db, // blue โ€” operator rejected the bundle
999
- hard: 0xff0000, // red โ€” process / infra error
1000
- halt: 0x2c2c2c, // dark grey โ€” operator pulled the cord
1001
- };
1002
- const FAILURE_EMOJI = {
1003
- sensor: "๐ŸŸง",
1004
- reviewer: "๐ŸŸช",
1005
- uat: "๐ŸŸฆ",
1006
- hard: "๐ŸŸฅ",
1007
- halt: "โšซ",
1008
- };
1009
- function buildPhaseEmbed(update) {
1010
- const isFailure = update.status === "failed" && update.failureClass !== undefined;
1011
- const color = isFailure
1012
- ? FAILURE_COLOR[update.failureClass] ?? 0xff0000
1013
- : PHASE_COLOR[update.status] ?? 0x808080;
1014
- const emoji = isFailure
1015
- ? FAILURE_EMOJI[update.failureClass] ?? "๐Ÿ”ด"
1016
- : PHASE_EMOJI[update.status] ?? "โ€ข";
1017
- const titleSuffix = isFailure ? ` โ€” failed: ${update.failureClass}` : "";
1018
- const phaseLabel = PHASE_LABEL[update.status] ?? update.status;
1019
- const descLines = [`**${phaseLabel}**`];
1020
- if (update.taskBody !== undefined && update.taskBody.length > 0) {
1021
- descLines.push("");
1022
- const trimmed = update.taskBody.trim();
1023
- const truncated = trimmed.length > 1024
1024
- ? `${trimmed.slice(0, 1010)}โ€ฆ`
1025
- : trimmed;
1026
- // Quote-block so the spec stays visually distinct from status.
1027
- descLines.push(...truncated.split("\n").map((line) => `> ${line}`));
1028
- }
1029
- if (update.recentEvents !== undefined && update.recentEvents.length > 0) {
1030
- descLines.push("");
1031
- descLines.push("**recent**");
1032
- for (const ev of update.recentEvents)
1033
- descLines.push(ev);
1034
- }
1035
- const description = descLines.join("\n").slice(0, 4000);
1036
- const embed = new EmbedBuilder()
1037
- .setTitle(`${emoji} ${update.taskId}${titleSuffix}`)
1038
- .setColor(color)
1039
- .setDescription(description);
1040
- if (update.runId) {
1041
- embed.addFields({ name: "run", value: `\`${update.runId}\``, inline: true });
1042
- }
1043
- if (update.activity !== undefined && update.activity.length > 0) {
1044
- embed.addFields({
1045
- name: "activity",
1046
- value: update.activity.slice(0, 1024),
1047
- inline: false,
1048
- });
1049
- }
1050
- if (update.tools?.files !== undefined &&
1051
- update.tools.files.length > 0) {
1052
- embed.addFields({
1053
- name: "files",
1054
- value: capFieldValue(update.tools.files.map((f) => `\`${f}\``).join("\n")),
1055
- inline: false,
1056
- });
1057
- }
1058
- if (update.tools?.bash !== undefined && update.tools.bash.length > 0) {
1059
- embed.addFields({
1060
- name: "shell",
1061
- value: capFieldValue(update.tools.bash.map((c) => `\`${c}\``).join("\n")),
1062
- inline: false,
1063
- });
1064
- }
1065
- if (update.tools?.searches !== undefined &&
1066
- update.tools.searches.length > 0) {
1067
- embed.addFields({
1068
- name: "searches",
1069
- value: capFieldValue(update.tools.searches.map((s) => `\`${s}\``).join("\n")),
1070
- inline: false,
1071
- });
1072
- }
1073
- if (update.body !== undefined && update.body.length > 0) {
1074
- let value = update.body;
1075
- if (value.length > 1024) {
1076
- value = `${value.slice(0, 1010)}โ€ฆ\n_+${update.body.length - 1010} chars in log.jsonl_`;
1077
- }
1078
- embed.addFields({ name: "details", value, inline: false });
1079
- }
1080
- if (update.remediation !== undefined) {
1081
- const lines = [
1082
- `**reason:** ${update.remediation.reason.slice(0, 400)}`,
1083
- ];
1084
- if (update.remediation.suggestedActions.length > 0) {
1085
- lines.push("**next:**");
1086
- for (const a of update.remediation.suggestedActions.slice(0, 5)) {
1087
- lines.push(`โ€ข ${a}`);
1088
- }
1089
- }
1090
- embed.addFields({
1091
- name: "remediation",
1092
- value: capFieldValue(lines.join("\n")),
1093
- inline: false,
1094
- });
1095
- }
1096
- embed.setTimestamp(new Date());
1097
- return embed;
1098
- }
1099
- function capFieldValue(text) {
1100
- return text.length > 1024 ? `${text.slice(0, 1018)}\nโ€ฆ` : text;
1101
- }
1102
- // re-export utilities for callers (orchestrator, smoke)
1103
- export { CATEGORY_NAMES, ensureCategories, moveChannelToCategory, slugifyForChannel, };
1104
- //# sourceMappingURL=index.js.map