reflectt-node 0.1.0

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 (486) hide show
  1. package/LICENSE +178 -0
  2. package/README.md +188 -0
  3. package/dist/activationEvents.d.ts +110 -0
  4. package/dist/activationEvents.d.ts.map +1 -0
  5. package/dist/activationEvents.js +378 -0
  6. package/dist/activationEvents.js.map +1 -0
  7. package/dist/activity-signal.d.ts +30 -0
  8. package/dist/activity-signal.d.ts.map +1 -0
  9. package/dist/activity-signal.js +93 -0
  10. package/dist/activity-signal.js.map +1 -0
  11. package/dist/alert-integrity.d.ts +100 -0
  12. package/dist/alert-integrity.d.ts.map +1 -0
  13. package/dist/alert-integrity.js +333 -0
  14. package/dist/alert-integrity.js.map +1 -0
  15. package/dist/alert-preflight.d.ts +40 -0
  16. package/dist/alert-preflight.d.ts.map +1 -0
  17. package/dist/alert-preflight.js +235 -0
  18. package/dist/alert-preflight.js.map +1 -0
  19. package/dist/analytics.d.ts +131 -0
  20. package/dist/analytics.d.ts.map +1 -0
  21. package/dist/analytics.js +371 -0
  22. package/dist/analytics.js.map +1 -0
  23. package/dist/artifact-mirror.d.ts +26 -0
  24. package/dist/artifact-mirror.d.ts.map +1 -0
  25. package/dist/artifact-mirror.js +170 -0
  26. package/dist/artifact-mirror.js.map +1 -0
  27. package/dist/artifact-resolver.d.ts +48 -0
  28. package/dist/artifact-resolver.d.ts.map +1 -0
  29. package/dist/artifact-resolver.js +164 -0
  30. package/dist/artifact-resolver.js.map +1 -0
  31. package/dist/assignment.d.ts +116 -0
  32. package/dist/assignment.d.ts.map +1 -0
  33. package/dist/assignment.js +475 -0
  34. package/dist/assignment.js.map +1 -0
  35. package/dist/auditLedger.d.ts +50 -0
  36. package/dist/auditLedger.d.ts.map +1 -0
  37. package/dist/auditLedger.js +136 -0
  38. package/dist/auditLedger.js.map +1 -0
  39. package/dist/boardHealthWorker.d.ts +134 -0
  40. package/dist/boardHealthWorker.d.ts.map +1 -0
  41. package/dist/boardHealthWorker.js +882 -0
  42. package/dist/boardHealthWorker.js.map +1 -0
  43. package/dist/bootstrap-team.d.ts +42 -0
  44. package/dist/bootstrap-team.d.ts.map +1 -0
  45. package/dist/bootstrap-team.js +111 -0
  46. package/dist/bootstrap-team.js.map +1 -0
  47. package/dist/buildInfo.d.ts +17 -0
  48. package/dist/buildInfo.d.ts.map +1 -0
  49. package/dist/buildInfo.js +56 -0
  50. package/dist/buildInfo.js.map +1 -0
  51. package/dist/calendar-events.d.ts +133 -0
  52. package/dist/calendar-events.d.ts.map +1 -0
  53. package/dist/calendar-events.js +615 -0
  54. package/dist/calendar-events.js.map +1 -0
  55. package/dist/calendar-ical.d.ts +41 -0
  56. package/dist/calendar-ical.d.ts.map +1 -0
  57. package/dist/calendar-ical.js +413 -0
  58. package/dist/calendar-ical.js.map +1 -0
  59. package/dist/calendar-reminder-engine.d.ts +10 -0
  60. package/dist/calendar-reminder-engine.d.ts.map +1 -0
  61. package/dist/calendar-reminder-engine.js +143 -0
  62. package/dist/calendar-reminder-engine.js.map +1 -0
  63. package/dist/calendar.d.ts +75 -0
  64. package/dist/calendar.d.ts.map +1 -0
  65. package/dist/calendar.js +391 -0
  66. package/dist/calendar.js.map +1 -0
  67. package/dist/canvas-multiplexer.d.ts +44 -0
  68. package/dist/canvas-multiplexer.d.ts.map +1 -0
  69. package/dist/canvas-multiplexer.js +150 -0
  70. package/dist/canvas-multiplexer.js.map +1 -0
  71. package/dist/canvas-slots.d.ts +83 -0
  72. package/dist/canvas-slots.d.ts.map +1 -0
  73. package/dist/canvas-slots.js +144 -0
  74. package/dist/canvas-slots.js.map +1 -0
  75. package/dist/canvas-types.d.ts +56 -0
  76. package/dist/canvas-types.d.ts.map +1 -0
  77. package/dist/canvas-types.js +54 -0
  78. package/dist/canvas-types.js.map +1 -0
  79. package/dist/cf-keepalive.d.ts +40 -0
  80. package/dist/cf-keepalive.d.ts.map +1 -0
  81. package/dist/cf-keepalive.js +153 -0
  82. package/dist/cf-keepalive.js.map +1 -0
  83. package/dist/changeFeed.d.ts +38 -0
  84. package/dist/changeFeed.d.ts.map +1 -0
  85. package/dist/changeFeed.js +324 -0
  86. package/dist/changeFeed.js.map +1 -0
  87. package/dist/channels.d.ts +28 -0
  88. package/dist/channels.d.ts.map +1 -0
  89. package/dist/channels.js +23 -0
  90. package/dist/channels.js.map +1 -0
  91. package/dist/chat-approval-detector.d.ts +47 -0
  92. package/dist/chat-approval-detector.d.ts.map +1 -0
  93. package/dist/chat-approval-detector.js +224 -0
  94. package/dist/chat-approval-detector.js.map +1 -0
  95. package/dist/chat.d.ts +119 -0
  96. package/dist/chat.d.ts.map +1 -0
  97. package/dist/chat.js +666 -0
  98. package/dist/chat.js.map +1 -0
  99. package/dist/cli.d.ts +3 -0
  100. package/dist/cli.d.ts.map +1 -0
  101. package/dist/cli.js +1142 -0
  102. package/dist/cli.js.map +1 -0
  103. package/dist/cloud.d.ts +45 -0
  104. package/dist/cloud.d.ts.map +1 -0
  105. package/dist/cloud.js +962 -0
  106. package/dist/cloud.js.map +1 -0
  107. package/dist/config.d.ts +17 -0
  108. package/dist/config.d.ts.map +1 -0
  109. package/dist/config.js +33 -0
  110. package/dist/config.js.map +1 -0
  111. package/dist/connectivity.d.ts +59 -0
  112. package/dist/connectivity.d.ts.map +1 -0
  113. package/dist/connectivity.js +173 -0
  114. package/dist/connectivity.js.map +1 -0
  115. package/dist/contacts.d.ts +59 -0
  116. package/dist/contacts.d.ts.map +1 -0
  117. package/dist/contacts.js +183 -0
  118. package/dist/contacts.js.map +1 -0
  119. package/dist/content.d.ts +130 -0
  120. package/dist/content.d.ts.map +1 -0
  121. package/dist/content.js +186 -0
  122. package/dist/content.js.map +1 -0
  123. package/dist/context-budget.d.ts +87 -0
  124. package/dist/context-budget.d.ts.map +1 -0
  125. package/dist/context-budget.js +459 -0
  126. package/dist/context-budget.js.map +1 -0
  127. package/dist/continuity-loop.d.ts +55 -0
  128. package/dist/continuity-loop.d.ts.map +1 -0
  129. package/dist/continuity-loop.js +267 -0
  130. package/dist/continuity-loop.js.map +1 -0
  131. package/dist/dashboard.d.ts +6 -0
  132. package/dist/dashboard.d.ts.map +1 -0
  133. package/dist/dashboard.js +2348 -0
  134. package/dist/dashboard.js.map +1 -0
  135. package/dist/db.d.ts +44 -0
  136. package/dist/db.d.ts.map +1 -0
  137. package/dist/db.js +648 -0
  138. package/dist/db.js.map +1 -0
  139. package/dist/doctor.d.ts +30 -0
  140. package/dist/doctor.d.ts.map +1 -0
  141. package/dist/doctor.js +159 -0
  142. package/dist/doctor.js.map +1 -0
  143. package/dist/duplicateClosureGuard.d.ts +31 -0
  144. package/dist/duplicateClosureGuard.d.ts.map +1 -0
  145. package/dist/duplicateClosureGuard.js +83 -0
  146. package/dist/duplicateClosureGuard.js.map +1 -0
  147. package/dist/embeddings.d.ts +13 -0
  148. package/dist/embeddings.d.ts.map +1 -0
  149. package/dist/embeddings.js +78 -0
  150. package/dist/embeddings.js.map +1 -0
  151. package/dist/escalation.d.ts +80 -0
  152. package/dist/escalation.d.ts.map +1 -0
  153. package/dist/escalation.js +213 -0
  154. package/dist/escalation.js.map +1 -0
  155. package/dist/events.d.ts +130 -0
  156. package/dist/events.d.ts.map +1 -0
  157. package/dist/events.js +382 -0
  158. package/dist/events.js.map +1 -0
  159. package/dist/executionSweeper.d.ts +97 -0
  160. package/dist/executionSweeper.d.ts.map +1 -0
  161. package/dist/executionSweeper.js +875 -0
  162. package/dist/executionSweeper.js.map +1 -0
  163. package/dist/experiments.d.ts +47 -0
  164. package/dist/experiments.d.ts.map +1 -0
  165. package/dist/experiments.js +133 -0
  166. package/dist/experiments.js.map +1 -0
  167. package/dist/feedback.d.ts +179 -0
  168. package/dist/feedback.d.ts.map +1 -0
  169. package/dist/feedback.js +397 -0
  170. package/dist/feedback.js.map +1 -0
  171. package/dist/files.d.ts +52 -0
  172. package/dist/files.d.ts.map +1 -0
  173. package/dist/files.js +172 -0
  174. package/dist/files.js.map +1 -0
  175. package/dist/format-duration.d.ts +19 -0
  176. package/dist/format-duration.d.ts.map +1 -0
  177. package/dist/format-duration.js +33 -0
  178. package/dist/format-duration.js.map +1 -0
  179. package/dist/github-actor-auth.d.ts +20 -0
  180. package/dist/github-actor-auth.d.ts.map +1 -0
  181. package/dist/github-actor-auth.js +54 -0
  182. package/dist/github-actor-auth.js.map +1 -0
  183. package/dist/github-ci.d.ts +16 -0
  184. package/dist/github-ci.d.ts.map +1 -0
  185. package/dist/github-ci.js +37 -0
  186. package/dist/github-ci.js.map +1 -0
  187. package/dist/github-identity.d.ts +30 -0
  188. package/dist/github-identity.d.ts.map +1 -0
  189. package/dist/github-identity.js +96 -0
  190. package/dist/github-identity.js.map +1 -0
  191. package/dist/github-reviews.d.ts +24 -0
  192. package/dist/github-reviews.d.ts.map +1 -0
  193. package/dist/github-reviews.js +56 -0
  194. package/dist/github-reviews.js.map +1 -0
  195. package/dist/health.d.ts +391 -0
  196. package/dist/health.d.ts.map +1 -0
  197. package/dist/health.js +1841 -0
  198. package/dist/health.js.map +1 -0
  199. package/dist/host-keepalive.d.ts +22 -0
  200. package/dist/host-keepalive.d.ts.map +1 -0
  201. package/dist/host-keepalive.js +126 -0
  202. package/dist/host-keepalive.js.map +1 -0
  203. package/dist/host-registry.d.ts +43 -0
  204. package/dist/host-registry.d.ts.map +1 -0
  205. package/dist/host-registry.js +93 -0
  206. package/dist/host-registry.js.map +1 -0
  207. package/dist/inbox.d.ts +87 -0
  208. package/dist/inbox.d.ts.map +1 -0
  209. package/dist/inbox.js +410 -0
  210. package/dist/inbox.js.map +1 -0
  211. package/dist/index.d.ts +2 -0
  212. package/dist/index.d.ts.map +1 -0
  213. package/dist/index.js +306 -0
  214. package/dist/index.js.map +1 -0
  215. package/dist/insight-mutation.d.ts +32 -0
  216. package/dist/insight-mutation.d.ts.map +1 -0
  217. package/dist/insight-mutation.js +160 -0
  218. package/dist/insight-mutation.js.map +1 -0
  219. package/dist/insight-promotion.d.ts +89 -0
  220. package/dist/insight-promotion.d.ts.map +1 -0
  221. package/dist/insight-promotion.js +278 -0
  222. package/dist/insight-promotion.js.map +1 -0
  223. package/dist/insight-task-bridge.d.ts +77 -0
  224. package/dist/insight-task-bridge.d.ts.map +1 -0
  225. package/dist/insight-task-bridge.js +556 -0
  226. package/dist/insight-task-bridge.js.map +1 -0
  227. package/dist/insights.d.ts +222 -0
  228. package/dist/insights.d.ts.map +1 -0
  229. package/dist/insights.js +871 -0
  230. package/dist/insights.js.map +1 -0
  231. package/dist/intake-pipeline.d.ts +74 -0
  232. package/dist/intake-pipeline.d.ts.map +1 -0
  233. package/dist/intake-pipeline.js +199 -0
  234. package/dist/intake-pipeline.js.map +1 -0
  235. package/dist/intensity.d.ts +31 -0
  236. package/dist/intensity.d.ts.map +1 -0
  237. package/dist/intensity.js +94 -0
  238. package/dist/intensity.js.map +1 -0
  239. package/dist/knowledge-auto-index.d.ts +37 -0
  240. package/dist/knowledge-auto-index.d.ts.map +1 -0
  241. package/dist/knowledge-auto-index.js +149 -0
  242. package/dist/knowledge-auto-index.js.map +1 -0
  243. package/dist/knowledge-docs.d.ts +45 -0
  244. package/dist/knowledge-docs.d.ts.map +1 -0
  245. package/dist/knowledge-docs.js +188 -0
  246. package/dist/knowledge-docs.js.map +1 -0
  247. package/dist/lane-config.d.ts +25 -0
  248. package/dist/lane-config.d.ts.map +1 -0
  249. package/dist/lane-config.js +105 -0
  250. package/dist/lane-config.js.map +1 -0
  251. package/dist/lineage.d.ts +86 -0
  252. package/dist/lineage.d.ts.map +1 -0
  253. package/dist/lineage.js +303 -0
  254. package/dist/lineage.js.map +1 -0
  255. package/dist/logStore.d.ts +25 -0
  256. package/dist/logStore.d.ts.map +1 -0
  257. package/dist/logStore.js +83 -0
  258. package/dist/logStore.js.map +1 -0
  259. package/dist/manage.d.ts +12 -0
  260. package/dist/manage.d.ts.map +1 -0
  261. package/dist/manage.js +253 -0
  262. package/dist/manage.js.map +1 -0
  263. package/dist/mcp.d.ts +5 -0
  264. package/dist/mcp.d.ts.map +1 -0
  265. package/dist/mcp.js +604 -0
  266. package/dist/mcp.js.map +1 -0
  267. package/dist/memory.d.ts +47 -0
  268. package/dist/memory.d.ts.map +1 -0
  269. package/dist/memory.js +149 -0
  270. package/dist/memory.js.map +1 -0
  271. package/dist/mention-ack.d.ts +80 -0
  272. package/dist/mention-ack.d.ts.map +1 -0
  273. package/dist/mention-ack.js +175 -0
  274. package/dist/mention-ack.js.map +1 -0
  275. package/dist/messageRouter.d.ts +60 -0
  276. package/dist/messageRouter.d.ts.map +1 -0
  277. package/dist/messageRouter.js +309 -0
  278. package/dist/messageRouter.js.map +1 -0
  279. package/dist/mutationAlert.d.ts +44 -0
  280. package/dist/mutationAlert.d.ts.map +1 -0
  281. package/dist/mutationAlert.js +174 -0
  282. package/dist/mutationAlert.js.map +1 -0
  283. package/dist/noise-budget.d.ts +136 -0
  284. package/dist/noise-budget.d.ts.map +1 -0
  285. package/dist/noise-budget.js +340 -0
  286. package/dist/noise-budget.js.map +1 -0
  287. package/dist/notifications.d.ts +67 -0
  288. package/dist/notifications.d.ts.map +1 -0
  289. package/dist/notifications.js +253 -0
  290. package/dist/notifications.js.map +1 -0
  291. package/dist/openclaw.d.ts +34 -0
  292. package/dist/openclaw.d.ts.map +1 -0
  293. package/dist/openclaw.js +208 -0
  294. package/dist/openclaw.js.map +1 -0
  295. package/dist/pause-controls.d.ts +31 -0
  296. package/dist/pause-controls.d.ts.map +1 -0
  297. package/dist/pause-controls.js +130 -0
  298. package/dist/pause-controls.js.map +1 -0
  299. package/dist/pidlock.d.ts +25 -0
  300. package/dist/pidlock.d.ts.map +1 -0
  301. package/dist/pidlock.js +179 -0
  302. package/dist/pidlock.js.map +1 -0
  303. package/dist/policy.d.ts +139 -0
  304. package/dist/policy.d.ts.map +1 -0
  305. package/dist/policy.js +264 -0
  306. package/dist/policy.js.map +1 -0
  307. package/dist/polls.d.ts +47 -0
  308. package/dist/polls.d.ts.map +1 -0
  309. package/dist/polls.js +162 -0
  310. package/dist/polls.js.map +1 -0
  311. package/dist/portability.d.ts +55 -0
  312. package/dist/portability.d.ts.map +1 -0
  313. package/dist/portability.js +292 -0
  314. package/dist/portability.js.map +1 -0
  315. package/dist/pr-integrity.d.ts +45 -0
  316. package/dist/pr-integrity.d.ts.map +1 -0
  317. package/dist/pr-integrity.js +124 -0
  318. package/dist/pr-integrity.js.map +1 -0
  319. package/dist/prAutoMerge.d.ts +62 -0
  320. package/dist/prAutoMerge.d.ts.map +1 -0
  321. package/dist/prAutoMerge.js +493 -0
  322. package/dist/prAutoMerge.js.map +1 -0
  323. package/dist/preflight.d.ts +66 -0
  324. package/dist/preflight.d.ts.map +1 -0
  325. package/dist/preflight.js +864 -0
  326. package/dist/preflight.js.map +1 -0
  327. package/dist/presence.d.ts +98 -0
  328. package/dist/presence.d.ts.map +1 -0
  329. package/dist/presence.js +347 -0
  330. package/dist/presence.js.map +1 -0
  331. package/dist/provisioning.d.ts +101 -0
  332. package/dist/provisioning.d.ts.map +1 -0
  333. package/dist/provisioning.js +430 -0
  334. package/dist/provisioning.js.map +1 -0
  335. package/dist/reflection-automation.d.ts +59 -0
  336. package/dist/reflection-automation.d.ts.map +1 -0
  337. package/dist/reflection-automation.js +350 -0
  338. package/dist/reflection-automation.js.map +1 -0
  339. package/dist/reflections.d.ts +65 -0
  340. package/dist/reflections.d.ts.map +1 -0
  341. package/dist/reflections.js +306 -0
  342. package/dist/reflections.js.map +1 -0
  343. package/dist/release.d.ts +67 -0
  344. package/dist/release.d.ts.map +1 -0
  345. package/dist/release.js +275 -0
  346. package/dist/release.js.map +1 -0
  347. package/dist/request-tracker.d.ts +36 -0
  348. package/dist/request-tracker.d.ts.map +1 -0
  349. package/dist/request-tracker.js +109 -0
  350. package/dist/request-tracker.js.map +1 -0
  351. package/dist/research.d.ts +75 -0
  352. package/dist/research.d.ts.map +1 -0
  353. package/dist/research.js +171 -0
  354. package/dist/research.js.map +1 -0
  355. package/dist/routing-approvals.d.ts +73 -0
  356. package/dist/routing-approvals.d.ts.map +1 -0
  357. package/dist/routing-approvals.js +88 -0
  358. package/dist/routing-approvals.js.map +1 -0
  359. package/dist/routing-override.d.ts +94 -0
  360. package/dist/routing-override.d.ts.map +1 -0
  361. package/dist/routing-override.js +290 -0
  362. package/dist/routing-override.js.map +1 -0
  363. package/dist/scope-routing.d.ts +18 -0
  364. package/dist/scope-routing.d.ts.map +1 -0
  365. package/dist/scope-routing.js +29 -0
  366. package/dist/scope-routing.js.map +1 -0
  367. package/dist/secrets.d.ts +77 -0
  368. package/dist/secrets.d.ts.map +1 -0
  369. package/dist/secrets.js +287 -0
  370. package/dist/secrets.js.map +1 -0
  371. package/dist/server.d.ts +3 -0
  372. package/dist/server.d.ts.map +1 -0
  373. package/dist/server.js +10887 -0
  374. package/dist/server.js.map +1 -0
  375. package/dist/service-probe.d.ts +53 -0
  376. package/dist/service-probe.d.ts.map +1 -0
  377. package/dist/service-probe.js +225 -0
  378. package/dist/service-probe.js.map +1 -0
  379. package/dist/shared-workspace-api.d.ts +73 -0
  380. package/dist/shared-workspace-api.d.ts.map +1 -0
  381. package/dist/shared-workspace-api.js +281 -0
  382. package/dist/shared-workspace-api.js.map +1 -0
  383. package/dist/shipped-heartbeat.d.ts +91 -0
  384. package/dist/shipped-heartbeat.d.ts.map +1 -0
  385. package/dist/shipped-heartbeat.js +272 -0
  386. package/dist/shipped-heartbeat.js.map +1 -0
  387. package/dist/starter-team.d.ts +23 -0
  388. package/dist/starter-team.d.ts.map +1 -0
  389. package/dist/starter-team.js +88 -0
  390. package/dist/starter-team.js.map +1 -0
  391. package/dist/suppression-ledger.d.ts +73 -0
  392. package/dist/suppression-ledger.d.ts.map +1 -0
  393. package/dist/suppression-ledger.js +125 -0
  394. package/dist/suppression-ledger.js.map +1 -0
  395. package/dist/system-loop-state.d.ts +4 -0
  396. package/dist/system-loop-state.d.ts.map +1 -0
  397. package/dist/system-loop-state.js +40 -0
  398. package/dist/system-loop-state.js.map +1 -0
  399. package/dist/taskCommentIngest.d.ts +43 -0
  400. package/dist/taskCommentIngest.d.ts.map +1 -0
  401. package/dist/taskCommentIngest.js +59 -0
  402. package/dist/taskCommentIngest.js.map +1 -0
  403. package/dist/taskPrecheck.d.ts +20 -0
  404. package/dist/taskPrecheck.d.ts.map +1 -0
  405. package/dist/taskPrecheck.js +329 -0
  406. package/dist/taskPrecheck.js.map +1 -0
  407. package/dist/taskStateSync.d.ts +8 -0
  408. package/dist/taskStateSync.d.ts.map +1 -0
  409. package/dist/taskStateSync.js +79 -0
  410. package/dist/taskStateSync.js.map +1 -0
  411. package/dist/tasks.d.ts +140 -0
  412. package/dist/tasks.d.ts.map +1 -0
  413. package/dist/tasks.js +1281 -0
  414. package/dist/tasks.js.map +1 -0
  415. package/dist/team-config.d.ts +24 -0
  416. package/dist/team-config.d.ts.map +1 -0
  417. package/dist/team-config.js +221 -0
  418. package/dist/team-config.js.map +1 -0
  419. package/dist/team-doctor.d.ts +22 -0
  420. package/dist/team-doctor.d.ts.map +1 -0
  421. package/dist/team-doctor.js +270 -0
  422. package/dist/team-doctor.js.map +1 -0
  423. package/dist/team-pulse.d.ts +52 -0
  424. package/dist/team-pulse.d.ts.map +1 -0
  425. package/dist/team-pulse.js +176 -0
  426. package/dist/team-pulse.js.map +1 -0
  427. package/dist/telemetry.d.ts +74 -0
  428. package/dist/telemetry.d.ts.map +1 -0
  429. package/dist/telemetry.js +256 -0
  430. package/dist/telemetry.js.map +1 -0
  431. package/dist/test-task-filter.d.ts +21 -0
  432. package/dist/test-task-filter.d.ts.map +1 -0
  433. package/dist/test-task-filter.js +48 -0
  434. package/dist/test-task-filter.js.map +1 -0
  435. package/dist/types.d.ts +126 -0
  436. package/dist/types.d.ts.map +1 -0
  437. package/dist/types.js +4 -0
  438. package/dist/types.js.map +1 -0
  439. package/dist/usage-tracking.d.ts +101 -0
  440. package/dist/usage-tracking.d.ts.map +1 -0
  441. package/dist/usage-tracking.js +325 -0
  442. package/dist/usage-tracking.js.map +1 -0
  443. package/dist/vector-store.d.ts +87 -0
  444. package/dist/vector-store.d.ts.map +1 -0
  445. package/dist/vector-store.js +247 -0
  446. package/dist/vector-store.js.map +1 -0
  447. package/dist/watchdog/idleNudgeLane.d.ts +22 -0
  448. package/dist/watchdog/idleNudgeLane.d.ts.map +1 -0
  449. package/dist/watchdog/idleNudgeLane.js +98 -0
  450. package/dist/watchdog/idleNudgeLane.js.map +1 -0
  451. package/dist/webhooks.d.ts +103 -0
  452. package/dist/webhooks.d.ts.map +1 -0
  453. package/dist/webhooks.js +398 -0
  454. package/dist/webhooks.js.map +1 -0
  455. package/dist/working-contract.d.ts +42 -0
  456. package/dist/working-contract.d.ts.map +1 -0
  457. package/dist/working-contract.js +228 -0
  458. package/dist/working-contract.js.map +1 -0
  459. package/dist/ws-heartbeat.d.ts +66 -0
  460. package/dist/ws-heartbeat.d.ts.map +1 -0
  461. package/dist/ws-heartbeat.js +174 -0
  462. package/dist/ws-heartbeat.js.map +1 -0
  463. package/package.json +87 -0
  464. package/plugins/reflectt-channel/README.md +96 -0
  465. package/plugins/reflectt-channel/index.ts +789 -0
  466. package/plugins/reflectt-channel/openclaw.plugin.json +23 -0
  467. package/plugins/reflectt-channel/package.json +23 -0
  468. package/plugins/reflectt-channel/src/channel.ts +433 -0
  469. package/plugins/reflectt-channel/src/types.ts +29 -0
  470. package/public/avatars/echo.png +0 -0
  471. package/public/avatars/harmony.png +0 -0
  472. package/public/avatars/kai.png +0 -0
  473. package/public/avatars/link.png +0 -0
  474. package/public/avatars/pixel.png +0 -0
  475. package/public/avatars/rhythm.png +0 -0
  476. package/public/avatars/ryan.png +0 -0
  477. package/public/avatars/sage.png +0 -0
  478. package/public/avatars/scout.png +0 -0
  479. package/public/avatars/spark.png +0 -0
  480. package/public/dashboard-animations.css +381 -0
  481. package/public/dashboard.js +3479 -0
  482. package/public/docs.md +1062 -0
  483. package/public/file-upload-mock.html +1097 -0
  484. package/public/og-card.png +0 -0
  485. package/public/ui-kit.html +318 -0
  486. package/public/widget/feedback.js +194 -0
