@aitne/daemon 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 (1386) hide show
  1. package/LICENSE +21 -0
  2. package/dist/adapters/composite-dashboard-stream.d.ts +42 -0
  3. package/dist/adapters/composite-dashboard-stream.d.ts.map +1 -0
  4. package/dist/adapters/composite-dashboard-stream.js +49 -0
  5. package/dist/adapters/composite-dashboard-stream.js.map +1 -0
  6. package/dist/adapters/dashboard-adapter.d.ts +104 -0
  7. package/dist/adapters/dashboard-adapter.d.ts.map +1 -0
  8. package/dist/adapters/dashboard-adapter.js +216 -0
  9. package/dist/adapters/dashboard-adapter.js.map +1 -0
  10. package/dist/adapters/discord.d.ts +77 -0
  11. package/dist/adapters/discord.d.ts.map +1 -0
  12. package/dist/adapters/discord.js +339 -0
  13. package/dist/adapters/discord.js.map +1 -0
  14. package/dist/adapters/docs-qa-adapter.d.ts +123 -0
  15. package/dist/adapters/docs-qa-adapter.d.ts.map +1 -0
  16. package/dist/adapters/docs-qa-adapter.js +218 -0
  17. package/dist/adapters/docs-qa-adapter.js.map +1 -0
  18. package/dist/adapters/message-hub.d.ts +70 -0
  19. package/dist/adapters/message-hub.d.ts.map +1 -0
  20. package/dist/adapters/message-hub.js +359 -0
  21. package/dist/adapters/message-hub.js.map +1 -0
  22. package/dist/adapters/notification-manager.d.ts +99 -0
  23. package/dist/adapters/notification-manager.d.ts.map +1 -0
  24. package/dist/adapters/notification-manager.js +498 -0
  25. package/dist/adapters/notification-manager.js.map +1 -0
  26. package/dist/adapters/outbound-text.d.ts +28 -0
  27. package/dist/adapters/outbound-text.d.ts.map +1 -0
  28. package/dist/adapters/outbound-text.js +58 -0
  29. package/dist/adapters/outbound-text.js.map +1 -0
  30. package/dist/adapters/slack-adapter.d.ts +82 -0
  31. package/dist/adapters/slack-adapter.d.ts.map +1 -0
  32. package/dist/adapters/slack-adapter.js +359 -0
  33. package/dist/adapters/slack-adapter.js.map +1 -0
  34. package/dist/adapters/telegram-adapter.d.ts +107 -0
  35. package/dist/adapters/telegram-adapter.d.ts.map +1 -0
  36. package/dist/adapters/telegram-adapter.js +477 -0
  37. package/dist/adapters/telegram-adapter.js.map +1 -0
  38. package/dist/adapters/types.d.ts +92 -0
  39. package/dist/adapters/types.d.ts.map +1 -0
  40. package/dist/adapters/types.js +2 -0
  41. package/dist/adapters/types.js.map +1 -0
  42. package/dist/adapters/whatsapp-adapter.d.ts +213 -0
  43. package/dist/adapters/whatsapp-adapter.d.ts.map +1 -0
  44. package/dist/adapters/whatsapp-adapter.js +1216 -0
  45. package/dist/adapters/whatsapp-adapter.js.map +1 -0
  46. package/dist/api/chat-binding-query.d.ts +36 -0
  47. package/dist/api/chat-binding-query.d.ts.map +1 -0
  48. package/dist/api/chat-binding-query.js +63 -0
  49. package/dist/api/chat-binding-query.js.map +1 -0
  50. package/dist/api/chat-session-resume.d.ts +12 -0
  51. package/dist/api/chat-session-resume.d.ts.map +1 -0
  52. package/dist/api/chat-session-resume.js +21 -0
  53. package/dist/api/chat-session-resume.js.map +1 -0
  54. package/dist/api/delegated-proxy-helper.d.ts +33 -0
  55. package/dist/api/delegated-proxy-helper.d.ts.map +1 -0
  56. package/dist/api/delegated-proxy-helper.js +54 -0
  57. package/dist/api/delegated-proxy-helper.js.map +1 -0
  58. package/dist/api/directory-picker.d.ts +38 -0
  59. package/dist/api/directory-picker.d.ts.map +1 -0
  60. package/dist/api/directory-picker.js +278 -0
  61. package/dist/api/directory-picker.js.map +1 -0
  62. package/dist/api/env-writer.d.ts +25 -0
  63. package/dist/api/env-writer.d.ts.map +1 -0
  64. package/dist/api/env-writer.js +421 -0
  65. package/dist/api/env-writer.js.map +1 -0
  66. package/dist/api/integration-route-gate.d.ts +60 -0
  67. package/dist/api/integration-route-gate.d.ts.map +1 -0
  68. package/dist/api/integration-route-gate.js +83 -0
  69. package/dist/api/integration-route-gate.js.map +1 -0
  70. package/dist/api/json-body.d.ts +29 -0
  71. package/dist/api/json-body.d.ts.map +1 -0
  72. package/dist/api/json-body.js +87 -0
  73. package/dist/api/json-body.js.map +1 -0
  74. package/dist/api/routes/activity-sources.d.ts +20 -0
  75. package/dist/api/routes/activity-sources.d.ts.map +1 -0
  76. package/dist/api/routes/activity-sources.js +18 -0
  77. package/dist/api/routes/activity-sources.js.map +1 -0
  78. package/dist/api/routes/agent.d.ts +4 -0
  79. package/dist/api/routes/agent.d.ts.map +1 -0
  80. package/dist/api/routes/agent.js +619 -0
  81. package/dist/api/routes/agent.js.map +1 -0
  82. package/dist/api/routes/apple-calendar.d.ts +31 -0
  83. package/dist/api/routes/apple-calendar.d.ts.map +1 -0
  84. package/dist/api/routes/apple-calendar.js +310 -0
  85. package/dist/api/routes/apple-calendar.js.map +1 -0
  86. package/dist/api/routes/attachments.d.ts +36 -0
  87. package/dist/api/routes/attachments.d.ts.map +1 -0
  88. package/dist/api/routes/attachments.js +305 -0
  89. package/dist/api/routes/attachments.js.map +1 -0
  90. package/dist/api/routes/backends.d.ts +4 -0
  91. package/dist/api/routes/backends.d.ts.map +1 -0
  92. package/dist/api/routes/backends.js +1132 -0
  93. package/dist/api/routes/backends.js.map +1 -0
  94. package/dist/api/routes/books.d.ts +63 -0
  95. package/dist/api/routes/books.d.ts.map +1 -0
  96. package/dist/api/routes/books.js +467 -0
  97. package/dist/api/routes/books.js.map +1 -0
  98. package/dist/api/routes/calendar.d.ts +36 -0
  99. package/dist/api/routes/calendar.d.ts.map +1 -0
  100. package/dist/api/routes/calendar.js +351 -0
  101. package/dist/api/routes/calendar.js.map +1 -0
  102. package/dist/api/routes/commands.d.ts +4 -0
  103. package/dist/api/routes/commands.d.ts.map +1 -0
  104. package/dist/api/routes/commands.js +251 -0
  105. package/dist/api/routes/commands.js.map +1 -0
  106. package/dist/api/routes/context.d.ts +57 -0
  107. package/dist/api/routes/context.d.ts.map +1 -0
  108. package/dist/api/routes/context.js +1765 -0
  109. package/dist/api/routes/context.js.map +1 -0
  110. package/dist/api/routes/dashboard.d.ts +29 -0
  111. package/dist/api/routes/dashboard.d.ts.map +1 -0
  112. package/dist/api/routes/dashboard.js +2062 -0
  113. package/dist/api/routes/dashboard.js.map +1 -0
  114. package/dist/api/routes/delegated-sync.d.ts +4 -0
  115. package/dist/api/routes/delegated-sync.d.ts.map +1 -0
  116. package/dist/api/routes/delegated-sync.js +192 -0
  117. package/dist/api/routes/delegated-sync.js.map +1 -0
  118. package/dist/api/routes/delegated.d.ts +42 -0
  119. package/dist/api/routes/delegated.d.ts.map +1 -0
  120. package/dist/api/routes/delegated.js +250 -0
  121. package/dist/api/routes/delegated.js.map +1 -0
  122. package/dist/api/routes/docs.d.ts +34 -0
  123. package/dist/api/routes/docs.d.ts.map +1 -0
  124. package/dist/api/routes/docs.js +580 -0
  125. package/dist/api/routes/docs.js.map +1 -0
  126. package/dist/api/routes/entities.d.ts +9 -0
  127. package/dist/api/routes/entities.d.ts.map +1 -0
  128. package/dist/api/routes/entities.js +176 -0
  129. package/dist/api/routes/entities.js.map +1 -0
  130. package/dist/api/routes/git-accounts.d.ts +23 -0
  131. package/dist/api/routes/git-accounts.d.ts.map +1 -0
  132. package/dist/api/routes/git-accounts.js +227 -0
  133. package/dist/api/routes/git-accounts.js.map +1 -0
  134. package/dist/api/routes/git-templates.d.ts +50 -0
  135. package/dist/api/routes/git-templates.d.ts.map +1 -0
  136. package/dist/api/routes/git-templates.js +276 -0
  137. package/dist/api/routes/git-templates.js.map +1 -0
  138. package/dist/api/routes/git.d.ts +34 -0
  139. package/dist/api/routes/git.d.ts.map +1 -0
  140. package/dist/api/routes/git.js +126 -0
  141. package/dist/api/routes/git.js.map +1 -0
  142. package/dist/api/routes/github.d.ts +34 -0
  143. package/dist/api/routes/github.d.ts.map +1 -0
  144. package/dist/api/routes/github.js +465 -0
  145. package/dist/api/routes/github.js.map +1 -0
  146. package/dist/api/routes/health.d.ts +4 -0
  147. package/dist/api/routes/health.d.ts.map +1 -0
  148. package/dist/api/routes/health.js +257 -0
  149. package/dist/api/routes/health.js.map +1 -0
  150. package/dist/api/routes/integrations-reconcile.d.ts +33 -0
  151. package/dist/api/routes/integrations-reconcile.d.ts.map +1 -0
  152. package/dist/api/routes/integrations-reconcile.js +463 -0
  153. package/dist/api/routes/integrations-reconcile.js.map +1 -0
  154. package/dist/api/routes/integrations.d.ts +19 -0
  155. package/dist/api/routes/integrations.d.ts.map +1 -0
  156. package/dist/api/routes/integrations.js +1384 -0
  157. package/dist/api/routes/integrations.js.map +1 -0
  158. package/dist/api/routes/knowledge.d.ts +4 -0
  159. package/dist/api/routes/knowledge.d.ts.map +1 -0
  160. package/dist/api/routes/knowledge.js +224 -0
  161. package/dist/api/routes/knowledge.js.map +1 -0
  162. package/dist/api/routes/mail.d.ts +39 -0
  163. package/dist/api/routes/mail.d.ts.map +1 -0
  164. package/dist/api/routes/mail.js +1406 -0
  165. package/dist/api/routes/mail.js.map +1 -0
  166. package/dist/api/routes/managed-tasks.d.ts +48 -0
  167. package/dist/api/routes/managed-tasks.d.ts.map +1 -0
  168. package/dist/api/routes/managed-tasks.js +844 -0
  169. package/dist/api/routes/managed-tasks.js.map +1 -0
  170. package/dist/api/routes/mcp.d.ts +50 -0
  171. package/dist/api/routes/mcp.d.ts.map +1 -0
  172. package/dist/api/routes/mcp.js +470 -0
  173. package/dist/api/routes/mcp.js.map +1 -0
  174. package/dist/api/routes/metrics.d.ts +13 -0
  175. package/dist/api/routes/metrics.d.ts.map +1 -0
  176. package/dist/api/routes/metrics.js +117 -0
  177. package/dist/api/routes/metrics.js.map +1 -0
  178. package/dist/api/routes/notion.d.ts +35 -0
  179. package/dist/api/routes/notion.d.ts.map +1 -0
  180. package/dist/api/routes/notion.js +442 -0
  181. package/dist/api/routes/notion.js.map +1 -0
  182. package/dist/api/routes/observations.d.ts +4 -0
  183. package/dist/api/routes/observations.d.ts.map +1 -0
  184. package/dist/api/routes/observations.js +177 -0
  185. package/dist/api/routes/observations.js.map +1 -0
  186. package/dist/api/routes/obsidian.d.ts +16 -0
  187. package/dist/api/routes/obsidian.d.ts.map +1 -0
  188. package/dist/api/routes/obsidian.js +321 -0
  189. package/dist/api/routes/obsidian.js.map +1 -0
  190. package/dist/api/routes/profile-questions.d.ts +17 -0
  191. package/dist/api/routes/profile-questions.d.ts.map +1 -0
  192. package/dist/api/routes/profile-questions.js +115 -0
  193. package/dist/api/routes/profile-questions.js.map +1 -0
  194. package/dist/api/routes/receipts.d.ts +4 -0
  195. package/dist/api/routes/receipts.d.ts.map +1 -0
  196. package/dist/api/routes/receipts.js +155 -0
  197. package/dist/api/routes/receipts.js.map +1 -0
  198. package/dist/api/routes/recurring-schedules.d.ts +4 -0
  199. package/dist/api/routes/recurring-schedules.d.ts.map +1 -0
  200. package/dist/api/routes/recurring-schedules.js +137 -0
  201. package/dist/api/routes/recurring-schedules.js.map +1 -0
  202. package/dist/api/routes/repositories.d.ts +40 -0
  203. package/dist/api/routes/repositories.d.ts.map +1 -0
  204. package/dist/api/routes/repositories.js +857 -0
  205. package/dist/api/routes/repositories.js.map +1 -0
  206. package/dist/api/routes/setup-migrate.d.ts +74 -0
  207. package/dist/api/routes/setup-migrate.d.ts.map +1 -0
  208. package/dist/api/routes/setup-migrate.js +944 -0
  209. package/dist/api/routes/setup-migrate.js.map +1 -0
  210. package/dist/api/routes/setup.d.ts +4 -0
  211. package/dist/api/routes/setup.d.ts.map +1 -0
  212. package/dist/api/routes/setup.js +443 -0
  213. package/dist/api/routes/setup.js.map +1 -0
  214. package/dist/api/routes/skill-curation.d.ts +5 -0
  215. package/dist/api/routes/skill-curation.d.ts.map +1 -0
  216. package/dist/api/routes/skill-curation.js +728 -0
  217. package/dist/api/routes/skill-curation.js.map +1 -0
  218. package/dist/api/routes/skills.d.ts +52 -0
  219. package/dist/api/routes/skills.d.ts.map +1 -0
  220. package/dist/api/routes/skills.js +429 -0
  221. package/dist/api/routes/skills.js.map +1 -0
  222. package/dist/api/routes/sot-bindings.d.ts +20 -0
  223. package/dist/api/routes/sot-bindings.d.ts.map +1 -0
  224. package/dist/api/routes/sot-bindings.js +163 -0
  225. package/dist/api/routes/sot-bindings.js.map +1 -0
  226. package/dist/api/routes/sse.d.ts +86 -0
  227. package/dist/api/routes/sse.d.ts.map +1 -0
  228. package/dist/api/routes/sse.js +378 -0
  229. package/dist/api/routes/sse.js.map +1 -0
  230. package/dist/api/routes/system.d.ts +4 -0
  231. package/dist/api/routes/system.d.ts.map +1 -0
  232. package/dist/api/routes/system.js +207 -0
  233. package/dist/api/routes/system.js.map +1 -0
  234. package/dist/api/routes/task-flows.d.ts +30 -0
  235. package/dist/api/routes/task-flows.d.ts.map +1 -0
  236. package/dist/api/routes/task-flows.js +155 -0
  237. package/dist/api/routes/task-flows.js.map +1 -0
  238. package/dist/api/routes/travel-bookings.d.ts +4 -0
  239. package/dist/api/routes/travel-bookings.d.ts.map +1 -0
  240. package/dist/api/routes/travel-bookings.js +142 -0
  241. package/dist/api/routes/travel-bookings.js.map +1 -0
  242. package/dist/api/routes/travel-time.d.ts +8 -0
  243. package/dist/api/routes/travel-time.d.ts.map +1 -0
  244. package/dist/api/routes/travel-time.js +87 -0
  245. package/dist/api/routes/travel-time.js.map +1 -0
  246. package/dist/api/routes/triggers.d.ts +4 -0
  247. package/dist/api/routes/triggers.d.ts.map +1 -0
  248. package/dist/api/routes/triggers.js +101 -0
  249. package/dist/api/routes/triggers.js.map +1 -0
  250. package/dist/api/routes/voice.d.ts +48 -0
  251. package/dist/api/routes/voice.d.ts.map +1 -0
  252. package/dist/api/routes/voice.js +232 -0
  253. package/dist/api/routes/voice.js.map +1 -0
  254. package/dist/api/server.d.ts +428 -0
  255. package/dist/api/server.d.ts.map +1 -0
  256. package/dist/api/server.js +558 -0
  257. package/dist/api/server.js.map +1 -0
  258. package/dist/config.d.ts +136 -0
  259. package/dist/config.d.ts.map +1 -0
  260. package/dist/config.js +699 -0
  261. package/dist/config.js.map +1 -0
  262. package/dist/core/agent-core.d.ts +517 -0
  263. package/dist/core/agent-core.d.ts.map +1 -0
  264. package/dist/core/agent-core.js +102 -0
  265. package/dist/core/agent-core.js.map +1 -0
  266. package/dist/core/alerts.d.ts +86 -0
  267. package/dist/core/alerts.d.ts.map +1 -0
  268. package/dist/core/alerts.js +304 -0
  269. package/dist/core/alerts.js.map +1 -0
  270. package/dist/core/atomic-write.d.ts +51 -0
  271. package/dist/core/atomic-write.d.ts.map +1 -0
  272. package/dist/core/atomic-write.js +135 -0
  273. package/dist/core/atomic-write.js.map +1 -0
  274. package/dist/core/backends/api-key-probe.d.ts +40 -0
  275. package/dist/core/backends/api-key-probe.d.ts.map +1 -0
  276. package/dist/core/backends/api-key-probe.js +116 -0
  277. package/dist/core/backends/api-key-probe.js.map +1 -0
  278. package/dist/core/backends/auth-health-monitor.d.ts +373 -0
  279. package/dist/core/backends/auth-health-monitor.d.ts.map +1 -0
  280. package/dist/core/backends/auth-health-monitor.js +950 -0
  281. package/dist/core/backends/auth-health-monitor.js.map +1 -0
  282. package/dist/core/backends/auth-recovery.d.ts +263 -0
  283. package/dist/core/backends/auth-recovery.d.ts.map +1 -0
  284. package/dist/core/backends/auth-recovery.js +1086 -0
  285. package/dist/core/backends/auth-recovery.js.map +1 -0
  286. package/dist/core/backends/auth-telemetry.d.ts +81 -0
  287. package/dist/core/backends/auth-telemetry.d.ts.map +1 -0
  288. package/dist/core/backends/auth-telemetry.js +108 -0
  289. package/dist/core/backends/auth-telemetry.js.map +1 -0
  290. package/dist/core/backends/backend-router.d.ts +272 -0
  291. package/dist/core/backends/backend-router.d.ts.map +1 -0
  292. package/dist/core/backends/backend-router.js +759 -0
  293. package/dist/core/backends/backend-router.js.map +1 -0
  294. package/dist/core/backends/claude-code-core.d.ts +299 -0
  295. package/dist/core/backends/claude-code-core.d.ts.map +1 -0
  296. package/dist/core/backends/claude-code-core.js +2541 -0
  297. package/dist/core/backends/claude-code-core.js.map +1 -0
  298. package/dist/core/backends/claude-credentials-store.d.ts +83 -0
  299. package/dist/core/backends/claude-credentials-store.d.ts.map +1 -0
  300. package/dist/core/backends/claude-credentials-store.js +243 -0
  301. package/dist/core/backends/claude-credentials-store.js.map +1 -0
  302. package/dist/core/backends/cli-utils.d.ts +95 -0
  303. package/dist/core/backends/cli-utils.d.ts.map +1 -0
  304. package/dist/core/backends/cli-utils.js +464 -0
  305. package/dist/core/backends/cli-utils.js.map +1 -0
  306. package/dist/core/backends/codex-core.d.ts +127 -0
  307. package/dist/core/backends/codex-core.d.ts.map +1 -0
  308. package/dist/core/backends/codex-core.js +1693 -0
  309. package/dist/core/backends/codex-core.js.map +1 -0
  310. package/dist/core/backends/gemini-cli-core.d.ts +367 -0
  311. package/dist/core/backends/gemini-cli-core.d.ts.map +1 -0
  312. package/dist/core/backends/gemini-cli-core.js +2331 -0
  313. package/dist/core/backends/gemini-cli-core.js.map +1 -0
  314. package/dist/core/backends/idle-watchdog.d.ts +77 -0
  315. package/dist/core/backends/idle-watchdog.d.ts.map +1 -0
  316. package/dist/core/backends/idle-watchdog.js +94 -0
  317. package/dist/core/backends/idle-watchdog.js.map +1 -0
  318. package/dist/core/backends/install-methods.d.ts +93 -0
  319. package/dist/core/backends/install-methods.d.ts.map +1 -0
  320. package/dist/core/backends/install-methods.js +267 -0
  321. package/dist/core/backends/install-methods.js.map +1 -0
  322. package/dist/core/backends/model-registry.d.ts +58 -0
  323. package/dist/core/backends/model-registry.d.ts.map +1 -0
  324. package/dist/core/backends/model-registry.js +539 -0
  325. package/dist/core/backends/model-registry.js.map +1 -0
  326. package/dist/core/backends/plan-presets.d.ts +123 -0
  327. package/dist/core/backends/plan-presets.d.ts.map +1 -0
  328. package/dist/core/backends/plan-presets.js +235 -0
  329. package/dist/core/backends/plan-presets.js.map +1 -0
  330. package/dist/core/backends/price-fetcher.d.ts +48 -0
  331. package/dist/core/backends/price-fetcher.d.ts.map +1 -0
  332. package/dist/core/backends/price-fetcher.js +248 -0
  333. package/dist/core/backends/price-fetcher.js.map +1 -0
  334. package/dist/core/backends/process-config-cascade.d.ts +68 -0
  335. package/dist/core/backends/process-config-cascade.d.ts.map +1 -0
  336. package/dist/core/backends/process-config-cascade.js +173 -0
  337. package/dist/core/backends/process-config-cascade.js.map +1 -0
  338. package/dist/core/backends/prompt-utils.d.ts +6 -0
  339. package/dist/core/backends/prompt-utils.d.ts.map +1 -0
  340. package/dist/core/backends/prompt-utils.js +80 -0
  341. package/dist/core/backends/prompt-utils.js.map +1 -0
  342. package/dist/core/backends/proxy-model-registry.d.ts +110 -0
  343. package/dist/core/backends/proxy-model-registry.d.ts.map +1 -0
  344. package/dist/core/backends/proxy-model-registry.js +195 -0
  345. package/dist/core/backends/proxy-model-registry.js.map +1 -0
  346. package/dist/core/backends/silent-api-error-detector.d.ts +31 -0
  347. package/dist/core/backends/silent-api-error-detector.d.ts.map +1 -0
  348. package/dist/core/backends/silent-api-error-detector.js +44 -0
  349. package/dist/core/backends/silent-api-error-detector.js.map +1 -0
  350. package/dist/core/bang-commands/commands-cost.d.ts +13 -0
  351. package/dist/core/bang-commands/commands-cost.d.ts.map +1 -0
  352. package/dist/core/bang-commands/commands-cost.js +91 -0
  353. package/dist/core/bang-commands/commands-cost.js.map +1 -0
  354. package/dist/core/bang-commands/commands-report.d.ts +18 -0
  355. package/dist/core/bang-commands/commands-report.d.ts.map +1 -0
  356. package/dist/core/bang-commands/commands-report.js +105 -0
  357. package/dist/core/bang-commands/commands-report.js.map +1 -0
  358. package/dist/core/bang-commands/commands-stop-start.d.ts +4 -0
  359. package/dist/core/bang-commands/commands-stop-start.d.ts.map +1 -0
  360. package/dist/core/bang-commands/commands-stop-start.js +88 -0
  361. package/dist/core/bang-commands/commands-stop-start.js.map +1 -0
  362. package/dist/core/bang-commands/format-utils.d.ts +34 -0
  363. package/dist/core/bang-commands/format-utils.d.ts.map +1 -0
  364. package/dist/core/bang-commands/format-utils.js +118 -0
  365. package/dist/core/bang-commands/format-utils.js.map +1 -0
  366. package/dist/core/bang-commands/index.d.ts +20 -0
  367. package/dist/core/bang-commands/index.d.ts.map +1 -0
  368. package/dist/core/bang-commands/index.js +31 -0
  369. package/dist/core/bang-commands/index.js.map +1 -0
  370. package/dist/core/bang-commands/registry.d.ts +72 -0
  371. package/dist/core/bang-commands/registry.d.ts.map +1 -0
  372. package/dist/core/bang-commands/registry.js +174 -0
  373. package/dist/core/bang-commands/registry.js.map +1 -0
  374. package/dist/core/bang-commands/user-commands.d.ts +86 -0
  375. package/dist/core/bang-commands/user-commands.d.ts.map +1 -0
  376. package/dist/core/bang-commands/user-commands.js +212 -0
  377. package/dist/core/bang-commands/user-commands.js.map +1 -0
  378. package/dist/core/channel-timeline.d.ts +28 -0
  379. package/dist/core/channel-timeline.d.ts.map +1 -0
  380. package/dist/core/channel-timeline.js +117 -0
  381. package/dist/core/channel-timeline.js.map +1 -0
  382. package/dist/core/character-block.d.ts +37 -0
  383. package/dist/core/character-block.d.ts.map +1 -0
  384. package/dist/core/character-block.js +162 -0
  385. package/dist/core/character-block.js.map +1 -0
  386. package/dist/core/context/activity-sources.d.ts +37 -0
  387. package/dist/core/context/activity-sources.d.ts.map +1 -0
  388. package/dist/core/context/activity-sources.js +69 -0
  389. package/dist/core/context/activity-sources.js.map +1 -0
  390. package/dist/core/context/activity-view-reconciler.d.ts +110 -0
  391. package/dist/core/context/activity-view-reconciler.d.ts.map +1 -0
  392. package/dist/core/context/activity-view-reconciler.js +252 -0
  393. package/dist/core/context/activity-view-reconciler.js.map +1 -0
  394. package/dist/core/context/activity-view-runner.d.ts +38 -0
  395. package/dist/core/context/activity-view-runner.d.ts.map +1 -0
  396. package/dist/core/context/activity-view-runner.js +402 -0
  397. package/dist/core/context/activity-view-runner.js.map +1 -0
  398. package/dist/core/context/default-schedules-reconciler.d.ts +85 -0
  399. package/dist/core/context/default-schedules-reconciler.d.ts.map +1 -0
  400. package/dist/core/context/default-schedules-reconciler.js +153 -0
  401. package/dist/core/context/default-schedules-reconciler.js.map +1 -0
  402. package/dist/core/context/default-schedules-runner.d.ts +40 -0
  403. package/dist/core/context/default-schedules-runner.d.ts.map +1 -0
  404. package/dist/core/context/default-schedules-runner.js +233 -0
  405. package/dist/core/context/default-schedules-runner.js.map +1 -0
  406. package/dist/core/context/domain-index-reconciler.d.ts +81 -0
  407. package/dist/core/context/domain-index-reconciler.d.ts.map +1 -0
  408. package/dist/core/context/domain-index-reconciler.js +199 -0
  409. package/dist/core/context/domain-index-reconciler.js.map +1 -0
  410. package/dist/core/context/domain-index-runner.d.ts +35 -0
  411. package/dist/core/context/domain-index-runner.d.ts.map +1 -0
  412. package/dist/core/context/domain-index-runner.js +223 -0
  413. package/dist/core/context/domain-index-runner.js.map +1 -0
  414. package/dist/core/context/entity-mirror.d.ts +227 -0
  415. package/dist/core/context/entity-mirror.d.ts.map +1 -0
  416. package/dist/core/context/entity-mirror.js +629 -0
  417. package/dist/core/context/entity-mirror.js.map +1 -0
  418. package/dist/core/context/entity-source-rename.d.ts +61 -0
  419. package/dist/core/context/entity-source-rename.d.ts.map +1 -0
  420. package/dist/core/context/entity-source-rename.js +237 -0
  421. package/dist/core/context/entity-source-rename.js.map +1 -0
  422. package/dist/core/context/index-reconciler.d.ts +61 -0
  423. package/dist/core/context/index-reconciler.d.ts.map +1 -0
  424. package/dist/core/context/index-reconciler.js +329 -0
  425. package/dist/core/context/index-reconciler.js.map +1 -0
  426. package/dist/core/context/policy-index-reconciler.d.ts +102 -0
  427. package/dist/core/context/policy-index-reconciler.d.ts.map +1 -0
  428. package/dist/core/context/policy-index-reconciler.js +202 -0
  429. package/dist/core/context/policy-index-reconciler.js.map +1 -0
  430. package/dist/core/context/policy-index-runner.d.ts +66 -0
  431. package/dist/core/context/policy-index-runner.d.ts.map +1 -0
  432. package/dist/core/context/policy-index-runner.js +406 -0
  433. package/dist/core/context/policy-index-runner.js.map +1 -0
  434. package/dist/core/context/reconciler-runner.d.ts +44 -0
  435. package/dist/core/context/reconciler-runner.d.ts.map +1 -0
  436. package/dist/core/context/reconciler-runner.js +273 -0
  437. package/dist/core/context/reconciler-runner.js.map +1 -0
  438. package/dist/core/context-builder.d.ts +115 -0
  439. package/dist/core/context-builder.d.ts.map +1 -0
  440. package/dist/core/context-builder.js +1148 -0
  441. package/dist/core/context-builder.js.map +1 -0
  442. package/dist/core/context-frontmatter-backfill.d.ts +33 -0
  443. package/dist/core/context-frontmatter-backfill.d.ts.map +1 -0
  444. package/dist/core/context-frontmatter-backfill.js +111 -0
  445. package/dist/core/context-frontmatter-backfill.js.map +1 -0
  446. package/dist/core/context-frontmatter.d.ts +13 -0
  447. package/dist/core/context-frontmatter.d.ts.map +1 -0
  448. package/dist/core/context-frontmatter.js +325 -0
  449. package/dist/core/context-frontmatter.js.map +1 -0
  450. package/dist/core/context-health.d.ts +51 -0
  451. package/dist/core/context-health.d.ts.map +1 -0
  452. package/dist/core/context-health.js +304 -0
  453. package/dist/core/context-health.js.map +1 -0
  454. package/dist/core/context-paths.d.ts +183 -0
  455. package/dist/core/context-paths.d.ts.map +1 -0
  456. package/dist/core/context-paths.js +241 -0
  457. package/dist/core/context-paths.js.map +1 -0
  458. package/dist/core/context-staleness.d.ts +45 -0
  459. package/dist/core/context-staleness.d.ts.map +1 -0
  460. package/dist/core/context-staleness.js +88 -0
  461. package/dist/core/context-staleness.js.map +1 -0
  462. package/dist/core/custom-routine-scheduler.d.ts +151 -0
  463. package/dist/core/custom-routine-scheduler.d.ts.map +1 -0
  464. package/dist/core/custom-routine-scheduler.js +335 -0
  465. package/dist/core/custom-routine-scheduler.js.map +1 -0
  466. package/dist/core/daemon-api-cli.d.ts +33 -0
  467. package/dist/core/daemon-api-cli.d.ts.map +1 -0
  468. package/dist/core/daemon-api-cli.js +614 -0
  469. package/dist/core/daemon-api-cli.js.map +1 -0
  470. package/dist/core/dashboard-session-cleanup.d.ts +39 -0
  471. package/dist/core/dashboard-session-cleanup.d.ts.map +1 -0
  472. package/dist/core/dashboard-session-cleanup.js +108 -0
  473. package/dist/core/dashboard-session-cleanup.js.map +1 -0
  474. package/dist/core/dashboard-session-controls.d.ts +41 -0
  475. package/dist/core/dashboard-session-controls.d.ts.map +1 -0
  476. package/dist/core/dashboard-session-controls.js +154 -0
  477. package/dist/core/dashboard-session-controls.js.map +1 -0
  478. package/dist/core/delegated-connector-health.d.ts +63 -0
  479. package/dist/core/delegated-connector-health.d.ts.map +1 -0
  480. package/dist/core/delegated-connector-health.js +157 -0
  481. package/dist/core/delegated-connector-health.js.map +1 -0
  482. package/dist/core/dispatcher.d.ts +999 -0
  483. package/dist/core/dispatcher.d.ts.map +1 -0
  484. package/dist/core/dispatcher.js +4378 -0
  485. package/dist/core/dispatcher.js.map +1 -0
  486. package/dist/core/dm-freshness-metrics.d.ts +73 -0
  487. package/dist/core/dm-freshness-metrics.d.ts.map +1 -0
  488. package/dist/core/dm-freshness-metrics.js +138 -0
  489. package/dist/core/dm-freshness-metrics.js.map +1 -0
  490. package/dist/core/docs/citation-validator.d.ts +73 -0
  491. package/dist/core/docs/citation-validator.d.ts.map +1 -0
  492. package/dist/core/docs/citation-validator.js +195 -0
  493. package/dist/core/docs/citation-validator.js.map +1 -0
  494. package/dist/core/docs/extract-terms.d.ts +78 -0
  495. package/dist/core/docs/extract-terms.d.ts.map +1 -0
  496. package/dist/core/docs/extract-terms.js +147 -0
  497. package/dist/core/docs/extract-terms.js.map +1 -0
  498. package/dist/core/docs/indexer.d.ts +104 -0
  499. package/dist/core/docs/indexer.d.ts.map +1 -0
  500. package/dist/core/docs/indexer.js +340 -0
  501. package/dist/core/docs/indexer.js.map +1 -0
  502. package/dist/core/drift-effects.d.ts +30 -0
  503. package/dist/core/drift-effects.d.ts.map +1 -0
  504. package/dist/core/drift-effects.js +384 -0
  505. package/dist/core/drift-effects.js.map +1 -0
  506. package/dist/core/event-bus.d.ts +56 -0
  507. package/dist/core/event-bus.d.ts.map +1 -0
  508. package/dist/core/event-bus.js +135 -0
  509. package/dist/core/event-bus.js.map +1 -0
  510. package/dist/core/git-project-docs.d.ts +77 -0
  511. package/dist/core/git-project-docs.d.ts.map +1 -0
  512. package/dist/core/git-project-docs.js +439 -0
  513. package/dist/core/git-project-docs.js.map +1 -0
  514. package/dist/core/health-monitor.d.ts +57 -0
  515. package/dist/core/health-monitor.d.ts.map +1 -0
  516. package/dist/core/health-monitor.js +137 -0
  517. package/dist/core/health-monitor.js.map +1 -0
  518. package/dist/core/heartbeat.d.ts +26 -0
  519. package/dist/core/heartbeat.d.ts.map +1 -0
  520. package/dist/core/heartbeat.js +48 -0
  521. package/dist/core/heartbeat.js.map +1 -0
  522. package/dist/core/integration-health.d.ts +49 -0
  523. package/dist/core/integration-health.d.ts.map +1 -0
  524. package/dist/core/integration-health.js +89 -0
  525. package/dist/core/integration-health.js.map +1 -0
  526. package/dist/core/integration-lifecycle.d.ts +79 -0
  527. package/dist/core/integration-lifecycle.d.ts.map +1 -0
  528. package/dist/core/integration-lifecycle.js +153 -0
  529. package/dist/core/integration-lifecycle.js.map +1 -0
  530. package/dist/core/integration-main-backend.d.ts +36 -0
  531. package/dist/core/integration-main-backend.d.ts.map +1 -0
  532. package/dist/core/integration-main-backend.js +59 -0
  533. package/dist/core/integration-main-backend.js.map +1 -0
  534. package/dist/core/integration-probe.d.ts +98 -0
  535. package/dist/core/integration-probe.d.ts.map +1 -0
  536. package/dist/core/integration-probe.js +152 -0
  537. package/dist/core/integration-probe.js.map +1 -0
  538. package/dist/core/management-md-write-lock.d.ts +68 -0
  539. package/dist/core/management-md-write-lock.d.ts.map +1 -0
  540. package/dist/core/management-md-write-lock.js +93 -0
  541. package/dist/core/management-md-write-lock.js.map +1 -0
  542. package/dist/core/management-md.d.ts +186 -0
  543. package/dist/core/management-md.d.ts.map +1 -0
  544. package/dist/core/management-md.js +652 -0
  545. package/dist/core/management-md.js.map +1 -0
  546. package/dist/core/management-registry.d.ts +245 -0
  547. package/dist/core/management-registry.d.ts.map +1 -0
  548. package/dist/core/management-registry.js +906 -0
  549. package/dist/core/management-registry.js.map +1 -0
  550. package/dist/core/management-telemetry.d.ts +100 -0
  551. package/dist/core/management-telemetry.d.ts.map +1 -0
  552. package/dist/core/management-telemetry.js +156 -0
  553. package/dist/core/management-telemetry.js.map +1 -0
  554. package/dist/core/message-recorder.d.ts +38 -0
  555. package/dist/core/message-recorder.d.ts.map +1 -0
  556. package/dist/core/message-recorder.js +88 -0
  557. package/dist/core/message-recorder.js.map +1 -0
  558. package/dist/core/metrics.d.ts +338 -0
  559. package/dist/core/metrics.d.ts.map +1 -0
  560. package/dist/core/metrics.js +747 -0
  561. package/dist/core/metrics.js.map +1 -0
  562. package/dist/core/migration-backup.d.ts +218 -0
  563. package/dist/core/migration-backup.d.ts.map +1 -0
  564. package/dist/core/migration-backup.js +934 -0
  565. package/dist/core/migration-backup.js.map +1 -0
  566. package/dist/core/overview-write-lock.d.ts +48 -0
  567. package/dist/core/overview-write-lock.d.ts.map +1 -0
  568. package/dist/core/overview-write-lock.js +56 -0
  569. package/dist/core/overview-write-lock.js.map +1 -0
  570. package/dist/core/path-compat.d.ts +22 -0
  571. package/dist/core/path-compat.d.ts.map +1 -0
  572. package/dist/core/path-compat.js +67 -0
  573. package/dist/core/path-compat.js.map +1 -0
  574. package/dist/core/path-rewrite.d.ts +58 -0
  575. package/dist/core/path-rewrite.d.ts.map +1 -0
  576. package/dist/core/path-rewrite.js +141 -0
  577. package/dist/core/path-rewrite.js.map +1 -0
  578. package/dist/core/policy-files.d.ts +108 -0
  579. package/dist/core/policy-files.d.ts.map +1 -0
  580. package/dist/core/policy-files.js +198 -0
  581. package/dist/core/policy-files.js.map +1 -0
  582. package/dist/core/profile-questions/seed.d.ts +44 -0
  583. package/dist/core/profile-questions/seed.d.ts.map +1 -0
  584. package/dist/core/profile-questions/seed.js +173 -0
  585. package/dist/core/profile-questions/seed.js.map +1 -0
  586. package/dist/core/profile-questions/slot-filled.d.ts +51 -0
  587. package/dist/core/profile-questions/slot-filled.d.ts.map +1 -0
  588. package/dist/core/profile-questions/slot-filled.js +118 -0
  589. package/dist/core/profile-questions/slot-filled.js.map +1 -0
  590. package/dist/core/prompts.d.ts +111 -0
  591. package/dist/core/prompts.d.ts.map +1 -0
  592. package/dist/core/prompts.js +267 -0
  593. package/dist/core/prompts.js.map +1 -0
  594. package/dist/core/quiet-hours-sync.d.ts +15 -0
  595. package/dist/core/quiet-hours-sync.d.ts.map +1 -0
  596. package/dist/core/quiet-hours-sync.js +51 -0
  597. package/dist/core/quiet-hours-sync.js.map +1 -0
  598. package/dist/core/read-sensitive-token-manager.d.ts +19 -0
  599. package/dist/core/read-sensitive-token-manager.d.ts.map +1 -0
  600. package/dist/core/read-sensitive-token-manager.js +29 -0
  601. package/dist/core/read-sensitive-token-manager.js.map +1 -0
  602. package/dist/core/recurrence.d.ts +24 -0
  603. package/dist/core/recurrence.d.ts.map +1 -0
  604. package/dist/core/recurrence.js +162 -0
  605. package/dist/core/recurrence.js.map +1 -0
  606. package/dist/core/reinstall.d.ts +107 -0
  607. package/dist/core/reinstall.d.ts.map +1 -0
  608. package/dist/core/reinstall.js +163 -0
  609. package/dist/core/reinstall.js.map +1 -0
  610. package/dist/core/release-assets.d.ts +106 -0
  611. package/dist/core/release-assets.d.ts.map +1 -0
  612. package/dist/core/release-assets.js +434 -0
  613. package/dist/core/release-assets.js.map +1 -0
  614. package/dist/core/repository-management-docs.d.ts +216 -0
  615. package/dist/core/repository-management-docs.d.ts.map +1 -0
  616. package/dist/core/repository-management-docs.js +855 -0
  617. package/dist/core/repository-management-docs.js.map +1 -0
  618. package/dist/core/retention.d.ts +164 -0
  619. package/dist/core/retention.d.ts.map +1 -0
  620. package/dist/core/retention.js +1008 -0
  621. package/dist/core/retention.js.map +1 -0
  622. package/dist/core/review-context.d.ts +48 -0
  623. package/dist/core/review-context.d.ts.map +1 -0
  624. package/dist/core/review-context.js +282 -0
  625. package/dist/core/review-context.js.map +1 -0
  626. package/dist/core/roadmap-horizon.d.ts +48 -0
  627. package/dist/core/roadmap-horizon.d.ts.map +1 -0
  628. package/dist/core/roadmap-horizon.js +213 -0
  629. package/dist/core/roadmap-horizon.js.map +1 -0
  630. package/dist/core/roadmap-ids.d.ts +57 -0
  631. package/dist/core/roadmap-ids.d.ts.map +1 -0
  632. package/dist/core/roadmap-ids.js +118 -0
  633. package/dist/core/roadmap-ids.js.map +1 -0
  634. package/dist/core/roadmap-merge.d.ts +7 -0
  635. package/dist/core/roadmap-merge.d.ts.map +1 -0
  636. package/dist/core/roadmap-merge.js +187 -0
  637. package/dist/core/roadmap-merge.js.map +1 -0
  638. package/dist/core/roadmap-refresh-triggers.d.ts +32 -0
  639. package/dist/core/roadmap-refresh-triggers.d.ts.map +1 -0
  640. package/dist/core/roadmap-refresh-triggers.js +51 -0
  641. package/dist/core/roadmap-refresh-triggers.js.map +1 -0
  642. package/dist/core/roadmap-truncate.d.ts +49 -0
  643. package/dist/core/roadmap-truncate.d.ts.map +1 -0
  644. package/dist/core/roadmap-truncate.js +152 -0
  645. package/dist/core/roadmap-truncate.js.map +1 -0
  646. package/dist/core/roadmap-validate.d.ts +31 -0
  647. package/dist/core/roadmap-validate.d.ts.map +1 -0
  648. package/dist/core/roadmap-validate.js +403 -0
  649. package/dist/core/roadmap-validate.js.map +1 -0
  650. package/dist/core/roadmap-write-lock.d.ts +53 -0
  651. package/dist/core/roadmap-write-lock.d.ts.map +1 -0
  652. package/dist/core/roadmap-write-lock.js +59 -0
  653. package/dist/core/roadmap-write-lock.js.map +1 -0
  654. package/dist/core/schedule-insert-helper.d.ts +46 -0
  655. package/dist/core/schedule-insert-helper.d.ts.map +1 -0
  656. package/dist/core/schedule-insert-helper.js +52 -0
  657. package/dist/core/schedule-insert-helper.js.map +1 -0
  658. package/dist/core/schedule-maintenance.d.ts +22 -0
  659. package/dist/core/schedule-maintenance.d.ts.map +1 -0
  660. package/dist/core/schedule-maintenance.js +57 -0
  661. package/dist/core/schedule-maintenance.js.map +1 -0
  662. package/dist/core/scheduler.d.ts +208 -0
  663. package/dist/core/scheduler.d.ts.map +1 -0
  664. package/dist/core/scheduler.js +896 -0
  665. package/dist/core/scheduler.js.map +1 -0
  666. package/dist/core/semaphore.d.ts +13 -0
  667. package/dist/core/semaphore.d.ts.map +1 -0
  668. package/dist/core/semaphore.js +31 -0
  669. package/dist/core/semaphore.js.map +1 -0
  670. package/dist/core/session-gate.d.ts +37 -0
  671. package/dist/core/session-gate.d.ts.map +1 -0
  672. package/dist/core/session-gate.js +69 -0
  673. package/dist/core/session-gate.js.map +1 -0
  674. package/dist/core/session-manager.d.ts +252 -0
  675. package/dist/core/session-manager.d.ts.map +1 -0
  676. package/dist/core/session-manager.js +716 -0
  677. package/dist/core/session-manager.js.map +1 -0
  678. package/dist/core/signal-detector.d.ts +97 -0
  679. package/dist/core/signal-detector.d.ts.map +1 -0
  680. package/dist/core/signal-detector.js +215 -0
  681. package/dist/core/signal-detector.js.map +1 -0
  682. package/dist/core/skeleton.d.ts +83 -0
  683. package/dist/core/skeleton.d.ts.map +1 -0
  684. package/dist/core/skeleton.js +255 -0
  685. package/dist/core/skeleton.js.map +1 -0
  686. package/dist/core/skill-curation/apply-proposal.d.ts +71 -0
  687. package/dist/core/skill-curation/apply-proposal.d.ts.map +1 -0
  688. package/dist/core/skill-curation/apply-proposal.js +175 -0
  689. package/dist/core/skill-curation/apply-proposal.js.map +1 -0
  690. package/dist/core/skill-curation/auto-revert.d.ts +43 -0
  691. package/dist/core/skill-curation/auto-revert.d.ts.map +1 -0
  692. package/dist/core/skill-curation/auto-revert.js +155 -0
  693. package/dist/core/skill-curation/auto-revert.js.map +1 -0
  694. package/dist/core/skill-curation/classify-diff.d.ts +27 -0
  695. package/dist/core/skill-curation/classify-diff.d.ts.map +1 -0
  696. package/dist/core/skill-curation/classify-diff.js +0 -0
  697. package/dist/core/skill-curation/classify-diff.js.map +1 -0
  698. package/dist/core/skill-curation/declarations.d.ts +32 -0
  699. package/dist/core/skill-curation/declarations.d.ts.map +1 -0
  700. package/dist/core/skill-curation/declarations.js +171 -0
  701. package/dist/core/skill-curation/declarations.js.map +1 -0
  702. package/dist/core/skill-curation/knowledge-map.d.ts +26 -0
  703. package/dist/core/skill-curation/knowledge-map.d.ts.map +1 -0
  704. package/dist/core/skill-curation/knowledge-map.js +154 -0
  705. package/dist/core/skill-curation/knowledge-map.js.map +1 -0
  706. package/dist/core/skill-curation/orphan-overlay.d.ts +35 -0
  707. package/dist/core/skill-curation/orphan-overlay.d.ts.map +1 -0
  708. package/dist/core/skill-curation/orphan-overlay.js +167 -0
  709. package/dist/core/skill-curation/orphan-overlay.js.map +1 -0
  710. package/dist/core/skill-curation/overlay-store.d.ts +41 -0
  711. package/dist/core/skill-curation/overlay-store.d.ts.map +1 -0
  712. package/dist/core/skill-curation/overlay-store.js +143 -0
  713. package/dist/core/skill-curation/overlay-store.js.map +1 -0
  714. package/dist/core/skill-curation/render/convention-notes.d.ts +4 -0
  715. package/dist/core/skill-curation/render/convention-notes.d.ts.map +1 -0
  716. package/dist/core/skill-curation/render/convention-notes.js +13 -0
  717. package/dist/core/skill-curation/render/convention-notes.js.map +1 -0
  718. package/dist/core/skill-curation/render/cross-references.d.ts +4 -0
  719. package/dist/core/skill-curation/render/cross-references.d.ts.map +1 -0
  720. package/dist/core/skill-curation/render/cross-references.js +10 -0
  721. package/dist/core/skill-curation/render/cross-references.js.map +1 -0
  722. package/dist/core/skill-curation/render/frontmatter-schema.d.ts +4 -0
  723. package/dist/core/skill-curation/render/frontmatter-schema.d.ts.map +1 -0
  724. package/dist/core/skill-curation/render/frontmatter-schema.js +25 -0
  725. package/dist/core/skill-curation/render/frontmatter-schema.js.map +1 -0
  726. package/dist/core/skill-curation/render/index.d.ts +5 -0
  727. package/dist/core/skill-curation/render/index.d.ts.map +1 -0
  728. package/dist/core/skill-curation/render/index.js +42 -0
  729. package/dist/core/skill-curation/render/index.js.map +1 -0
  730. package/dist/core/skill-curation/render/knowledge-layout.d.ts +4 -0
  731. package/dist/core/skill-curation/render/knowledge-layout.d.ts.map +1 -0
  732. package/dist/core/skill-curation/render/knowledge-layout.js +36 -0
  733. package/dist/core/skill-curation/render/knowledge-layout.js.map +1 -0
  734. package/dist/core/skill-curation/render/routing-table.d.ts +4 -0
  735. package/dist/core/skill-curation/render/routing-table.d.ts.map +1 -0
  736. package/dist/core/skill-curation/render/routing-table.js +37 -0
  737. package/dist/core/skill-curation/render/routing-table.js.map +1 -0
  738. package/dist/core/skill-curation/render/search-recipes.d.ts +4 -0
  739. package/dist/core/skill-curation/render/search-recipes.d.ts.map +1 -0
  740. package/dist/core/skill-curation/render/search-recipes.js +39 -0
  741. package/dist/core/skill-curation/render/search-recipes.js.map +1 -0
  742. package/dist/core/skill-curation/run-token.d.ts +27 -0
  743. package/dist/core/skill-curation/run-token.d.ts.map +1 -0
  744. package/dist/core/skill-curation/run-token.js +81 -0
  745. package/dist/core/skill-curation/run-token.js.map +1 -0
  746. package/dist/core/skill-curation/signals.d.ts +49 -0
  747. package/dist/core/skill-curation/signals.d.ts.map +1 -0
  748. package/dist/core/skill-curation/signals.js +149 -0
  749. package/dist/core/skill-curation/signals.js.map +1 -0
  750. package/dist/core/skill-curation/smoke-test.d.ts +39 -0
  751. package/dist/core/skill-curation/smoke-test.d.ts.map +1 -0
  752. package/dist/core/skill-curation/smoke-test.js +313 -0
  753. package/dist/core/skill-curation/smoke-test.js.map +1 -0
  754. package/dist/core/skill-curation/splicer.d.ts +16 -0
  755. package/dist/core/skill-curation/splicer.d.ts.map +1 -0
  756. package/dist/core/skill-curation/splicer.js +78 -0
  757. package/dist/core/skill-curation/splicer.js.map +1 -0
  758. package/dist/core/skill-curation/workdir.d.ts +40 -0
  759. package/dist/core/skill-curation/workdir.d.ts.map +1 -0
  760. package/dist/core/skill-curation/workdir.js +242 -0
  761. package/dist/core/skill-curation/workdir.js.map +1 -0
  762. package/dist/core/skills-compiler.d.ts +391 -0
  763. package/dist/core/skills-compiler.d.ts.map +1 -0
  764. package/dist/core/skills-compiler.js +1271 -0
  765. package/dist/core/skills-compiler.js.map +1 -0
  766. package/dist/core/skills-manifest.d.ts +8 -0
  767. package/dist/core/skills-manifest.d.ts.map +1 -0
  768. package/dist/core/skills-manifest.js +408 -0
  769. package/dist/core/skills-manifest.js.map +1 -0
  770. package/dist/core/system-reset.d.ts +268 -0
  771. package/dist/core/system-reset.d.ts.map +1 -0
  772. package/dist/core/system-reset.js +816 -0
  773. package/dist/core/system-reset.js.map +1 -0
  774. package/dist/core/template-store.d.ts +170 -0
  775. package/dist/core/template-store.d.ts.map +1 -0
  776. package/dist/core/template-store.js +388 -0
  777. package/dist/core/template-store.js.map +1 -0
  778. package/dist/core/template-versions.d.ts +95 -0
  779. package/dist/core/template-versions.d.ts.map +1 -0
  780. package/dist/core/template-versions.js +175 -0
  781. package/dist/core/template-versions.js.map +1 -0
  782. package/dist/core/today-agent-plan.d.ts +33 -0
  783. package/dist/core/today-agent-plan.d.ts.map +1 -0
  784. package/dist/core/today-agent-plan.js +120 -0
  785. package/dist/core/today-agent-plan.js.map +1 -0
  786. package/dist/core/today-direct-writer.d.ts +62 -0
  787. package/dist/core/today-direct-writer.d.ts.map +1 -0
  788. package/dist/core/today-direct-writer.js +132 -0
  789. package/dist/core/today-direct-writer.js.map +1 -0
  790. package/dist/core/today-write-lock.d.ts +89 -0
  791. package/dist/core/today-write-lock.d.ts.map +1 -0
  792. package/dist/core/today-write-lock.js +154 -0
  793. package/dist/core/today-write-lock.js.map +1 -0
  794. package/dist/core/trigger-dispatch.d.ts +31 -0
  795. package/dist/core/trigger-dispatch.d.ts.map +1 -0
  796. package/dist/core/trigger-dispatch.js +100 -0
  797. package/dist/core/trigger-dispatch.js.map +1 -0
  798. package/dist/core/trigger-evaluator.d.ts +59 -0
  799. package/dist/core/trigger-evaluator.d.ts.map +1 -0
  800. package/dist/core/trigger-evaluator.js +243 -0
  801. package/dist/core/trigger-evaluator.js.map +1 -0
  802. package/dist/core/workdir.d.ts +241 -0
  803. package/dist/core/workdir.d.ts.map +1 -0
  804. package/dist/core/workdir.js +565 -0
  805. package/dist/core/workdir.js.map +1 -0
  806. package/dist/db/automation-triggers.d.ts +90 -0
  807. package/dist/db/automation-triggers.d.ts.map +1 -0
  808. package/dist/db/automation-triggers.js +199 -0
  809. package/dist/db/automation-triggers.js.map +1 -0
  810. package/dist/db/client.d.ts +6 -0
  811. package/dist/db/client.d.ts.map +1 -0
  812. package/dist/db/client.js +47 -0
  813. package/dist/db/client.js.map +1 -0
  814. package/dist/db/entities-store.d.ts +92 -0
  815. package/dist/db/entities-store.d.ts.map +1 -0
  816. package/dist/db/entities-store.js +180 -0
  817. package/dist/db/entities-store.js.map +1 -0
  818. package/dist/db/hourly-check-signals.d.ts +78 -0
  819. package/dist/db/hourly-check-signals.d.ts.map +1 -0
  820. package/dist/db/hourly-check-signals.js +289 -0
  821. package/dist/db/hourly-check-signals.js.map +1 -0
  822. package/dist/db/integration-probe-store.d.ts +27 -0
  823. package/dist/db/integration-probe-store.d.ts.map +1 -0
  824. package/dist/db/integration-probe-store.js +75 -0
  825. package/dist/db/integration-probe-store.js.map +1 -0
  826. package/dist/db/integrations-store.d.ts +19 -0
  827. package/dist/db/integrations-store.d.ts.map +1 -0
  828. package/dist/db/integrations-store.js +85 -0
  829. package/dist/db/integrations-store.js.map +1 -0
  830. package/dist/db/managed-tasks-store.d.ts +130 -0
  831. package/dist/db/managed-tasks-store.d.ts.map +1 -0
  832. package/dist/db/managed-tasks-store.js +238 -0
  833. package/dist/db/managed-tasks-store.js.map +1 -0
  834. package/dist/db/management-parse-failures-store.d.ts +45 -0
  835. package/dist/db/management-parse-failures-store.d.ts.map +1 -0
  836. package/dist/db/management-parse-failures-store.js +36 -0
  837. package/dist/db/management-parse-failures-store.js.map +1 -0
  838. package/dist/db/observations.d.ts +145 -0
  839. package/dist/db/observations.d.ts.map +1 -0
  840. package/dist/db/observations.js +287 -0
  841. package/dist/db/observations.js.map +1 -0
  842. package/dist/db/recurring-schedules.d.ts +70 -0
  843. package/dist/db/recurring-schedules.d.ts.map +1 -0
  844. package/dist/db/recurring-schedules.js +213 -0
  845. package/dist/db/recurring-schedules.js.map +1 -0
  846. package/dist/db/repositories-store.d.ts +296 -0
  847. package/dist/db/repositories-store.d.ts.map +1 -0
  848. package/dist/db/repositories-store.js +754 -0
  849. package/dist/db/repositories-store.js.map +1 -0
  850. package/dist/db/runtime-state.d.ts +61 -0
  851. package/dist/db/runtime-state.d.ts.map +1 -0
  852. package/dist/db/runtime-state.js +104 -0
  853. package/dist/db/runtime-state.js.map +1 -0
  854. package/dist/db/schema.d.ts +4 -0
  855. package/dist/db/schema.d.ts.map +1 -0
  856. package/dist/db/schema.js +1338 -0
  857. package/dist/db/schema.js.map +1 -0
  858. package/dist/db/sot-bindings-store.d.ts +41 -0
  859. package/dist/db/sot-bindings-store.d.ts.map +1 -0
  860. package/dist/db/sot-bindings-store.js +64 -0
  861. package/dist/db/sot-bindings-store.js.map +1 -0
  862. package/dist/db/test-schemas.d.ts +23 -0
  863. package/dist/db/test-schemas.d.ts.map +1 -0
  864. package/dist/db/test-schemas.js +111 -0
  865. package/dist/db/test-schemas.js.map +1 -0
  866. package/dist/db/voice-transcripts-store.d.ts +28 -0
  867. package/dist/db/voice-transcripts-store.d.ts.map +1 -0
  868. package/dist/db/voice-transcripts-store.js +43 -0
  869. package/dist/db/voice-transcripts-store.js.map +1 -0
  870. package/dist/index.d.ts +2 -0
  871. package/dist/index.d.ts.map +1 -0
  872. package/dist/index.js +2913 -0
  873. package/dist/index.js.map +1 -0
  874. package/dist/init.d.ts +7 -0
  875. package/dist/init.d.ts.map +1 -0
  876. package/dist/init.js +32 -0
  877. package/dist/init.js.map +1 -0
  878. package/dist/log-buffer.d.ts +71 -0
  879. package/dist/log-buffer.d.ts.map +1 -0
  880. package/dist/log-buffer.js +201 -0
  881. package/dist/log-buffer.js.map +1 -0
  882. package/dist/logging.d.ts +5 -0
  883. package/dist/logging.d.ts.map +1 -0
  884. package/dist/logging.js +130 -0
  885. package/dist/logging.js.map +1 -0
  886. package/dist/management-rules.d.ts +2 -0
  887. package/dist/management-rules.d.ts.map +1 -0
  888. package/dist/management-rules.js +62 -0
  889. package/dist/management-rules.js.map +1 -0
  890. package/dist/messaging/constants.d.ts +33 -0
  891. package/dist/messaging/constants.d.ts.map +1 -0
  892. package/dist/messaging/constants.js +52 -0
  893. package/dist/messaging/constants.js.map +1 -0
  894. package/dist/messaging/magic-phrase.d.ts +16 -0
  895. package/dist/messaging/magic-phrase.d.ts.map +1 -0
  896. package/dist/messaging/magic-phrase.js +103 -0
  897. package/dist/messaging/magic-phrase.js.map +1 -0
  898. package/dist/messaging/owner-channels.d.ts +20 -0
  899. package/dist/messaging/owner-channels.d.ts.map +1 -0
  900. package/dist/messaging/owner-channels.js +41 -0
  901. package/dist/messaging/owner-channels.js.map +1 -0
  902. package/dist/observers/calendar-poller.d.ts +51 -0
  903. package/dist/observers/calendar-poller.d.ts.map +1 -0
  904. package/dist/observers/calendar-poller.js +128 -0
  905. package/dist/observers/calendar-poller.js.map +1 -0
  906. package/dist/observers/context-index-reconciler-observer.d.ts +72 -0
  907. package/dist/observers/context-index-reconciler-observer.d.ts.map +1 -0
  908. package/dist/observers/context-index-reconciler-observer.js +253 -0
  909. package/dist/observers/context-index-reconciler-observer.js.map +1 -0
  910. package/dist/observers/delegated-probe-observer.d.ts +83 -0
  911. package/dist/observers/delegated-probe-observer.d.ts.map +1 -0
  912. package/dist/observers/delegated-probe-observer.js +237 -0
  913. package/dist/observers/delegated-probe-observer.js.map +1 -0
  914. package/dist/observers/delegated-sync-worker.d.ts +375 -0
  915. package/dist/observers/delegated-sync-worker.d.ts.map +1 -0
  916. package/dist/observers/delegated-sync-worker.js +1087 -0
  917. package/dist/observers/delegated-sync-worker.js.map +1 -0
  918. package/dist/observers/entity-mirror-observer.d.ts +55 -0
  919. package/dist/observers/entity-mirror-observer.d.ts.map +1 -0
  920. package/dist/observers/entity-mirror-observer.js +73 -0
  921. package/dist/observers/entity-mirror-observer.js.map +1 -0
  922. package/dist/observers/git-delegated-cron.d.ts +41 -0
  923. package/dist/observers/git-delegated-cron.d.ts.map +1 -0
  924. package/dist/observers/git-delegated-cron.js +159 -0
  925. package/dist/observers/git-delegated-cron.js.map +1 -0
  926. package/dist/observers/git-event-classifier.d.ts +52 -0
  927. package/dist/observers/git-event-classifier.d.ts.map +1 -0
  928. package/dist/observers/git-event-classifier.js +70 -0
  929. package/dist/observers/git-event-classifier.js.map +1 -0
  930. package/dist/observers/git-watcher.d.ts +162 -0
  931. package/dist/observers/git-watcher.d.ts.map +1 -0
  932. package/dist/observers/git-watcher.js +768 -0
  933. package/dist/observers/git-watcher.js.map +1 -0
  934. package/dist/observers/github-poller-classifier.d.ts +101 -0
  935. package/dist/observers/github-poller-classifier.d.ts.map +1 -0
  936. package/dist/observers/github-poller-classifier.js +199 -0
  937. package/dist/observers/github-poller-classifier.js.map +1 -0
  938. package/dist/observers/github-poller.d.ts +291 -0
  939. package/dist/observers/github-poller.d.ts.map +1 -0
  940. package/dist/observers/github-poller.js +609 -0
  941. package/dist/observers/github-poller.js.map +1 -0
  942. package/dist/observers/imminent-event-scheduler.d.ts +34 -0
  943. package/dist/observers/imminent-event-scheduler.d.ts.map +1 -0
  944. package/dist/observers/imminent-event-scheduler.js +125 -0
  945. package/dist/observers/imminent-event-scheduler.js.map +1 -0
  946. package/dist/observers/mail-poller.d.ts +133 -0
  947. package/dist/observers/mail-poller.d.ts.map +1 -0
  948. package/dist/observers/mail-poller.js +563 -0
  949. package/dist/observers/mail-poller.js.map +1 -0
  950. package/dist/observers/mail-reconciliation.d.ts +87 -0
  951. package/dist/observers/mail-reconciliation.d.ts.map +1 -0
  952. package/dist/observers/mail-reconciliation.js +241 -0
  953. package/dist/observers/mail-reconciliation.js.map +1 -0
  954. package/dist/observers/manager.d.ts +67 -0
  955. package/dist/observers/manager.d.ts.map +1 -0
  956. package/dist/observers/manager.js +136 -0
  957. package/dist/observers/manager.js.map +1 -0
  958. package/dist/observers/notion-poller.d.ts +43 -0
  959. package/dist/observers/notion-poller.d.ts.map +1 -0
  960. package/dist/observers/notion-poller.js +184 -0
  961. package/dist/observers/notion-poller.js.map +1 -0
  962. package/dist/observers/observation-summarizer/index.d.ts +13 -0
  963. package/dist/observers/observation-summarizer/index.d.ts.map +1 -0
  964. package/dist/observers/observation-summarizer/index.js +13 -0
  965. package/dist/observers/observation-summarizer/index.js.map +1 -0
  966. package/dist/observers/observation-summarizer/pre-filter.d.ts +62 -0
  967. package/dist/observers/observation-summarizer/pre-filter.d.ts.map +1 -0
  968. package/dist/observers/observation-summarizer/pre-filter.js +189 -0
  969. package/dist/observers/observation-summarizer/pre-filter.js.map +1 -0
  970. package/dist/observers/observation-summarizer/response-parser.d.ts +30 -0
  971. package/dist/observers/observation-summarizer/response-parser.d.ts.map +1 -0
  972. package/dist/observers/observation-summarizer/response-parser.js +106 -0
  973. package/dist/observers/observation-summarizer/response-parser.js.map +1 -0
  974. package/dist/observers/observation-summarizer/summarizer-client.d.ts +83 -0
  975. package/dist/observers/observation-summarizer/summarizer-client.d.ts.map +1 -0
  976. package/dist/observers/observation-summarizer/summarizer-client.js +185 -0
  977. package/dist/observers/observation-summarizer/summarizer-client.js.map +1 -0
  978. package/dist/observers/observation-summarizer/summarizer-prompts.d.ts +51 -0
  979. package/dist/observers/observation-summarizer/summarizer-prompts.d.ts.map +1 -0
  980. package/dist/observers/observation-summarizer/summarizer-prompts.js +286 -0
  981. package/dist/observers/observation-summarizer/summarizer-prompts.js.map +1 -0
  982. package/dist/observers/observation-summarizer/worker.d.ts +106 -0
  983. package/dist/observers/observation-summarizer/worker.d.ts.map +1 -0
  984. package/dist/observers/observation-summarizer/worker.js +311 -0
  985. package/dist/observers/observation-summarizer/worker.js.map +1 -0
  986. package/dist/observers/obsidian-watcher.d.ts +90 -0
  987. package/dist/observers/obsidian-watcher.d.ts.map +1 -0
  988. package/dist/observers/obsidian-watcher.js +166 -0
  989. package/dist/observers/obsidian-watcher.js.map +1 -0
  990. package/dist/observers/primary-vault-watcher.d.ts +73 -0
  991. package/dist/observers/primary-vault-watcher.d.ts.map +1 -0
  992. package/dist/observers/primary-vault-watcher.js +115 -0
  993. package/dist/observers/primary-vault-watcher.js.map +1 -0
  994. package/dist/observers/repository-management-cron.d.ts +70 -0
  995. package/dist/observers/repository-management-cron.d.ts.map +1 -0
  996. package/dist/observers/repository-management-cron.js +166 -0
  997. package/dist/observers/repository-management-cron.js.map +1 -0
  998. package/dist/observers/skill-curation-walker.d.ts +33 -0
  999. package/dist/observers/skill-curation-walker.d.ts.map +1 -0
  1000. package/dist/observers/skill-curation-walker.js +216 -0
  1001. package/dist/observers/skill-curation-walker.js.map +1 -0
  1002. package/dist/safety/absolute-block-audit.d.ts +22 -0
  1003. package/dist/safety/absolute-block-audit.d.ts.map +1 -0
  1004. package/dist/safety/absolute-block-audit.js +32 -0
  1005. package/dist/safety/absolute-block-audit.js.map +1 -0
  1006. package/dist/safety/agent-write-tracker.d.ts +42 -0
  1007. package/dist/safety/agent-write-tracker.d.ts.map +1 -0
  1008. package/dist/safety/agent-write-tracker.js +82 -0
  1009. package/dist/safety/agent-write-tracker.js.map +1 -0
  1010. package/dist/safety/always-disallowed.d.ts +66 -0
  1011. package/dist/safety/always-disallowed.d.ts.map +1 -0
  1012. package/dist/safety/always-disallowed.js +347 -0
  1013. package/dist/safety/always-disallowed.js.map +1 -0
  1014. package/dist/safety/audit.d.ts +118 -0
  1015. package/dist/safety/audit.d.ts.map +1 -0
  1016. package/dist/safety/audit.js +324 -0
  1017. package/dist/safety/audit.js.map +1 -0
  1018. package/dist/safety/integration-write-tracker.d.ts +58 -0
  1019. package/dist/safety/integration-write-tracker.d.ts.map +1 -0
  1020. package/dist/safety/integration-write-tracker.js +41 -0
  1021. package/dist/safety/integration-write-tracker.js.map +1 -0
  1022. package/dist/safety/risk-classifier.d.ts +65 -0
  1023. package/dist/safety/risk-classifier.d.ts.map +1 -0
  1024. package/dist/safety/risk-classifier.js +763 -0
  1025. package/dist/safety/risk-classifier.js.map +1 -0
  1026. package/dist/scheduler/hourly-check-gate.d.ts +73 -0
  1027. package/dist/scheduler/hourly-check-gate.d.ts.map +1 -0
  1028. package/dist/scheduler/hourly-check-gate.js +128 -0
  1029. package/dist/scheduler/hourly-check-gate.js.map +1 -0
  1030. package/dist/secrets/backend-api-key-env.d.ts +104 -0
  1031. package/dist/secrets/backend-api-key-env.d.ts.map +1 -0
  1032. package/dist/secrets/backend-api-key-env.js +197 -0
  1033. package/dist/secrets/backend-api-key-env.js.map +1 -0
  1034. package/dist/secrets/codex-home-materializer.d.ts +35 -0
  1035. package/dist/secrets/codex-home-materializer.d.ts.map +1 -0
  1036. package/dist/secrets/codex-home-materializer.js +76 -0
  1037. package/dist/secrets/codex-home-materializer.js.map +1 -0
  1038. package/dist/secrets/encrypted-blob-store.d.ts +20 -0
  1039. package/dist/secrets/encrypted-blob-store.d.ts.map +1 -0
  1040. package/dist/secrets/encrypted-blob-store.js +80 -0
  1041. package/dist/secrets/encrypted-blob-store.js.map +1 -0
  1042. package/dist/secrets/platform-secret-store.d.ts +17 -0
  1043. package/dist/secrets/platform-secret-store.d.ts.map +1 -0
  1044. package/dist/secrets/platform-secret-store.js +37 -0
  1045. package/dist/secrets/platform-secret-store.js.map +1 -0
  1046. package/dist/secrets/redaction.d.ts +2 -0
  1047. package/dist/secrets/redaction.d.ts.map +1 -0
  1048. package/dist/secrets/redaction.js +2 -0
  1049. package/dist/secrets/redaction.js.map +1 -0
  1050. package/dist/secrets/secret-broker.d.ts +61 -0
  1051. package/dist/secrets/secret-broker.d.ts.map +1 -0
  1052. package/dist/secrets/secret-broker.js +160 -0
  1053. package/dist/secrets/secret-broker.js.map +1 -0
  1054. package/dist/secrets/secret-names.d.ts +34 -0
  1055. package/dist/secrets/secret-names.d.ts.map +1 -0
  1056. package/dist/secrets/secret-names.js +39 -0
  1057. package/dist/secrets/secret-names.js.map +1 -0
  1058. package/dist/secrets/secret-store.d.ts +8 -0
  1059. package/dist/secrets/secret-store.d.ts.map +1 -0
  1060. package/dist/secrets/secret-store.js +2 -0
  1061. package/dist/secrets/secret-store.js.map +1 -0
  1062. package/dist/secrets/types.d.ts +7 -0
  1063. package/dist/secrets/types.d.ts.map +1 -0
  1064. package/dist/secrets/types.js +2 -0
  1065. package/dist/secrets/types.js.map +1 -0
  1066. package/dist/services/apple-calendar/caldav-client.d.ts +48 -0
  1067. package/dist/services/apple-calendar/caldav-client.d.ts.map +1 -0
  1068. package/dist/services/apple-calendar/caldav-client.js +86 -0
  1069. package/dist/services/apple-calendar/caldav-client.js.map +1 -0
  1070. package/dist/services/apple-calendar/caldav-codec.d.ts +67 -0
  1071. package/dist/services/apple-calendar/caldav-codec.d.ts.map +1 -0
  1072. package/dist/services/apple-calendar/caldav-codec.js +341 -0
  1073. package/dist/services/apple-calendar/caldav-codec.js.map +1 -0
  1074. package/dist/services/apple-calendar/index.d.ts +3 -0
  1075. package/dist/services/apple-calendar/index.d.ts.map +1 -0
  1076. package/dist/services/apple-calendar/index.js +2 -0
  1077. package/dist/services/apple-calendar/index.js.map +1 -0
  1078. package/dist/services/apple-calendar/service.d.ts +75 -0
  1079. package/dist/services/apple-calendar/service.d.ts.map +1 -0
  1080. package/dist/services/apple-calendar/service.js +374 -0
  1081. package/dist/services/apple-calendar/service.js.map +1 -0
  1082. package/dist/services/apple-calendar/types.d.ts +78 -0
  1083. package/dist/services/apple-calendar/types.d.ts.map +1 -0
  1084. package/dist/services/apple-calendar/types.js +17 -0
  1085. package/dist/services/apple-calendar/types.js.map +1 -0
  1086. package/dist/services/attachments/hardlink.d.ts +11 -0
  1087. package/dist/services/attachments/hardlink.d.ts.map +1 -0
  1088. package/dist/services/attachments/hardlink.js +56 -0
  1089. package/dist/services/attachments/hardlink.js.map +1 -0
  1090. package/dist/services/attachments/sanitize.d.ts +21 -0
  1091. package/dist/services/attachments/sanitize.d.ts.map +1 -0
  1092. package/dist/services/attachments/sanitize.js +128 -0
  1093. package/dist/services/attachments/sanitize.js.map +1 -0
  1094. package/dist/services/attachments/store.d.ts +146 -0
  1095. package/dist/services/attachments/store.d.ts.map +1 -0
  1096. package/dist/services/attachments/store.js +477 -0
  1097. package/dist/services/attachments/store.js.map +1 -0
  1098. package/dist/services/calendar/outlook/graph-calendar-client.d.ts +114 -0
  1099. package/dist/services/calendar/outlook/graph-calendar-client.d.ts.map +1 -0
  1100. package/dist/services/calendar/outlook/graph-calendar-client.js +146 -0
  1101. package/dist/services/calendar/outlook/graph-calendar-client.js.map +1 -0
  1102. package/dist/services/calendar.d.ts +115 -0
  1103. package/dist/services/calendar.d.ts.map +1 -0
  1104. package/dist/services/calendar.js +281 -0
  1105. package/dist/services/calendar.js.map +1 -0
  1106. package/dist/services/delegated-backend-invoker.d.ts +414 -0
  1107. package/dist/services/delegated-backend-invoker.d.ts.map +1 -0
  1108. package/dist/services/delegated-backend-invoker.js +2372 -0
  1109. package/dist/services/delegated-backend-invoker.js.map +1 -0
  1110. package/dist/services/delegated-proxy-config.d.ts +93 -0
  1111. package/dist/services/delegated-proxy-config.d.ts.map +1 -0
  1112. package/dist/services/delegated-proxy-config.js +98 -0
  1113. package/dist/services/delegated-proxy-config.js.map +1 -0
  1114. package/dist/services/delegated-task-result-cache.d.ts +176 -0
  1115. package/dist/services/delegated-task-result-cache.d.ts.map +1 -0
  1116. package/dist/services/delegated-task-result-cache.js +0 -0
  1117. package/dist/services/delegated-task-result-cache.js.map +1 -0
  1118. package/dist/services/delegated-task-runtime.d.ts +346 -0
  1119. package/dist/services/delegated-task-runtime.d.ts.map +1 -0
  1120. package/dist/services/delegated-task-runtime.js +589 -0
  1121. package/dist/services/delegated-task-runtime.js.map +1 -0
  1122. package/dist/services/delegated-task-session-pool.d.ts +182 -0
  1123. package/dist/services/delegated-task-session-pool.d.ts.map +1 -0
  1124. package/dist/services/delegated-task-session-pool.js +292 -0
  1125. package/dist/services/delegated-task-session-pool.js.map +1 -0
  1126. package/dist/services/delegated-tool-runtime.d.ts +50 -0
  1127. package/dist/services/delegated-tool-runtime.d.ts.map +1 -0
  1128. package/dist/services/delegated-tool-runtime.js +120 -0
  1129. package/dist/services/delegated-tool-runtime.js.map +1 -0
  1130. package/dist/services/fts5.d.ts +40 -0
  1131. package/dist/services/fts5.d.ts.map +1 -0
  1132. package/dist/services/fts5.js +54 -0
  1133. package/dist/services/fts5.js.map +1 -0
  1134. package/dist/services/git-account-registry.d.ts +164 -0
  1135. package/dist/services/git-account-registry.d.ts.map +1 -0
  1136. package/dist/services/git-account-registry.js +297 -0
  1137. package/dist/services/git-account-registry.js.map +1 -0
  1138. package/dist/services/github.d.ts +49 -0
  1139. package/dist/services/github.d.ts.map +1 -0
  1140. package/dist/services/github.js +123 -0
  1141. package/dist/services/github.js.map +1 -0
  1142. package/dist/services/gmail-classifier.d.ts +62 -0
  1143. package/dist/services/gmail-classifier.d.ts.map +1 -0
  1144. package/dist/services/gmail-classifier.js +221 -0
  1145. package/dist/services/gmail-classifier.js.map +1 -0
  1146. package/dist/services/gmail.d.ts +192 -0
  1147. package/dist/services/gmail.d.ts.map +1 -0
  1148. package/dist/services/gmail.js +678 -0
  1149. package/dist/services/gmail.js.map +1 -0
  1150. package/dist/services/google-auth.d.ts +16 -0
  1151. package/dist/services/google-auth.d.ts.map +1 -0
  1152. package/dist/services/google-auth.js +37 -0
  1153. package/dist/services/google-auth.js.map +1 -0
  1154. package/dist/services/google-maps.d.ts +35 -0
  1155. package/dist/services/google-maps.d.ts.map +1 -0
  1156. package/dist/services/google-maps.js +82 -0
  1157. package/dist/services/google-maps.js.map +1 -0
  1158. package/dist/services/integrations/extract-write-item-id.d.ts +64 -0
  1159. package/dist/services/integrations/extract-write-item-id.d.ts.map +1 -0
  1160. package/dist/services/integrations/extract-write-item-id.js +188 -0
  1161. package/dist/services/integrations/extract-write-item-id.js.map +1 -0
  1162. package/dist/services/integrations/reconcile.d.ts +136 -0
  1163. package/dist/services/integrations/reconcile.d.ts.map +1 -0
  1164. package/dist/services/integrations/reconcile.js +218 -0
  1165. package/dist/services/integrations/reconcile.js.map +1 -0
  1166. package/dist/services/integrations/snapshot-partitions.d.ts +40 -0
  1167. package/dist/services/integrations/snapshot-partitions.d.ts.map +1 -0
  1168. package/dist/services/integrations/snapshot-partitions.js +113 -0
  1169. package/dist/services/integrations/snapshot-partitions.js.map +1 -0
  1170. package/dist/services/journal/render.d.ts +15 -0
  1171. package/dist/services/journal/render.d.ts.map +1 -0
  1172. package/dist/services/journal/render.js +17 -0
  1173. package/dist/services/journal/render.js.map +1 -0
  1174. package/dist/services/journal/writer.d.ts +26 -0
  1175. package/dist/services/journal/writer.d.ts.map +1 -0
  1176. package/dist/services/journal/writer.js +50 -0
  1177. package/dist/services/journal/writer.js.map +1 -0
  1178. package/dist/services/mail/account-registry.d.ts +208 -0
  1179. package/dist/services/mail/account-registry.d.ts.map +1 -0
  1180. package/dist/services/mail/account-registry.js +554 -0
  1181. package/dist/services/mail/account-registry.js.map +1 -0
  1182. package/dist/services/mail/gmail/auth-failure-classifier.d.ts +24 -0
  1183. package/dist/services/mail/gmail/auth-failure-classifier.d.ts.map +1 -0
  1184. package/dist/services/mail/gmail/auth-failure-classifier.js +67 -0
  1185. package/dist/services/mail/gmail/auth-failure-classifier.js.map +1 -0
  1186. package/dist/services/mail/gmail/gmail-provider.d.ts +58 -0
  1187. package/dist/services/mail/gmail/gmail-provider.d.ts.map +1 -0
  1188. package/dist/services/mail/gmail/gmail-provider.js +434 -0
  1189. package/dist/services/mail/gmail/gmail-provider.js.map +1 -0
  1190. package/dist/services/mail/gmail/legacy-row.d.ts +24 -0
  1191. package/dist/services/mail/gmail/legacy-row.d.ts.map +1 -0
  1192. package/dist/services/mail/gmail/legacy-row.js +71 -0
  1193. package/dist/services/mail/gmail/legacy-row.js.map +1 -0
  1194. package/dist/services/mail/gmail/poll-cursor.d.ts +12 -0
  1195. package/dist/services/mail/gmail/poll-cursor.d.ts.map +1 -0
  1196. package/dist/services/mail/gmail/poll-cursor.js +32 -0
  1197. package/dist/services/mail/gmail/poll-cursor.js.map +1 -0
  1198. package/dist/services/mail/html-to-plaintext.d.ts +27 -0
  1199. package/dist/services/mail/html-to-plaintext.d.ts.map +1 -0
  1200. package/dist/services/mail/html-to-plaintext.js +163 -0
  1201. package/dist/services/mail/html-to-plaintext.js.map +1 -0
  1202. package/dist/services/mail/imap/app-password.d.ts +27 -0
  1203. package/dist/services/mail/imap/app-password.d.ts.map +1 -0
  1204. package/dist/services/mail/imap/app-password.js +86 -0
  1205. package/dist/services/mail/imap/app-password.js.map +1 -0
  1206. package/dist/services/mail/imap/auth-failure-classifier.d.ts +21 -0
  1207. package/dist/services/mail/imap/auth-failure-classifier.d.ts.map +1 -0
  1208. package/dist/services/mail/imap/auth-failure-classifier.js +54 -0
  1209. package/dist/services/mail/imap/auth-failure-classifier.js.map +1 -0
  1210. package/dist/services/mail/imap/capabilities.d.ts +30 -0
  1211. package/dist/services/mail/imap/capabilities.d.ts.map +1 -0
  1212. package/dist/services/mail/imap/capabilities.js +70 -0
  1213. package/dist/services/mail/imap/capabilities.js.map +1 -0
  1214. package/dist/services/mail/imap/client.d.ts +15 -0
  1215. package/dist/services/mail/imap/client.d.ts.map +1 -0
  1216. package/dist/services/mail/imap/client.js +60 -0
  1217. package/dist/services/mail/imap/client.js.map +1 -0
  1218. package/dist/services/mail/imap/cursor.d.ts +19 -0
  1219. package/dist/services/mail/imap/cursor.d.ts.map +1 -0
  1220. package/dist/services/mail/imap/cursor.js +47 -0
  1221. package/dist/services/mail/imap/cursor.js.map +1 -0
  1222. package/dist/services/mail/imap/folder-resolver.d.ts +24 -0
  1223. package/dist/services/mail/imap/folder-resolver.d.ts.map +1 -0
  1224. package/dist/services/mail/imap/folder-resolver.js +58 -0
  1225. package/dist/services/mail/imap/folder-resolver.js.map +1 -0
  1226. package/dist/services/mail/imap/icloud-provider.d.ts +5 -0
  1227. package/dist/services/mail/imap/icloud-provider.d.ts.map +1 -0
  1228. package/dist/services/mail/imap/icloud-provider.js +5 -0
  1229. package/dist/services/mail/imap/icloud-provider.js.map +1 -0
  1230. package/dist/services/mail/imap/imap-provider-base.d.ts +173 -0
  1231. package/dist/services/mail/imap/imap-provider-base.d.ts.map +1 -0
  1232. package/dist/services/mail/imap/imap-provider-base.js +1004 -0
  1233. package/dist/services/mail/imap/imap-provider-base.js.map +1 -0
  1234. package/dist/services/mail/imap/query-translator.d.ts +13 -0
  1235. package/dist/services/mail/imap/query-translator.d.ts.map +1 -0
  1236. package/dist/services/mail/imap/query-translator.js +114 -0
  1237. package/dist/services/mail/imap/query-translator.js.map +1 -0
  1238. package/dist/services/mail/imap/reconcile-planner.d.ts +56 -0
  1239. package/dist/services/mail/imap/reconcile-planner.d.ts.map +1 -0
  1240. package/dist/services/mail/imap/reconcile-planner.js +52 -0
  1241. package/dist/services/mail/imap/reconcile-planner.js.map +1 -0
  1242. package/dist/services/mail/imap/reply-mime.d.ts +24 -0
  1243. package/dist/services/mail/imap/reply-mime.d.ts.map +1 -0
  1244. package/dist/services/mail/imap/reply-mime.js +77 -0
  1245. package/dist/services/mail/imap/reply-mime.js.map +1 -0
  1246. package/dist/services/mail/imap/yahoo-provider.d.ts +5 -0
  1247. package/dist/services/mail/imap/yahoo-provider.d.ts.map +1 -0
  1248. package/dist/services/mail/imap/yahoo-provider.js +5 -0
  1249. package/dist/services/mail/imap/yahoo-provider.js.map +1 -0
  1250. package/dist/services/mail/mail-search.d.ts +35 -0
  1251. package/dist/services/mail/mail-search.d.ts.map +1 -0
  1252. package/dist/services/mail/mail-search.js +59 -0
  1253. package/dist/services/mail/mail-search.js.map +1 -0
  1254. package/dist/services/mail/outlook/auth-failure-classifier.d.ts +38 -0
  1255. package/dist/services/mail/outlook/auth-failure-classifier.d.ts.map +1 -0
  1256. package/dist/services/mail/outlook/auth-failure-classifier.js +91 -0
  1257. package/dist/services/mail/outlook/auth-failure-classifier.js.map +1 -0
  1258. package/dist/services/mail/outlook/client-config.d.ts +34 -0
  1259. package/dist/services/mail/outlook/client-config.d.ts.map +1 -0
  1260. package/dist/services/mail/outlook/client-config.js +58 -0
  1261. package/dist/services/mail/outlook/client-config.js.map +1 -0
  1262. package/dist/services/mail/outlook/delta-cursor.d.ts +66 -0
  1263. package/dist/services/mail/outlook/delta-cursor.d.ts.map +1 -0
  1264. package/dist/services/mail/outlook/delta-cursor.js +85 -0
  1265. package/dist/services/mail/outlook/delta-cursor.js.map +1 -0
  1266. package/dist/services/mail/outlook/graph-client.d.ts +98 -0
  1267. package/dist/services/mail/outlook/graph-client.d.ts.map +1 -0
  1268. package/dist/services/mail/outlook/graph-client.js +198 -0
  1269. package/dist/services/mail/outlook/graph-client.js.map +1 -0
  1270. package/dist/services/mail/outlook/msal-app-factory.d.ts +20 -0
  1271. package/dist/services/mail/outlook/msal-app-factory.d.ts.map +1 -0
  1272. package/dist/services/mail/outlook/msal-app-factory.js +62 -0
  1273. package/dist/services/mail/outlook/msal-app-factory.js.map +1 -0
  1274. package/dist/services/mail/outlook/msal-cache-plugin.d.ts +19 -0
  1275. package/dist/services/mail/outlook/msal-cache-plugin.d.ts.map +1 -0
  1276. package/dist/services/mail/outlook/msal-cache-plugin.js +30 -0
  1277. package/dist/services/mail/outlook/msal-cache-plugin.js.map +1 -0
  1278. package/dist/services/mail/outlook/oauth-device-code.d.ts +26 -0
  1279. package/dist/services/mail/outlook/oauth-device-code.d.ts.map +1 -0
  1280. package/dist/services/mail/outlook/oauth-device-code.js +32 -0
  1281. package/dist/services/mail/outlook/oauth-device-code.js.map +1 -0
  1282. package/dist/services/mail/outlook/oauth-loopback.d.ts +41 -0
  1283. package/dist/services/mail/outlook/oauth-loopback.d.ts.map +1 -0
  1284. package/dist/services/mail/outlook/oauth-loopback.js +223 -0
  1285. package/dist/services/mail/outlook/oauth-loopback.js.map +1 -0
  1286. package/dist/services/mail/outlook/outlook-provider.d.ts +100 -0
  1287. package/dist/services/mail/outlook/outlook-provider.d.ts.map +1 -0
  1288. package/dist/services/mail/outlook/outlook-provider.js +619 -0
  1289. package/dist/services/mail/outlook/outlook-provider.js.map +1 -0
  1290. package/dist/services/mail/outlook/query-translator.d.ts +10 -0
  1291. package/dist/services/mail/outlook/query-translator.d.ts.map +1 -0
  1292. package/dist/services/mail/outlook/query-translator.js +103 -0
  1293. package/dist/services/mail/outlook/query-translator.js.map +1 -0
  1294. package/dist/services/mail/provider.d.ts +267 -0
  1295. package/dist/services/mail/provider.d.ts.map +1 -0
  1296. package/dist/services/mail/provider.js +34 -0
  1297. package/dist/services/mail/provider.js.map +1 -0
  1298. package/dist/services/mail/query-utils.d.ts +13 -0
  1299. package/dist/services/mail/query-utils.d.ts.map +1 -0
  1300. package/dist/services/mail/query-utils.js +18 -0
  1301. package/dist/services/mail/query-utils.js.map +1 -0
  1302. package/dist/services/mail-classifier.d.ts +25 -0
  1303. package/dist/services/mail-classifier.d.ts.map +1 -0
  1304. package/dist/services/mail-classifier.js +52 -0
  1305. package/dist/services/mail-classifier.js.map +1 -0
  1306. package/dist/services/mail-ingestion.d.ts +139 -0
  1307. package/dist/services/mail-ingestion.d.ts.map +1 -0
  1308. package/dist/services/mail-ingestion.js +223 -0
  1309. package/dist/services/mail-ingestion.js.map +1 -0
  1310. package/dist/services/mcp/auto-probe.d.ts +76 -0
  1311. package/dist/services/mcp/auto-probe.d.ts.map +1 -0
  1312. package/dist/services/mcp/auto-probe.js +147 -0
  1313. package/dist/services/mcp/auto-probe.js.map +1 -0
  1314. package/dist/services/mcp/generators/claude.d.ts +18 -0
  1315. package/dist/services/mcp/generators/claude.d.ts.map +1 -0
  1316. package/dist/services/mcp/generators/claude.js +90 -0
  1317. package/dist/services/mcp/generators/claude.js.map +1 -0
  1318. package/dist/services/mcp/generators/codex.d.ts +22 -0
  1319. package/dist/services/mcp/generators/codex.d.ts.map +1 -0
  1320. package/dist/services/mcp/generators/codex.js +102 -0
  1321. package/dist/services/mcp/generators/codex.js.map +1 -0
  1322. package/dist/services/mcp/generators/gemini.d.ts +20 -0
  1323. package/dist/services/mcp/generators/gemini.d.ts.map +1 -0
  1324. package/dist/services/mcp/generators/gemini.js +97 -0
  1325. package/dist/services/mcp/generators/gemini.js.map +1 -0
  1326. package/dist/services/mcp/generators/index.d.ts +20 -0
  1327. package/dist/services/mcp/generators/index.d.ts.map +1 -0
  1328. package/dist/services/mcp/generators/index.js +29 -0
  1329. package/dist/services/mcp/generators/index.js.map +1 -0
  1330. package/dist/services/mcp/generators/types.d.ts +47 -0
  1331. package/dist/services/mcp/generators/types.d.ts.map +1 -0
  1332. package/dist/services/mcp/generators/types.js +40 -0
  1333. package/dist/services/mcp/generators/types.js.map +1 -0
  1334. package/dist/services/mcp/probe.d.ts +31 -0
  1335. package/dist/services/mcp/probe.d.ts.map +1 -0
  1336. package/dist/services/mcp/probe.js +437 -0
  1337. package/dist/services/mcp/probe.js.map +1 -0
  1338. package/dist/services/mcp/registry.d.ts +84 -0
  1339. package/dist/services/mcp/registry.d.ts.map +1 -0
  1340. package/dist/services/mcp/registry.js +387 -0
  1341. package/dist/services/mcp/registry.js.map +1 -0
  1342. package/dist/services/mcp/risk.d.ts +82 -0
  1343. package/dist/services/mcp/risk.d.ts.map +1 -0
  1344. package/dist/services/mcp/risk.js +126 -0
  1345. package/dist/services/mcp/risk.js.map +1 -0
  1346. package/dist/services/mcp/session-materializer.d.ts +123 -0
  1347. package/dist/services/mcp/session-materializer.d.ts.map +1 -0
  1348. package/dist/services/mcp/session-materializer.js +361 -0
  1349. package/dist/services/mcp/session-materializer.js.map +1 -0
  1350. package/dist/services/mcp/tool-audit.d.ts +53 -0
  1351. package/dist/services/mcp/tool-audit.d.ts.map +1 -0
  1352. package/dist/services/mcp/tool-audit.js +74 -0
  1353. package/dist/services/mcp/tool-audit.js.map +1 -0
  1354. package/dist/services/mcp/types.d.ts +88 -0
  1355. package/dist/services/mcp/types.d.ts.map +1 -0
  1356. package/dist/services/mcp/types.js +94 -0
  1357. package/dist/services/mcp/types.js.map +1 -0
  1358. package/dist/services/notion.d.ts +134 -0
  1359. package/dist/services/notion.d.ts.map +1 -0
  1360. package/dist/services/notion.js +350 -0
  1361. package/dist/services/notion.js.map +1 -0
  1362. package/dist/services/obsidian.d.ts +116 -0
  1363. package/dist/services/obsidian.d.ts.map +1 -0
  1364. package/dist/services/obsidian.js +305 -0
  1365. package/dist/services/obsidian.js.map +1 -0
  1366. package/dist/services/service-registry.d.ts +31 -0
  1367. package/dist/services/service-registry.d.ts.map +1 -0
  1368. package/dist/services/service-registry.js +15 -0
  1369. package/dist/services/service-registry.js.map +1 -0
  1370. package/dist/services/voice/transcriber-impl.d.ts +15 -0
  1371. package/dist/services/voice/transcriber-impl.d.ts.map +1 -0
  1372. package/dist/services/voice/transcriber-impl.js +129 -0
  1373. package/dist/services/voice/transcriber-impl.js.map +1 -0
  1374. package/dist/services/voice/transcriber.d.ts +117 -0
  1375. package/dist/services/voice/transcriber.d.ts.map +1 -0
  1376. package/dist/services/voice/transcriber.js +201 -0
  1377. package/dist/services/voice/transcriber.js.map +1 -0
  1378. package/dist/settings/runtime-settings.d.ts +232 -0
  1379. package/dist/settings/runtime-settings.d.ts.map +1 -0
  1380. package/dist/settings/runtime-settings.js +769 -0
  1381. package/dist/settings/runtime-settings.js.map +1 -0
  1382. package/dist/settings/settings-store.d.ts +13 -0
  1383. package/dist/settings/settings-store.d.ts.map +1 -0
  1384. package/dist/settings/settings-store.js +87 -0
  1385. package/dist/settings/settings-store.js.map +1 -0
  1386. package/package.json +85 -0
package/dist/index.js ADDED
@@ -0,0 +1,2913 @@
1
+ import { serve } from "@hono/node-server";
2
+ import { existsSync, readFileSync } from "node:fs";
3
+ import { join, resolve } from "node:path";
4
+ import { randomBytes } from "node:crypto";
5
+ import { loadConfig, getContextDir, isRoadmapStale, mergeRuntimeSettingsFromDb, runVaultHealthProbe, validateExternalObsidianVaultPath, } from "./config.js";
6
+ import { getDegradedMode, isSetupCompleted, isUserPaused, isDegraded as readDegradedMode, } from "./db/runtime-state.js";
7
+ import { createDefaultBangCommandRegistry } from "./core/bang-commands/index.js";
8
+ import { initDirectories } from "./init.js";
9
+ import { createDatabase } from "./db/client.js";
10
+ import { applySchema } from "./db/schema.js";
11
+ import { readIntegrations } from "./db/integrations-store.js";
12
+ import { getRepositoryByGithub, getRepositoryByLocalPath, listRepositories, selectGithubRepoSlugs, selectGitWatchedRepos, } from "./db/repositories-store.js";
13
+ import { dispatchMatchingTriggers } from "./core/trigger-dispatch.js";
14
+ import { EventBus } from "./core/event-bus.js";
15
+ import { AgentScheduler } from "./core/scheduler.js";
16
+ import { CustomRoutineScheduler } from "./core/custom-routine-scheduler.js";
17
+ import { HealthMonitor } from "./core/health-monitor.js";
18
+ import { Heartbeat } from "./core/heartbeat.js";
19
+ import { MessageHub } from "./adapters/message-hub.js";
20
+ import { ObserverManager } from "./observers/manager.js";
21
+ import { ObsidianWatcher } from "./observers/obsidian-watcher.js";
22
+ import { PrimaryVaultWatcher } from "./observers/primary-vault-watcher.js";
23
+ import { ContextIndexReconcilerObserver } from "./observers/context-index-reconciler-observer.js";
24
+ import { EntityMirrorObserver } from "./observers/entity-mirror-observer.js";
25
+ import { GitWatcher } from "./observers/git-watcher.js";
26
+ import { GitHubPoller } from "./observers/github-poller.js";
27
+ import { GitAccountRegistry } from "./services/git-account-registry.js";
28
+ import { GitDelegatedCronObserver, hasActiveDelegatedGitLifecycleIntegration, } from "./observers/git-delegated-cron.js";
29
+ import { RepositoryManagementCron } from "./observers/repository-management-cron.js";
30
+ import { ObservationSummarizerWorker, AnthropicSummarizerClient, UnsupportedSummarizerClient, } from "./observers/observation-summarizer/index.js";
31
+ import { CalendarPoller } from "./observers/calendar-poller.js";
32
+ import { ImminentEventScheduler } from "./observers/imminent-event-scheduler.js";
33
+ import { DelegatedSyncWorker, hasActiveDelegatedSyncIntegration, } from "./observers/delegated-sync-worker.js";
34
+ import { applyIntegrationModeChange, shouldStartObserversFor, } from "./core/integration-lifecycle.js";
35
+ import { NotionPoller } from "./observers/notion-poller.js";
36
+ import { DiscordAdapter } from "./adapters/discord.js";
37
+ import { SlackAdapter } from "./adapters/slack-adapter.js";
38
+ import { TelegramAdapter } from "./adapters/telegram-adapter.js";
39
+ import { DashboardAdapter } from "./adapters/dashboard-adapter.js";
40
+ import { WhatsAppAdapter } from "./adapters/whatsapp-adapter.js";
41
+ import { CalendarService } from "./services/calendar.js";
42
+ import { AppleCalendarService } from "./services/apple-calendar/index.js";
43
+ import { GmailService } from "./services/gmail.js";
44
+ import { detectGoogleCredentialType } from "./services/google-auth.js";
45
+ import { ObsidianService } from "./services/obsidian.js";
46
+ import { NotionService } from "./services/notion.js";
47
+ import { GitHubService } from "./services/github.js";
48
+ import { createServiceRegistry } from "./services/service-registry.js";
49
+ import { SignalDetector } from "./core/signal-detector.js";
50
+ import { EventDispatcher } from "./core/dispatcher.js";
51
+ import { ClaudeCodeCore } from "./core/backends/claude-code-core.js";
52
+ import { BackendRouter } from "./core/backends/backend-router.js";
53
+ import { ensureBackendMaterialized, syncAllUserSkills, buildConfiguredServices, refreshDmSessionWorkdirs, validateDelegatedStartup, } from "./core/workdir.js";
54
+ import { CodexCore } from "./core/backends/codex-core.js";
55
+ import { GeminiCliCore } from "./core/backends/gemini-cli-core.js";
56
+ import { PriceFetcher } from "./core/backends/price-fetcher.js";
57
+ import { AuthTelemetry } from "./core/backends/auth-telemetry.js";
58
+ import { AuthHealthMonitor, AUTH_PROBE_NOTIFICATION_CATEGORY } from "./core/backends/auth-health-monitor.js";
59
+ import { AuthRecovery } from "./core/backends/auth-recovery.js";
60
+ import { ContextBuilder } from "./core/context-builder.js";
61
+ import { getTaskFlow, initTaskFlows } from "./core/prompts.js";
62
+ import { ensureSkeletonFiles, resolveTemplatesRoot } from "./core/skeleton.js";
63
+ import { normalizeGitWatchedRepos, queueGitProjectUpdate, queueMissingGitProjectInits, seedGitProjectDocTemplates, } from "./core/git-project-docs.js";
64
+ import { reconcileTemplateAssets, recordInstructionAssetStatus, recordSkillAssetStatus, } from "./core/release-assets.js";
65
+ import { SessionManager } from "./core/session-manager.js";
66
+ import { MessageRecorder } from "./core/message-recorder.js";
67
+ import { ScopedReadSensitiveTokenManager } from "./core/read-sensitive-token-manager.js";
68
+ import { NotificationManager } from "./adapters/notification-manager.js";
69
+ import { recordProactiveForwardDeliveries } from "./core/channel-timeline.js";
70
+ import { continueDashboardSession as continueDashboardSessionFromHistory, endDashboardSession as endDashboardSessionFromChannel, markContextChanged, } from "./core/dashboard-session-controls.js";
71
+ import { AuditLogger } from "./safety/audit.js";
72
+ import { bootstrapManagementMd, migrateLegacyManagementMd, startManagementMdWatcher, } from "./core/management-md.js";
73
+ import { bootstrapManagementRegistry, startManagementRegistryWatcher, } from "./core/management-registry.js";
74
+ import { bootstrapManagedTaskSeq } from "./db/managed-tasks-store.js";
75
+ import { startDocsIndexer, } from "./core/docs/indexer.js";
76
+ import { makeDbLookup as makeDocsCitationLookup } from "./core/docs/citation-validator.js";
77
+ import { createDocsRoutes } from "./api/routes/docs.js";
78
+ import { DocsQAAdapter } from "./adapters/docs-qa-adapter.js";
79
+ import { CompositeDashboardStream } from "./adapters/composite-dashboard-stream.js";
80
+ import { createApp } from "./api/server.js";
81
+ import { EventBroadcaster } from "./api/routes/sse.js";
82
+ import { APP_NAME, createEvent, EventPriority, formatSqliteDatetime, getAgentDayBoundsUtc, getAgentDayDateStr, getAgentDayProgressMinutes, getBackendIds, nowInTimezone, parseSqliteUtcMs, } from "@aitne/shared";
83
+ import { getOwnerChannel } from "./messaging/owner-channels.js";
84
+ import { SUPPORTED_MESSAGING_PLATFORMS, } from "./messaging/constants.js";
85
+ import { AgentWriteTracker } from "./safety/agent-write-tracker.js";
86
+ import { discardStalePendingSchedules, hasActionInWindow, recoverOrphanedRunningSchedules, } from "./core/schedule-maintenance.js";
87
+ import { ContextWriteGate, InMemoryTodayWriteLockManager, MigrationLock, getTodayWriteLockTimeoutMs, } from "./core/today-write-lock.js";
88
+ import { applyPromptContextStaleness, } from "./core/context-staleness.js";
89
+ import { InMemoryRoadmapWriteLockManager, getRoadmapWriteLockTimeoutMs, } from "./core/roadmap-write-lock.js";
90
+ import { sweepExpiredMigrationBackups } from "./api/routes/setup-migrate.js";
91
+ import { createSettingsStore } from "./settings/settings-store.js";
92
+ import { PlatformSecretStore } from "./secrets/platform-secret-store.js";
93
+ import { FileEncryptedBlobStore } from "./secrets/encrypted-blob-store.js";
94
+ import { AttachmentStore } from "./services/attachments/store.js";
95
+ import { VoiceTranscriber } from "./services/voice/transcriber.js";
96
+ import { DelegatedBackendInvoker, runDelegatedTaskOrphanJanitor, runProxyTempdirJanitor, } from "./services/delegated-backend-invoker.js";
97
+ import { runSessionPoolTempdirJanitor } from "./services/delegated-task-session-pool.js";
98
+ import { MailAccountRegistry } from "./services/mail/account-registry.js";
99
+ import { loadOutlookClientConfig, OutlookClientConfigMissingError, } from "./services/mail/outlook/client-config.js";
100
+ import { createRuntimeMsalApp } from "./services/mail/outlook/msal-app-factory.js";
101
+ import { OutlookGraphProvider } from "./services/mail/outlook/outlook-provider.js";
102
+ import { parseImapAccountSecret } from "./services/mail/imap/app-password.js";
103
+ import { ICloudImapProvider } from "./services/mail/imap/icloud-provider.js";
104
+ import { YahooImapProvider } from "./services/mail/imap/yahoo-provider.js";
105
+ import { GmailProvider } from "./services/mail/gmail/gmail-provider.js";
106
+ import { ensureLegacyGmailRow } from "./services/mail/gmail/legacy-row.js";
107
+ import { syncLegacyGmailAccountState } from "./services/mail/gmail/legacy-row.js";
108
+ import { MailPoller } from "./observers/mail-poller.js";
109
+ import { MailReconciliationJob } from "./observers/mail-reconciliation.js";
110
+ import { SecretBroker } from "./secrets/secret-broker.js";
111
+ import { captureOriginalShellEnv, syncBackendApiKeyToEnv, } from "./secrets/backend-api-key-env.js";
112
+ import { createLogger, toSafeErrorMessage } from "./logging.js";
113
+ const logger = createLogger("daemon", {
114
+ transport: {
115
+ target: "pino-pretty",
116
+ options: { colorize: !process.env.PA_DAEMONIZED },
117
+ },
118
+ });
119
+ const startedAt = new Date();
120
+ async function startup() {
121
+ logger.info({ logLevel: logger.level }, `${APP_NAME} Daemon starting...`);
122
+ // Snapshot the original shell-set values for ANTHROPIC_API_KEY /
123
+ // OPENAI_API_KEY / GEMINI_API_KEY+GOOGLE_API_KEY *before* any keychain
124
+ // mirroring runs. The capture is the source of truth for "fall back to
125
+ // shell env" when the operator clears the keychain entry via the
126
+ // dashboard. See `secrets/backend-api-key-env.ts` for precedence.
127
+ captureOriginalShellEnv();
128
+ // ── 1. Configuration ──
129
+ const config = loadConfig();
130
+ const secretBroker = new SecretBroker(new PlatformSecretStore());
131
+ const releaseAssetBackupRoot = join(config.dataDir, "backups", "release-assets");
132
+ // Tighten .env permissions BEFORE the first read/write so bootstrap
133
+ // values aren't world-readable between env-writer's later chmod calls.
134
+ // Best-effort: log and continue if chmod fails (some filesystems and
135
+ // CI sandboxes don't honor mode bits).
136
+ const { getEnvFilePath: getEnvFilePathEarly, ensureEnvFilePermissions: ensureEnvFilePermissionsEarly, } = await import("./api/env-writer.js");
137
+ try {
138
+ ensureEnvFilePermissionsEarly(getEnvFilePathEarly());
139
+ }
140
+ catch (err) {
141
+ logger.warn({ err }, "Could not chmod .env to 0o600 — secrets may be world-readable");
142
+ }
143
+ logger.info({ dataDir: config.dataDir, apiPort: config.apiPort }, "Config loaded");
144
+ // ── 2. Directory structure ──
145
+ initDirectories(config);
146
+ // ── 3. Database ──
147
+ const db = createDatabase(config);
148
+ applySchema(db);
149
+ // §12 ("managed_tasks.id collision after restore from backup") — ensure
150
+ // `managed_task_seq.next_id` is greater than the max existing mt id so a
151
+ // backup-restored DB cannot collide with the seq counter on the next
152
+ // POST. No-op when the table is empty (steady-state cost: one SELECT).
153
+ bootstrapManagedTaskSeq(db);
154
+ // Close any dashboard_chat sessions that were left `active` from before
155
+ // the daemon restart. They cannot be resumed cleanly: the SSE channel is
156
+ // gone, and `Dispatcher.currentSetupMode` is in-process state lost on
157
+ // restart, so a setup conversation half-way through would otherwise be
158
+ // re-adopted by `findActiveDashboardSessionId` and processed as regular
159
+ // chat against stale prompts. The setup wizard's resume path on the
160
+ // client-side checks active status; closing here flips the check so the
161
+ // wizard correctly falls through to a fresh /setup/start.
162
+ try {
163
+ const result = db
164
+ .prepare(`UPDATE conversation_sessions
165
+ SET status = 'closed'
166
+ WHERE scope = 'dashboard_chat' AND status = 'active'`)
167
+ .run();
168
+ if (result.changes > 0) {
169
+ logger.info({ closed: result.changes }, "Closed orphaned dashboard_chat sessions from previous run");
170
+ }
171
+ }
172
+ catch (err) {
173
+ logger.warn({ err }, "Failed to close orphaned dashboard_chat sessions");
174
+ }
175
+ // Chat attachment store — constructed early so adapter reload functions
176
+ // can reference it in closure without TypeScript "used before declaration" issues.
177
+ const attachmentStore = new AttachmentStore(db, config.dataDir);
178
+ attachmentStore.reapOrphans(24);
179
+ void new PriceFetcher(config.dataDir, db).refresh();
180
+ const settingsStore = createSettingsStore(db);
181
+ const persistedSettings = settingsStore.getAll();
182
+ mergeRuntimeSettingsFromDb(config, persistedSettings);
183
+ try {
184
+ seedGitProjectDocTemplates(config.dataDir, config.workspaceDir);
185
+ }
186
+ catch (err) {
187
+ logger.warn({ err }, "Failed to seed git project document templates");
188
+ }
189
+ // ── Default-correction for `delegatedTaskModeEnabled` ──
190
+ // The legacy `/integrations/:key/invoke` RPC was retired 2026-05-01
191
+ // and every delegated skill / task flow now goes through `/exec`,
192
+ // which is gated by this flag. Its Phase-1 canary default of `false`
193
+ // is now a footgun: any user with delegated integrations from before
194
+ // the runtime PATCH-time auto-enable landed will hit 503 on every
195
+ // /exec call.
196
+ //
197
+ // Heal once at boot, only when the operator never explicitly chose:
198
+ // - `delegatedTaskModeEnabled` row is *absent* from settings (the
199
+ // `in` check distinguishes "never set" from "explicitly false"),
200
+ // - and at least one integration is currently in `delegated` mode.
201
+ //
202
+ // An explicit `false` row in `settings` is treated as operator intent
203
+ // and respected — emergency-disable still works.
204
+ if (!("delegatedTaskModeEnabled" in persistedSettings)
205
+ && !config.delegatedTaskModeEnabled) {
206
+ const integrationsAtBoot = readIntegrations(db);
207
+ const delegatedKeys = Object.entries(integrationsAtBoot)
208
+ .filter(([, state]) => state.mode === "delegated")
209
+ .map(([key]) => key);
210
+ if (delegatedKeys.length > 0) {
211
+ try {
212
+ settingsStore.set("delegatedTaskModeEnabled", true);
213
+ config.delegatedTaskModeEnabled = true;
214
+ logger.info({ delegatedKeys }, "auto-enabled delegatedTaskModeEnabled at startup — delegated integrations exist and the flag was never explicitly set");
215
+ }
216
+ catch (err) {
217
+ logger.error({ err, delegatedKeys }, "startup auto-enable of delegatedTaskModeEnabled failed; /exec calls may continue to 503 until PATCH /api/config sets the flag");
218
+ }
219
+ }
220
+ }
221
+ // ── Integration Delegation Framework (Phase 1) ──
222
+ // Reconcile `<dataDir>/integrations.md` with the DB integrations map.
223
+ // Creates the file on first run, parses hand-edits if present, and
224
+ // re-renders unconditionally so daemon-owned columns are canonical.
225
+ // The watcher is started later (after the observer manager) so fs-watch
226
+ // errors don't block boot.
227
+ let managementMdWatcher = null;
228
+ let managementRegistryWatcher = null;
229
+ try {
230
+ migrateLegacyManagementMd(config.dataDir);
231
+ await bootstrapManagementMd(config.dataDir, db, config.workspaceDir, {
232
+ externalObsidianVaultPath: config.externalObsidianVaultPath,
233
+ externalObsidianWatch: config.externalObsidianWatch,
234
+ });
235
+ }
236
+ catch (err) {
237
+ logger.error({ err, dataDir: config.dataDir }, "integrations.md bootstrap failed; continuing with DB state");
238
+ }
239
+ // ── Docs corpus indexer (DOCS_QA_DESIGN.md P1) ──
240
+ // Seeds `docs/user/` from `agent-assets/docs/` on first launch, then
241
+ // runs a boot-scan and starts a chokidar watcher with the same 300ms
242
+ // debounce window the management-md watcher uses. A startup failure
243
+ // is non-fatal — the daemon still serves but `/api/docs/health`
244
+ // surfaces `status: "degraded"`.
245
+ let docsIndexer = null;
246
+ try {
247
+ docsIndexer = await startDocsIndexer(db, {
248
+ workspaceDir: config.workspaceDir,
249
+ backupRoot: releaseAssetBackupRoot,
250
+ });
251
+ }
252
+ catch (err) {
253
+ logger.error({ err }, "docs indexer failed to start");
254
+ }
255
+ try {
256
+ recordInstructionAssetStatus(db, config.workspaceDir);
257
+ const skillStatus = recordSkillAssetStatus(db, config.dataDir, config.workspaceDir);
258
+ if (skillStatus.builtinShadowedUserSkills.length > 0) {
259
+ logger.warn({ slugs: skillStatus.builtinShadowedUserSkills }, "User skills are shadowed by newly-shipped built-in skills");
260
+ }
261
+ }
262
+ catch (err) {
263
+ logger.warn({ err }, "release asset status scan failed");
264
+ }
265
+ // ── Management Mode startup validation (plan §5.4) ──
266
+ // Delegated to `runVaultHealthProbe` so startup and the 30-second timer
267
+ // use identical logic. The probe internally handles:
268
+ // - plain mode → clear any stale degraded state
269
+ // - obsidian mode + setup incomplete → clear (bootstrapping bypass so
270
+ // the DM-driven setup flow is not blocked by 503)
271
+ // - obsidian mode + primaryVaultPath null → degraded
272
+ // - obsidian mode + path unreachable → degraded
273
+ // - obsidian mode + path reachable → lift
274
+ const startupProbe = runVaultHealthProbe(config, db);
275
+ if (startupProbe.action === "entered") {
276
+ logger.warn({ reason: startupProbe.reason }, "Management Mode degraded at startup");
277
+ }
278
+ else if (startupProbe.action === "lifted") {
279
+ logger.info("Management Mode degraded state cleared at startup");
280
+ }
281
+ if (process.env.PA_VAULT_STRICT === "1" && readDegradedMode(db)) {
282
+ const state = getDegradedMode(db);
283
+ logger.fatal({ degradedState: state }, "PA_VAULT_STRICT=1 and degraded mode active — exiting");
284
+ process.exit(1);
285
+ }
286
+ if (isSetupCompleted(db) && !readDegradedMode(db)) {
287
+ const contextDir = getContextDir(config);
288
+ ensureSkeletonFiles(contextDir, config.workspaceDir);
289
+ // ── Management Registry boot reconcile (design 21 §7.2 / P2) ──
290
+ // Mirrors `bootstrapManagementMd` (which owns integrations.md). Reads
291
+ // `<contextDir>/rules/management.md` and reconciles its A-section with
292
+ // `settings.sot_bindings`; renders a fresh v3 file when the on-disk
293
+ // copy is v2 (no schema_version) or forward-incompat (>= v4). Run
294
+ // after `ensureSkeletonFiles` so the seeded template is the parse
295
+ // target on first install.
296
+ try {
297
+ await bootstrapManagementRegistry(contextDir, db);
298
+ }
299
+ catch (err) {
300
+ logger.error({ err, contextDir }, "rules/management.md bootstrap failed; continuing with DB state");
301
+ }
302
+ // Release asset reconcile (future-proofing for format changes).
303
+ // Missing templates are added, unedited versioned templates are
304
+ // refreshed from the shipped tree, and edited files are preserved and
305
+ // reported in `runtime_state.templates.pending` for review.
306
+ try {
307
+ const templatesRoot = resolveTemplatesRoot(config.workspaceDir);
308
+ const templateStatus = reconcileTemplateAssets({
309
+ db,
310
+ templatesRoot,
311
+ contextDir,
312
+ backupRoot: releaseAssetBackupRoot,
313
+ });
314
+ if (templateStatus.pending.length > 0) {
315
+ logger.warn({ pending: templateStatus.pending, count: templateStatus.pending.length }, "template upgrades pending — user-edited files were preserved (inspect via /api/health.templatesPending)");
316
+ }
317
+ if (templateStatus.autoUpdated > 0 || templateStatus.added > 0) {
318
+ logger.info({ added: templateStatus.added, autoUpdated: templateStatus.autoUpdated }, "release template assets reconciled");
319
+ }
320
+ }
321
+ catch (err) {
322
+ logger.warn({ err }, "template asset reconcile failed — continuing startup; pending snapshot unchanged");
323
+ }
324
+ }
325
+ if (config.externalObsidianVaultPath) {
326
+ const externalCheck = validateExternalObsidianVaultPath(config.externalObsidianVaultPath, config);
327
+ if (!externalCheck.ok) {
328
+ logger.warn({
329
+ path: config.externalObsidianVaultPath,
330
+ error: externalCheck.error,
331
+ message: externalCheck.message,
332
+ }, "externalObsidianVaultPath invalid — the Obsidian CLI skill will be degraded");
333
+ }
334
+ }
335
+ // Auto-generate API token if it does not exist in the secret store yet.
336
+ if (!(await secretBroker.getApiToken())) {
337
+ await secretBroker.set("apiToken", randomBytes(32).toString("hex"));
338
+ logger.info("Generated PA_API_TOKEN in secret store");
339
+ }
340
+ // Mirror keychain-stored backend API keys into process.env so the
341
+ // existing Claude SDK / Codex CLI / Gemini CLI subprocesses pick them
342
+ // up via the unchanged `process.env.ANTHROPIC_API_KEY` etc. read paths.
343
+ // Keychain values take precedence over shell env (the captured
344
+ // snapshot above acts as the fallback when the operator clears the
345
+ // dashboard entry).
346
+ for (const backendId of getBackendIds()) {
347
+ try {
348
+ const result = await syncBackendApiKeyToEnv(secretBroker, backendId, process.env, { dataDir: config.dataDir });
349
+ if (result.source === "keychain") {
350
+ logger.info({ backendId, source: result.source }, "Mirrored backend API key from keychain into process.env");
351
+ }
352
+ }
353
+ catch (err) {
354
+ logger.warn({ err, backendId }, "Failed to read backend API key from keychain — backend will fall back to existing env / CLI auth");
355
+ }
356
+ }
357
+ logger.info("Database initialized");
358
+ // ── 4. Core components ──
359
+ const eventBus = new EventBus();
360
+ const writeTracker = new AgentWriteTracker();
361
+ // Morning-routine and roadmap write locks are constructed here (earlier
362
+ // than their historical home at §11) so the reconciler observer can
363
+ // consult the morning-routine lock when its cron trigger fires. Both
364
+ // locks are passed into the dispatcher later without re-construction.
365
+ const morningRoutineLock = new InMemoryTodayWriteLockManager(getTodayWriteLockTimeoutMs(config.executeTimeoutMinutes));
366
+ const roadmapWriteLock = new InMemoryRoadmapWriteLockManager(getRoadmapWriteLockTimeoutMs(config.executeTimeoutMinutes));
367
+ // ── 5. Messaging adapters ──
368
+ const messageHub = new MessageHub(config, db);
369
+ /**
370
+ * Persist a freshly-detected owner ID into .env + in-memory config and
371
+ * send a welcome DM. Called by Slack/Telegram/Discord adapters when
372
+ * discovery (or a Telegram pair token) captures the owner from the first
373
+ * inbound DM.
374
+ *
375
+ * Why this lives in index.ts (not the adapter): the adapter must not
376
+ * import env-writer directly — that would couple the adapter layer to the
377
+ * config-persistence layer. The adapter just calls back; index.ts owns
378
+ * the wiring.
379
+ */
380
+ async function recordDetectedOwner(platform, ownerId) {
381
+ const fieldByPlatform = {
382
+ slack: "slackOwnerUserId",
383
+ telegram: "telegramOwnerChatId",
384
+ discord: "discordOwnerUserId",
385
+ };
386
+ const { applyConfigUpdates } = await import("./api/env-writer.js");
387
+ // No `db` option needed — owner-id pairing never touches Note Sources
388
+ // keys, so the §6.2 side-effect inside applyConfigUpdates is a no-op.
389
+ const result = await applyConfigUpdates(config, settingsStore, {
390
+ [fieldByPlatform[platform]]: ownerId,
391
+ });
392
+ if (Object.keys(result.errors).length > 0) {
393
+ logger.error({ platform, ownerId, errors: result.errors }, "Failed to persist detected owner ID to .env");
394
+ return;
395
+ }
396
+ logger.info({ platform, ownerId }, "auto-paired owner from discovery");
397
+ // Greet the user so they know pairing landed. Route this through the
398
+ // adapter's normal owner-channel resolution path ("user") rather than
399
+ // assuming the captured owner identifier is itself a channel id.
400
+ //
401
+ // Why: Telegram captures a chat id, but Slack/Discord capture a user id
402
+ // that must be resolved to a DM channel before sending.
403
+ try {
404
+ await messageHub.sendToPlatform(platform, "user", `Pairing successful — this channel is now linked as your owner DM.`);
405
+ }
406
+ catch (err) {
407
+ logger.warn({ err, platform }, "Failed to deliver welcome DM after auto-pairing");
408
+ }
409
+ }
410
+ let discordAdapter = null;
411
+ let slackAdapter = null;
412
+ let telegramAdapter = null;
413
+ let whatsappAdapter = null;
414
+ async function reloadDiscordAdapter(startNow) {
415
+ const botToken = await secretBroker.getDiscordBotToken();
416
+ const configured = !!botToken;
417
+ messageHub.setPlatformConfigured("discord", configured);
418
+ if (discordAdapter) {
419
+ try {
420
+ await discordAdapter.stop();
421
+ }
422
+ catch (err) {
423
+ logger.warn({ err }, "Failed to stop Discord adapter during reload");
424
+ }
425
+ messageHub.unregister("discord");
426
+ discordAdapter = null;
427
+ }
428
+ if (!botToken) {
429
+ return;
430
+ }
431
+ discordAdapter = new DiscordAdapter({
432
+ botToken,
433
+ ownerUserId: config.discordOwnerUserId,
434
+ onMessage: (event) => void eventBus.put(event),
435
+ onOwnerDetected: (userId) => recordDetectedOwner("discord", userId),
436
+ attachmentStore,
437
+ });
438
+ messageHub.register(discordAdapter);
439
+ if (startNow) {
440
+ // Mark as "connecting" while the websocket handshake is in flight so
441
+ // /health doesn't briefly report "ok" before the adapter actually
442
+ // comes up (register() defaults to "ok" to keep the notification
443
+ // pipeline usable in unit tests; this override is the real state).
444
+ messageHub.setPlatformRuntimeStatus("discord", { runtimeState: "connecting", error: null });
445
+ try {
446
+ await discordAdapter.start();
447
+ messageHub.setPlatformRuntimeStatus("discord", { runtimeState: "ok", error: null });
448
+ }
449
+ catch (err) {
450
+ const message = toSafeErrorMessage(err, "Discord adapter failed to start");
451
+ messageHub.setPlatformRuntimeStatus("discord", { runtimeState: "error", error: message });
452
+ logger.error({ err }, "Failed to start Discord adapter during reload");
453
+ }
454
+ }
455
+ }
456
+ async function reloadSlackAdapter(startNow) {
457
+ const [botToken, appToken] = await Promise.all([
458
+ secretBroker.getSlackBotToken(),
459
+ secretBroker.getSlackAppToken(),
460
+ ]);
461
+ const configured = !!(botToken && appToken);
462
+ messageHub.setPlatformConfigured("slack", configured);
463
+ if (slackAdapter) {
464
+ try {
465
+ await slackAdapter.stop();
466
+ }
467
+ catch (err) {
468
+ logger.warn({ err }, "Failed to stop Slack adapter during reload");
469
+ }
470
+ messageHub.unregister("slack");
471
+ slackAdapter = null;
472
+ }
473
+ if (!botToken || !appToken) {
474
+ return;
475
+ }
476
+ slackAdapter = new SlackAdapter({
477
+ botToken,
478
+ appToken,
479
+ ownerUserId: config.slackOwnerUserId,
480
+ onMessage: (event) => void eventBus.put(event),
481
+ onOwnerDetected: (userId) => recordDetectedOwner("slack", userId),
482
+ attachmentStore,
483
+ });
484
+ messageHub.register(slackAdapter);
485
+ if (startNow) {
486
+ messageHub.setPlatformRuntimeStatus("slack", { runtimeState: "connecting", error: null });
487
+ try {
488
+ await slackAdapter.start();
489
+ messageHub.setPlatformRuntimeStatus("slack", { runtimeState: "ok", error: null });
490
+ }
491
+ catch (err) {
492
+ const message = toSafeErrorMessage(err, "Slack adapter failed to start");
493
+ messageHub.setPlatformRuntimeStatus("slack", { runtimeState: "error", error: message });
494
+ logger.error({ err }, "Failed to start Slack adapter during reload");
495
+ }
496
+ }
497
+ }
498
+ async function reloadTelegramAdapter(startNow) {
499
+ const botToken = await secretBroker.getTelegramBotToken();
500
+ const configured = !!botToken;
501
+ messageHub.setPlatformConfigured("telegram", configured);
502
+ if (telegramAdapter) {
503
+ try {
504
+ await telegramAdapter.stop();
505
+ }
506
+ catch (err) {
507
+ logger.warn({ err }, "Failed to stop Telegram adapter during reload");
508
+ }
509
+ messageHub.unregister("telegram");
510
+ telegramAdapter = null;
511
+ }
512
+ if (!botToken) {
513
+ return;
514
+ }
515
+ telegramAdapter = new TelegramAdapter({
516
+ botToken,
517
+ ownerChatId: config.telegramOwnerChatId,
518
+ onMessage: (event) => void eventBus.put(event),
519
+ onOwnerDetected: (chatId) => recordDetectedOwner("telegram", chatId),
520
+ attachmentStore,
521
+ });
522
+ messageHub.register(telegramAdapter);
523
+ if (startNow) {
524
+ messageHub.setPlatformRuntimeStatus("telegram", { runtimeState: "connecting", error: null });
525
+ try {
526
+ await telegramAdapter.start();
527
+ messageHub.setPlatformRuntimeStatus("telegram", { runtimeState: "ok", error: null });
528
+ }
529
+ catch (err) {
530
+ const message = toSafeErrorMessage(err, "Telegram adapter failed to start");
531
+ messageHub.setPlatformRuntimeStatus("telegram", { runtimeState: "error", error: message });
532
+ logger.error({ err }, "Failed to start Telegram adapter during reload");
533
+ }
534
+ }
535
+ }
536
+ await Promise.all([
537
+ reloadDiscordAdapter(false),
538
+ reloadSlackAdapter(false),
539
+ reloadTelegramAdapter(false),
540
+ ]);
541
+ /**
542
+ * Build (or rebuild) the WhatsApp adapter from current config and register
543
+ * it with the MessageHub. Idempotent: returns the existing adapter if config
544
+ * is unchanged. Throws if whatsappOwnerPhone is missing.
545
+ */
546
+ function buildWhatsAppAdapter() {
547
+ if (!config.whatsappOwnerPhone) {
548
+ throw new Error("Cannot enable WhatsApp: PA_WHATSAPP_OWNER_PHONE is not set");
549
+ }
550
+ const existing = messageHub.getAdapter("whatsapp");
551
+ if (existing && whatsappAdapter && existing === whatsappAdapter) {
552
+ return whatsappAdapter;
553
+ }
554
+ const adapter = new WhatsAppAdapter({
555
+ ownerPhone: config.whatsappOwnerPhone,
556
+ authDir: config.whatsappAuthDir ?? join(config.dataDir, "whatsapp", "auth"),
557
+ onMessage: (event) => void eventBus.put(event),
558
+ attachmentStore,
559
+ onLoggedOut: async () => {
560
+ try {
561
+ await messageHub.sendToUser("WhatsApp session logged out — re-run foreground pairing");
562
+ }
563
+ catch (err) {
564
+ logger.error({ err }, "Failed to deliver WhatsApp logout notification via fallback channel");
565
+ }
566
+ },
567
+ });
568
+ messageHub.register(adapter);
569
+ whatsappAdapter = adapter;
570
+ return adapter;
571
+ }
572
+ /**
573
+ * Tear down the WhatsApp adapter completely. Used by the dashboard
574
+ * `whatsappEnabled=false` toggle so we don't keep a stale Baileys socket
575
+ * around. Logs but does not throw on socket close errors.
576
+ */
577
+ async function teardownWhatsAppAdapter() {
578
+ if (!whatsappAdapter)
579
+ return;
580
+ try {
581
+ await whatsappAdapter.stop();
582
+ }
583
+ catch (err) {
584
+ logger.warn({ err }, "Error stopping WhatsApp adapter during teardown");
585
+ }
586
+ messageHub.unregister("whatsapp");
587
+ whatsappAdapter = null;
588
+ }
589
+ if (config.whatsappEnabled) {
590
+ if (!config.whatsappOwnerPhone) {
591
+ throw new Error("PA_WHATSAPP_ENABLED=true but PA_WHATSAPP_OWNER_PHONE is not set");
592
+ }
593
+ buildWhatsAppAdapter();
594
+ }
595
+ /**
596
+ * Build the per-platform pairing helpers (`messagingControls`) for the
597
+ * dashboard API. Each block returns `undefined` when its adapter wasn't
598
+ * registered, so the dashboard route can branch on existence to render
599
+ * "not configured" messaging without 404'ing the user.
600
+ *
601
+ * Why a builder, not three separate consts: keeps the wiring co-located
602
+ * with the adapter declarations and short-circuits the cases where
603
+ * dynamic imports (e.g. `qrcode` for Telegram QR rendering) would
604
+ * otherwise be loaded for adapters the user never configured.
605
+ */
606
+ /**
607
+ * Refuse to start a pairing flow on an adapter that isn't actually
608
+ * connected to its upstream service. Without this gate the dashboard
609
+ * would happily display a QR or magic phrase even though the daemon's
610
+ * Slack/Telegram/Discord client failed to come up — the user would scan
611
+ * or type the phrase forever and nothing would arrive.
612
+ */
613
+ function assertAdapterReady(platform) {
614
+ const status = messageHub.getPlatformRuntimeStatus(platform);
615
+ if (status.runtimeState !== "ok") {
616
+ throw new Error(`${platform} adapter is not connected (${status.error ?? status.runtimeState}). `
617
+ + `Verify the token, then save and retry.`);
618
+ }
619
+ }
620
+ function buildTelegramControls() {
621
+ return {
622
+ testToken: async (candidate) => {
623
+ // Prefer the candidate token from the request body so the
624
+ // dashboard can validate an unsaved draft. Falls back to the
625
+ // currently-saved token if no candidate was supplied.
626
+ const tokenToTest = candidate ?? await secretBroker.getTelegramBotToken();
627
+ if (!tokenToTest) {
628
+ throw new Error("No Telegram bot token provided.");
629
+ }
630
+ const info = await TelegramAdapter.fetchBotInfo(tokenToTest);
631
+ return {
632
+ ok: true,
633
+ id: info.id,
634
+ username: info.username,
635
+ firstName: info.firstName,
636
+ };
637
+ },
638
+ startPairing: async (ttlMs = 5 * 60_000) => {
639
+ assertAdapterReady("telegram");
640
+ const adapter = telegramAdapter;
641
+ if (!adapter) {
642
+ throw new Error("Telegram adapter is not initialized. Save the token and retry.");
643
+ }
644
+ const savedToken = await secretBroker.getTelegramBotToken();
645
+ if (!savedToken) {
646
+ throw new Error("No Telegram bot token provided.");
647
+ }
648
+ // Always re-fetch bot info on pair start. Caching it on adapter
649
+ // start meant a stale username (e.g. user renamed the bot via
650
+ // BotFather) would silently break the deep link.
651
+ const info = await TelegramAdapter.fetchBotInfo(savedToken);
652
+ if (!info.username) {
653
+ throw new Error("Telegram bot has no username — set one via @BotFather (/setname or /newbot) before pairing.");
654
+ }
655
+ // 96 bits of entropy in the pair token. The QR encodes a deep link
656
+ // of the form `https://t.me/<bot>?start=<token>`; when the user
657
+ // taps START in Telegram, the bot receives `/start <token>` and
658
+ // the matcher below promotes them to owner. WITHOUT a separate
659
+ // discovery fallback — that combination was unsafe (any DM during
660
+ // the window would claim the role even without the token).
661
+ const pairToken = randomBytes(12).toString("base64url");
662
+ const expiresAt = Date.now() + ttlMs;
663
+ // Matcher rules:
664
+ // - The `/start` and optional `@<botname>` prefix are matched
665
+ // case-INSENSITIVELY. Telegram bot usernames are case-
666
+ // insensitive in URLs and mentions, and clients normalize
667
+ // differently — `MyBot`, `mybot`, `MYBOT` must all work.
668
+ // - The token itself is matched case-SENSITIVELY (base64url
669
+ // uses both cases) so we keep the full 96-bit search space.
670
+ // - Anything after the token (extra args, trailing whitespace
671
+ // beyond what trim() handles) rejects, so we can't be tricked
672
+ // into matching a longer payload that happens to start with
673
+ // `/start <token>`.
674
+ const escapedUsername = info.username.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
675
+ const prefixRe = new RegExp(`^/start(?:@${escapedUsername})?\\s+`, "i");
676
+ adapter.startPairing({
677
+ match: (text) => {
678
+ const trimmed = text.trim();
679
+ const prefixMatch = trimmed.match(prefixRe);
680
+ if (!prefixMatch)
681
+ return false;
682
+ const remainder = trimmed.slice(prefixMatch[0].length);
683
+ return remainder === pairToken;
684
+ },
685
+ expiresAt,
686
+ });
687
+ const deepLink = `https://t.me/${info.username}?start=${pairToken}`;
688
+ const qrcodeMod = await import("qrcode");
689
+ const toDataURL = (qrcodeMod.default ?? qrcodeMod).toDataURL;
690
+ const qrDataUrl = await toDataURL(deepLink, { width: 320, margin: 2 });
691
+ return {
692
+ pairToken,
693
+ deepLink,
694
+ qrDataUrl,
695
+ expiresAt,
696
+ botUsername: info.username,
697
+ };
698
+ },
699
+ getPairingStatus: () => ({
700
+ paired: telegramAdapter?.getOwnerChatId() !== null,
701
+ ownerChatId: telegramAdapter?.getOwnerChatId() ?? null,
702
+ pairingActive: telegramAdapter?.isPairingActive() ?? false,
703
+ }),
704
+ cancelPairing: () => {
705
+ telegramAdapter?.cancelPairing();
706
+ },
707
+ };
708
+ }
709
+ function buildSlackControls() {
710
+ return {
711
+ testToken: async (candidate) => {
712
+ const tokenToTest = candidate ?? await secretBroker.getSlackBotToken();
713
+ if (!tokenToTest) {
714
+ throw new Error("No Slack bot token provided.");
715
+ }
716
+ const info = await SlackAdapter.fetchBotInfo(tokenToTest);
717
+ return {
718
+ ok: true,
719
+ botUserId: info.botUserId,
720
+ botName: info.botName,
721
+ team: info.team,
722
+ url: info.url,
723
+ };
724
+ },
725
+ startPairing: async (ttlMs = 5 * 60_000) => {
726
+ assertAdapterReady("slack");
727
+ const adapter = slackAdapter;
728
+ if (!adapter) {
729
+ throw new Error("Slack adapter is not initialized. Save the tokens and retry.");
730
+ }
731
+ // Magic-phrase pairing: the dashboard displays the phrase, the
732
+ // user copies/types it into their bot DM, the matcher (closed over
733
+ // the normalized phrase) accepts only DMs containing it. Defeats
734
+ // the previous "first DM wins" race entirely because attackers
735
+ // who can't see the dashboard can't see the phrase.
736
+ const { generateMagicPhrase, buildPhraseMatcher } = await import("./messaging/magic-phrase.js");
737
+ const phrase = generateMagicPhrase();
738
+ const expiresAt = Date.now() + ttlMs;
739
+ adapter.startPairing({
740
+ match: buildPhraseMatcher(phrase),
741
+ expiresAt,
742
+ });
743
+ return { phrase, expiresAt };
744
+ },
745
+ cancelPairing: () => {
746
+ slackAdapter?.cancelPairing();
747
+ },
748
+ getPairingStatus: () => ({
749
+ paired: slackAdapter?.getOwnerUserId() !== null,
750
+ ownerUserId: slackAdapter?.getOwnerUserId() ?? null,
751
+ pairingActive: slackAdapter?.isPairingActive() ?? false,
752
+ }),
753
+ };
754
+ }
755
+ function buildDiscordControls() {
756
+ return {
757
+ testToken: async (candidate) => {
758
+ const tokenToTest = candidate ?? await secretBroker.getDiscordBotToken();
759
+ if (!tokenToTest) {
760
+ throw new Error("No Discord bot token provided.");
761
+ }
762
+ const info = await DiscordAdapter.fetchBotInfo(tokenToTest);
763
+ return {
764
+ ok: true,
765
+ id: info.id,
766
+ username: info.username,
767
+ discriminator: info.discriminator,
768
+ avatarUrl: info.avatarUrl,
769
+ };
770
+ },
771
+ startPairing: async (ttlMs = 5 * 60_000) => {
772
+ assertAdapterReady("discord");
773
+ const adapter = discordAdapter;
774
+ if (!adapter) {
775
+ throw new Error("Discord adapter is not initialized. Save the token and retry.");
776
+ }
777
+ const { generateMagicPhrase, buildPhraseMatcher } = await import("./messaging/magic-phrase.js");
778
+ const phrase = generateMagicPhrase();
779
+ const expiresAt = Date.now() + ttlMs;
780
+ adapter.startPairing({
781
+ match: buildPhraseMatcher(phrase),
782
+ expiresAt,
783
+ });
784
+ return { phrase, expiresAt };
785
+ },
786
+ cancelPairing: () => {
787
+ discordAdapter?.cancelPairing();
788
+ },
789
+ getPairingStatus: () => ({
790
+ paired: discordAdapter?.getOwnerUserId() !== null,
791
+ ownerUserId: discordAdapter?.getOwnerUserId() ?? null,
792
+ pairingActive: discordAdapter?.isPairingActive() ?? false,
793
+ }),
794
+ };
795
+ }
796
+ function whatsappQrResponseFromAdapter(adapter, snapshotOverride) {
797
+ if (!adapter) {
798
+ return {
799
+ dataUrl: null,
800
+ payload: null,
801
+ generatedAt: null,
802
+ expiresAt: null,
803
+ state: "not_initialized",
804
+ error: "WhatsApp adapter not enabled",
805
+ };
806
+ }
807
+ const snapshot = snapshotOverride !== undefined
808
+ ? snapshotOverride
809
+ : adapter.getQrSnapshot();
810
+ return {
811
+ dataUrl: snapshot?.dataUrl ?? null,
812
+ payload: snapshot?.payload ?? null,
813
+ generatedAt: snapshot?.generatedAt ?? null,
814
+ expiresAt: snapshot?.expiresAt ?? null,
815
+ state: adapter.getStatus(),
816
+ error: adapter.getStatusError(),
817
+ };
818
+ }
819
+ // Dashboard adapter — always registered (activates on SSE connect)
820
+ const dashboardAdapter = new DashboardAdapter((event) => void eventBus.put(event));
821
+ messageHub.register(dashboardAdapter);
822
+ dashboardAdapter.setAttachmentStore(attachmentStore);
823
+ // ── 6. External services (mutable registry for hot-reload) ──
824
+ const services = createServiceRegistry();
825
+ const blobStore = new FileEncryptedBlobStore(resolve(config.dataDir, "secrets", "blobs"), new PlatformSecretStore());
826
+ // Forward-declared so the registry constructor can wire its scope-change
827
+ // observer before `sessionManager` / `eventBroadcaster` exist. The real
828
+ // implementation is assigned below once those deps are live — until then,
829
+ // `onMailScopeChanged` is a no-op (which is correct: no DM sessions can
830
+ // exist pre-dispatcher).
831
+ let onMailScopeChanged = () => undefined;
832
+ services.mail = new MailAccountRegistry({
833
+ db,
834
+ blobStore: blobStore,
835
+ getEnabledKinds: () => config.enabledMailProviders,
836
+ onScopeChanged: (reason) => onMailScopeChanged(reason),
837
+ providerFactories: {
838
+ // Gmail credentials remain on the shared Google OAuth path, so the
839
+ // row stores a sentinel in `secret_blob_name` and the factory reads
840
+ // the live GmailService from the registry instead of the blob store.
841
+ gmail: (account) => {
842
+ const service = services.gmail;
843
+ if (!service || !service.available) {
844
+ throw new Error(`Gmail service not initialized for account ${account.id}. Complete Google OAuth in the dashboard.`);
845
+ }
846
+ return new GmailProvider({ account, service });
847
+ },
848
+ outlook: async (account, ctx) => {
849
+ const clientConfig = await loadOutlookClientConfig(blobStore);
850
+ if (!clientConfig)
851
+ throw new OutlookClientConfigMissingError();
852
+ const msalApp = createRuntimeMsalApp(clientConfig, account.id, blobStore);
853
+ return new OutlookGraphProvider({
854
+ account,
855
+ msalApp,
856
+ abortSignal: ctx.signal,
857
+ });
858
+ },
859
+ yahoo: async (account) => {
860
+ const raw = await blobStore.readUtf8(`mail:${account.kind}:${account.id}`);
861
+ if (!raw)
862
+ throw new Error(`Missing IMAP secret for ${account.id}`);
863
+ return new YahooImapProvider({
864
+ account,
865
+ secret: parseImapAccountSecret(raw),
866
+ onCapabilitiesProbed: (id, caps) => {
867
+ services.mail?.updateCapabilities(id, caps);
868
+ },
869
+ });
870
+ },
871
+ icloud: async (account) => {
872
+ const raw = await blobStore.readUtf8(`mail:${account.kind}:${account.id}`);
873
+ if (!raw)
874
+ throw new Error(`Missing IMAP secret for ${account.id}`);
875
+ return new ICloudImapProvider({
876
+ account,
877
+ secret: parseImapAccountSecret(raw),
878
+ onCapabilitiesProbed: (id, caps) => {
879
+ services.mail?.updateCapabilities(id, caps);
880
+ },
881
+ });
882
+ },
883
+ },
884
+ });
885
+ const secretState = {
886
+ googleCredentialsConfigured: false,
887
+ googleTokenConfigured: false,
888
+ googleCredentialType: null,
889
+ notionConfigured: false,
890
+ githubConfigured: false,
891
+ githubWebhookConfigured: false,
892
+ };
893
+ async function refreshGoogleSecretState() {
894
+ const [credentialsRaw, tokenRaw] = await Promise.all([
895
+ secretBroker.getGoogleCredentialsJson(),
896
+ secretBroker.getGoogleTokenJson(),
897
+ ]);
898
+ secretState.googleCredentialsConfigured = !!credentialsRaw;
899
+ secretState.googleTokenConfigured = !!tokenRaw;
900
+ secretState.googleCredentialType = detectGoogleCredentialType(credentialsRaw);
901
+ }
902
+ async function reloadGoogleServices() {
903
+ await refreshGoogleSecretState();
904
+ services.calendar = null;
905
+ services.gmail = null;
906
+ delete services.errors.googleCalendar;
907
+ delete services.errors.gmail;
908
+ if (!secretState.googleCredentialsConfigured) {
909
+ if (services.mail) {
910
+ syncLegacyGmailAccountState(db, services.mail, {
911
+ available: false,
912
+ error: "Google credentials are not configured.",
913
+ });
914
+ }
915
+ return;
916
+ }
917
+ // OAuth2 pre-auth: credentials uploaded but the user has not completed the
918
+ // browser flow yet (no token in the keychain). Initializing the services
919
+ // would fail with a "missing token" error that the dashboard would then
920
+ // render as red "Error" under the Google card — but this is the expected
921
+ // mid-setup state, not a failure. Skip init and leave services.errors
922
+ // unset so /health reports error: null until the user finishes OAuth or a
923
+ // real init error occurs.
924
+ const oauth2PreAuth = secretState.googleCredentialType === "oauth2"
925
+ && !secretState.googleTokenConfigured;
926
+ if (oauth2PreAuth) {
927
+ if (services.mail) {
928
+ syncLegacyGmailAccountState(db, services.mail, {
929
+ available: false,
930
+ error: "Awaiting Google OAuth authorization.",
931
+ });
932
+ }
933
+ return;
934
+ }
935
+ const calendarService = new CalendarService(config, secretBroker);
936
+ try {
937
+ await calendarService.init();
938
+ services.calendar = calendarService;
939
+ }
940
+ catch (err) {
941
+ const msg = err.message;
942
+ logger.error({ error: msg }, "Calendar service init failed, continuing without it");
943
+ services.errors.googleCalendar = msg;
944
+ }
945
+ const gmailService = new GmailService(secretBroker);
946
+ try {
947
+ await gmailService.init();
948
+ services.gmail = gmailService;
949
+ }
950
+ catch (err) {
951
+ const msg = err.message;
952
+ logger.error({ error: msg }, "Gmail service init failed, continuing without it");
953
+ services.errors.gmail = msg;
954
+ }
955
+ // Ensure the shared-Google-OAuth Gmail identity exists as a unified
956
+ // mail account (idempotent; returns `exists` on subsequent boots).
957
+ if (services.gmail?.available) {
958
+ try {
959
+ await ensureLegacyGmailRow(db, services.gmail);
960
+ }
961
+ catch (err) {
962
+ logger.error({ err }, "Failed to ensure shared-Google-OAuth Gmail mail_accounts row");
963
+ }
964
+ if (services.mail) {
965
+ syncLegacyGmailAccountState(db, services.mail, { available: true });
966
+ }
967
+ }
968
+ else if (services.mail) {
969
+ syncLegacyGmailAccountState(db, services.mail, {
970
+ available: false,
971
+ error: services.errors.gmail ?? "Gmail is not configured.",
972
+ });
973
+ }
974
+ }
975
+ // Google Maps (F-08: commute optimization)
976
+ {
977
+ const { GoogleMapsService } = await import("./services/google-maps.js");
978
+ const mapsService = new GoogleMapsService(secretBroker);
979
+ try {
980
+ await mapsService.init();
981
+ if (mapsService.available) {
982
+ services.googleMaps = mapsService;
983
+ }
984
+ }
985
+ catch (err) {
986
+ const msg = err.message;
987
+ logger.error({ error: msg }, "Google Maps service init failed, continuing without it");
988
+ services.errors.googleMaps = msg;
989
+ }
990
+ }
991
+ // Obsidian
992
+ if (config.externalObsidianVaultName) {
993
+ const obsidianService = new ObsidianService(config);
994
+ if (obsidianService.available) {
995
+ logger.info({ vaultName: config.externalObsidianVaultName }, "Probing Obsidian CLI...");
996
+ const running = await obsidianService.isRunning();
997
+ if (running) {
998
+ services.obsidian = obsidianService;
999
+ logger.info({ vaultName: config.externalObsidianVaultName }, "Obsidian service available");
1000
+ }
1001
+ else {
1002
+ services.errors.obsidian = "Obsidian CLI not accessible — is Obsidian running?";
1003
+ logger.warn("Obsidian CLI not accessible — is Obsidian running?");
1004
+ }
1005
+ }
1006
+ }
1007
+ else if (config.externalObsidianVaultPath && !config.externalObsidianVaultName) {
1008
+ services.errors.obsidian = "externalObsidianVaultName is required for the Obsidian CLI service (externalObsidianVaultPath alone enables file watching only)";
1009
+ }
1010
+ async function reloadAppleCalendarService() {
1011
+ const raw = await secretBroker.getAppleCalendarCredentialsJson();
1012
+ services.appleCalendar = null;
1013
+ delete services.errors.appleCalendar;
1014
+ if (!raw) {
1015
+ return;
1016
+ }
1017
+ const service = new AppleCalendarService(secretBroker);
1018
+ try {
1019
+ await service.init();
1020
+ if (service.available) {
1021
+ services.appleCalendar = service;
1022
+ }
1023
+ else {
1024
+ // Surface the underlying iCloud error verbatim — the dashboard
1025
+ // shows it on the Connections card so the user can act
1026
+ // (`401 Unauthorized` → regenerate password; network error →
1027
+ // retry; etc.). Falls back to a generic placeholder only if
1028
+ // init() failed without recording a message.
1029
+ services.errors.appleCalendar =
1030
+ service.initError
1031
+ ?? "Apple Calendar credentials present but iCloud discovery did not return a usable calendar — verify the app-specific password.";
1032
+ }
1033
+ }
1034
+ catch (err) {
1035
+ const msg = err.message;
1036
+ logger.error({ error: msg }, "Apple Calendar service init failed, continuing without it");
1037
+ services.errors.appleCalendar = msg;
1038
+ }
1039
+ }
1040
+ async function reloadNotionService() {
1041
+ const apiKey = await secretBroker.getNotionApiKey();
1042
+ secretState.notionConfigured = !!apiKey;
1043
+ services.notion = null;
1044
+ delete services.errors.notion;
1045
+ if (!apiKey) {
1046
+ return;
1047
+ }
1048
+ const notionService = new NotionService(config, secretBroker);
1049
+ try {
1050
+ await notionService.init();
1051
+ services.notion = notionService;
1052
+ }
1053
+ catch (err) {
1054
+ const msg = err.message;
1055
+ logger.error({ error: msg }, "Notion service init failed, continuing without it");
1056
+ services.errors.notion = msg;
1057
+ }
1058
+ }
1059
+ async function reloadGitHubService() {
1060
+ const [token, webhookSecret] = await Promise.all([
1061
+ secretBroker.getGitHubToken(),
1062
+ secretBroker.getGitHubWebhookSecret(),
1063
+ ]);
1064
+ secretState.githubConfigured = !!token;
1065
+ secretState.githubWebhookConfigured = !!webhookSecret;
1066
+ services.github = null;
1067
+ delete services.errors.github;
1068
+ if (!token) {
1069
+ return;
1070
+ }
1071
+ const githubService = new GitHubService(token, webhookSecret);
1072
+ try {
1073
+ await githubService.init();
1074
+ services.github = githubService;
1075
+ }
1076
+ catch (err) {
1077
+ const msg = err.message;
1078
+ logger.error({ error: msg }, "GitHub service init failed, continuing without it");
1079
+ services.errors.github = msg;
1080
+ }
1081
+ }
1082
+ await Promise.all([
1083
+ reloadGoogleServices(),
1084
+ reloadAppleCalendarService(),
1085
+ reloadNotionService(),
1086
+ reloadGitHubService(),
1087
+ ]);
1088
+ /** Build integration status snapshot for /api/health */
1089
+ const getIntegrationStatus = () => {
1090
+ const whatsappState = config.whatsappEnabled
1091
+ ? (whatsappAdapter?.getStatus() ?? "disabled")
1092
+ : "not_configured";
1093
+ return {
1094
+ google: {
1095
+ configured: secretState.googleCredentialsConfigured,
1096
+ connected: services.calendar !== null || services.gmail !== null,
1097
+ error: services.errors.googleCalendar
1098
+ ? toSafeErrorMessage(services.errors.googleCalendar)
1099
+ : null,
1100
+ services: {
1101
+ calendar: {
1102
+ connected: services.calendar !== null,
1103
+ error: services.errors.googleCalendar
1104
+ ? toSafeErrorMessage(services.errors.googleCalendar)
1105
+ : null,
1106
+ },
1107
+ gmail: {
1108
+ connected: services.gmail !== null,
1109
+ error: services.errors.gmail
1110
+ ? toSafeErrorMessage(services.errors.gmail)
1111
+ : null,
1112
+ },
1113
+ },
1114
+ },
1115
+ appleCalendar: {
1116
+ // `configured` reflects whether credentials are stored in the
1117
+ // keychain; `connected` reflects whether last principal-discovery
1118
+ // succeeded. The Overview's "Apple selected but not connected"
1119
+ // banner reads `appleCalendar.configured && !appleCalendar.connected`.
1120
+ configured: services.appleCalendar !== null || !!services.errors.appleCalendar,
1121
+ connected: services.appleCalendar?.available ?? false,
1122
+ error: services.errors.appleCalendar
1123
+ ? toSafeErrorMessage(services.errors.appleCalendar)
1124
+ : null,
1125
+ },
1126
+ obsidian: {
1127
+ configured: !!(config.externalObsidianVaultPath || config.externalObsidianVaultName),
1128
+ connected: services.obsidian !== null,
1129
+ error: services.errors.obsidian
1130
+ ? toSafeErrorMessage(services.errors.obsidian)
1131
+ : null,
1132
+ },
1133
+ notion: {
1134
+ configured: secretState.notionConfigured,
1135
+ connected: services.notion !== null,
1136
+ error: services.errors.notion
1137
+ ? toSafeErrorMessage(services.errors.notion)
1138
+ : null,
1139
+ },
1140
+ whatsapp: {
1141
+ configured: config.whatsappEnabled,
1142
+ connected: whatsappState === "ok",
1143
+ error: whatsappState === "disconnected"
1144
+ ? "WhatsApp disconnected"
1145
+ : whatsappState === "logged_out"
1146
+ ? "WhatsApp logged out"
1147
+ : null,
1148
+ state: whatsappState,
1149
+ },
1150
+ googleMaps: {
1151
+ configured: services.googleMaps !== null,
1152
+ connected: services.googleMaps !== null,
1153
+ error: services.errors.googleMaps
1154
+ ? toSafeErrorMessage(services.errors.googleMaps)
1155
+ : null,
1156
+ },
1157
+ };
1158
+ };
1159
+ const getMessagingStatus = () => {
1160
+ return Object.fromEntries(SUPPORTED_MESSAGING_PLATFORMS.map((platform) => {
1161
+ const ownerChannel = getOwnerChannel(db, platform);
1162
+ const configured = messageHub.isPlatformConfigured(platform);
1163
+ const ownerConfigured = messageHub.isOwnerConfigured(platform);
1164
+ const { runtimeState, error } = messageHub.getPlatformRuntimeStatus(platform);
1165
+ const ownerChannelKnown = platform === "dashboard"
1166
+ ? dashboardAdapter.getActiveChannels().length > 0 || !!ownerChannel
1167
+ : platform === "telegram"
1168
+ ? !!config.telegramOwnerChatId
1169
+ : platform === "whatsapp"
1170
+ ? !!config.whatsappOwnerPhone
1171
+ : !!ownerChannel;
1172
+ const notificationEligible = messageHub.isPlatformNotificationEligible(platform);
1173
+ return [
1174
+ platform,
1175
+ {
1176
+ configured,
1177
+ runtimeState,
1178
+ ownerConfigured,
1179
+ ownerChannelKnown,
1180
+ notificationEligible,
1181
+ lastInboundAt: ownerChannel?.last_inbound_at ?? null,
1182
+ error,
1183
+ },
1184
+ ];
1185
+ }));
1186
+ };
1187
+ // ── 7. Observers ──
1188
+ const observerManager = new ObserverManager();
1189
+ // Mutable indirection: observers are constructed in this block, but the
1190
+ // dispatcher (owner of `emitRoadmapRefresh`) is created later in §7.3.
1191
+ // Pollers store this callback and only invoke it from their poll loops,
1192
+ // which run after `observerManager.startAll()` — by then the dispatcher
1193
+ // has been wired via the assignment in §7.3 and the indirection resolves.
1194
+ // ROADMAP-REDESIGN §3.4 RFC-C.
1195
+ let emitRoadmapRefreshSink = null;
1196
+ const triggerRoadmapRefresh = (source) => {
1197
+ emitRoadmapRefreshSink?.(source);
1198
+ };
1199
+ const getNormalizedGitRepos = () => normalizeGitWatchedRepos({ gitWatchedRepos: selectGitWatchedRepos(db) });
1200
+ // P5 multi-account — registry resolves `gitAccounts[<alias>]` to a
1201
+ // `{GH_TOKEN, GIT_ASKPASS, ...}` env overlay each observer applies
1202
+ // per-call. The `getAccounts` thunk reads `config.gitAccounts` lazily
1203
+ // so a `PATCH /api/config` that mutates `config` in place via
1204
+ // `applyConfigUpdates`'s `Object.assign(config, runtimeUpdates)`
1205
+ // flows through to the next resolver call without re-instantiation
1206
+ // — which is why `gitAccounts` is NOT in `RESTART_REQUIRED_KEY_TUPLE`.
1207
+ // Per-repo PAT secrets live in the OS keychain at
1208
+ // `git.account.<alias>` (see secret-names.ts ScopedSecretName).
1209
+ const gitAccountRegistry = new GitAccountRegistry({
1210
+ dataDir: config.dataDir,
1211
+ secretBroker,
1212
+ getAccounts: () => config.gitAccounts,
1213
+ });
1214
+ // One-time info note: github-side rows without a local clone always
1215
+ // poll under the default `gh` profile when the row's `github_account`
1216
+ // is null. Surface this if both `gitAccounts` and github-only rows
1217
+ // exist so a user staring at unaccounted-for traffic from "the wrong
1218
+ // account" sees it in `aitne logs` rather than guessing.
1219
+ const githubOnlyRows = selectGithubRepoSlugs(db).length;
1220
+ if (Object.keys(config.gitAccounts ?? {}).length > 0
1221
+ && githubOnlyRows > 0) {
1222
+ logger.info({
1223
+ gitAccounts: Object.keys(config.gitAccounts).length,
1224
+ githubRepos: githubOnlyRows,
1225
+ }, "GitHub-side repository rows fall back to the default gh profile when github_account is unset. Set the row's account alias from /api/repositories or the dashboard.");
1226
+ }
1227
+ const lookupRepoAlias = (repoPath, fullName) => {
1228
+ const repos = getNormalizedGitRepos();
1229
+ if (repoPath) {
1230
+ const byPath = repos.find((r) => r.path === repoPath);
1231
+ if (byPath?.accountAlias)
1232
+ return byPath.accountAlias;
1233
+ }
1234
+ if (fullName) {
1235
+ const byOrgRepo = repos.find((r) => {
1236
+ if (!r.org)
1237
+ return false;
1238
+ // Repos resolved from `githubRepos` (not local paths) match by full name.
1239
+ return fullName.toLowerCase() === `${r.org}/${r.slug}`.toLowerCase();
1240
+ });
1241
+ if (byOrgRepo?.accountAlias)
1242
+ return byOrgRepo.accountAlias;
1243
+ }
1244
+ return undefined;
1245
+ };
1246
+ const queueGitProjectInitsForCurrentConfig = (source) => {
1247
+ if (!isSetupCompleted(db) || readDegradedMode(db))
1248
+ return;
1249
+ const repos = getNormalizedGitRepos();
1250
+ if (repos.length === 0)
1251
+ return;
1252
+ try {
1253
+ const inserted = queueMissingGitProjectInits({
1254
+ db,
1255
+ contextDir: getContextDir(config, db),
1256
+ dataDir: config.dataDir,
1257
+ workspaceDir: config.workspaceDir,
1258
+ repos,
1259
+ });
1260
+ if (inserted > 0) {
1261
+ logger.info({ inserted, source }, "Queued missing git project documentation init tasks");
1262
+ }
1263
+ }
1264
+ catch (err) {
1265
+ logger.warn({ err, source }, "Failed to queue git project documentation init tasks");
1266
+ }
1267
+ };
1268
+ const gitWatchedRepos = getNormalizedGitRepos();
1269
+ const gitRepoPaths = gitWatchedRepos.map((repo) => repo.path);
1270
+ let gitWatcher = null;
1271
+ const buildGitWatcher = () => {
1272
+ if (gitRepoPaths.length === 0)
1273
+ return null;
1274
+ // Per-row poll cadence (unified-repositories §5). Sourced from each
1275
+ // row's `poll_interval_sec` column; rows with null fall through to
1276
+ // the global `gitPollIntervalSeconds`.
1277
+ const repoIntervals = new Map(gitWatchedRepos.map((row) => [row.path, row.pollIntervalSec ?? null]));
1278
+ const watcher = new GitWatcher(gitRepoPaths, db, config.gitPollIntervalSeconds, {
1279
+ eventBus,
1280
+ pushOverdueMinutes: config.gitPushOverdueMinutes,
1281
+ repoIntervals,
1282
+ repoEnvResolver: async (repoPath) => {
1283
+ const alias = lookupRepoAlias(repoPath);
1284
+ if (!alias)
1285
+ return undefined;
1286
+ return (await gitAccountRegistry.buildSpawnEnv(alias)) ?? undefined;
1287
+ },
1288
+ onRepoBaseline: (repoPath) => {
1289
+ const repo = getNormalizedGitRepos().find((item) => item.path === repoPath);
1290
+ if (!repo)
1291
+ return;
1292
+ queueGitProjectInitsForCurrentConfig("git-baseline");
1293
+ },
1294
+ onLifecycleObservation: (classification) => {
1295
+ if (!isSetupCompleted(db) || readDegradedMode(db))
1296
+ return;
1297
+ const repoPath = typeof classification.payload.repoPath === "string"
1298
+ ? classification.payload.repoPath
1299
+ : classification.source.replace(/^git:/, "");
1300
+ const repo = getNormalizedGitRepos().find((item) => item.path === repoPath);
1301
+ if (!repo)
1302
+ return;
1303
+ const result = queueGitProjectUpdate({
1304
+ db,
1305
+ dataDir: config.dataDir,
1306
+ workspaceDir: config.workspaceDir,
1307
+ repo,
1308
+ event: classification,
1309
+ debounceMinutes: config.gitProjectUpdateDebounceMinutes,
1310
+ });
1311
+ if (result === "queued" || result === "merged") {
1312
+ logger.info({
1313
+ repo: repo.path,
1314
+ eventType: classification.eventType,
1315
+ result,
1316
+ }, "Queued git project documentation update");
1317
+ }
1318
+ // Unified-repositories §4.4 — fire any per-repo triggers
1319
+ // configured for this lifecycle event. Triggers ride alongside
1320
+ // the task-flow pipeline above; they do not consume the event.
1321
+ const repositoryRow = getRepositoryByLocalPath(db, repoPath);
1322
+ if (repositoryRow) {
1323
+ void dispatchMatchingTriggers({ db, eventBus }, repositoryRow.id, classification.eventType, classification.payload);
1324
+ }
1325
+ },
1326
+ });
1327
+ gitWatcher = watcher;
1328
+ return watcher;
1329
+ };
1330
+ if (shouldStartObserversFor(db, "git")) {
1331
+ const watcher = buildGitWatcher();
1332
+ if (watcher)
1333
+ observerManager.register(watcher);
1334
+ }
1335
+ // GitHubPoller — daemon-side notification + workflow_run polling via the
1336
+ // user's `gh auth login` keychain entry. Replaces the webhook receiver
1337
+ // for local-first installs where exposing a public URL is impractical.
1338
+ // Registered only while github.mode === "direct" now that GitHub is a
1339
+ // first-class integration. Direct remains the default, preserving the
1340
+ // historical "notifications on when gh is authenticated" behaviour.
1341
+ const buildGithubPoller = () => {
1342
+ // Per-row poll cadence (unified-repositories §5). The map is keyed by
1343
+ // `owner/repo` to match `RepoBinding.fullName`; rows are sourced from
1344
+ // the unified table. Local-only rows have no `owner/repo` and fall
1345
+ // out of the map naturally.
1346
+ const repoIntervals = new Map();
1347
+ for (const row of listRepositories(db, { hasGithub: true })) {
1348
+ if (row.githubOwner && row.githubRepo) {
1349
+ repoIntervals.set(`${row.githubOwner}/${row.githubRepo}`, row.pollIntervalSec ?? null);
1350
+ }
1351
+ }
1352
+ return new GitHubPoller({
1353
+ db,
1354
+ eventBus,
1355
+ repoPaths: gitRepoPaths,
1356
+ repoFullNames: selectGithubRepoSlugs(db),
1357
+ pollIntervalSeconds: config.githubPollIntervalSeconds,
1358
+ repoIntervals,
1359
+ repoAccountAliasResolver: ({ localPath, fullName }) => lookupRepoAlias(localPath, fullName),
1360
+ accountResolver: async (binding) => {
1361
+ if (!binding.accountAlias)
1362
+ return undefined;
1363
+ return ((await gitAccountRegistry.buildSpawnEnv(binding.accountAlias)) ?? undefined);
1364
+ },
1365
+ // Unified-repositories §4.4 — resolve the binding's owner/repo to a
1366
+ // repositories.id and fire any per-repo triggers configured for
1367
+ // this event type. Failures are logged inside dispatch and do not
1368
+ // bubble out so a misconfigured trigger cannot stall the poll loop.
1369
+ onTriggerableEvent: async (event) => {
1370
+ if (!isSetupCompleted(db) || readDegradedMode(db))
1371
+ return;
1372
+ let repoRow = null;
1373
+ if (event.binding) {
1374
+ repoRow = getRepositoryByGithub(db, event.binding.owner, event.binding.repo);
1375
+ }
1376
+ if (!repoRow) {
1377
+ // Notifications without a `directNotificationRepos` match still
1378
+ // arrive here when a `repository.full_name` is in the payload.
1379
+ const fullName = typeof event.payload.repository === "string"
1380
+ ? event.payload.repository
1381
+ : null;
1382
+ if (fullName) {
1383
+ const [owner, repo] = fullName.split("/");
1384
+ if (owner && repo) {
1385
+ repoRow = getRepositoryByGithub(db, owner, repo);
1386
+ }
1387
+ }
1388
+ }
1389
+ if (!repoRow)
1390
+ return;
1391
+ await dispatchMatchingTriggers({ db, eventBus }, repoRow.id, event.eventType, event.payload);
1392
+ },
1393
+ });
1394
+ };
1395
+ if (shouldStartObserversFor(db, "github")) {
1396
+ observerManager.register(buildGithubPoller());
1397
+ }
1398
+ // Each call returns a fresh observer so the integration-lifecycle helper
1399
+ // re-registers a new instance after a mode flip — picking up any
1400
+ // gitPollIntervalSeconds / gitPushOverdueMinutes / hourlyCheckEnabled
1401
+ // PATCH that landed while the cron was idle.
1402
+ const buildGitDelegatedCronObserver = () => new GitDelegatedCronObserver({
1403
+ db,
1404
+ eventBus,
1405
+ repoPaths: gitRepoPaths,
1406
+ githubRepos: selectGithubRepoSlugs(db),
1407
+ cadenceSeconds: config.gitPollIntervalSeconds,
1408
+ pushOverdueMinutes: config.gitPushOverdueMinutes,
1409
+ hourlyCheckEnabled: config.hourlyCheckEnabled,
1410
+ });
1411
+ if (hasActiveDelegatedGitLifecycleIntegration(db)) {
1412
+ observerManager.register(buildGitDelegatedCronObserver());
1413
+ }
1414
+ // Unified-repositories daily management cron — see
1415
+ // docs/design/appendices/unified-repositories.md §4.5. Iterates rows
1416
+ // whose `repository_management.enabled = 1` and writes the required
1417
+ // journal/overview markdown for each row that's due.
1418
+ observerManager.register(new RepositoryManagementCron({
1419
+ db,
1420
+ eventBus,
1421
+ contextDir: () => getContextDir(config, db),
1422
+ timezone: config.timezone || undefined,
1423
+ writeTracker,
1424
+ }));
1425
+ // Coexistence note: the legacy /webhook/github handler also calls
1426
+ // recordObservation + EventBus.put, so a user with both webhooks AND
1427
+ // the poller live will receive two events per `review_requested`. This
1428
+ // is a low-probability scenario (webhooks need a public URL most users
1429
+ // don't expose), so we log a one-time warning rather than disabling
1430
+ // either path automatically.
1431
+ if (secretState.githubWebhookConfigured && shouldStartObserversFor(db, "github")) {
1432
+ logger.warn("GitHub webhook secret configured AND GitHubPoller running — "
1433
+ + "duplicate events possible. Remove the webhook secret or unregister "
1434
+ + "the GitHub webhook on github.com to silence.");
1435
+ }
1436
+ // SETUP-FLOW-REDESIGN-PLAN §6.3 — `externalObsidianWatch` is a kill
1437
+ // switch for the external-vault branch of the watcher. When false the
1438
+ // path is preserved (the Obsidian CLI skill still sees it via
1439
+ // `config.externalObsidianVaultPath`) but no chokidar instance is
1440
+ // registered. Sized for power users with very large vaults that would
1441
+ // otherwise produce noisy observation churn (§13 open question).
1442
+ if (config.externalObsidianVaultPath && config.externalObsidianWatch) {
1443
+ observerManager.register(new ObsidianWatcher(config.externalObsidianVaultPath, db, config.obsidianDebounceSeconds, writeTracker, { source: "obsidian:external", name: "obsidian:external" }));
1444
+ }
1445
+ // Primary-vault watcher — registered unconditionally. Stays dormant
1446
+ // until `setVaultPath` points it at a real directory. The migration
1447
+ // endpoint's `onPrimaryVaultPathChange` callback calls setVaultPath
1448
+ // explicitly after every commit, so plain → obsidian transitions
1449
+ // and obsidian-path changes both re-target without dynamic
1450
+ // register/unregister plumbing.
1451
+ const primaryVaultWatcher = new PrimaryVaultWatcher(db, config.obsidianDebounceSeconds, writeTracker);
1452
+ await primaryVaultWatcher.setVaultPath(config.vaultMode === "obsidian" ? config.primaryVaultPath : null);
1453
+ observerManager.register(primaryVaultWatcher);
1454
+ // Build CalendarPoller from current services. Returns null when
1455
+ // `services.calendar` is unavailable so the integration-lifecycle
1456
+ // module can no-op (for example: integration flips to direct before
1457
+ // OAuth is set up). The §4.5.1 gate `google_calendar.mode === "direct"`
1458
+ // is checked BEFORE invoking the builder.
1459
+ const buildCalendarPoller = () => {
1460
+ if (!services.calendar)
1461
+ return null;
1462
+ return new CalendarPoller(services.calendar, db, config.calendarPollIntervalSeconds, config.googleCalendarId, writeTracker, triggerRoadmapRefresh, morningRoutineLock, config.timezone);
1463
+ };
1464
+ if (services.calendar && shouldStartObserversFor(db, "google_calendar")) {
1465
+ const poller = buildCalendarPoller();
1466
+ if (poller)
1467
+ observerManager.register(poller);
1468
+ }
1469
+ observerManager.register(new ImminentEventScheduler(db, eventBus, config.googleCalendarId));
1470
+ // Build NotionPoller from current services + config. Returns null when
1471
+ // `services.notion` is unavailable or no databases are configured so
1472
+ // the integration-lifecycle module can no-op (e.g. integration flips
1473
+ // to direct before the API key is set up). The §4.5.1 gate
1474
+ // `notion.mode === "direct"` is checked BEFORE invoking the builder.
1475
+ const buildNotionPoller = () => {
1476
+ if (!services.notion)
1477
+ return null;
1478
+ if (Object.keys(config.notionDatabaseIds).length === 0)
1479
+ return null;
1480
+ return new NotionPoller({
1481
+ notionService: services.notion,
1482
+ databaseIds: config.notionDatabaseIds,
1483
+ pollIntervalSeconds: config.notionPollIntervalSeconds,
1484
+ db,
1485
+ writeTracker,
1486
+ });
1487
+ };
1488
+ if (services.notion
1489
+ && Object.keys(config.notionDatabaseIds).length > 0
1490
+ && shouldStartObserversFor(db, "notion")) {
1491
+ const poller = buildNotionPoller();
1492
+ if (poller)
1493
+ observerManager.register(poller);
1494
+ }
1495
+ if (services.mail) {
1496
+ observerManager.register(new MailPoller({
1497
+ registry: services.mail,
1498
+ db,
1499
+ writeTracker,
1500
+ pollIntervalSeconds: config.mailPollIntervalSeconds,
1501
+ maxMessagesPerPoll: config.mailMaxMessagesPerPoll,
1502
+ authFailureRetryHours: config.mailAuthFailureRetryHours,
1503
+ providerPollIntervalsSeconds: {
1504
+ gmail: config.gmailPollIntervalSeconds,
1505
+ },
1506
+ notifyOwner: async (message) => {
1507
+ await messageHub.sendToUser(message);
1508
+ },
1509
+ triggerRoadmapRefresh,
1510
+ }));
1511
+ observerManager.register(new MailReconciliationJob({
1512
+ registry: services.mail,
1513
+ db,
1514
+ }));
1515
+ }
1516
+ // ── 7.04 Skill-curation observers (P22 — appendix p22-skill-self-optimization.md) ──
1517
+ // Hourly walker accumulates `structure_diff` signals for the curation
1518
+ // cohort. Outcomes/feedback collection was dropped from the Preview scope
1519
+ // — the feature optimizes silently in the background, no metrics surface.
1520
+ {
1521
+ const { SkillCurationWalker } = await import("./observers/skill-curation-walker.js");
1522
+ observerManager.register(new SkillCurationWalker(db, getContextDir(config), join(config.workspaceDir, "agent-assets", "skills"), config.dataDir));
1523
+ // §5.4 — boot-time orphan-overlay scan. Emits one log line per orphan
1524
+ // and seeds `runtime_state.skill_curation.orphan_overlays` so the
1525
+ // dashboard banner can read it without re-walking the FS.
1526
+ try {
1527
+ const { scanAndRecordOrphanOverlays } = await import("./core/skill-curation/orphan-overlay.js");
1528
+ scanAndRecordOrphanOverlays(db, config.dataDir, join(config.workspaceDir, "agent-assets", "skills"));
1529
+ }
1530
+ catch (err) {
1531
+ logger.warn({ err }, "Skill-curation orphan-overlay scan failed at boot");
1532
+ }
1533
+ }
1534
+ // ── 7.05 Context-index reconciler (B-004 Phase 2a) ──
1535
+ // Keeps `context/context-index.md` in sync with the filesystem so the
1536
+ // per-flow review-context loader can treat the index as authoritative.
1537
+ // Combines: startup one-shot (30 s after boot), internal chokidar
1538
+ // watcher on contextDir (10 s debounce), cron nightly (wired via
1539
+ // `AgentScheduler.setContextIndexReconcilerCallback`), and API-route
1540
+ // hints via `onIndexableContextChange` below.
1541
+ let contextIndexReconcilerPromptSink = null;
1542
+ const contextIndexReconciler = new ContextIndexReconcilerObserver({
1543
+ db,
1544
+ contextDir: getContextDir(config),
1545
+ writeTracker,
1546
+ onPromptContextChanged: (path, reason, tier, metadata) => {
1547
+ // The reconciler writes one path (`context-index.md`) — route the
1548
+ // prompt-cache invalidation through the same handler every other
1549
+ // context write uses. The real handler is installed in §11
1550
+ // (`onPromptContextChanged` ApiDependency); this indirection lets
1551
+ // the observer start at §7 before the handler exists.
1552
+ contextIndexReconcilerPromptSink?.(path, reason, tier, metadata);
1553
+ },
1554
+ morningRoutineLock,
1555
+ timezone: config.timezone || undefined,
1556
+ });
1557
+ observerManager.register(contextIndexReconciler);
1558
+ // docs/design/21-management-registry-and-entities.md §7.6 P5 —
1559
+ // entity-mirror watcher. Owns its own chokidar watcher (independent
1560
+ // of the context-index reconciler's debounce path) so single-file
1561
+ // L2 writes converge into the SQLite mirror within NFR-9's 500 ms
1562
+ // budget — the §7.6 lookup contract relies on this freshness at
1563
+ // scheduled-task fire time. Boot pass + watcher lifecycle handled
1564
+ // by the observer wrapper.
1565
+ //
1566
+ // §7.2 chain — fan an L2 entity delta out to the context-index
1567
+ // observer's `requestReconcile` so the rendered domain-index +
1568
+ // activity-view files refresh on the same 10 s debounce as other
1569
+ // fs_event triggers. Without this, those views only converged on
1570
+ // the nightly cron + 30 s startup pass because `shouldIndexPath`
1571
+ // filters L2 paths out of the context-index's own watcher
1572
+ // (followups item 7).
1573
+ observerManager.register(new EntityMirrorObserver({
1574
+ db,
1575
+ contextDir: getContextDir(config),
1576
+ writeTracker,
1577
+ onEntityChanged: () => contextIndexReconciler.requestReconcile("fs_event"),
1578
+ }));
1579
+ // ── 7.1 MCP auto-probe (B-003 Phase 4.3) ──
1580
+ // Walks enabled mcp_servers rows every `mcpAutoProbeIntervalMinutes` and
1581
+ // re-runs the probe sandbox. Set the interval to 0 to disable. The
1582
+ // observer never flips `enabled` on its own — failure surfaces through
1583
+ // the dashboard card's status dot, user decides whether to disable.
1584
+ {
1585
+ const { McpAutoProbe } = await import("./services/mcp/auto-probe.js");
1586
+ observerManager.register(new McpAutoProbe({
1587
+ db,
1588
+ blobStore,
1589
+ dataDir: config.dataDir,
1590
+ intervalMinutes: config.mcpAutoProbeIntervalMinutes,
1591
+ }));
1592
+ }
1593
+ // ── 7.2 Observation summarizer (cost-reduction-structural §A) ──
1594
+ // Drains pending observation rows asynchronously: pre-filter → per-
1595
+ // source LLM call → `summary_text` + `novelty_score` written back to
1596
+ // the row. The hourly_check skill consumes summaries instead of
1597
+ // re-fetching raw content. Disabled cleanly via
1598
+ // `observationSummarizerEnabled` — when off, observations stay
1599
+ // pending and the skill drops to legacy fetch-on-doubt.
1600
+ if (config.observationSummarizerEnabled) {
1601
+ const summarizerBinding = (() => {
1602
+ try {
1603
+ const row = db
1604
+ .prepare(`SELECT main_backend, main_model FROM process_backend_config WHERE process_key = 'observation.summarize'`)
1605
+ .get();
1606
+ if (!row?.main_backend || !row.main_model)
1607
+ return null;
1608
+ return { backendId: row.main_backend, modelId: row.main_model };
1609
+ }
1610
+ catch (err) {
1611
+ logger.debug({ err }, "Failed to read observation.summarize binding; using fallback");
1612
+ return null;
1613
+ }
1614
+ })();
1615
+ const summarizerClient = (() => {
1616
+ const fallbackBackend = summarizerBinding?.backendId ?? "claude";
1617
+ const fallbackModel = summarizerBinding?.modelId ?? "claude-haiku-4-5-20251001";
1618
+ if (fallbackBackend === "claude") {
1619
+ return new AnthropicSummarizerClient({
1620
+ modelId: fallbackModel,
1621
+ getApiKey: async () => {
1622
+ const direct = await secretBroker.getBackendApiKey("claude");
1623
+ if (direct)
1624
+ return direct;
1625
+ // Fall back to env (matches the daemon's API-key bridging policy).
1626
+ return process.env.ANTHROPIC_API_KEY ?? null;
1627
+ },
1628
+ });
1629
+ }
1630
+ // Codex / Gemini summarizer support is not yet implemented; the
1631
+ // worker translates `unsupported_backend` into a 'skipped' row so
1632
+ // the hourly_check skill drops to its legacy fetch path.
1633
+ return new UnsupportedSummarizerClient(fallbackBackend, fallbackModel);
1634
+ })();
1635
+ observerManager.register(new ObservationSummarizerWorker({
1636
+ db,
1637
+ client: summarizerClient,
1638
+ concurrency: config.observationSummarizerConcurrency,
1639
+ perCallTimeoutMs: config.observationSummarizerTimeoutMs,
1640
+ maxLlmCallsPerMinute: config.observationSummarizerMaxCallsPerMinute,
1641
+ queueDepthLimit: config.observationSummarizerQueueLimit,
1642
+ preFilter: { vipMailSenders: config.vipMailSenders },
1643
+ }));
1644
+ }
1645
+ else {
1646
+ logger.info("Observation summarizer disabled — pending rows stay pending");
1647
+ }
1648
+ // ── 8. Health Monitor ──
1649
+ const healthMonitor = new HealthMonitor({
1650
+ db,
1651
+ config,
1652
+ eventBus,
1653
+ messageHub,
1654
+ observerManager,
1655
+ startedAt,
1656
+ });
1657
+ // Notifications Center heartbeat (see docs/design/20-notifications-center.md).
1658
+ // Updates an in-memory tick timestamp every 30s; surfaced via /api/health
1659
+ // so the dashboard can detect a frozen event loop.
1660
+ const heartbeat = new Heartbeat();
1661
+ // ── 9. Scheduler ──
1662
+ const scheduler = new AgentScheduler(eventBus, db, config);
1663
+ // ── 9.1 Custom routine scheduler (B-007 §5.8) ──
1664
+ // Reads `routines/custom/*.md` from the context dir and registers a
1665
+ // cron job per enabled routine. Reloaded from the context API whenever
1666
+ // the agent or dashboard edits a file under that directory.
1667
+ const customRoutineScheduler = new CustomRoutineScheduler({
1668
+ contextDir: getContextDir(config),
1669
+ eventBus,
1670
+ timezone: config.timezone || undefined,
1671
+ });
1672
+ // ── 9.5 Signal Detector ──
1673
+ const signalDetector = new SignalDetector(config);
1674
+ // ── 10. Event Processing Pipeline ──
1675
+ const agentCore = new ClaudeCodeCore(config, writeTracker);
1676
+ const codexCore = new CodexCore(config);
1677
+ const geminiCore = new GeminiCliCore(config, writeTracker, undefined, db);
1678
+ // B-003 Phase 3 — wire the MCP session context so per-session workdirs pick
1679
+ // up the current DB + keychain state at spawn time. Each core stays
1680
+ // backward-compatible: without this call it simply runs without MCP.
1681
+ const mcpContext = { db, blobStore };
1682
+ agentCore.setMcpContext(mcpContext);
1683
+ codexCore.setMcpContext(mcpContext);
1684
+ geminiCore.setMcpContext(mcpContext);
1685
+ // DELEGATED-PROXY-API-DESIGN.md Phase A — boot janitor sweeps stale
1686
+ // `agent-sessions/proxy-*` tempdirs left by SIGKILL'd proxy invocations,
1687
+ // then construct the invoker so /api/mail/* and /api/calendar/* route
1688
+ // handlers can dispatch through it once Phase B wires the route shims.
1689
+ const janitorRemoved = runProxyTempdirJanitor(config.dataDir);
1690
+ if (janitorRemoved > 0) {
1691
+ logger.info({ removed: janitorRemoved }, "Boot janitor cleared stale delegated-proxy tempdirs");
1692
+ }
1693
+ const poolJanitorRemoved = runSessionPoolTempdirJanitor(join(config.dataDir, "agent-sessions"));
1694
+ if (poolJanitorRemoved > 0) {
1695
+ logger.info({ removed: poolJanitorRemoved }, "Boot janitor cleared stale delegated-task pool tempdirs");
1696
+ }
1697
+ // DELEGATED-TASK-MODE-DESIGN.md §11.1 — close `delegated_task.exec`
1698
+ // rows that were `in_progress` when the daemon last crashed. Without
1699
+ // this, the dashboard's in-flight counter and audit views drift forever.
1700
+ const taskOrphansClosed = runDelegatedTaskOrphanJanitor(db);
1701
+ if (taskOrphansClosed > 0) {
1702
+ logger.info({ closed: taskOrphansClosed }, "Boot janitor closed orphaned delegated_task in-progress rows");
1703
+ }
1704
+ const delegatedBackendInvoker = new DelegatedBackendInvoker({
1705
+ db,
1706
+ config,
1707
+ cores: { claude: agentCore, codex: codexCore, gemini: geminiCore },
1708
+ });
1709
+ let delegatedSyncWorker = null;
1710
+ const buildDelegatedSyncWorker = () => {
1711
+ if (!delegatedSyncWorker) {
1712
+ delegatedSyncWorker = new DelegatedSyncWorker({
1713
+ db,
1714
+ invoker: delegatedBackendInvoker,
1715
+ calendarId: config.googleCalendarId,
1716
+ timezone: config.timezone,
1717
+ todayWriteLock: morningRoutineLock,
1718
+ triggerRoadmapRefresh,
1719
+ });
1720
+ }
1721
+ return delegatedSyncWorker;
1722
+ };
1723
+ if (hasActiveDelegatedSyncIntegration(db)) {
1724
+ observerManager.register(buildDelegatedSyncWorker());
1725
+ }
1726
+ // ── Delegated probe observer (DELEGATED-MODE-V2 §7.1) ──
1727
+ // Hourly re-probe of delegated integrations' connector tools so the
1728
+ // `integration_probes` cache reflects current sign-in state. Without
1729
+ // this, `consultDelegatedConnectorHealth` (§4.5) has no signal to fire
1730
+ // on after the wizard's first probe — the §10 "user signed out hours
1731
+ // ago" risk row would stay silent.
1732
+ //
1733
+ // Registered here (mid-§10) rather than in §7 because it depends on the
1734
+ // three agent cores constructed at the top of this section. The
1735
+ // observerManager.startAll() call further down picks it up unchanged.
1736
+ {
1737
+ const { DelegatedProbeObserver } = await import("./observers/delegated-probe-observer.js");
1738
+ observerManager.register(new DelegatedProbeObserver({
1739
+ db,
1740
+ agentBackends: [agentCore, codexCore, geminiCore],
1741
+ intervalMinutes: config.delegatedProbeIntervalMinutes,
1742
+ }));
1743
+ }
1744
+ const notificationManager = new NotificationManager(messageHub, db, config);
1745
+ const authTelemetry = new AuthTelemetry(db);
1746
+ // Shared notifier factory for auth-health-monitor and auth-recovery (D3 fix).
1747
+ // Maps `kind` to notification_type + category so both callers share the
1748
+ // same routing / quiet-hours-bypass logic without duplicating 30+ lines.
1749
+ //
1750
+ // Probe-failure and recovery DMs bypass NotificationManager's own quiet-hours
1751
+ // + rate-limit gates via `category: "error"` (SAFETY_CATEGORIES member).
1752
+ // Keepalive DMs stay on the standard path. See Phase 4 self-critique B2 for
1753
+ // the anti-spam reasoning (clock-driven, ≤1 DM per cron tick).
1754
+ const makeAuthNotifier = (source) => ({
1755
+ send: async (message, options) => {
1756
+ const kind = options?.kind ?? "keepalive";
1757
+ const typeMap = {
1758
+ probe_failure: "auth.probe_failure",
1759
+ recovery: "auth.recovery",
1760
+ keepalive: "auth.keepalive_reminder",
1761
+ };
1762
+ const notificationType = typeMap[kind] ?? "auth.keepalive_reminder";
1763
+ // probe_failure and recovery bypass quiet-hours; keepalive does not.
1764
+ const category = kind === "keepalive" ? "auth-health" : AUTH_PROBE_NOTIFICATION_CATEGORY;
1765
+ await notificationManager.send(message, {
1766
+ type: notificationType,
1767
+ source,
1768
+ priority: EventPriority.NORMAL,
1769
+ timestamp: new Date(),
1770
+ data: {},
1771
+ correlationId: randomBytes(8).toString("hex"),
1772
+ }, {
1773
+ priority: "normal",
1774
+ category,
1775
+ destinationMode: "configured_only",
1776
+ });
1777
+ },
1778
+ });
1779
+ const authHealthMonitor = new AuthHealthMonitor(db, {
1780
+ claude: agentCore,
1781
+ codex: codexCore,
1782
+ gemini: geminiCore,
1783
+ }, authTelemetry, {
1784
+ notifier: makeAuthNotifier("auth-health-monitor"),
1785
+ // Forward references — these gate closures are registered here
1786
+ // but only fire once `scheduler.start()` ticks the hourly cron,
1787
+ // by which time `dispatcher` is fully constructed (see the
1788
+ // declaration further down in this file). Calling any of these
1789
+ // before then would hit TDZ; in practice the constructor and
1790
+ // `reconcilePendingRecoveries` / `runKeepaliveSweep` never do.
1791
+ isMorningRoutineActive: () => dispatcher.isMorningRoutineActive(),
1792
+ isQuietHours: () => notificationManager.isQuietHours(),
1793
+ probeDisabled: () => config.authProbeDisabled,
1794
+ });
1795
+ // Reset any recoveries that were in-flight when the daemon was last killed.
1796
+ const recovered = authHealthMonitor.reconcilePendingRecoveries();
1797
+ if (recovered > 0) {
1798
+ logger.info({ count: recovered }, "Reconciled stuck auth recoveries on startup");
1799
+ }
1800
+ // Run the 60-day keepalive sweep once on startup. Hourly probe is
1801
+ // registered via scheduler.setAuthProbeCallback — see §9.5.4.
1802
+ void authHealthMonitor.runKeepaliveSweep().catch((err) => {
1803
+ logger.warn({ err }, "Initial auth keepalive sweep failed");
1804
+ });
1805
+ const keepaliveTimer = setInterval(() => {
1806
+ void authHealthMonitor.runKeepaliveSweep().catch((err) => {
1807
+ logger.warn({ err }, "Periodic auth keepalive sweep failed");
1808
+ });
1809
+ }, 24 * 60 * 60 * 1000);
1810
+ keepaliveTimer.unref?.();
1811
+ // Phase 5/6: Interactive auth recovery manager. Uses the same notifier
1812
+ // sink as the AuthHealthMonitor so recovery DMs flow through the same
1813
+ // notification pipeline with the same anti-spam guarantees.
1814
+ const authRecovery = new AuthRecovery(db, authTelemetry, authHealthMonitor, makeAuthNotifier("auth-recovery"), {
1815
+ claudeRecoveryTimeoutMin: 10,
1816
+ codexRecoveryTimeoutMin: 15,
1817
+ geminiRecoveryTimeoutMin: 5,
1818
+ });
1819
+ // ── Scoped read tokens for ReadSensitive API endpoints ──
1820
+ // Tokens rotate per session/workdir execution and are only injected into
1821
+ // backend subprocess env, never into prompt-visible instruction files.
1822
+ const readTokenManager = new ScopedReadSensitiveTokenManager();
1823
+ const agentRouter = new BackendRouter(db, config, [agentCore, codexCore, geminiCore], notificationManager, authTelemetry,
1824
+ // Callback to materialize instruction files for a fallback backend in an
1825
+ // existing session workdir. Without this, a Claude→Codex heavy-tier
1826
+ // fallback would leave the dir with only CLAUDE.md and no AGENTS.md.
1827
+ // After materializing built-in files, re-sync user-authored skills so
1828
+ // the newly created backend-specific dir (e.g. .codex/skills/) also
1829
+ // receives user skills. The dispatcher's syncAllUserSkills ran BEFORE
1830
+ // router.execute(), so the fallback dir didn't exist at that point.
1831
+ (sessionDir, backendId, eventType, processKey) => {
1832
+ // services is the mutable ServiceRegistry — reading .calendar here
1833
+ // picks up the current availability (e.g. after mid-flight OAuth).
1834
+ // GitHub presence is read fresh from the unified `repositories` table
1835
+ // (rows with a github side); legacy `githubRepos` config is gone.
1836
+ const cfgServices = buildConfiguredServices(config, {
1837
+ ...services,
1838
+ github: selectGithubRepoSlugs(db).length > 0,
1839
+ });
1840
+ const mailAccounts = services.mail?.listActiveAccounts() ?? [];
1841
+ ensureBackendMaterialized(config.workspaceDir, sessionDir, backendId, eventType, processKey, cfgServices, mailAccounts, readIntegrations(db), config.character);
1842
+ syncAllUserSkills(sessionDir, join(config.dataDir, "skills"));
1843
+ });
1844
+ // Startup validation — warn if any delegated-mode variant files are
1845
+ // missing. Now checks both skill and task-flow variants for every
1846
+ // currently-delegated integration against its configured `delegatedBackend`
1847
+ // (not the union of all possible backends — see validateDelegatedStartup
1848
+ // docstring). Never throws; the PATCH route + /health do the hard-
1849
+ // rejection and user-surfacing downstream.
1850
+ {
1851
+ const startupIntegrations = readIntegrations(db);
1852
+ const missing = validateDelegatedStartup(config.workspaceDir, startupIntegrations);
1853
+ if (missing.skills.length > 0 || missing.taskFlows.length > 0) {
1854
+ logger.warn({ missingSkills: missing.skills, missingTaskFlows: missing.taskFlows }, "Delegated-mode variant files missing — agent will fall back to SKILL.md / direct task-flow for affected entries; surface in /health.integrationModes");
1855
+ }
1856
+ }
1857
+ const contextBuilder = new ContextBuilder(config, db, services);
1858
+ // Task flows are simple code constants — no file system, no hot-reload needed
1859
+ const sessionManager = new SessionManager(db, config);
1860
+ const messageRecorder = new MessageRecorder(db);
1861
+ const eventBroadcaster = new EventBroadcaster();
1862
+ const auditLogger = new AuditLogger(db, {
1863
+ // `/api/events/stream` is defined in terms of persisted agent_actions rows,
1864
+ // not raw EventBus payloads, so the broadcaster subscribes at the audit layer.
1865
+ onRowInserted: (row) => eventBroadcaster.broadcastEvent(row),
1866
+ });
1867
+ // Shared body for "something changed that affects what's baked into
1868
+ // active DM workdirs — refresh them in place." Used by:
1869
+ // - `onMailScopeChanged` (MailAccountRegistry scope-change hook)
1870
+ // - the integration-mode lifecycle (DELEGATED-PROXY-API-DESIGN.md
1871
+ // Phase F §4.8 — every mode flip re-materializes so the unified
1872
+ // skill body and per-backend instruction file reflect the new
1873
+ // state on the next turn without tearing down the SDK session)
1874
+ //
1875
+ // Returns null when there's no active DM session to refresh; otherwise
1876
+ // the per-session summary so callers can attach it to a broadcast or
1877
+ // structured log. Per-session failures stay inside the helper.
1878
+ const rematerializeActiveDmWorkdirs = (reason) => {
1879
+ const sessions = sessionManager.listActiveDmSessions();
1880
+ if (sessions.length === 0) {
1881
+ logger.debug({ reason }, "DM workdir refresh requested — no active DM sessions");
1882
+ return null;
1883
+ }
1884
+ const cfgServices = buildConfiguredServices(config, {
1885
+ ...services,
1886
+ github: selectGithubRepoSlugs(db).length > 0,
1887
+ });
1888
+ const mailAccounts = services.mail?.listActiveAccounts() ?? [];
1889
+ // Read integration state fresh inside the closure so a Phase F mode
1890
+ // flip's pre-`writeIntegrations` row is not what gets baked. The
1891
+ // `SkillsCompiler` uses this for variant resolution (non-proxy
1892
+ // integrations like Notion need `SKILL.delegated.<backend>.md`) and
1893
+ // to re-emit `applyAllDeniedToolsForSkill` soft-deny prose. Passing
1894
+ // an empty object would silently regress to the latent staleness bug
1895
+ // that Phase F surfaced.
1896
+ const integrations = readIntegrations(db);
1897
+ const summary = refreshDmSessionWorkdirs({
1898
+ projectRoot: config.workspaceDir,
1899
+ dataDir: config.dataDir,
1900
+ sessions,
1901
+ configuredServices: cfgServices,
1902
+ mailAccounts,
1903
+ integrations,
1904
+ character: config.character,
1905
+ });
1906
+ return { summary, mailAccounts };
1907
+ };
1908
+ // Real implementation of the mail-scope-changed hook. Wired by the
1909
+ // MailAccountRegistry constructor above through a forward-reference so the
1910
+ // registry can be built before sessionManager / eventBroadcaster exist.
1911
+ // Re-materializes every active DM session workdir so `accounts.md` and the
1912
+ // `external-services` skill reflect the new scope on the next turn without
1913
+ // tearing down the SDK session.
1914
+ onMailScopeChanged = (reason) => {
1915
+ const result = rematerializeActiveDmWorkdirs(reason);
1916
+ if (!result)
1917
+ return;
1918
+ logger.info({ reason, ...result.summary }, "Mail scope changed — DM session workdirs re-materialized");
1919
+ eventBroadcaster.broadcastEvent({
1920
+ kind: "mail_scope_changed",
1921
+ reason,
1922
+ activeAccounts: result.mailAccounts.length,
1923
+ ...result.summary,
1924
+ });
1925
+ };
1926
+ // Management Mode Phase 2 — shared migration primitives. Long timeout
1927
+ // because cross-fs copies of large vaults may legitimately run
1928
+ // multiple minutes; the lock itself is cheap and only blocks a
1929
+ // second concurrent /api/setup/migrate-context call.
1930
+ const migrationLock = new MigrationLock(60 * 60 * 1000);
1931
+ const contextWriteGate = new ContextWriteGate();
1932
+ initTaskFlows(config.workspaceDir, config.dataDir);
1933
+ const dispatcher = new EventDispatcher(eventBus, agentRouter, contextBuilder, getTaskFlow, notificationManager, sessionManager, messageRecorder, auditLogger, db, config, morningRoutineLock, services, roadmapWriteLock, writeTracker);
1934
+ dispatcher.setSignalDetector(signalDetector);
1935
+ // DOCS_QA_B7_DESIGN.md §11.1 — persistence-side citation validator
1936
+ // for docs_qa sessions. Lookup runs over fts_docs (indexed by the
1937
+ // docs indexer); the dispatcher only consults it when
1938
+ // `isDocsQAMessage(event)` is true, so this hook is inert for
1939
+ // chat/DM/routine flows and for installs that haven't ingested docs.
1940
+ const docsCitationLookup = makeDocsCitationLookup(db);
1941
+ dispatcher.setDocsCitationLookup(docsCitationLookup);
1942
+ // Docs-QA SSE adapter — DOCS_QA_B7_DESIGN.md §S4 / §S8. Sits
1943
+ // alongside `dashboardAdapter` on the same `platform="dashboard"`
1944
+ // surface; the `intent: "docs_qa"` discriminator on inbound events
1945
+ // forks dispatch into the docs-qa task flow + skill set + light
1946
+ // tier clamp. The adapter is exposed to `createDocsRoutes` for the
1947
+ // QA POST/SSE endpoints; outbound text reaches it via the dispatcher's
1948
+ // `IDashboardStream` slot fanned out by `CompositeDashboardStream`.
1949
+ //
1950
+ // It is intentionally NOT registered with `messageHub`: the hub keys
1951
+ // adapters by `platformName`, and both `DashboardAdapter` and this
1952
+ // adapter declare `platformName="dashboard"`. A second registration
1953
+ // would silently replace `dashboardAdapter` in the hub map (only a
1954
+ // warn-log), breaking `getAdapter("dashboard")`, the
1955
+ // start/stop lifecycle for the chat adapter, and any
1956
+ // `sendToPlatform("dashboard", ...)` caller (DocsQAAdapter.sendMessage
1957
+ // throws — it's streaming-only). DocsQAAdapter's lifecycle is owned
1958
+ // by the SSE route itself: clients register on `GET /docs/qa/stream`
1959
+ // and unregister on the request's `onAbort`.
1960
+ const docsQAAdapter = new DocsQAAdapter((event) => void eventBus.put(event), docsCitationLookup);
1961
+ // Fan-out: the dispatcher's single `IDashboardStream` slot drives
1962
+ // both the chat and docs-QA adapters. Each adapter no-ops on
1963
+ // unknown channelIds (per F3.2), so only the adapter that owns
1964
+ // the destination channelId actually emits — fan-out is naturally
1965
+ // safe (DOCS_QA_B7_DESIGN.md §9 F3.4 / §11.4).
1966
+ dispatcher.setDashboardStream(new CompositeDashboardStream([dashboardAdapter, docsQAAdapter]));
1967
+ dispatcher.setAttachmentStore(attachmentStore);
1968
+ // Local-Whisper voice transcription for inbound audio attachments.
1969
+ // See docs/design/appendices/voice-transcription.md.
1970
+ //
1971
+ // `enabled` is driven by the `voiceTranscriptionEnabled` runtime setting
1972
+ // (default `false`). The flag is opt-in via the dashboard's Voice Mode
1973
+ // card, which goes through `POST /api/voice/install` so the model
1974
+ // download and the daemon restart happen atomically — the constructor
1975
+ // here observes the persisted flag on the next boot.
1976
+ //
1977
+ // Env vars stay live for advanced operators who want to override the
1978
+ // model id, language, or duration cap without a settings round-trip.
1979
+ // `PA_VOICE_TRANSCRIPTION_ENABLED` is honoured as a fallback when the
1980
+ // setting is unset (legacy behaviour for installs predating the flag).
1981
+ const voiceTranscriberMaxDuration = Number(process.env.PA_VOICE_TRANSCRIPTION_MAX_DURATION_SEC ?? "600");
1982
+ const voiceEnvOverride = process.env.PA_VOICE_TRANSCRIPTION_ENABLED;
1983
+ // When `PA_VOICE_TRANSCRIPTION_ENABLED` is set, env wins (operator
1984
+ // override). Otherwise read live from `config` so the dashboard's
1985
+ // voice install flow — which mutates `config.voiceTranscriptionEnabled`
1986
+ // and persists the setting in the same boot — takes effect on the
1987
+ // next inbound audio attachment without waiting for a daemon restart.
1988
+ const voiceTranscriberEnabled = voiceEnvOverride !== undefined
1989
+ ? voiceEnvOverride.toLowerCase() !== "false"
1990
+ : () => config.voiceTranscriptionEnabled;
1991
+ dispatcher.setVoiceTranscriber(new VoiceTranscriber({
1992
+ db,
1993
+ modelDir: join(config.dataDir, "models", "whisper"),
1994
+ enabled: voiceTranscriberEnabled,
1995
+ model: process.env.PA_VOICE_TRANSCRIPTION_MODEL,
1996
+ language: process.env.PA_VOICE_TRANSCRIPTION_LANGUAGE ?? null,
1997
+ maxDurationSec: Number.isFinite(voiceTranscriberMaxDuration)
1998
+ ? voiceTranscriberMaxDuration
1999
+ : 600,
2000
+ }));
2001
+ dispatcher.setAuthRecovery(authRecovery);
2002
+ dispatcher.setAuthHealthMonitor(authHealthMonitor);
2003
+ // Wire the delegated-sync refresh callback. The thunk reads the live
2004
+ // `delegatedSyncWorker` reference each call so the dispatcher tracks
2005
+ // re-registration when an integration mode flips. When no delegated
2006
+ // integration is present, the worker is null and the call is a no-op
2007
+ // — the hourly check proceeds without extra latency.
2008
+ // See `docs/design/appendices/delegated-sync-opt-in.md` and the
2009
+ // worker's `runDisabledCadencesForHourlyCheck` doc-comment.
2010
+ dispatcher.setDelegatedSyncRefresh(async () => {
2011
+ await delegatedSyncWorker?.runDisabledCadencesForHourlyCheck();
2012
+ });
2013
+ // Messaging bang-commands (`!stop`/`!start`/`!cost`/`!report`) — owner DM
2014
+ // chokepoint that runs ahead of every other interceptor in handleMessage.
2015
+ // See docs/design/backlog/messaging-bang-commands.md.
2016
+ dispatcher.setBangCommandRegistry(createDefaultBangCommandRegistry());
2017
+ // P22 — wire the optimizer-workdir hooks. The `materialize` callback
2018
+ // captures `db`, `dataDir`, `workspaceDir`, `contextDir`, and `secretStore`
2019
+ // so the dispatcher branch can invoke it without importing the workdir
2020
+ // module directly. `teardown` cleans up under PA_DATA_DIR/optimizer-workdir/.
2021
+ {
2022
+ const { materializeOptimizerWorkdir, teardownOptimizerWorkdir } = await import("./core/skill-curation/workdir.js");
2023
+ dispatcher.setSkillCurationHooks({
2024
+ materialize: (opts) => materializeOptimizerWorkdir({
2025
+ db,
2026
+ dataDir: config.dataDir,
2027
+ workspaceDir: config.workspaceDir,
2028
+ contextDir: getContextDir(config),
2029
+ secretStore: secretBroker,
2030
+ cadence: readSkillCurationCadence(db),
2031
+ ...(opts?.manual ? { manual: true } : {}),
2032
+ ...(opts?.targetSkillsOverride ? { targetSkillsOverride: opts.targetSkillsOverride } : {}),
2033
+ }),
2034
+ teardown: teardownOptimizerWorkdir,
2035
+ });
2036
+ }
2037
+ if (isUserPaused(db)) {
2038
+ logger.info("Restored user-paused state, autonomous work remains paused");
2039
+ }
2040
+ emitRoadmapRefreshSink = (source) => dispatcher.emitRoadmapRefresh(source);
2041
+ agentCore.setReadTokenManager?.(readTokenManager);
2042
+ geminiCore.setReadTokenManager?.(readTokenManager);
2043
+ notificationManager.setSignalDetector(signalDetector);
2044
+ let startupComplete = false;
2045
+ let pendingGoogleServicesReady = false;
2046
+ const handleGoogleServicesReady = () => {
2047
+ // Start CalendarPoller if calendar was just hot-loaded AND the
2048
+ // google_calendar integration is in `direct` mode. When the integration
2049
+ // is `delegated`/`disabled` we keep the poller dormant — OAuth is wired
2050
+ // up but the user has explicitly chosen not to poll.
2051
+ if (services.calendar
2052
+ && !observerManager.has("calendar")
2053
+ && shouldStartObserversFor(db, "google_calendar")) {
2054
+ const poller = buildCalendarPoller();
2055
+ if (poller) {
2056
+ observerManager.register(poller);
2057
+ void poller.start();
2058
+ logger.info("CalendarPoller started via hot-reload");
2059
+ }
2060
+ }
2061
+ // Trigger morning_routine catchup if today.md is stale or missing
2062
+ // (same logic as runCatchup, ensures schedule generation after first auth)
2063
+ const contextDir = getContextDir(config);
2064
+ const todayMdPath = join(contextDir, "today.md");
2065
+ let needsMorning = false;
2066
+ if (existsSync(todayMdPath)) {
2067
+ const firstLine = readFileSync(todayMdPath, "utf-8").split("\n")[0];
2068
+ const today = getAgentDayDateStr(config.timezone || undefined, config.dayBoundaryHour);
2069
+ if (!firstLine.includes(today)) {
2070
+ needsMorning = true;
2071
+ }
2072
+ }
2073
+ else {
2074
+ needsMorning = true;
2075
+ }
2076
+ if (needsMorning) {
2077
+ // Morning routine's post-completion hook will also check roadmap staleness.
2078
+ logger.info("Google services ready — today.md stale, queueing morning_routine wake");
2079
+ scheduler.queueMorningRoutineWake("google_auth_ready");
2080
+ return;
2081
+ }
2082
+ // Only refresh roadmap independently when today.md is already current.
2083
+ // If morning_routine is needed, its post-completion hook will handle stale
2084
+ // roadmap regeneration after the day context has been rebuilt.
2085
+ if (isRoadmapStale(contextDir)) {
2086
+ logger.info("Google services ready — roadmap stale, emitting roadmap_refresh");
2087
+ dispatcher.emitRoadmapRefresh("google_auth_ready");
2088
+ }
2089
+ };
2090
+ const handleSecretChange = async (scope) => {
2091
+ switch (scope) {
2092
+ case "slack":
2093
+ await reloadSlackAdapter(true);
2094
+ return;
2095
+ case "telegram":
2096
+ await reloadTelegramAdapter(true);
2097
+ return;
2098
+ case "discord":
2099
+ await reloadDiscordAdapter(true);
2100
+ return;
2101
+ case "notion":
2102
+ await reloadNotionService();
2103
+ // Hot-reload: if the user just added the Notion API key while
2104
+ // the integration is already in `direct` mode, register the
2105
+ // poller now so they don't have to restart the daemon.
2106
+ // Mirrors the calendar-side `handleGoogleServicesReady` path —
2107
+ // the mode-flip route covers `delegated→direct`, and this
2108
+ // covers the orthogonal "key arrives after direct was set"
2109
+ // case. Idempotent via observerManager.has().
2110
+ if (services.notion
2111
+ && !observerManager.has("notion-poller")
2112
+ && Object.keys(config.notionDatabaseIds).length > 0
2113
+ && shouldStartObserversFor(db, "notion")) {
2114
+ const poller = buildNotionPoller();
2115
+ if (poller) {
2116
+ observerManager.register(poller);
2117
+ void poller.start();
2118
+ logger.info("NotionPoller started via hot-reload");
2119
+ }
2120
+ }
2121
+ return;
2122
+ case "github":
2123
+ await reloadGitHubService();
2124
+ if (gitWatcher && secretState.githubWebhookConfigured) {
2125
+ gitWatcher.enableWebhookMode();
2126
+ }
2127
+ return;
2128
+ case "google":
2129
+ await reloadGoogleServices();
2130
+ return;
2131
+ case "apple_calendar":
2132
+ await reloadAppleCalendarService();
2133
+ return;
2134
+ case "apiToken":
2135
+ default:
2136
+ return;
2137
+ }
2138
+ };
2139
+ // ── 11. Hono HTTP Server ──
2140
+ // Enable webhook fallback: when GitHub webhook is configured, reduce GitWatcher frequency
2141
+ const startupGitWatcher = gitWatcher;
2142
+ if (startupGitWatcher && secretState.githubWebhookConfigured) {
2143
+ startupGitWatcher.enableWebhookMode();
2144
+ }
2145
+ const handlePromptContextChanged = (path, reason, tier, metadata) => {
2146
+ const setupMode = dispatcher.getCurrentSetupMode();
2147
+ const decision = applyPromptContextStaleness({ path, reason, tier, metadata }, {
2148
+ dmStalenessStrict: config.dmStalenessStrict,
2149
+ setupInProgress: setupMode !== null,
2150
+ markContextChanged: () => markContextChanged(db),
2151
+ markActiveDmSessionsStale: (staleReason) => sessionManager.markActiveDmSessionsStale(staleReason),
2152
+ });
2153
+ logger.debug({
2154
+ path,
2155
+ reason,
2156
+ tier_decided: decision.effectiveTier,
2157
+ tier_requested: decision.requestedTier,
2158
+ tier_reason: metadata?.tierReason,
2159
+ dmStalenessStrict: config.dmStalenessStrict,
2160
+ mode: setupMode,
2161
+ invalidatesDmSessions: decision.invalidatesDmSessions,
2162
+ skippedForSetup: decision.skippedForSetup,
2163
+ }, "Prompt context staleness classified");
2164
+ if (decision.skippedForSetup) {
2165
+ logger.info({ path, reason, mode: setupMode }, "Skipping DM session stale flag - setup in progress");
2166
+ }
2167
+ };
2168
+ const app = createApp({
2169
+ db,
2170
+ config,
2171
+ secretBroker,
2172
+ readTokenValidator: (token) => readTokenManager.isValid(token),
2173
+ enforceReadToken: config.enforceReadToken,
2174
+ agentBackends: [agentCore, codexCore, geminiCore],
2175
+ authHealthMonitor,
2176
+ authRecovery,
2177
+ authTelemetry,
2178
+ eventBus,
2179
+ morningRoutineLock,
2180
+ roadmapWriteLock,
2181
+ migrationLock,
2182
+ contextWriteGate,
2183
+ observerManager,
2184
+ delegatedInvoker: delegatedBackendInvoker,
2185
+ gitAccountRegistry,
2186
+ onPrimaryVaultPathChange: (newPath) => primaryVaultWatcher.setVaultPath(newPath),
2187
+ onGitReposChanged: () => queueGitProjectInitsForCurrentConfig("config-patch"),
2188
+ getInFlightExecutions: () => dispatcher.getInFlightExecutions(),
2189
+ getHealthData: () => {
2190
+ const status = healthMonitor.getStatus();
2191
+ return {
2192
+ uptime: status.daemonUptime,
2193
+ eventBusSize: status.eventBusSize,
2194
+ activeSessions: status.activeSessions,
2195
+ connectedPlatforms: status.connectedPlatforms,
2196
+ registeredObservers: status.registeredObservers,
2197
+ missingContextFiles: status.missingContextFiles,
2198
+ contextFilesOk: status.contextFilesOk,
2199
+ };
2200
+ },
2201
+ getLastTickAt: () => heartbeat.getLastTickAt(),
2202
+ services,
2203
+ isStartupComplete: () => startupComplete,
2204
+ getIntegrationStatus,
2205
+ getMessagingStatus,
2206
+ // Bang-commands `/api/health` surface — messaging-bang-commands.md §6.1.
2207
+ // Returns `"ok"` for the unblocked case so dashboards never have to
2208
+ // null-check, while preserving the original gate enum for the blocked
2209
+ // states.
2210
+ getAutonomousState: () => dispatcher.isAutonomousAllowed() ?? "ok",
2211
+ getNotificationDestinations: () => ({
2212
+ defaultPlatforms: config.defaultNotificationPlatforms,
2213
+ effectiveFallbackPlatforms: messageHub.getEffectiveFallbackPlatforms(),
2214
+ }),
2215
+ getIntegrationDriftSyncStatus: () => delegatedSyncWorker?.getStatus() ?? {
2216
+ workerRunning: false,
2217
+ lastSuccessAt: null,
2218
+ circuitState: "ok",
2219
+ activeHours: { startHour: 4, endHour: 24 },
2220
+ withinActiveHours: false,
2221
+ cadences: {},
2222
+ unrecognizedIntervalKeys: [],
2223
+ ttlContractViolations: [],
2224
+ },
2225
+ // delegated-sync opt-in routes consume the live worker reference for
2226
+ // cadence Run Now + status snapshot. When no integration is in
2227
+ // delegated mode the worker is null and the routes report a
2228
+ // worker_unavailable / empty-status response — see
2229
+ // `docs/design/appendices/delegated-sync-opt-in.md`.
2230
+ get delegatedSyncWorker() {
2231
+ return delegatedSyncWorker ?? undefined;
2232
+ },
2233
+ sendNotification: async ({ message, platforms, priority, notificationType, originSessionId, }) => {
2234
+ const dispatchId = randomBytes(16).toString("hex");
2235
+ const deliveries = await messageHub.sendToUser(message, platforms, {
2236
+ dispatchId,
2237
+ notificationType: notificationType ?? "agent",
2238
+ priority: priority ?? "normal",
2239
+ contentSummary: message.slice(0, 200),
2240
+ });
2241
+ if (deliveries.length > 0) {
2242
+ const insert = db.prepare(`INSERT INTO notification_log (
2243
+ dispatch_id,
2244
+ notification_type,
2245
+ priority,
2246
+ platform,
2247
+ delivery_channel,
2248
+ delivery_message_id,
2249
+ content_summary,
2250
+ status,
2251
+ delivered_at
2252
+ )
2253
+ VALUES (?, ?, ?, ?, ?, ?, ?, 'delivered', CURRENT_TIMESTAMP)`);
2254
+ for (const delivery of deliveries) {
2255
+ insert.run(dispatchId, notificationType ?? "agent", priority ?? "normal", delivery.platform, delivery.channel, delivery.messageId ?? null, message.slice(0, 200));
2256
+ }
2257
+ recordProactiveForwardDeliveries({
2258
+ db,
2259
+ config,
2260
+ deliveries,
2261
+ content: message,
2262
+ dispatchId,
2263
+ dispatchIds: [dispatchId],
2264
+ originSessionIds: originSessionId !== undefined ? [originSessionId] : [],
2265
+ notificationType: "proactive_forward",
2266
+ });
2267
+ }
2268
+ return { dispatchId, deliveries };
2269
+ },
2270
+ markEventNotified: (correlationId) => {
2271
+ dispatcher.markEventNotified(correlationId);
2272
+ },
2273
+ onGoogleServicesReady: () => {
2274
+ if (!startupComplete) {
2275
+ pendingGoogleServicesReady = true;
2276
+ logger.info("Google services ready during bootstrap — deferring startup-sensitive actions");
2277
+ return;
2278
+ }
2279
+ handleGoogleServicesReady();
2280
+ },
2281
+ onScheduleConfigChanged: () => scheduler.reloadCrons(),
2282
+ onIntegrationModeChange: async (key, prev, next) => {
2283
+ const buildObserver = (observerName) => {
2284
+ // Registry-gated direct observers. Mail's multi-provider poller is a
2285
+ // deferred follow-up — gmail.observersTouched is empty, so that
2286
+ // branch remains unreachable today.
2287
+ if (observerName === "calendar")
2288
+ return buildCalendarPoller();
2289
+ if (observerName === "notion-poller")
2290
+ return buildNotionPoller();
2291
+ if (observerName === "git")
2292
+ return buildGitWatcher();
2293
+ if (observerName === "github")
2294
+ return buildGithubPoller();
2295
+ return null;
2296
+ };
2297
+ await applyIntegrationModeChange({
2298
+ db,
2299
+ observerManager,
2300
+ buildObserver,
2301
+ buildDelegatedSyncWorker,
2302
+ buildGitDelegatedCronObserver,
2303
+ // DELEGATED-PROXY-API-DESIGN.md Phase F (§4.8) — every mode
2304
+ // change re-materializes active DM workdirs so the next turn
2305
+ // picks up the new skill body / accounts.md / instruction file
2306
+ // without tearing down the SDK session.
2307
+ rematerializeDmSessions: (reason) => {
2308
+ const result = rematerializeActiveDmWorkdirs(reason);
2309
+ if (!result)
2310
+ return;
2311
+ logger.info({ reason, ...result.summary }, "Integration mode changed — DM session workdirs re-materialized");
2312
+ },
2313
+ }, key, prev, next);
2314
+ if (key === "git" && next.mode !== "direct") {
2315
+ gitWatcher = null;
2316
+ }
2317
+ },
2318
+ onMainBackendChange: (reason) => {
2319
+ // DELEGATED-MODE-V2-DESIGN.md §4.4 — every main-backend flip
2320
+ // re-materializes active DM workdirs so the next turn's skill
2321
+ // variant and instruction file reflect the new same- vs.
2322
+ // cross-backend topology. Mirrors the `onMailScopeChanged`
2323
+ // pattern: helper handles the "no active sessions" no-op + per-
2324
+ // session failure containment, and we broadcast a structured
2325
+ // event so the dashboard can refresh without polling.
2326
+ const result = rematerializeActiveDmWorkdirs(reason);
2327
+ if (!result)
2328
+ return;
2329
+ logger.info({ reason, ...result.summary }, "Main backend changed — DM session workdirs re-materialized");
2330
+ eventBroadcaster.broadcastEvent({
2331
+ kind: "main_backend_changed",
2332
+ reason,
2333
+ ...result.summary,
2334
+ });
2335
+ },
2336
+ onSetupStart: (mode) => {
2337
+ dispatcher.beginSetupMode(mode);
2338
+ },
2339
+ onSecretChanged: handleSecretChange,
2340
+ onSetupComplete: () => {
2341
+ dispatcher.clearSetupMode();
2342
+ // Kick off an immediate morning routine so today.md is generated
2343
+ // right away — otherwise the user completes setup mid-day and sits
2344
+ // without a populated today.md until 04:00 next morning. We go
2345
+ // through the scheduler's queueMorningRoutineWake so:
2346
+ // - the dedup check prevents a double-run if startup catchup was
2347
+ // about to kick off the same routine (race on first boot after
2348
+ // setup completes)
2349
+ // - the wake task is durable: if the daemon crashes before the
2350
+ // routine runs, it replays on restart via ScheduleWatcher
2351
+ // - ScheduleWatcher obeys the autonomous-work gate, so in the
2352
+ // unlikely case clearSetupMode fails to persist, the gate
2353
+ // still prevents the routine from running mid-setup.
2354
+ // Don't emit roadmap_refresh here — skeleton has "(Not yet configured)"
2355
+ // which isRoadmapStale() detects. The refresh will be triggered by:
2356
+ // - morning_routine's post-completion hook, or
2357
+ // - onGoogleServicesReady (once calendar auth completes)
2358
+ // This avoids generating a roadmap before calendar data is available.
2359
+ try {
2360
+ scheduler.queueMorningRoutineWake("setup_complete");
2361
+ }
2362
+ catch (err) {
2363
+ logger.error({ err }, "Failed to queue post-setup morning routine — today.md will not be generated until next 04:00");
2364
+ }
2365
+ },
2366
+ onPromptContextChanged: (path, reason, tier, metadata) => {
2367
+ // Layer-3 defense for the Customize Your Rules bug: even if the
2368
+ // setup conversation's own agent writes to today/roadmap/management-
2369
+ // rules mid-flight (not typical, but possible via skills), we must
2370
+ // NOT mark the owner-DM session stale. Marking stale would refresh
2371
+ // the conversation_sessions row on the next turn, drop the stored
2372
+ // Claude SDK session_id, and force a fresh `setup.initial` execute
2373
+ // that loses prior Q&A history. Layers 1 (autonomous-work gate)
2374
+ // and 2 (scope-agnostic currentSetupMode) keep prompt selection
2375
+ // correct, but only layer 3 preserves in-conversation continuity.
2376
+ // For loud-tier changes, persist the change moment FIRST so a crash
2377
+ // between the DB write and stale-flag update still leaves the durable
2378
+ // signal in place — the in-memory flag on SessionManager is lost on
2379
+ // restart, so if it were written before the DB write we could lose
2380
+ // the signal entirely on a process crash mid-hook.
2381
+ //
2382
+ // The persisted `dashboard_context_changed_at` is consulted by both
2383
+ // `continueDashboardSession` (resume path) and `getOrCreateDm`
2384
+ // (every DM turn) to decide whether the stored SDK session has gone
2385
+ // stale and must be discarded before resume.
2386
+ handlePromptContextChanged(path, reason, tier, metadata);
2387
+ },
2388
+ onIndexableContextChange: (_path) => {
2389
+ // API-route hint: any successful PUT/PATCH/DELETE under `/context/*`
2390
+ // queues a reconcile. The observer's own chokidar watcher already
2391
+ // catches manual edits the API bypasses (e.g. the user editing via
2392
+ // Obsidian when contextDir *is* the Obsidian vault) — this hint
2393
+ // shortens reconcile latency for API-origin writes from chokidar
2394
+ // debounce + stabilityThreshold to the observer's 10s debounce. The
2395
+ // reconciler short-circuits when nothing changed, so firing for
2396
+ // non-indexed paths is harmless.
2397
+ contextIndexReconciler.requestReconcile("manual");
2398
+ },
2399
+ onCustomRoutinesChanged: () => {
2400
+ try {
2401
+ customRoutineScheduler.reload();
2402
+ }
2403
+ catch (err) {
2404
+ logger.error({ err }, "Custom routine reload failed");
2405
+ }
2406
+ },
2407
+ triggerHourlyCheck: (source, options) => dispatcher.triggerHourlyCheck(source, options),
2408
+ triggerRoadmapRefresh: (source, options) => dispatcher.emitRoadmapRefresh(source, options),
2409
+ endDashboardSession: (channelId) => endDashboardSessionFromChannel({
2410
+ sessionManager,
2411
+ channelId,
2412
+ }),
2413
+ continueDashboardSession: async (sessionId) => continueDashboardSessionFromHistory({
2414
+ db,
2415
+ dataDir: config.dataDir,
2416
+ sessionManager,
2417
+ sessionId,
2418
+ }),
2419
+ writeTracker,
2420
+ blobStore,
2421
+ dashboardAdapter,
2422
+ eventBroadcaster,
2423
+ attachmentStore,
2424
+ auditLogger,
2425
+ validateAttachmentTurnToken: (token) => dispatcher.validateAttachmentTurnToken(token),
2426
+ whatsappControls: {
2427
+ isInitialized: () => whatsappAdapter !== null,
2428
+ enable: async () => {
2429
+ const adapter = buildWhatsAppAdapter();
2430
+ if (adapter.getStatus() === "disabled") {
2431
+ await adapter.start();
2432
+ }
2433
+ },
2434
+ disable: async () => {
2435
+ await teardownWhatsAppAdapter();
2436
+ },
2437
+ requestQr: async () => {
2438
+ if (!whatsappAdapter) {
2439
+ await whatsappControls_enable();
2440
+ }
2441
+ await whatsappAdapter.requestQR();
2442
+ },
2443
+ waitForQr: async (timeoutMs = 10_000) => {
2444
+ if (!whatsappAdapter) {
2445
+ await whatsappControls_enable();
2446
+ }
2447
+ const snapshot = await whatsappAdapter.waitForQr(timeoutMs);
2448
+ return whatsappQrResponseFromAdapter(whatsappAdapter, snapshot);
2449
+ },
2450
+ getQrResponse: () => whatsappQrResponseFromAdapter(whatsappAdapter),
2451
+ },
2452
+ messagingControls: {
2453
+ telegram: buildTelegramControls(),
2454
+ slack: buildSlackControls(),
2455
+ discord: buildDiscordControls(),
2456
+ },
2457
+ });
2458
+ // Local helper avoiding circular reference inside the object literal above.
2459
+ async function whatsappControls_enable() {
2460
+ const adapter = buildWhatsAppAdapter();
2461
+ if (adapter.getStatus() === "disabled") {
2462
+ await adapter.start();
2463
+ }
2464
+ }
2465
+ // Mount /api/docs/* (DOCS_QA_DESIGN.md §10.4 + DOCS_QA_B7_DESIGN.md
2466
+ // §S5–S6) after createApp so the indexer handle and the QA SSE
2467
+ // adapter can be threaded in without extending ApiDependencies. The
2468
+ // read endpoints don't need messaging/dispatcher deps; the QA
2469
+ // POST/SSE pair leans on `docsQAAdapter` to register clients and
2470
+ // enqueue docs_qa events.
2471
+ app.route("/api", createDocsRoutes({
2472
+ db,
2473
+ ...(docsIndexer ? { indexer: docsIndexer } : {}),
2474
+ docsQAAdapter,
2475
+ }));
2476
+ const server = serve({
2477
+ fetch: app.fetch,
2478
+ hostname: "127.0.0.1",
2479
+ port: config.apiPort,
2480
+ });
2481
+ logger.info({ port: config.apiPort }, "API server listening");
2482
+ void dispatcher.run(); // Start consuming dashboard events as soon as the API is live
2483
+ // Notifications Center heartbeat (docs/design/20-notifications-center.md
2484
+ // §"Daemon heartbeat"). MUST start immediately after the API server is
2485
+ // listening — i.e. before any awaited startup work below (catchup,
2486
+ // observers, etc.). `/api/health.lastTickAt` is exposed the moment the
2487
+ // server accepts requests, so any window in which the heartbeat is
2488
+ // constructed but not yet ticking shows the dashboard a stale timestamp
2489
+ // and trips the client-side `system.daemon_frozen` alert (90s threshold).
2490
+ // Startup catchup that runs a morning_routine inline can take several
2491
+ // minutes, so this ordering is load-bearing, not cosmetic.
2492
+ heartbeat.start();
2493
+ // ── 12. Catchup (recover missed actions after restart) ──
2494
+ // Register day boundary callback: summarize DM sessions at 4 AM before morning routine
2495
+ scheduler.setDayBoundaryCallback(async () => {
2496
+ await dispatcher.summarizeDmSessions();
2497
+ });
2498
+ // Register direct DM callback: sends scheduled messages without running an agent
2499
+ scheduler.setSendDmCallback(async (message, platforms) => {
2500
+ if (platforms && platforms.length > 0) {
2501
+ const deliveries = [];
2502
+ for (const platform of platforms) {
2503
+ deliveries.push(await messageHub.sendToPlatform(platform, "user", message));
2504
+ }
2505
+ return deliveries;
2506
+ }
2507
+ return messageHub.sendToUser(message);
2508
+ });
2509
+ scheduler.setHourlyCheckCallback((source) => dispatcher.triggerHourlyCheck(source));
2510
+ // B-004 Phase 2a — nightly context-index reconciler. The design doc
2511
+ // (§4.1, §5.3) originally proposed an `agent_schedule` row with
2512
+ // `task_type: "internal.reconcile_context_index"`, but the dispatcher's
2513
+ // non-"dm" scheduled-task path runs a model-backed task flow. A direct
2514
+ // scheduler-owned cron callback keeps all scheduled work visible in
2515
+ // `AgentScheduler` — the intent of the design — without dispatching a
2516
+ // backend for an internal daemon job.
2517
+ scheduler.setContextIndexReconcilerCallback(() => contextIndexReconciler.requestReconcile("cron"));
2518
+ // Reconciler writes go through the same prompt-cache invalidation path as
2519
+ // API-origin context writes. Installing the sink here (post-dispatcher)
2520
+ // means a reconcile during setup.initial does not destroy in-flight
2521
+ // setup session state — see the matching `onPromptContextChanged`
2522
+ // handler in the createApp dependencies above for the layered guards.
2523
+ contextIndexReconcilerPromptSink = handlePromptContextChanged;
2524
+ // Phase 4 auth probe — runs BEFORE the hourly check on each cron
2525
+ // tick so auth health detection happens independently of the
2526
+ // observation-threshold gate. checkAll() owns its own kill switch
2527
+ // (authProbeDisabled), morning-routine skip, and in-flight dedupe.
2528
+ scheduler.setAuthProbeCallback(() => authHealthMonitor.checkAll());
2529
+ // Wire the autonomous-work gate: when rules/management.md is missing
2530
+ // or a setup conversation is active, the scheduler pauses cron routines
2531
+ // and ScheduleWatcher claims. This prevents any autonomous turn from
2532
+ // racing with the dashboard setup flow and triggering the stale-session
2533
+ // bug that killed setup mode mid-conversation.
2534
+ scheduler.setAutonomousGate(() => dispatcher.isAutonomousAllowed());
2535
+ const startupCatchup = await runCatchup(db, dispatcher, config);
2536
+ // ── 13. Start all components ──
2537
+ await messageHub.startAll();
2538
+ scheduler.start();
2539
+ customRoutineScheduler.start();
2540
+ signalDetector.start();
2541
+ const registeredPlatforms = messageHub.getPlatforms();
2542
+ if (registeredPlatforms.length > 0 && !messageHub.getAdapter(config.primaryPlatform)) {
2543
+ const fallbackPlatform = messageHub.getEffectiveFallbackPlatforms()[0]
2544
+ ?? registeredPlatforms[0];
2545
+ logger.error({
2546
+ requestedPrimary: config.primaryPlatform,
2547
+ fallbackPrimary: fallbackPlatform,
2548
+ }, "Primary platform is not registered, falling back");
2549
+ messageHub.setPrimaryPlatform(fallbackPlatform);
2550
+ config.primaryPlatform = fallbackPlatform;
2551
+ }
2552
+ await observerManager.startAll();
2553
+ // Start the integrations.md fs-watcher after the observer manager is up so
2554
+ // a chokidar initialization error surfaces alongside the main observer
2555
+ // lifecycle instead of during boot critical path.
2556
+ try {
2557
+ managementMdWatcher = startManagementMdWatcher(config.dataDir, db, {
2558
+ workspaceDir: config.workspaceDir,
2559
+ // SETUP-FLOW-REDESIGN-PLAN §6.2 — supply live external-vault state
2560
+ // each reconcile so the Note Sources section tracks
2561
+ // `PATCH /api/config` edits that don't touch integrations.md
2562
+ // directly.
2563
+ getNoteSources: () => ({
2564
+ externalObsidianVaultPath: config.externalObsidianVaultPath,
2565
+ externalObsidianWatch: config.externalObsidianWatch,
2566
+ }),
2567
+ sendNotification: async (params) => {
2568
+ await notificationManager.send(params.message, {
2569
+ type: params.notificationType ?? "integration.variant_missing",
2570
+ source: "management-md-watcher",
2571
+ priority: EventPriority.NORMAL,
2572
+ timestamp: new Date(),
2573
+ data: {},
2574
+ correlationId: randomBytes(8).toString("hex"),
2575
+ }, {
2576
+ priority: params.priority ?? "normal",
2577
+ destinationMode: "configured_only",
2578
+ });
2579
+ },
2580
+ });
2581
+ }
2582
+ catch (err) {
2583
+ logger.error({ err }, "integrations.md watcher failed to start");
2584
+ }
2585
+ // Start the rules/management.md fs-watcher alongside its integrations.md
2586
+ // sibling so hand-edits to A-section bindings flow back into the DB. The
2587
+ // watcher is a no-op when the post-setup branch above did not run
2588
+ // (contextDir absent), since chokidar against a missing path is silent.
2589
+ if (isSetupCompleted(db) && !readDegradedMode(db)) {
2590
+ try {
2591
+ managementRegistryWatcher = startManagementRegistryWatcher(getContextDir(config), db);
2592
+ }
2593
+ catch (err) {
2594
+ logger.error({ err }, "rules/management.md watcher failed to start");
2595
+ }
2596
+ }
2597
+ healthMonitor.start();
2598
+ // heartbeat.start() ran earlier — see the comment near the API listen
2599
+ // call. Keeping it there is required so the dashboard's frozen-alert
2600
+ // does not fire during a long startup catchup.
2601
+ // ── Management Mode health probe (plan §5.4) ──
2602
+ // Poll every 30s. Probe is read-only (no mkdir) so a user deleting their
2603
+ // vault directory flips us to degraded instead of silently re-creating.
2604
+ // Timer runs in all modes so `vaultMode: plain ↔ obsidian` flips via
2605
+ // PATCH /api/config are picked up without restart.
2606
+ const vaultHealthTimer = setInterval(() => {
2607
+ try {
2608
+ const probe = runVaultHealthProbe(config, db);
2609
+ if (probe.action === "entered") {
2610
+ logger.warn({ reason: probe.reason }, "Vault health probe entered degraded mode");
2611
+ }
2612
+ else if (probe.action === "lifted") {
2613
+ logger.info("Vault health probe lifted degraded mode");
2614
+ }
2615
+ }
2616
+ catch (err) {
2617
+ logger.error({ err }, "vault health probe failed");
2618
+ }
2619
+ }, 30_000);
2620
+ vaultHealthTimer.unref();
2621
+ // Management Mode Phase 2 — daily backup retention sweep. Runs once
2622
+ // per day to remove completed `migration-backups/*` directories whose
2623
+ // 7-day retention has expired. An initial tick fires shortly after
2624
+ // startup so a daemon that runs for less than a full day still sweeps
2625
+ // old backups left from a previous session.
2626
+ const migrationBackupSweepInitial = setTimeout(() => {
2627
+ try {
2628
+ sweepExpiredMigrationBackups(db);
2629
+ }
2630
+ catch (err) {
2631
+ logger.error({ err }, "Initial migration-backup sweep failed");
2632
+ }
2633
+ }, 60_000);
2634
+ migrationBackupSweepInitial.unref();
2635
+ const migrationBackupSweepTimer = setInterval(() => {
2636
+ try {
2637
+ sweepExpiredMigrationBackups(db);
2638
+ }
2639
+ catch (err) {
2640
+ logger.error({ err }, "Daily migration-backup sweep failed");
2641
+ }
2642
+ }, 24 * 60 * 60 * 1000);
2643
+ migrationBackupSweepTimer.unref();
2644
+ startupComplete = true;
2645
+ if (pendingGoogleServicesReady) {
2646
+ pendingGoogleServicesReady = false;
2647
+ handleGoogleServicesReady();
2648
+ }
2649
+ logger.info("All components started");
2650
+ void runPostMessagingCatchup(dispatcher, startupCatchup).catch((err) => {
2651
+ logger.error({ err }, "Post-messaging catchup failed");
2652
+ });
2653
+ // ── 15. Graceful shutdown ──
2654
+ const shutdown = async (signal) => {
2655
+ logger.info({ signal }, "Shutdown signal received");
2656
+ dispatcher.stop(); // Signals dispatcher to exit run() loop
2657
+ scheduler.stop();
2658
+ customRoutineScheduler.stop();
2659
+ healthMonitor.stop();
2660
+ heartbeat.stop();
2661
+ signalDetector.stop();
2662
+ notificationManager.stop(); // Clear pending batch-flush timer
2663
+ authRecovery.shutdown(); // Kill any active recovery subprocesses
2664
+ clearInterval(vaultHealthTimer);
2665
+ clearTimeout(migrationBackupSweepInitial);
2666
+ clearInterval(migrationBackupSweepTimer);
2667
+ if (managementMdWatcher) {
2668
+ await managementMdWatcher.stop().catch((err) => {
2669
+ logger.warn({ err }, "integrations.md watcher stop failed");
2670
+ });
2671
+ }
2672
+ if (managementRegistryWatcher) {
2673
+ await managementRegistryWatcher.stop().catch((err) => {
2674
+ logger.warn({ err }, "rules/management.md watcher stop failed");
2675
+ });
2676
+ }
2677
+ if (docsIndexer) {
2678
+ await docsIndexer.stop().catch((err) => {
2679
+ logger.warn({ err }, "docs indexer stop failed");
2680
+ });
2681
+ }
2682
+ await observerManager.stopAll();
2683
+ await messageHub.stopAll();
2684
+ eventBus.close();
2685
+ // Close HTTP server
2686
+ if ("close" in server) {
2687
+ server.close();
2688
+ }
2689
+ db.close();
2690
+ logger.info("Daemon stopped");
2691
+ process.exit(0);
2692
+ };
2693
+ process.on("SIGINT", () => void shutdown("SIGINT"));
2694
+ process.on("SIGTERM", () => void shutdown("SIGTERM"));
2695
+ if (process.platform === "win32") {
2696
+ process.on("SIGBREAK", () => void shutdown("SIGBREAK"));
2697
+ }
2698
+ logger.info(`${APP_NAME} Daemon ready`);
2699
+ }
2700
+ async function runCatchup(db, dispatcher, config) {
2701
+ // Setup gate — on first boot (before rules/management.md exists) we must
2702
+ // NOT run the morning routine. Without user/profile.md / rules/management.md
2703
+ // the generated today.md is meaningless AND it fires
2704
+ // onPromptContextChanged → markActiveDmSessionsStale, which destroys the
2705
+ // dashboard setup conversation on the user's next turn. Autonomous
2706
+ // catchup will run on the first normal boot *after* setup completes.
2707
+ const gateReason = dispatcher.isAutonomousAllowed();
2708
+ if (gateReason !== null) {
2709
+ logger.info({ reason: gateReason }, "Skipping startup catchup — autonomous work paused for setup");
2710
+ return {
2711
+ postMessagingRoadmapRefresh: false,
2712
+ postMessagingRoutines: [],
2713
+ postMessagingHourlyCheck: false,
2714
+ };
2715
+ }
2716
+ const now = new Date();
2717
+ const tz = config.timezone || undefined;
2718
+ const contextDir = getContextDir(config);
2719
+ const todayMdPath = join(contextDir, "today.md");
2720
+ const { start: agentDayStartUtc, end: agentDayEndUtc } = getAgentDayBoundsUtc(tz, config.dayBoundaryHour, now);
2721
+ const skippedPending = discardStalePendingSchedules(db, agentDayStartUtc);
2722
+ if (skippedPending > 0) {
2723
+ logger.warn({ count: skippedPending }, "Discarded stale pending schedules at startup");
2724
+ }
2725
+ const runningRecovery = recoverOrphanedRunningSchedules(db, agentDayStartUtc);
2726
+ if (runningRecovery.skipped > 0 || runningRecovery.failed > 0) {
2727
+ logger.warn(runningRecovery, "Recovered orphaned running schedules without replay");
2728
+ }
2729
+ // Check if morning routine is needed
2730
+ let needsMorning = false;
2731
+ if (existsSync(todayMdPath)) {
2732
+ const firstLine = readFileSync(todayMdPath, "utf-8").split("\n")[0];
2733
+ const today = getAgentDayDateStr(tz, config.dayBoundaryHour, now);
2734
+ if (!firstLine.includes(today)) {
2735
+ needsMorning = true;
2736
+ }
2737
+ }
2738
+ else {
2739
+ needsMorning = true;
2740
+ }
2741
+ const dueCatchupRoutines = getDueCatchupRoutines(db, config, agentDayStartUtc, agentDayEndUtc, now);
2742
+ const needsHourlyCheckCatchup = shouldCatchUpHourlyCheck(db, config, now);
2743
+ let ranMorningCatchup = false;
2744
+ if (needsMorning) {
2745
+ try {
2746
+ await dispatcher.summarizeDmSessions();
2747
+ }
2748
+ catch (err) {
2749
+ logger.error({ err }, "DM summarization catchup failed before morning routine");
2750
+ }
2751
+ logger.info("Stale today.md detected, running morning_routine catchup inline");
2752
+ await dispatcher.processInline({
2753
+ ...createEvent({
2754
+ type: "routine.morning_routine",
2755
+ source: "catchup",
2756
+ priority: EventPriority.HIGH,
2757
+ data: {
2758
+ postCatchupRoutines: dueCatchupRoutines,
2759
+ postCatchupHourlyCheck: needsHourlyCheckCatchup,
2760
+ deferPostMorningCatchupsUntilStartupReady: true,
2761
+ },
2762
+ }),
2763
+ routine: "morning_routine",
2764
+ });
2765
+ ranMorningCatchup = true;
2766
+ if (!hasFreshAgentDayTodayMd(todayMdPath, tz, config.dayBoundaryHour)) {
2767
+ logger.warn("Startup morning catchup did not produce a fresh today.md — deferring remaining catchup work");
2768
+ return {
2769
+ postMessagingRoadmapRefresh: false,
2770
+ postMessagingRoutines: [],
2771
+ postMessagingHourlyCheck: false,
2772
+ };
2773
+ }
2774
+ return {
2775
+ postMessagingRoadmapRefresh: isRoadmapStale(contextDir),
2776
+ postMessagingRoutines: dueCatchupRoutines,
2777
+ postMessagingHourlyCheck: needsHourlyCheckCatchup,
2778
+ };
2779
+ }
2780
+ if (!ranMorningCatchup && isRoadmapStale(contextDir)) {
2781
+ logger.info("Roadmap stale at startup, running roadmap_refresh catchup inline");
2782
+ await processRoutineCatchup(dispatcher, "roadmap_refresh");
2783
+ }
2784
+ return {
2785
+ postMessagingRoadmapRefresh: false,
2786
+ postMessagingRoutines: dueCatchupRoutines,
2787
+ postMessagingHourlyCheck: needsHourlyCheckCatchup,
2788
+ };
2789
+ }
2790
+ async function runPostMessagingCatchup(dispatcher, catchup) {
2791
+ if (catchup.postMessagingRoadmapRefresh) {
2792
+ logger.info("Running roadmap_refresh catchup after messaging startup");
2793
+ await processRoutineCatchup(dispatcher, "roadmap_refresh");
2794
+ }
2795
+ for (const routine of catchup.postMessagingRoutines) {
2796
+ logger.info({ routine }, "Running same-day routine catchup after messaging startup");
2797
+ await processRoutineCatchup(dispatcher, routine);
2798
+ }
2799
+ if (catchup.postMessagingHourlyCheck) {
2800
+ logger.info("Triggering hourly_check catchup after messaging startup");
2801
+ await dispatcher.triggerHourlyCheck("catchup_startup", { force: false });
2802
+ }
2803
+ }
2804
+ async function processRoutineCatchup(dispatcher, routine) {
2805
+ await dispatcher.processInline({
2806
+ ...createEvent({
2807
+ type: `routine.${routine}`,
2808
+ source: "catchup",
2809
+ priority: routine === "hourly_check" ? EventPriority.NORMAL : EventPriority.HIGH,
2810
+ }),
2811
+ routine,
2812
+ });
2813
+ }
2814
+ function getDueCatchupRoutines(db, config, agentDayStartUtc, agentDayEndUtc, now) {
2815
+ const tz = config.timezone || undefined;
2816
+ const progressMinutes = getAgentDayProgressMinutes(tz, config.dayBoundaryHour, now);
2817
+ const dueAt18 = getProgressMinutesForHour(18, config.dayBoundaryHour);
2818
+ if (progressMinutes < dueAt18) {
2819
+ return [];
2820
+ }
2821
+ const routines = [];
2822
+ const agentDayStartMs = parseSqliteUtcMs(agentDayStartUtc);
2823
+ const agentDayLocal = nowInTimezone(tz, new Date(agentDayStartMs));
2824
+ const tomorrowLocal = nowInTimezone(tz, new Date(agentDayStartMs + 24 * 60 * 60 * 1000));
2825
+ if (!hasActionInWindow(db, "routine.evening_review", agentDayStartUtc, agentDayEndUtc)) {
2826
+ routines.push("evening_review");
2827
+ }
2828
+ if (agentDayLocal.dayOfWeek === 5 &&
2829
+ !hasActionInWindow(db, "routine.weekly_review", agentDayStartUtc, agentDayEndUtc)) {
2830
+ routines.push("weekly_review");
2831
+ }
2832
+ if (tomorrowLocal.day === 1 &&
2833
+ !hasActionInWindow(db, "routine.monthly_review", agentDayStartUtc, agentDayEndUtc)) {
2834
+ routines.push("monthly_review");
2835
+ }
2836
+ return routines;
2837
+ }
2838
+ function shouldCatchUpHourlyCheck(db, config, now) {
2839
+ if (!config.hourlyCheckEnabled) {
2840
+ return false;
2841
+ }
2842
+ const tz = config.timezone || undefined;
2843
+ const local = nowInTimezone(tz, now);
2844
+ if (local.hours < config.hourlyCheckActiveStartHour ||
2845
+ local.hours >= config.hourlyCheckActiveEndHour ||
2846
+ local.hours === config.dayBoundaryHour) {
2847
+ return false;
2848
+ }
2849
+ // Slot anchors to `activeStartHour`, mirroring shouldFireHourlyTickAt
2850
+ // in scheduler.ts so the catch-up function picks the same slot the
2851
+ // cron callback would have fired at. The earlier branch already
2852
+ // returned false when local.hours < activeStartHour, so the offset is
2853
+ // always non-negative here.
2854
+ const anchorMinutes = config.hourlyCheckActiveStartHour * 60;
2855
+ const offsetFromAnchor = local.hours * 60 + local.minutes - anchorMinutes;
2856
+ const slotOffsetFromAnchor = Math.floor(offsetFromAnchor / config.hourlyCheckIntervalMinutes) *
2857
+ config.hourlyCheckIntervalMinutes;
2858
+ const slotMinutesSinceMidnight = anchorMinutes + slotOffsetFromAnchor;
2859
+ const dayStartUtc = getAgentDayBoundsUtc(tz, 0, now).start;
2860
+ const slotStartMs = parseSqliteUtcMs(dayStartUtc) + slotMinutesSinceMidnight * 60 * 1000;
2861
+ const slotStartUtc = formatSqliteDatetime(new Date(slotStartMs));
2862
+ return !hasActionInWindow(db, "routine.hourly_check", slotStartUtc, formatSqliteDatetime(now));
2863
+ }
2864
+ function getProgressMinutesForHour(hour, dayBoundaryHour) {
2865
+ const scheduledMinutes = hour * 60;
2866
+ const boundaryMinutes = dayBoundaryHour * 60;
2867
+ return scheduledMinutes >= boundaryMinutes
2868
+ ? scheduledMinutes - boundaryMinutes
2869
+ : 24 * 60 - boundaryMinutes + scheduledMinutes;
2870
+ }
2871
+ function hasFreshAgentDayTodayMd(todayMdPath, timezone, dayBoundaryHour) {
2872
+ if (!existsSync(todayMdPath)) {
2873
+ return false;
2874
+ }
2875
+ const firstLine = readFileSync(todayMdPath, "utf-8").split("\n")[0] ?? "";
2876
+ const today = getAgentDayDateStr(timezone, dayBoundaryHour);
2877
+ return firstLine.includes(today);
2878
+ }
2879
+ // P22 — read the operator's chosen cadence for skill curation runs.
2880
+ // Mirrors the helper in `core/scheduler.ts` so the dispatcher hook here can
2881
+ // resolve cadence at runtime without crossing module boundaries.
2882
+ function readSkillCurationCadence(db) {
2883
+ const row = db
2884
+ .prepare(`SELECT value_json FROM runtime_state WHERE key = 'skill_curation.config'`)
2885
+ .get();
2886
+ if (!row)
2887
+ return "weekly";
2888
+ try {
2889
+ const v = JSON.parse(row.value_json);
2890
+ return v.cadence ?? "weekly";
2891
+ }
2892
+ catch {
2893
+ return "weekly";
2894
+ }
2895
+ }
2896
+ // ── Global safety net ──
2897
+ // Catch unhandled rejections from fire-and-forget patterns (void async calls)
2898
+ // so they are logged before Node.js 22+ terminates the process.
2899
+ process.on("unhandledRejection", (reason) => {
2900
+ const err = reason instanceof Error ? reason : new Error(String(reason));
2901
+ logger.fatal({ error: err.message, stack: err.stack }, "Unhandled promise rejection — this is a bug");
2902
+ });
2903
+ process.on("uncaughtException", (err) => {
2904
+ logger.fatal({ error: err.message, stack: err.stack }, "Uncaught exception — process will exit");
2905
+ process.exit(1);
2906
+ });
2907
+ // ── Entry point ──
2908
+ startup().catch((err) => {
2909
+ const e = err;
2910
+ logger.fatal({ error: e?.message, code: e?.code, stack: e?.stack }, "Daemon startup failed");
2911
+ process.exit(1);
2912
+ });
2913
+ //# sourceMappingURL=index.js.map