@@ -0,0 +1,882 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Copyright (c) Reflectt AI
3
+ /**
4
+ * Board-Health Execution Worker
5
+ *
6
+ * Automated board hygiene with full audit trail and rollback:
7
+ * - Auto-block stale doing tasks (configurable threshold)
8
+ * - Suggest close for abandoned tasks
9
+ * - Emit periodic digest to chat
10
+ * - Audit log for every automated action
11
+ * - Rollback window for reversing decisions
12
+ */
13
+ import { taskManager } from './tasks.js';
14
+ import { routeMessage } from './messageRouter.js';
15
+ import { validateTaskTimestamp, verifyTaskExists } from './health.js';
16
+ import { policyManager } from './policy.js';
17
+ import { getEffectiveActivity } from './activity-signal.js';
18
+ import { presenceManager } from './presence.js';
19
+ import { suggestReviewer } from './assignment.js';
20
+ import { isTestHarnessTask } from './test-task-filter.js';
21
+ import { recordSystemLoopTick } from './system-loop-state.js';
22
+ const DEFAULT_CONFIG = {
23
+ enabled: true,
24
+ intervalMs: 5 * 60 * 1000, // 5 minutes
25
+ staleDoingThresholdMin: 240, // 4 hours
26
+ suggestCloseThresholdMin: 1440, // 24 hours
27
+ rollbackWindowMs: 60 * 60 * 1000, // 1 hour
28
+ digestIntervalMs: 4 * 60 * 60 * 1000, // 4 hours
29
+ digestChannel: 'ops',
30
+ quietHoursStart: 0,
31
+ quietHoursEnd: 6,
32
+ dryRun: false,
33
+ maxActionsPerTick: 5,
34
+ inactiveAgentThresholdMin: 1440, // 24 hours
35
+ reviewSlaThresholdMin: 480, // 8 hours
36
+ reviewEscalationTarget: 'ryan',
37
+ };
38
+ // ── Worker ─────────────────────────────────────────────────────────────────
39
+ export class BoardHealthWorker {
40
+ config;
41
+ auditLog = [];
42
+ lastDigestAt = 0;
43
+ lastTickAt = 0;
44
+ tickCount = 0;
45
+ timer = null;
46
+ constructor(config) {
47
+ this.config = { ...DEFAULT_CONFIG, ...config };
48
+ }
49
+ // ── Lifecycle ──────────────────────────────────────────────────────────
50
+ start() {
51
+ if (this.timer)
52
+ return;
53
+ if (!this.config.enabled)
54
+ return;
55
+ this.timer = setInterval(() => {
56
+ this.tick().catch(() => { });
57
+ }, this.config.intervalMs);
58
+ this.timer.unref();
59
+ }
60
+ stop() {
61
+ if (this.timer) {
62
+ clearInterval(this.timer);
63
+ this.timer = null;
64
+ }
65
+ }
66
+ updateConfig(patch) {
67
+ const wasEnabled = this.config.enabled;
68
+ this.config = { ...this.config, ...patch };
69
+ // Restart timer if interval changed or enable toggled
70
+ if (this.timer) {
71
+ this.stop();
72
+ }
73
+ if (this.config.enabled) {
74
+ this.start();
75
+ }
76
+ }
77
+ getConfig() {
78
+ return { ...this.config };
79
+ }
80
+ // ── Core tick ──────────────────────────────────────────────────────────
81
+ async tick(options) {
82
+ const now = Date.now();
83
+ const dryRun = options?.dryRun ?? this.config.dryRun;
84
+ const force = options?.force ?? false;
85
+ // Quiet hours check
86
+ if (!force && this.isQuietHours(now)) {
87
+ return { actions: [], digest: null, skipped: true, reason: 'quiet-hours' };
88
+ }
89
+ this.tickCount++;
90
+ this.lastTickAt = now;
91
+ // Persist tick time so /health/system can prove this worker is actually running.
92
+ recordSystemLoopTick('board_health', now);
93
+ const actions = [];
94
+ // 1. Detect stale doing tasks
95
+ const staleDoing = this.findStaleDoingTasks(now);
96
+ let actionCount = 0;
97
+ for (const task of staleDoing) {
98
+ if (actionCount >= this.config.maxActionsPerTick)
99
+ break;
100
+ // Don't re-block already blocked tasks or test-harness tasks
101
+ if (isTestHarnessTask(task))
102
+ continue;
103
+ const action = await this.applyAutoBlockStale(task, now, dryRun);
104
+ if (action) {
105
+ actions.push(action);
106
+ actionCount++;
107
+ }
108
+ }
109
+ // 2. Detect tasks that should be suggested for close
110
+ const abandonedTasks = this.findAbandonedTasks(now);
111
+ for (const task of abandonedTasks) {
112
+ if (actionCount >= this.config.maxActionsPerTick)
113
+ break;
114
+ if (isTestHarnessTask(task))
115
+ continue;
116
+ const action = await this.applySuggestClose(task, now, dryRun);
117
+ if (action) {
118
+ actions.push(action);
119
+ actionCount++;
120
+ }
121
+ }
122
+ // 3. Ready-queue floor check
123
+ const rqfActions = await this.checkReadyQueueFloor(now, dryRun);
124
+ actions.push(...rqfActions);
125
+ // 3a. Review SLA auto-reassignment
126
+ const reviewActions = await this.checkReviewSla(now, dryRun);
127
+ actions.push(...reviewActions);
128
+ // 3b. Reflection automation nudges
129
+ if (!dryRun) {
130
+ try {
131
+ const { tickReflectionNudges } = await import('./reflection-automation.js');
132
+ await tickReflectionNudges();
133
+ }
134
+ catch { /* reflection automation may not be loaded */ }
135
+ }
136
+ // 3c. Working contract enforcement (auto-requeue stale doing tasks)
137
+ if (!dryRun) {
138
+ try {
139
+ const { tickWorkingContract } = await import('./working-contract.js');
140
+ const wcResult = await tickWorkingContract();
141
+ if (wcResult.requeued > 0 || wcResult.warnings > 0) {
142
+ for (const action of wcResult.actions) {
143
+ actions.push({
144
+ id: `wc-${action.timestamp}-${Math.random().toString(36).slice(2, 7)}`,
145
+ kind: (action.type === 'auto_requeue' ? 'auto-requeue' : 'working-contract-warning'),
146
+ taskId: action.taskId,
147
+ agent: action.agent,
148
+ description: action.reason,
149
+ previousState: { type: action.type },
150
+ appliedAt: action.timestamp,
151
+ rolledBack: false,
152
+ rolledBackAt: null,
153
+ rollbackBy: null,
154
+ });
155
+ }
156
+ }
157
+ }
158
+ catch { /* working-contract module may not be loaded */ }
159
+ }
160
+ // 3d. Ready-queue sweeper: auto-create placeholder tasks for empty lanes
161
+ const sweeperActions = await this.sweepReadyQueue(now, dryRun);
162
+ actions.push(...sweeperActions);
163
+ // 3e. Continuity loop: auto-replenish queues from promoted insights
164
+ if (!dryRun) {
165
+ try {
166
+ const { tickContinuityLoop } = await import('./continuity-loop.js');
167
+ const clResult = await tickContinuityLoop();
168
+ if (clResult.replenished > 0) {
169
+ actions.push(...clResult.actions.map(a => ({
170
+ id: a.id,
171
+ kind: 'continuity-replenish',
172
+ taskId: a.taskId ?? null,
173
+ agent: a.agent,
174
+ description: a.detail,
175
+ previousState: { insightId: a.insightId },
176
+ appliedAt: a.timestamp,
177
+ rolledBack: false,
178
+ rolledBackAt: null,
179
+ rollbackBy: null,
180
+ })));
181
+ }
182
+ }
183
+ catch { /* continuity loop may not be loaded */ }
184
+ }
185
+ // 4. Emit digest if interval elapsed
186
+ let digest = null;
187
+ if (force || now - this.lastDigestAt >= this.config.digestIntervalMs) {
188
+ digest = await this.emitDigest(now, actions, dryRun);
189
+ if (!dryRun) {
190
+ this.lastDigestAt = now;
191
+ }
192
+ }
193
+ return { actions, digest, skipped: false };
194
+ }
195
+ // ── Policy: Auto-block stale doing ────────────────────────────────────
196
+ findStaleDoingTasks(now) {
197
+ const thresholdMs = this.config.staleDoingThresholdMin * 60_000;
198
+ const doingTasks = taskManager.listTasks({ status: 'doing' });
199
+ return doingTasks.filter(task => {
200
+ // Verify task still exists (guards against stale cache entries)
201
+ if (!verifyTaskExists(task.id))
202
+ return false;
203
+ const lastActivity = this.getTaskLastActivityAt(task);
204
+ if (!lastActivity)
205
+ return false;
206
+ // Validate timestamp is within reasonable bounds
207
+ const validatedTs = validateTaskTimestamp(lastActivity, now);
208
+ if (!validatedTs)
209
+ return false;
210
+ return now - validatedTs > thresholdMs;
211
+ });
212
+ }
213
+ async applyAutoBlockStale(task, now, dryRun) {
214
+ // Check if we already acted on this task recently
215
+ const recentAction = this.auditLog.find(a => a.taskId === task.id && a.kind === 'auto-block-stale' && !a.rolledBack
216
+ && now - a.appliedAt < this.config.rollbackWindowMs);
217
+ if (recentAction)
218
+ return null;
219
+ const staleMinutes = Math.floor((now - this.getTaskLastActivityAt(task)) / 60_000);
220
+ const action = {
221
+ id: `bh-${now}-${Math.random().toString(36).slice(2, 8)}`,
222
+ kind: 'auto-block-stale',
223
+ taskId: task.id,
224
+ agent: task.assignee || null,
225
+ description: `Auto-blocked stale doing task (${staleMinutes}m inactive, threshold: ${this.config.staleDoingThresholdMin}m)`,
226
+ previousState: {
227
+ status: task.status,
228
+ metadata: task.metadata ? { ...task.metadata } : null,
229
+ },
230
+ appliedAt: now,
231
+ rolledBack: false,
232
+ rolledBackAt: null,
233
+ rollbackBy: null,
234
+ };
235
+ if (!dryRun) {
236
+ try {
237
+ await taskManager.updateTask(task.id, {
238
+ status: 'blocked',
239
+ metadata: {
240
+ ...(task.metadata || {}),
241
+ board_health_action: 'auto-blocked-stale',
242
+ board_health_action_at: now,
243
+ board_health_action_id: action.id,
244
+ board_health_stale_minutes: staleMinutes,
245
+ },
246
+ });
247
+ // Notify the assignee
248
+ if (task.assignee) {
249
+ await routeMessage({
250
+ from: 'system',
251
+ content: `⚠️ Board health: auto-blocked **${task.id}** (${task.title}) — ${staleMinutes}m with no activity. @${task.assignee} update status or rollback via \`POST /board-health/rollback/${action.id}\`.`,
252
+ category: 'watchdog-alert',
253
+ severity: 'warning',
254
+ taskId: task.id,
255
+ mentions: [task.assignee],
256
+ });
257
+ }
258
+ }
259
+ catch {
260
+ return null;
261
+ }
262
+ }
263
+ this.auditLog.push(action);
264
+ return action;
265
+ }
266
+ // ── Policy: Suggest close ─────────────────────────────────────────────
267
+ findAbandonedTasks(now) {
268
+ const thresholdMs = this.config.suggestCloseThresholdMin * 60_000;
269
+ const candidates = taskManager.listTasks({}).filter(t => t.status === 'blocked' || t.status === 'todo');
270
+ return candidates.filter(task => {
271
+ // Verify task still exists (guards against stale cache)
272
+ if (!verifyTaskExists(task.id))
273
+ return false;
274
+ const lastActivity = this.getTaskLastActivityAt(task);
275
+ if (!lastActivity) {
276
+ // If no activity at all, check createdAt with validation
277
+ const createdAt = validateTaskTimestamp(task.createdAt, now);
278
+ return createdAt !== null && now - createdAt > thresholdMs;
279
+ }
280
+ const validatedTs = validateTaskTimestamp(lastActivity, now);
281
+ if (!validatedTs)
282
+ return false;
283
+ return now - validatedTs > thresholdMs;
284
+ });
285
+ }
286
+ async applySuggestClose(task, now, dryRun) {
287
+ // Check if we already suggested close for this task
288
+ const recentAction = this.auditLog.find(a => a.taskId === task.id && a.kind === 'suggest-close' && !a.rolledBack
289
+ && now - a.appliedAt < 24 * 60 * 60 * 1000);
290
+ if (recentAction)
291
+ return null;
292
+ const lastActivity = this.getTaskLastActivityAt(task);
293
+ const staleMinutes = lastActivity
294
+ ? Math.floor((now - lastActivity) / 60_000)
295
+ : Math.floor((now - (typeof task.createdAt === 'number' ? task.createdAt : now)) / 60_000);
296
+ const action = {
297
+ id: `bh-${now}-${Math.random().toString(36).slice(2, 8)}`,
298
+ kind: 'suggest-close',
299
+ taskId: task.id,
300
+ agent: task.assignee || null,
301
+ description: `Suggested close for abandoned task (${staleMinutes}m inactive, status: ${task.status})`,
302
+ previousState: null, // No state change — just a suggestion
303
+ appliedAt: now,
304
+ rolledBack: false,
305
+ rolledBackAt: null,
306
+ rollbackBy: null,
307
+ };
308
+ if (!dryRun) {
309
+ try {
310
+ // Add a comment to the task
311
+ const comments = taskManager.getTaskComments(task.id);
312
+ const hasRecentBotComment = comments.some(c => c.author === 'system' && now - (typeof c.timestamp === 'number' ? c.timestamp : 0) < 24 * 60 * 60 * 1000);
313
+ if (!hasRecentBotComment) {
314
+ await taskManager.addTaskComment(task.id, 'system', `🔍 Board health: this task has been inactive for ${Math.floor(staleMinutes / 60)}h${staleMinutes % 60}m. Consider closing if no longer needed. Action ID: ${action.id}`);
315
+ }
316
+ }
317
+ catch {
318
+ return null;
319
+ }
320
+ }
321
+ this.auditLog.push(action);
322
+ return action;
323
+ }
324
+ // ── Policy: Ready-queue floor ──────────────────────────────────────────
325
+ /** Track last alert time per agent to enforce cooldown */
326
+ readyQueueLastAlertAt = {};
327
+ /** Track last alert state fingerprint per agent to suppress duplicate alerts */
328
+ readyQueueLastState = {};
329
+ /** Track when each agent's queue first went empty (for idle escalation) */
330
+ idleQueueSince = {};
331
+ async checkReadyQueueFloor(now, dryRun) {
332
+ const policy = policyManager.get();
333
+ const rqf = policy.readyQueueFloor;
334
+ if (!rqf?.enabled)
335
+ return [];
336
+ const actions = [];
337
+ for (const agent of rqf.agents) {
338
+ // Count unblocked todo tasks for this agent
339
+ const todoTasks = taskManager.listTasks({ status: 'todo', assignee: agent });
340
+ const unblockedTodo = todoTasks.filter(t => {
341
+ const blocked = t.metadata?.blocked_by;
342
+ if (!blocked)
343
+ return true;
344
+ // Check if blocker is still open
345
+ const blocker = taskManager.getTask(blocked);
346
+ return !blocker || blocker.status === 'done';
347
+ });
348
+ const doingTasks = taskManager.listTasks({ status: 'doing', assignee: agent });
349
+ const validatingTasks = taskManager.listTasks({ status: 'validating', assignee: agent });
350
+ const readyCount = unblockedTodo.length;
351
+ const activeCount = doingTasks.length + validatingTasks.length;
352
+ const belowFloor = readyCount < rqf.minReady;
353
+ // Breach definition: below-floor AND no active work (doing/validating).
354
+ // If the agent is active, we may still emit an informational note, but it is not a breach.
355
+ const isBreach = belowFloor && activeCount === 0;
356
+ // Check cooldown
357
+ let lastAlert = this.readyQueueLastAlertAt[agent] || 0;
358
+ const cooldownMs = (rqf.cooldownMin || 30) * 60_000;
359
+ // Ready-queue floor check (breach vs info)
360
+ if (belowFloor && now - lastAlert > cooldownMs) {
361
+ const deficit = rqf.minReady - readyCount;
362
+ // State fingerprint: suppress if identical to last alert
363
+ const blockedTasks = todoTasks.filter(t => !unblockedTodo.includes(t));
364
+ const stateFingerprint = `${readyCount}:${todoTasks.length}:${blockedTasks.map(t => t.id).sort().join(',')}:${doingTasks.length}:${validatingTasks.length}`;
365
+ const lastState = this.readyQueueLastState[agent];
366
+ if (lastState === stateFingerprint) {
367
+ // State unchanged since last alert — skip (debounce)
368
+ continue;
369
+ }
370
+ // Build breakdown: show blocked tasks and why
371
+ let breakdown = '';
372
+ if (todoTasks.length > readyCount) {
373
+ breakdown += `\n 📊 todo=${todoTasks.length}, unblocked=${readyCount}, blocked=${blockedTasks.length}, doing=${doingTasks.length}, validating=${validatingTasks.length}`;
374
+ const capped = blockedTasks.slice(0, 5);
375
+ for (const bt of capped) {
376
+ const blockedBy = bt.metadata?.blocked_by || 'unknown';
377
+ breakdown += `\n • ${bt.id} (${(bt.title || '').slice(0, 50)}) — blocked_by: ${blockedBy}`;
378
+ }
379
+ if (blockedTasks.length > 5)
380
+ breakdown += `\n … and ${blockedTasks.length - 5} more`;
381
+ }
382
+ else {
383
+ breakdown += `\n 📊 todo=${todoTasks.length} (all unblocked), doing=${doingTasks.length}, validating=${validatingTasks.length}`;
384
+ }
385
+ // Snapshot timestamp for freshness judgment
386
+ const snapshotTime = new Date(now).toISOString().replace('T', ' ').slice(0, 19) + ' UTC';
387
+ const msg = isBreach
388
+ ? `⚠️ Ready-queue floor (idle): @${agent} has ${readyCount}/${rqf.minReady} unblocked todo tasks (need ${deficit} more). @sage @pixel — please spec/assign tasks to keep engineering lane fed.${breakdown}\n 🕐 snapshot: ${snapshotTime}`
389
+ : `ℹ️ Ready-queue in-flight: @${agent} is active (doing=${doingTasks.length}, validating=${validatingTasks.length}). In validating, next task suggested via /tasks/next. Queue below floor (unblocked todo=${readyCount}, floor=${rqf.minReady}, need ${deficit} more).${breakdown}\n 🕐 snapshot: ${snapshotTime}`;
390
+ if (!dryRun) {
391
+ try {
392
+ await routeMessage({
393
+ from: 'system',
394
+ content: msg,
395
+ category: 'watchdog-alert',
396
+ severity: isBreach ? 'warning' : 'info',
397
+ forceChannel: rqf.channel || 'general',
398
+ });
399
+ }
400
+ catch { /* chat may not be available in test */ }
401
+ this.readyQueueLastAlertAt[agent] = now;
402
+ this.readyQueueLastState[agent] = stateFingerprint;
403
+ lastAlert = now; // prevent same-tick escalation based on stale local lastAlert
404
+ }
405
+ if (isBreach) {
406
+ const action = {
407
+ id: `rqf-${agent}-${now}`,
408
+ kind: 'ready-queue-warning',
409
+ taskId: null,
410
+ agent,
411
+ description: `Ready queue below floor: ${readyCount}/${rqf.minReady} for @${agent}`,
412
+ previousState: { readyCount, doingCount: doingTasks.length, validatingCount: validatingTasks.length },
413
+ appliedAt: now,
414
+ rolledBack: false,
415
+ rolledBackAt: null,
416
+ rollbackBy: null,
417
+ };
418
+ this.auditLog.push(action);
419
+ actions.push(action);
420
+ }
421
+ }
422
+ // Clear state fingerprint when floor is met (so next breach alerts fresh)
423
+ if (readyCount >= rqf.minReady) {
424
+ delete this.readyQueueLastState[agent];
425
+ }
426
+ // Idle escalation: agent has 0 doing + 0 validating + 0 (unblocked) todo for too long
427
+ const totalActive = doingTasks.length + validatingTasks.length + readyCount;
428
+ if (totalActive === 0) {
429
+ if (!this.idleQueueSince[agent]) {
430
+ this.idleQueueSince[agent] = now;
431
+ }
432
+ const idleMinutes = Math.floor((now - this.idleQueueSince[agent]) / 60_000);
433
+ if (idleMinutes >= (rqf.escalateAfterMin || 60) && now - lastAlert > cooldownMs) {
434
+ const idleSnapshotTime = new Date(now).toISOString().replace('T', ' ').slice(0, 19) + ' UTC';
435
+ const msg = `🚨 Idle escalation: @${agent} has had 0 tasks (doing + validating + todo) for ${idleMinutes}m. Immediate assignment needed. @sage\n 🕐 snapshot: ${idleSnapshotTime}`;
436
+ if (!dryRun) {
437
+ try {
438
+ await routeMessage({ from: 'system', content: msg, forceChannel: rqf.channel || 'general', category: 'escalation', severity: 'critical' });
439
+ }
440
+ catch { /* chat may not be available in test */ }
441
+ this.readyQueueLastAlertAt[agent] = now;
442
+ }
443
+ const action = {
444
+ id: `idle-${agent}-${now}`,
445
+ kind: 'idle-queue-escalation',
446
+ taskId: null,
447
+ agent,
448
+ description: `Idle queue escalation: @${agent} idle for ${idleMinutes}m`,
449
+ previousState: { idleMinutes },
450
+ appliedAt: now,
451
+ rolledBack: false,
452
+ rolledBackAt: null,
453
+ rollbackBy: null,
454
+ };
455
+ this.auditLog.push(action);
456
+ actions.push(action);
457
+ }
458
+ }
459
+ else {
460
+ // Reset idle tracker when agent has work
461
+ delete this.idleQueueSince[agent];
462
+ }
463
+ }
464
+ return actions;
465
+ }
466
+ // ── Ready-queue sweeper ────────────────────────────────────────────────
467
+ /** Track last replenish time per agent to enforce cooldown between auto-creates */
468
+ replenishLastAt = {};
469
+ /**
470
+ * Sweeper tick: for each lane below readyFloor, emit a digest warning.
471
+ *
472
+ * Does NOT create placeholder tasks — the continuity loop (3e) handles
473
+ * real replenishment from promoted insights. Creating empty placeholders
474
+ * clutters the board with scopeless tasks that no one can work on.
475
+ *
476
+ * One warning per agent-deficit per tick, with a 30-minute per-agent cooldown.
477
+ */
478
+ async sweepReadyQueue(now, dryRun) {
479
+ const { getLanesConfig } = await import('./lane-config.js');
480
+ const lanes = getLanesConfig();
481
+ const actions = [];
482
+ const cooldownMs = 30 * 60_000;
483
+ for (const lane of lanes) {
484
+ for (const agent of lane.agents) {
485
+ // Skip ghost agents that have never checked in
486
+ if (!presenceManager.getPresence(agent))
487
+ continue;
488
+ // Skip inactive agents (configurable threshold, default 24h)
489
+ const presence = presenceManager.getPresence(agent);
490
+ const lastActive = presence?.lastUpdate ?? 0;
491
+ const inactiveThresholdMs = this.config.inactiveAgentThresholdMin * 60_000;
492
+ if (lastActive > 0 && (now - lastActive) > inactiveThresholdMs)
493
+ continue;
494
+ // Enforce per-agent cooldown to avoid spam
495
+ const lastReplenish = this.replenishLastAt[agent] ?? 0;
496
+ if (now - lastReplenish < cooldownMs)
497
+ continue;
498
+ // Count unblocked todo tasks for this agent
499
+ const todoTasks = taskManager.listTasks({ status: 'todo', assignee: agent });
500
+ const unblockedTodo = todoTasks.filter(t => {
501
+ const blocked = t.metadata?.blocked_by;
502
+ if (!blocked)
503
+ return true;
504
+ const blocker = taskManager.getTask(blocked);
505
+ return !blocker || blocker.status === 'done';
506
+ });
507
+ const deficit = lane.readyFloor - unblockedTodo.length;
508
+ if (deficit <= 0)
509
+ continue;
510
+ // Emit a warning action — do NOT create placeholder tasks.
511
+ // The continuity loop will attempt real replenishment from insights.
512
+ const action = {
513
+ id: `rqs-${agent}-${now}-0`,
514
+ kind: 'ready-queue-replenish',
515
+ taskId: null,
516
+ agent,
517
+ description: `Ready queue below floor for @${agent} in lane "${lane.name}" (${unblockedTodo.length}/${lane.readyFloor} ready). Deferring to continuity loop for scoped replenishment.`,
518
+ previousState: { readyCount: unblockedTodo.length, readyFloor: lane.readyFloor },
519
+ appliedAt: now,
520
+ rolledBack: false,
521
+ rolledBackAt: null,
522
+ rollbackBy: null,
523
+ };
524
+ this.auditLog.push(action);
525
+ actions.push(action);
526
+ this.replenishLastAt[agent] = now;
527
+ }
528
+ }
529
+ return actions;
530
+ }
531
+ // ── Policy: Review SLA auto-reassignment ──────────────────────────────
532
+ /** Track last reassignment per task to avoid churning */
533
+ reviewReassignLastAt = {};
534
+ async checkReviewSla(now, dryRun) {
535
+ const thresholdMs = this.config.reviewSlaThresholdMin * 60_000;
536
+ const cooldownMs = thresholdMs; // Don't re-reassign within one SLA window
537
+ const actions = [];
538
+ const normalizeEpochMs = (v) => {
539
+ if (typeof v !== 'number' || !Number.isFinite(v))
540
+ return 0;
541
+ // Heuristic: values below ~2001-09-09 in ms are likely seconds.
542
+ if (v > 0 && v < 100_000_000_000)
543
+ return v * 1000;
544
+ // Clamp future timestamps
545
+ if (v > now + 60_000)
546
+ return now;
547
+ return v;
548
+ };
549
+ const validatingTasks = taskManager.listTasks({ status: 'validating' })
550
+ .filter(t => !isTestHarnessTask(t) && t.reviewer);
551
+ for (const task of validatingTasks) {
552
+ // Skip if we already reassigned this task recently
553
+ const lastReassignAt = this.reviewReassignLastAt[task.id] ?? 0;
554
+ if (now - lastReassignAt < cooldownMs)
555
+ continue;
556
+ // Check reviewer activity on this task using the review_last_activity_at metadata field
557
+ const meta = (task.metadata || {});
558
+ const reviewEnteredAt = normalizeEpochMs(meta.entered_validating_at) || (task.updatedAt ?? task.createdAt);
559
+ const reviewLastActivityAt = normalizeEpochMs(meta.review_last_activity_at) || reviewEnteredAt;
560
+ // Use the more recent of entered_validating and review_last_activity
561
+ const lastReviewActivity = Math.max(reviewEnteredAt || 0, reviewLastActivityAt || 0);
562
+ if (!lastReviewActivity || now - lastReviewActivity < thresholdMs)
563
+ continue;
564
+ // Race guard: task may have left validating between listTasks() and now.
565
+ // Never act on done/closed tasks.
566
+ const latest = taskManager.getTask(task.id);
567
+ if (!latest || latest.status !== 'validating')
568
+ continue;
569
+ const rawStaleMs = now - lastReviewActivity;
570
+ // Clamp to 30 days max — anything larger is a timestamp bug
571
+ const MAX_REVIEW_STALE_MS = 30 * 24 * 60 * 60_000;
572
+ if (rawStaleMs > MAX_REVIEW_STALE_MS) {
573
+ console.warn(`[board-health] review-sla: skipping ${task.id} — implausible stale time ${Math.floor(rawStaleMs / 60_000)}m (likely timestamp bug)`);
574
+ continue;
575
+ }
576
+ const staleMinutes = Math.floor(rawStaleMs / 60_000);
577
+ const currentReviewer = task.reviewer;
578
+ const newReviewer = this.pickAlternateReviewer(task, currentReviewer);
579
+ const action = {
580
+ id: `bh-${now}-${Math.random().toString(36).slice(2, 8)}`,
581
+ kind: 'review-reassign',
582
+ taskId: task.id,
583
+ agent: currentReviewer,
584
+ description: `Review SLA breach: reassigned reviewer ${currentReviewer} → ${newReviewer} (${staleMinutes}m without review activity, threshold: ${this.config.reviewSlaThresholdMin}m)`,
585
+ previousState: {
586
+ reviewer: currentReviewer,
587
+ review_state: meta.review_state ?? null,
588
+ },
589
+ appliedAt: now,
590
+ rolledBack: false,
591
+ rolledBackAt: null,
592
+ rollbackBy: null,
593
+ };
594
+ this.auditLog.push(action);
595
+ actions.push(action);
596
+ if (!dryRun) {
597
+ try {
598
+ await taskManager.updateTask(task.id, {
599
+ reviewer: newReviewer,
600
+ metadata: {
601
+ ...meta,
602
+ review_reassigned_from: currentReviewer,
603
+ review_reassigned_at: now,
604
+ review_reassign_reason: `SLA breach: ${staleMinutes}m without reviewer activity (threshold: ${this.config.reviewSlaThresholdMin}m)`,
605
+ board_health_action: 'review-reassign',
606
+ board_health_action_id: action.id,
607
+ },
608
+ });
609
+ }
610
+ catch (err) {
611
+ console.warn(`[board-health] review-reassign updateTask failed for ${task.id}:`, err.message);
612
+ }
613
+ try {
614
+ await routeMessage({
615
+ from: 'system',
616
+ content: `🔄 Review SLA: reassigned reviewer on **${task.id}** (${(task.title || '').slice(0, 60)}) from @${currentReviewer} → @${newReviewer} (${staleMinutes}m without activity). ${newReviewer === this.config.reviewEscalationTarget ? '⚡ Escalated — no active reviewer available.' : ''}`,
617
+ category: 'watchdog-alert',
618
+ severity: 'warning',
619
+ taskId: task.id,
620
+ mentions: [newReviewer, currentReviewer],
621
+ });
622
+ }
623
+ catch {
624
+ // Message routing failure is non-critical
625
+ }
626
+ this.reviewReassignLastAt[task.id] = now;
627
+ }
628
+ }
629
+ return actions;
630
+ }
631
+ /**
632
+ * Pick an alternate reviewer for a task.
633
+ *
634
+ * IMPORTANT: reviewer reassignment must respect routing guardrails.
635
+ * We should not drift to designers/voice roles for ops/infra tasks just
636
+ * because they happened to be "most recently active".
637
+ */
638
+ pickAlternateReviewer(task, currentReviewer) {
639
+ const assignee = (task.assignee || '').toLowerCase();
640
+ const current = currentReviewer.toLowerCase();
641
+ const allPresence = presenceManager.getAllPresence();
642
+ const now = Date.now();
643
+ const activeThresholdMs = 60 * 60 * 1000; // active if seen in last hour
644
+ const active = allPresence
645
+ .filter(p => p.status !== 'offline' && now - p.lastUpdate < activeThresholdMs)
646
+ .map(p => ({ agent: p.agent, agentLower: p.agent.toLowerCase(), lastUpdate: p.lastUpdate }));
647
+ const activeSet = new Set(active.map(a => a.agentLower));
648
+ // No active agent at all → escalate
649
+ if (active.length === 0)
650
+ return this.config.reviewEscalationTarget;
651
+ // Rank reviewers via assignment engine (respects opt-in/neverRoute guardrails)
652
+ let allTasks = [];
653
+ try {
654
+ allTasks = taskManager.listTasks({});
655
+ }
656
+ catch { /* ok */ }
657
+ const suggestion = suggestReviewer({
658
+ title: task.title,
659
+ assignee: task.assignee,
660
+ tags: task.tags,
661
+ done_criteria: task.done_criteria,
662
+ metadata: task.metadata,
663
+ }, allTasks);
664
+ const eligibleByScore = (suggestion.scores || []).map(s => s.agent);
665
+ const eligibleSet = new Set(eligibleByScore.map(a => a.toLowerCase()));
666
+ // Primary: pick the highest-ranked eligible reviewer who is active.
667
+ for (const candidate of eligibleByScore) {
668
+ const c = candidate.toLowerCase();
669
+ if (c === current || c === assignee)
670
+ continue;
671
+ if (c === this.config.reviewEscalationTarget.toLowerCase())
672
+ continue;
673
+ if (!activeSet.has(c))
674
+ continue;
675
+ return candidate;
676
+ }
677
+ // Secondary: if no ranked candidate is active, fall back to most-recent ACTIVE
678
+ // among eligible reviewers.
679
+ const fallback = active
680
+ .filter(p => {
681
+ if (p.agentLower === current || p.agentLower === assignee)
682
+ return false;
683
+ if (p.agentLower === this.config.reviewEscalationTarget.toLowerCase())
684
+ return false;
685
+ return eligibleSet.has(p.agentLower);
686
+ })
687
+ .sort((a, b) => b.lastUpdate - a.lastUpdate);
688
+ if (fallback.length > 0)
689
+ return fallback[0].agent;
690
+ // No eligible active reviewer available — escalate.
691
+ return this.config.reviewEscalationTarget;
692
+ }
693
+ // ── Digest ────────────────────────────────────────────────────────────
694
+ async emitDigest(now, recentActions, dryRun) {
695
+ const allTasks = taskManager.listTasks({});
696
+ const doingTasks = allTasks.filter(t => t.status === 'doing');
697
+ const blockedTasks = allTasks.filter(t => t.status === 'blocked');
698
+ const todoTasks = allTasks.filter(t => t.status === 'todo');
699
+ const validatingTasks = allTasks.filter(t => t.status === 'validating');
700
+ const staleDoingCount = this.findStaleDoingTasks(now).length;
701
+ const suggestedCloseCount = this.findAbandonedTasks(now).length;
702
+ const blockedTaskIds = recentActions
703
+ .filter(a => a.kind === 'auto-block-stale' && a.taskId)
704
+ .map(a => a.taskId);
705
+ const suggestedCloseTaskIds = recentActions
706
+ .filter(a => a.kind === 'suggest-close' && a.taskId)
707
+ .map(a => a.taskId);
708
+ const lines = [
709
+ `📊 **Board Health Digest**`,
710
+ ``,
711
+ `**Board:** ${todoTasks.length} todo · ${doingTasks.length} doing · ${validatingTasks.length} validating · ${blockedTasks.length} blocked`,
712
+ `**Stale doing:** ${staleDoingCount} tasks (>${this.config.staleDoingThresholdMin}m threshold)`,
713
+ `**Abandoned candidates:** ${suggestedCloseCount} tasks (>${Math.floor(this.config.suggestCloseThresholdMin / 60)}h threshold)`,
714
+ ];
715
+ if (recentActions.length > 0) {
716
+ lines.push(``, `**Actions this cycle:** ${recentActions.length}`);
717
+ for (const a of recentActions.slice(0, 5)) {
718
+ lines.push(`- ${a.kind}: ${a.taskId || 'n/a'} — ${a.description}`);
719
+ }
720
+ if (recentActions.length > 5) {
721
+ lines.push(`- ... and ${recentActions.length - 5} more`);
722
+ }
723
+ }
724
+ else {
725
+ lines.push(``, `**Actions this cycle:** none (board is healthy ✅)`);
726
+ }
727
+ const summary = lines.join('\n');
728
+ const digest = {
729
+ timestamp: now,
730
+ staleDoingCount,
731
+ suggestedCloseCount,
732
+ actionsApplied: recentActions.length,
733
+ blockedTasks: blockedTaskIds,
734
+ suggestedCloseTasks: suggestedCloseTaskIds,
735
+ summary,
736
+ };
737
+ if (!dryRun) {
738
+ await routeMessage({
739
+ from: 'system',
740
+ content: summary,
741
+ category: 'digest',
742
+ severity: 'info',
743
+ }).catch(() => { });
744
+ // Log digest as audit action
745
+ this.auditLog.push({
746
+ id: `bh-digest-${now}`,
747
+ kind: 'digest-emitted',
748
+ taskId: null,
749
+ agent: null,
750
+ description: `Digest emitted: ${recentActions.length} actions, ${staleDoingCount} stale, ${suggestedCloseCount} abandoned`,
751
+ previousState: null,
752
+ appliedAt: now,
753
+ rolledBack: false,
754
+ rolledBackAt: null,
755
+ rollbackBy: null,
756
+ });
757
+ }
758
+ return digest;
759
+ }
760
+ // ── Rollback ──────────────────────────────────────────────────────────
761
+ async rollback(actionId, rolledBackBy = 'manual') {
762
+ const action = this.auditLog.find(a => a.id === actionId);
763
+ if (!action) {
764
+ return { success: false, message: `Action ${actionId} not found` };
765
+ }
766
+ if (action.rolledBack) {
767
+ return { success: false, message: `Action ${actionId} already rolled back at ${new Date(action.rolledBackAt).toISOString()}` };
768
+ }
769
+ const now = Date.now();
770
+ if (now - action.appliedAt > this.config.rollbackWindowMs) {
771
+ return {
772
+ success: false,
773
+ message: `Rollback window expired (${Math.floor(this.config.rollbackWindowMs / 60_000)}m). Action was applied ${Math.floor((now - action.appliedAt) / 60_000)}m ago.`,
774
+ };
775
+ }
776
+ // Only auto-block-stale is rollbackable (it changes task state)
777
+ if (action.kind === 'auto-block-stale' && action.taskId && action.previousState) {
778
+ try {
779
+ const prev = action.previousState;
780
+ await taskManager.updateTask(action.taskId, {
781
+ status: (prev.status || 'doing'),
782
+ metadata: {
783
+ ...(prev.metadata || {}),
784
+ board_health_rollback: true,
785
+ board_health_rollback_at: now,
786
+ board_health_rollback_by: rolledBackBy,
787
+ },
788
+ });
789
+ action.rolledBack = true;
790
+ action.rolledBackAt = now;
791
+ action.rollbackBy = rolledBackBy;
792
+ await routeMessage({
793
+ from: 'system',
794
+ content: `↩️ Board health rollback: **${action.taskId}** restored to \`${prev.status}\` (action ${actionId} reversed by ${rolledBackBy}).`,
795
+ category: 'system-info',
796
+ severity: 'info',
797
+ taskId: action.taskId || undefined,
798
+ }).catch(() => { });
799
+ return { success: true, message: `Rolled back action ${actionId}`, action };
800
+ }
801
+ catch (err) {
802
+ return { success: false, message: `Rollback failed: ${err.message || 'unknown error'}` };
803
+ }
804
+ }
805
+ if (action.kind === 'suggest-close') {
806
+ // Suggest-close doesn't change state, just mark as rolled back to suppress re-suggestion
807
+ action.rolledBack = true;
808
+ action.rolledBackAt = now;
809
+ action.rollbackBy = rolledBackBy;
810
+ return { success: true, message: `Close suggestion dismissed for ${action.taskId}`, action };
811
+ }
812
+ return { success: false, message: `Action kind '${action.kind}' is not rollbackable` };
813
+ }
814
+ // ── Query ─────────────────────────────────────────────────────────────
815
+ getStatus() {
816
+ const now = Date.now();
817
+ const recent = this.auditLog.filter(a => now - a.appliedAt < 24 * 60 * 60 * 1000);
818
+ const rollbackable = this.auditLog.filter(a => !a.rolledBack && a.previousState !== null && now - a.appliedAt < this.config.rollbackWindowMs);
819
+ return {
820
+ config: { ...this.config },
821
+ running: this.timer !== null,
822
+ lastTickAt: this.lastTickAt,
823
+ lastDigestAt: this.lastDigestAt,
824
+ tickCount: this.tickCount,
825
+ auditLogSize: this.auditLog.length,
826
+ recentActions: recent,
827
+ rollbackableActions: rollbackable,
828
+ };
829
+ }
830
+ getAuditLog(options) {
831
+ let log = this.auditLog;
832
+ if (options?.since) {
833
+ log = log.filter(a => a.appliedAt >= options.since);
834
+ }
835
+ if (options?.kind) {
836
+ log = log.filter(a => a.kind === options.kind);
837
+ }
838
+ // Most recent first
839
+ log = log.slice().sort((a, b) => b.appliedAt - a.appliedAt);
840
+ if (options?.limit) {
841
+ log = log.slice(0, options.limit);
842
+ }
843
+ return log;
844
+ }
845
+ // ── Helpers ───────────────────────────────────────────────────────────
846
+ getTaskLastActivityAt(task) {
847
+ // getEffectiveActivity() has internal DB-availability guards + createdAt fallback
848
+ const signal = getEffectiveActivity(task.id, task.assignee, task.createdAt);
849
+ return signal.effectiveActivityTs;
850
+ }
851
+ isQuietHours(now) {
852
+ const hour = new Date(now).getHours();
853
+ if (this.config.quietHoursStart <= this.config.quietHoursEnd) {
854
+ return hour >= this.config.quietHoursStart && hour < this.config.quietHoursEnd;
855
+ }
856
+ // Wraps midnight (e.g., 22-6)
857
+ return hour >= this.config.quietHoursStart || hour < this.config.quietHoursEnd;
858
+ }
859
+ // ── Cleanup ───────────────────────────────────────────────────────────
860
+ /** Prune audit log entries older than 7 days */
861
+ pruneAuditLog(maxAgeDays = 7) {
862
+ const cutoff = Date.now() - maxAgeDays * 24 * 60 * 60 * 1000;
863
+ const before = this.auditLog.length;
864
+ this.auditLog = this.auditLog.filter(a => a.appliedAt >= cutoff);
865
+ return before - this.auditLog.length;
866
+ }
867
+ }
868
+ // ── Singleton ────────────────────────────────────────────────────────────
869
+ export const boardHealthWorker = new BoardHealthWorker({
870
+ enabled: process.env.BOARD_HEALTH_ENABLED !== 'false',
871
+ intervalMs: Number(process.env.BOARD_HEALTH_INTERVAL_MS || 5 * 60 * 1000),
872
+ staleDoingThresholdMin: Number(process.env.BOARD_HEALTH_STALE_DOING_MIN || 240),
873
+ suggestCloseThresholdMin: Number(process.env.BOARD_HEALTH_SUGGEST_CLOSE_MIN || 1440),
874
+ rollbackWindowMs: Number(process.env.BOARD_HEALTH_ROLLBACK_WINDOW_MS || 60 * 60 * 1000),
875
+ digestIntervalMs: Number(process.env.BOARD_HEALTH_DIGEST_INTERVAL_MS || 4 * 60 * 60 * 1000),
876
+ digestChannel: process.env.BOARD_HEALTH_DIGEST_CHANNEL || 'ops',
877
+ quietHoursStart: Number(process.env.BOARD_HEALTH_QUIET_START || 0),
878
+ quietHoursEnd: Number(process.env.BOARD_HEALTH_QUIET_END || 6),
879
+ dryRun: process.env.BOARD_HEALTH_DRY_RUN === 'true',
880
+ maxActionsPerTick: Number(process.env.BOARD_HEALTH_MAX_ACTIONS || 5),
881
+ });
882
+ //# sourceMappingURL=boardHealthWorker.js.map