elanous 0.2.22 → 0.2.23

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 (705) hide show
  1. package/NOTICE +7 -0
  2. package/README.md +32 -22
  3. package/apps/pwa/out/404/index.html +1 -1
  4. package/apps/pwa/out/404.html +1 -1
  5. package/apps/pwa/out/_next/static/chunks/{119fc678.6f591cdb84ca5791.js → 0673cc54.8e2ef1c23e49e190.js} +1 -1
  6. package/apps/pwa/out/_next/static/chunks/{a9f2b54d-40c16bd0efdb04b8.js → 0a09e062-34f747a17d8d8c2e.js} +1 -1
  7. package/apps/pwa/out/_next/static/chunks/{f8626309.a4e5d362b1d55e59.js → 0e3acd34.d42b2ef87fdf4234.js} +1 -1
  8. package/apps/pwa/out/_next/static/chunks/1168.a0fb3d9db76c00bd.js +77 -0
  9. package/apps/pwa/out/_next/static/chunks/{7175-350f4ce2873bc038.js → 1325-dc3f3c21eb181cb4.js} +1 -1
  10. package/apps/pwa/out/_next/static/chunks/1357.a843c41d2cd7ec31.js +34 -0
  11. package/apps/pwa/out/_next/static/chunks/1430.53f49882b17607c2.js +24 -0
  12. package/apps/pwa/out/_next/static/chunks/1459-be6509b3419d8a9f.js +1 -0
  13. package/apps/pwa/out/_next/static/chunks/{6767.8e6c5f6e744fcd9f.js → 1505.5826671109111e75.js} +2 -2
  14. package/apps/pwa/out/_next/static/chunks/153.0a4e3cd516930491.js +1 -0
  15. package/apps/pwa/out/_next/static/chunks/1572.7bc291352e241a45.js +1 -0
  16. package/apps/pwa/out/_next/static/chunks/1593-d07ad068c120dfb0.js +1 -0
  17. package/apps/pwa/out/_next/static/chunks/1608.b7c900baa56cab32.js +1 -0
  18. package/apps/pwa/out/_next/static/chunks/1615-4d8156febaac4b35.js +1 -0
  19. package/apps/pwa/out/_next/static/chunks/1646.e03f0fbc26bc72f6.js +1 -0
  20. package/apps/pwa/out/_next/static/chunks/{7327-2d69fb321ba73389.js → 1669-da843ed73aa301f5.js} +1 -1
  21. package/apps/pwa/out/_next/static/chunks/176.13ba0659e3e96fd4.js +1 -0
  22. package/apps/pwa/out/_next/static/chunks/1990.9c60f495ef33c070.js +36 -0
  23. package/apps/pwa/out/_next/static/chunks/2042e469.f86158dd5c1e51a1.js +122 -0
  24. package/apps/pwa/out/_next/static/chunks/2528.0af5c0e1fad88df6.js +1 -0
  25. package/apps/pwa/out/_next/static/chunks/2543-28ec6aeb80a5e1e1.js +1 -0
  26. package/apps/pwa/out/_next/static/chunks/2561.43c99d6ee914ee6a.js +62 -0
  27. package/apps/pwa/out/_next/static/chunks/2638.b39ab2f674caa1a5.js +1 -0
  28. package/apps/pwa/out/_next/static/chunks/272-b23d2e92161d11fd.js +1 -0
  29. package/apps/pwa/out/_next/static/chunks/{7899.597af4a9996c907f.js → 2721.bc9a8f36bd85c07a.js} +1 -1
  30. package/apps/pwa/out/_next/static/chunks/2778.67e0c1d484189ccf.js +1 -0
  31. package/apps/pwa/out/_next/static/chunks/2841.10ff309f6a3168af.js +1 -0
  32. package/apps/pwa/out/_next/static/chunks/2886-9639f97d523d752a.js +1 -0
  33. package/apps/pwa/out/_next/static/chunks/2978.57fb87ce21cc5a85.js +1 -0
  34. package/apps/pwa/out/_next/static/chunks/3664.15f968c39d15bd28.js +149 -0
  35. package/apps/pwa/out/_next/static/chunks/3786-935eb3fcde90e496.js +1 -0
  36. package/apps/pwa/out/_next/static/chunks/3813.df4a01ae89c6f63f.js +1 -0
  37. package/apps/pwa/out/_next/static/chunks/3961-fbef064c213b2b18.js +1 -0
  38. package/apps/pwa/out/_next/static/chunks/3992.f79c70480e8c4077.js +309 -0
  39. package/apps/pwa/out/_next/static/chunks/40-413e8641de9ed1e5.js +1 -0
  40. package/apps/pwa/out/_next/static/chunks/4054-521f11a503daed64.js +1 -0
  41. package/apps/pwa/out/_next/static/chunks/4315.e7356f34c7d8ec79.js +1 -0
  42. package/apps/pwa/out/_next/static/chunks/4489-bf629de6f04f7681.js +1 -0
  43. package/apps/pwa/out/_next/static/chunks/4540.557733cc88c6a82b.js +1 -0
  44. package/apps/pwa/out/_next/static/chunks/462.0275b4338c5b6337.js +1 -0
  45. package/apps/pwa/out/_next/static/chunks/4658-7ed9266098a787e4.js +1 -0
  46. package/apps/pwa/out/_next/static/chunks/4945-0f4046cfc3228ea3.js +1 -0
  47. package/apps/pwa/out/_next/static/chunks/{d70879db.c66c56a95a91efc6.js → 4c5e95ce.83e9b16add6fcd8c.js} +1 -1
  48. package/apps/pwa/out/_next/static/chunks/5105-18b4165c5d3a86b0.js +1 -0
  49. package/apps/pwa/out/_next/static/chunks/5205-6992210fe31f45f4.js +1 -0
  50. package/apps/pwa/out/_next/static/chunks/5287-72898b126d722457.js +1 -0
  51. package/apps/pwa/out/_next/static/chunks/{6158.566c890adc06a459.js → 5313.1697309c532b9856.js} +2 -2
  52. package/apps/pwa/out/_next/static/chunks/{79b78388.663188133449d707.js → 53746282.1be36038d1abd949.js} +1 -1
  53. package/apps/pwa/out/_next/static/chunks/5474.991b90688c3cb321.js +1 -0
  54. package/apps/pwa/out/_next/static/chunks/5544-871146192d095594.js +1 -0
  55. package/apps/pwa/out/_next/static/chunks/5578.ac963634373b28c6.js +286 -0
  56. package/apps/pwa/out/_next/static/chunks/5935.d5e8ca3e096772e8.js +1 -0
  57. package/apps/pwa/out/_next/static/chunks/5967-c0a80f8ffe5d4050.js +1 -0
  58. package/apps/pwa/out/_next/static/chunks/{1268.0840ff91e083fcbd.js → 5974.360f8441eb0ac1ec.js} +1 -1
  59. package/apps/pwa/out/_next/static/chunks/6006.84e8b418a7a0db97.js +1 -0
  60. package/apps/pwa/out/_next/static/chunks/6013-1267ba7454d6764b.js +1 -0
  61. package/apps/pwa/out/_next/static/chunks/6038-1e0f7ee1086da4cf.js +1 -0
  62. package/apps/pwa/out/_next/static/chunks/617.709dd0019fe99796.js +1 -0
  63. package/apps/pwa/out/_next/static/chunks/6261.fb9bf94947094636.js +74 -0
  64. package/apps/pwa/out/_next/static/chunks/6411.1966866fae31ffa9.js +1 -0
  65. package/apps/pwa/out/_next/static/chunks/6481-b829d99aa8d158f5.js +1 -0
  66. package/apps/pwa/out/_next/static/chunks/6541.2b7daa043100d0fa.js +1 -0
  67. package/apps/pwa/out/_next/static/chunks/6571-926ddf024a6ad5df.js +1 -0
  68. package/apps/pwa/out/_next/static/chunks/6597.2d7a6d77ab485b27.js +64 -0
  69. package/apps/pwa/out/_next/static/chunks/67.5dd2dc543c2fa3de.js +1 -0
  70. package/apps/pwa/out/_next/static/chunks/{9832.2ab2e818013574c7.js → 6702.ebbd165d5e4a4a14.js} +1 -1
  71. package/apps/pwa/out/_next/static/chunks/6777-312496b0d774719b.js +1 -0
  72. package/apps/pwa/out/_next/static/chunks/681.0116f7036cfb4e8d.js +32 -0
  73. package/apps/pwa/out/_next/static/chunks/6871.30b2bf23b3126e36.js +24 -0
  74. package/apps/pwa/out/_next/static/chunks/6882.737d366d54e29d83.js +1 -0
  75. package/apps/pwa/out/_next/static/chunks/7035.54c2c535164b1c9b.js +1 -0
  76. package/apps/pwa/out/_next/static/chunks/7086-8bfa7e8f796a03d9.js +1 -0
  77. package/apps/pwa/out/_next/static/chunks/7126.5e5599f251726b67.js +93 -0
  78. package/apps/pwa/out/_next/static/chunks/7203-47ff0740a293e284.js +1 -0
  79. package/apps/pwa/out/_next/static/chunks/7268.017f49dafb5f0ff3.js +1 -0
  80. package/apps/pwa/out/_next/static/chunks/7355.5d65a87e9567ae08.js +1 -0
  81. package/apps/pwa/out/_next/static/chunks/7392.4018c2a62177f296.js +1 -0
  82. package/apps/pwa/out/_next/static/chunks/{6110.1297d4093fdfbb0d.js → 740.90b31555290236a9.js} +2 -2
  83. package/apps/pwa/out/_next/static/chunks/7471.3a3d1be50559cfb8.js +148 -0
  84. package/apps/pwa/out/_next/static/chunks/7500.73489a345cef2201.js +226 -0
  85. package/apps/pwa/out/_next/static/chunks/{9070.067c106528ec1ae6.js → 7518.49161e8f5814abdb.js} +1 -1
  86. package/apps/pwa/out/_next/static/chunks/7565.c2f72a15c1ed0396.js +27 -0
  87. package/apps/pwa/out/_next/static/chunks/7651.6d1c984d3d8587a4.js +1 -0
  88. package/apps/pwa/out/_next/static/chunks/7713.7ab863ed59b2d661.js +1 -0
  89. package/apps/pwa/out/_next/static/chunks/7793.d7cd2026767ddd54.js +115 -0
  90. package/apps/pwa/out/_next/static/chunks/7837.c7c267e5dceadb11.js +1 -0
  91. package/apps/pwa/out/_next/static/chunks/7891.09266382daf70d1c.js +1 -0
  92. package/apps/pwa/out/_next/static/chunks/7905.b230b9d06cb4ad5d.js +1 -0
  93. package/apps/pwa/out/_next/static/chunks/7990-cbf6a01bcbdcb7e6.js +1 -0
  94. package/apps/pwa/out/_next/static/chunks/8119-819c549e0c197786.js +1 -0
  95. package/apps/pwa/out/_next/static/chunks/8186-74ea19a3700d04a8.js +1 -0
  96. package/apps/pwa/out/_next/static/chunks/{8194.484f51e273d9abd2.js → 8264.00e434fbd02e9311.js} +16 -16
  97. package/apps/pwa/out/_next/static/chunks/8267-e7b3262b735ae106.js +1 -0
  98. package/apps/pwa/out/_next/static/chunks/8386.86518e55b75f7cf5.js +8 -0
  99. package/apps/pwa/out/_next/static/chunks/{088d8e8a.8069543d082bec12.js → 83cfc5c8.349535543c1548b6.js} +1 -1
  100. package/apps/pwa/out/_next/static/chunks/8437.ba6eeeb29590042f.js +23 -0
  101. package/apps/pwa/out/_next/static/chunks/{1948.5f35b39eef222a94.js → 8438.ee7f0df3b3f6fcb4.js} +1 -1
  102. package/apps/pwa/out/_next/static/chunks/8469-1500be57e582296f.js +1 -0
  103. package/apps/pwa/out/_next/static/chunks/8483-7164cecdaf178ca6.js +1 -0
  104. package/apps/pwa/out/_next/static/chunks/{4588.ad3364cb7fb97ada.js → 8532.a86e99acf9b761cb.js} +2 -2
  105. package/apps/pwa/out/_next/static/chunks/8569.b853a8daa8f31d12.js +1 -0
  106. package/apps/pwa/out/_next/static/chunks/8722-4c940f02c496ad36.js +1 -0
  107. package/apps/pwa/out/_next/static/chunks/887-41d0dcf5af48c628.js +1 -0
  108. package/apps/pwa/out/_next/static/chunks/8925-2abfb48af477fb7f.js +1 -0
  109. package/apps/pwa/out/_next/static/chunks/{88729267.9c6c000157c83f2f.js → 89849432.9eaa2f4c9c010965.js} +3 -3
  110. package/apps/pwa/out/_next/static/chunks/9039.da86361496797522.js +90 -0
  111. package/apps/pwa/out/_next/static/chunks/905.d5745a25884754d4.js +94 -0
  112. package/apps/pwa/out/_next/static/chunks/9089-d909f3d4478b0628.js +1 -0
  113. package/apps/pwa/out/_next/static/chunks/9096.dc5c7a267f83790c.js +41 -0
  114. package/apps/pwa/out/_next/static/chunks/93.a567257b6bdb7fe4.js +1 -0
  115. package/apps/pwa/out/_next/static/chunks/9320.419acb9665b46597.js +287 -0
  116. package/apps/pwa/out/_next/static/chunks/9356.b8ceda6634612ff8.js +79 -0
  117. package/apps/pwa/out/_next/static/chunks/94.e9e1043b35cd3eed.js +1 -0
  118. package/apps/pwa/out/_next/static/chunks/9400-7498ea90b2e6ee8f.js +1 -0
  119. package/apps/pwa/out/_next/static/chunks/{3522-1de1319d028ac89c.js → 9502-32c5dc22e8f120f5.js} +3 -3
  120. package/apps/pwa/out/_next/static/chunks/9930-58644af9c813d6e1.js +1 -0
  121. package/apps/pwa/out/_next/static/chunks/9932.367cf4725fedd6e2.js +1 -0
  122. package/apps/pwa/out/_next/static/chunks/9935.1628b904cdc8ce0e.js +104 -0
  123. package/apps/pwa/out/_next/static/chunks/{7630.2b9a68659053ff4e.js → 9976.8f28864317639fe6.js} +16 -16
  124. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-a2aa8709d193ee0d.js +1 -0
  125. package/apps/pwa/out/_next/static/chunks/app/approvals/page-d5341ac96203a983.js +1 -0
  126. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-0160ab83c8685a12.js +1 -0
  127. package/apps/pwa/out/_next/static/chunks/app/board/page-55c631c032dcc47e.js +1 -0
  128. package/apps/pwa/out/_next/static/chunks/app/botlab/page-ad3bd8084048bee4.js +1 -0
  129. package/apps/pwa/out/_next/static/chunks/app/bots/page-e6784759a2531031.js +1 -0
  130. package/apps/pwa/out/_next/static/chunks/app/ceo/page-5c5d0e370f495ef6.js +1 -0
  131. package/apps/pwa/out/_next/static/chunks/app/chat/page-c325e21543cd48a8.js +1 -0
  132. package/apps/pwa/out/_next/static/chunks/app/consult/{page-80ed511e02c81412.js → page-6168a7c43e6ef8f5.js} +1 -1
  133. package/apps/pwa/out/_next/static/chunks/app/control/page-31417d3604154a6d.js +1 -0
  134. package/apps/pwa/out/_next/static/chunks/app/decisions/page-e9606dc0bdec9305.js +1 -0
  135. package/apps/pwa/out/_next/static/chunks/app/design-check/page-a648bc464c9dc754.js +1 -0
  136. package/apps/pwa/out/_next/static/chunks/app/editor/page-25bb425afdb27541.js +1 -0
  137. package/apps/pwa/out/_next/static/chunks/app/exec/page-32d72fdef1afe11a.js +1 -0
  138. package/apps/pwa/out/_next/static/chunks/app/field/page-d27b794c8ba76bbb.js +1 -0
  139. package/apps/pwa/out/_next/static/chunks/app/healthz/route-a2aa8709d193ee0d.js +1 -0
  140. package/apps/pwa/out/_next/static/chunks/app/inside/page-2bd9ce9c020d02d4.js +1 -0
  141. package/apps/pwa/out/_next/static/chunks/app/intake/page-a34096245e63b9f2.js +1 -0
  142. package/apps/pwa/out/_next/static/chunks/app/layout-22170f4f00a15496.js +1 -0
  143. package/apps/pwa/out/_next/static/chunks/app/live/page-4cc66d22ca4ec502.js +1 -0
  144. package/apps/pwa/out/_next/static/chunks/app/live-run/page-83b14c729fcea837.js +1 -0
  145. package/apps/pwa/out/_next/static/chunks/app/loops/page-6e2781c523dad761.js +1 -0
  146. package/apps/pwa/out/_next/static/chunks/app/market/page-26dd415c8f13f341.js +1 -0
  147. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-ccdb83ed40c8d724.js +1 -0
  148. package/apps/pwa/out/_next/static/chunks/app/morning/page-7ad3de8fa06fdea9.js +1 -0
  149. package/apps/pwa/out/_next/static/chunks/app/not-found-015c02c4fec4023e.js +1 -0
  150. package/apps/pwa/out/_next/static/chunks/app/observatory/page-6b0ae5b5c42f6d2f.js +1 -0
  151. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-786111f30b6f0969.js +1 -0
  152. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-48fb31810693c357.js +1 -0
  153. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-4e17cce5e2ed0fee.js +1 -0
  154. package/apps/pwa/out/_next/static/chunks/app/outputs/page-cb154acadb025414.js +1 -0
  155. package/apps/pwa/out/_next/static/chunks/app/page-16258e956ea93fc2.js +1 -0
  156. package/apps/pwa/out/_next/static/chunks/app/reflection/page-c2dd2b19c93f90ab.js +1 -0
  157. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-85f35dd1dd96ae87.js +1 -0
  158. package/apps/pwa/out/_next/static/chunks/app/sessions/page-d3c007c8a0ddd7ee.js +1 -0
  159. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-7837ce2aacb97b04.js +1 -0
  160. package/apps/pwa/out/_next/static/chunks/app/settings/page-7350ee93f4fe1033.js +1 -0
  161. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-00723458575f2b19.js +1 -0
  162. package/apps/pwa/out/_next/static/chunks/app/setup/layout-a2aa8709d193ee0d.js +1 -0
  163. package/apps/pwa/out/_next/static/chunks/app/setup/page-745d9b6c015076f8.js +1 -0
  164. package/apps/pwa/out/_next/static/chunks/app/share/page-736d33c7414a8a1a.js +1 -0
  165. package/apps/pwa/out/_next/static/chunks/app/showroom/page-119be9a0444d2482.js +1 -0
  166. package/apps/pwa/out/_next/static/chunks/app/tasks/page-fa806c018833e332.js +1 -0
  167. package/apps/pwa/out/_next/static/chunks/app/term/page-c782041b5f5199ba.js +1 -0
  168. package/apps/pwa/out/_next/static/chunks/app/today/page-d431fbdea6ad93dd.js +1 -0
  169. package/apps/pwa/out/_next/static/chunks/app/trace/page-0c056abff82b9891.js +1 -0
  170. package/apps/pwa/out/_next/static/chunks/app/vault/page-242d668e4fbf72c4.js +1 -0
  171. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-847c3de6ca8a4416.js +1 -0
  172. package/apps/pwa/out/_next/static/chunks/app/workflows/page-81dd0d8ae923ed72.js +1 -0
  173. package/apps/pwa/out/_next/static/chunks/app/workspace/page-74e69ba2c8e970d4.js +1 -0
  174. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-70038f41ae7cf931.js +1 -0
  175. package/apps/pwa/out/_next/static/chunks/b4e12462-5fab4a813a63dadd.js +1 -0
  176. package/apps/pwa/out/_next/static/chunks/cd39fa05.fac4545f42a3ef70.js +1 -0
  177. package/apps/pwa/out/_next/static/chunks/{f6b7c9ba.64a895b8b2fedbdc.js → ce383d63.d2ede338788ed18e.js} +1 -1
  178. package/apps/pwa/out/_next/static/chunks/{framework-5c169757551134ed.js → framework-b2763b10dc6b3836.js} +1 -1
  179. package/apps/pwa/out/_next/static/chunks/main-app-27893e93b0ec193d.js +1 -0
  180. package/apps/pwa/out/_next/static/chunks/main-f280047194e1daf8.js +1 -0
  181. package/apps/pwa/out/_next/static/chunks/pages/_app-e90eff2d0b70564c.js +1 -0
  182. package/apps/pwa/out/_next/static/chunks/pages/_error-70f2d654b3435d34.js +1 -0
  183. package/apps/pwa/out/_next/static/chunks/webpack-4794f053bd5e4230.js +1 -0
  184. package/apps/pwa/out/_next/static/css/5619d59e87eddf66.css +1 -0
  185. package/apps/pwa/out/_next/static/css/d0a48dc099ed86cf.css +1 -0
  186. package/apps/pwa/out/_next/static/{aBi6EyMtLsrl7J8eH4kF7 → rfkMm4movqkqMebBrCXiR}/_buildManifest.js +1 -1
  187. package/apps/pwa/out/approvals/index.html +1 -1
  188. package/apps/pwa/out/approvals/index.txt +23 -23
  189. package/apps/pwa/out/autopilot/index.html +1 -1
  190. package/apps/pwa/out/autopilot/index.txt +23 -23
  191. package/apps/pwa/out/board/index.html +1 -1
  192. package/apps/pwa/out/board/index.txt +23 -23
  193. package/apps/pwa/out/botlab/index.html +1 -1
  194. package/apps/pwa/out/botlab/index.txt +22 -22
  195. package/apps/pwa/out/bots/index.html +1 -1
  196. package/apps/pwa/out/bots/index.txt +22 -22
  197. package/apps/pwa/out/ceo/index.html +1 -1
  198. package/apps/pwa/out/ceo/index.txt +23 -23
  199. package/apps/pwa/out/chat/index.html +1 -1
  200. package/apps/pwa/out/chat/index.txt +22 -22
  201. package/apps/pwa/out/consult/index.html +1 -1
  202. package/apps/pwa/out/consult/index.txt +23 -23
  203. package/apps/pwa/out/control/index.html +1 -1
  204. package/apps/pwa/out/control/index.txt +23 -23
  205. package/apps/pwa/out/decisions/index.html +1 -1
  206. package/apps/pwa/out/decisions/index.txt +22 -22
  207. package/apps/pwa/out/design-check/index.html +1 -1
  208. package/apps/pwa/out/design-check/index.txt +23 -23
  209. package/apps/pwa/out/editor/index.html +1 -1
  210. package/apps/pwa/out/editor/index.txt +24 -23
  211. package/apps/pwa/out/exec/index.html +1 -1
  212. package/apps/pwa/out/exec/index.txt +23 -23
  213. package/apps/pwa/out/field/index.html +1 -1
  214. package/apps/pwa/out/field/index.txt +23 -23
  215. package/apps/pwa/out/healthz +1 -1
  216. package/apps/pwa/out/index.html +1 -1
  217. package/apps/pwa/out/index.txt +22 -22
  218. package/apps/pwa/out/inside/index.html +1 -1
  219. package/apps/pwa/out/inside/index.txt +22 -22
  220. package/apps/pwa/out/intake/index.html +1 -1
  221. package/apps/pwa/out/intake/index.txt +23 -23
  222. package/apps/pwa/out/live/index.html +1 -1
  223. package/apps/pwa/out/live/index.txt +23 -23
  224. package/apps/pwa/out/live-run/index.html +1 -1
  225. package/apps/pwa/out/live-run/index.txt +24 -23
  226. package/apps/pwa/out/loops/index.html +1 -1
  227. package/apps/pwa/out/loops/index.txt +22 -22
  228. package/apps/pwa/out/market/index.html +1 -1
  229. package/apps/pwa/out/market/index.txt +22 -22
  230. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  231. package/apps/pwa/out/missions/placeholder/index.txt +22 -22
  232. package/apps/pwa/out/morning/index.html +1 -1
  233. package/apps/pwa/out/morning/index.txt +23 -23
  234. package/apps/pwa/out/observatory/index.html +1 -1
  235. package/apps/pwa/out/observatory/index.txt +23 -23
  236. package/apps/pwa/out/ops/checklist/index.html +1 -1
  237. package/apps/pwa/out/ops/checklist/index.txt +22 -22
  238. package/apps/pwa/out/ops/release/index.html +1 -1
  239. package/apps/pwa/out/ops/release/index.txt +22 -22
  240. package/apps/pwa/out/ops/seats/index.html +1 -1
  241. package/apps/pwa/out/ops/seats/index.txt +22 -22
  242. package/apps/pwa/out/outputs/index.html +1 -1
  243. package/apps/pwa/out/outputs/index.txt +22 -22
  244. package/apps/pwa/out/reflection/index.html +1 -1
  245. package/apps/pwa/out/reflection/index.txt +23 -23
  246. package/apps/pwa/out/scheduler/index.html +1 -1
  247. package/apps/pwa/out/scheduler/index.txt +23 -23
  248. package/apps/pwa/out/sessions/index.html +1 -1
  249. package/apps/pwa/out/sessions/index.txt +22 -22
  250. package/apps/pwa/out/settings/devices/index.html +1 -1
  251. package/apps/pwa/out/settings/devices/index.txt +23 -23
  252. package/apps/pwa/out/settings/index.html +1 -1
  253. package/apps/pwa/out/settings/index.txt +23 -23
  254. package/apps/pwa/out/setup/done/index.html +1 -1
  255. package/apps/pwa/out/setup/done/index.txt +23 -23
  256. package/apps/pwa/out/setup/index.html +1 -1
  257. package/apps/pwa/out/setup/index.txt +23 -23
  258. package/apps/pwa/out/share/index.html +1 -1
  259. package/apps/pwa/out/share/index.txt +23 -23
  260. package/apps/pwa/out/showroom/index.html +1 -1
  261. package/apps/pwa/out/showroom/index.txt +22 -22
  262. package/apps/pwa/out/tasks/index.html +1 -1
  263. package/apps/pwa/out/tasks/index.txt +23 -23
  264. package/apps/pwa/out/term/index.html +1 -1
  265. package/apps/pwa/out/term/index.txt +23 -23
  266. package/apps/pwa/out/today/index.html +1 -1
  267. package/apps/pwa/out/today/index.txt +23 -23
  268. package/apps/pwa/out/trace/index.html +1 -1
  269. package/apps/pwa/out/trace/index.txt +23 -23
  270. package/apps/pwa/out/vault/index.html +1 -1
  271. package/apps/pwa/out/vault/index.txt +23 -23
  272. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  273. package/apps/pwa/out/workflows/chat-ui/index.txt +23 -23
  274. package/apps/pwa/out/workflows/index.html +1 -1
  275. package/apps/pwa/out/workflows/index.txt +23 -23
  276. package/apps/pwa/out/workspace/index.html +1 -1
  277. package/apps/pwa/out/workspace/index.txt +23 -23
  278. package/apps/pwa/out/worktrees/index.html +1 -1
  279. package/apps/pwa/out/worktrees/index.txt +23 -23
  280. package/catalog/models/anthropic/claude-haiku-5-5.yaml +32 -0
  281. package/catalog/models/anthropic/claude-opus-5-5.yaml +5 -0
  282. package/catalog/models/anthropic/claude-sonnet-5-5.yaml +31 -0
  283. package/catalog/models/anthropic/claude-sonnet-5.yaml +8 -2
  284. package/catalog/models/openai/gpt-6-astra.yaml +6 -0
  285. package/catalog/models/openai/gpt-6-luna.yaml +6 -0
  286. package/catalog/models/openai/gpt-6-sol.yaml +6 -0
  287. package/graphs/log-horizon/log-horizon-watch.yaml +25 -0
  288. package/graphs/log-horizon/recipes.yaml +3 -0
  289. package/graphs/release/recipes.yaml +1 -1
  290. package/graphs/release/release-loop.yaml +6 -2
  291. package/graphs/release-rehearsal/recipes.yaml +12 -0
  292. package/graphs/release-rehearsal/release-rehearsal.yaml +17 -0
  293. package/package.json +4 -1
  294. package/scripts/bench-report.ts +82 -3
  295. package/scripts/ci-typecheck-changed.ts +10 -1
  296. package/scripts/hq/hq-fence-wrapper.sh +148 -0
  297. package/scripts/lib/typecheck-ratchet.ts +51 -0
  298. package/scripts/openai-relay-server.ts +5 -1
  299. package/scripts/release-loop/docs-node.ts +258 -43
  300. package/scripts/release-loop/gate-method.ts +59 -0
  301. package/scripts/release-loop/gate-node.ts +73 -7
  302. package/scripts/release-loop/gate-wall.ts +208 -0
  303. package/scripts/release-loop/known-issues-node.ts +53 -8
  304. package/scripts/release-loop/publish-at.ts +14 -0
  305. package/scripts/release-loop/publish-node.ts +17 -2
  306. package/scripts/release-loop/resume-release.ts +34 -4
  307. package/scripts/release-loop/resume.ts +53 -0
  308. package/scripts/release-loop/shard-plan.ts +3 -11
  309. package/scripts/release-loop/tui-node-checks.ts +22 -0
  310. package/scripts/release-loop/unattended-release.ts +8 -1
  311. package/scripts/rhythm/daily.ts +51 -2
  312. package/scripts/rhythm/weekly.ts +3 -2
  313. package/scripts/tsc-main-safety-net.ts +189 -0
  314. package/scripts/usage-rollup.ts +8 -4
  315. package/src/acp/codex-plugins.ts +48 -6
  316. package/src/agent/monad-agent-turn.ts +67 -5
  317. package/src/agent/run-context.ts +10 -0
  318. package/src/agent/tool-profile.ts +28 -2
  319. package/src/agent-mission/acp-judge.ts +8 -2
  320. package/src/agent-mission/capabilities-cli.ts +16 -1
  321. package/src/agent-mission/driver.ts +1 -1
  322. package/src/agent-mission/pr-review-watch.ts +6 -2
  323. package/src/agent-mission/review-loop.ts +33 -5
  324. package/src/agent-substrate/acp-reviewer.ts +17 -8
  325. package/src/agent-substrate/pr-reviewer.ts +164 -28
  326. package/src/agent-substrate/review-intent.ts +6 -4
  327. package/src/agent-substrate/self-review-cli.ts +111 -16
  328. package/src/agent-substrate/shard-boundary-intent.ts +92 -26
  329. package/src/ask-user-question/pending-questions.ts +8 -2
  330. package/src/autopilot/build/llm-conflict-merge.ts +216 -12
  331. package/src/autopilot/codebase-grounding.ts +44 -0
  332. package/src/autopilot/llm-control-brain.ts +13 -1
  333. package/src/autopilot/mission-codebase-gate.ts +8 -77
  334. package/src/autopilot/pr-manager.ts +12 -3
  335. package/src/boot/acp-server.ts +4 -7
  336. package/src/boot/acp-socket-path.ts +7 -0
  337. package/src/briefing/brief-items.ts +21 -13
  338. package/src/chat/index.ts +12 -3
  339. package/src/cli/agent-cli.ts +20 -0
  340. package/src/cli/brief-cli.ts +27 -21
  341. package/src/cli/cli-user-error.ts +9 -2
  342. package/src/cli/log-event-names.ts +3 -0
  343. package/src/cli/logs-cli.ts +1 -0
  344. package/src/cli/mcp-login.ts +111 -37
  345. package/src/cli/plugin-cli.ts +21 -1
  346. package/src/cli/pod-cli.ts +26 -0
  347. package/src/cli/pr-cli.ts +30 -2
  348. package/src/cli/pr-land-overlap-evidence.ts +131 -0
  349. package/src/cli/release-cli.ts +53 -11
  350. package/src/cli/self-update.ts +173 -10
  351. package/src/cli/skills-cli.ts +12 -3
  352. package/src/cli/tasks-cli.ts +133 -27
  353. package/src/context-bus/context-first.ts +51 -2
  354. package/src/context-bus/context-now-surfaces.ts +13 -2
  355. package/src/context-bus/long-term-memory.ts +3 -1
  356. package/src/context-bus/nightly-condense.ts +3 -2
  357. package/src/core-turn/run-goal-loop.ts +86 -2
  358. package/src/dashboard/chat-fold.ts +20 -0
  359. package/src/dashboard/first-screen-band.ts +151 -1
  360. package/src/dashboard/index.ts +27 -5
  361. package/src/dashboard/slash-runtime/dashboard-handlers.ts +5 -3
  362. package/src/dashboard/stale-install-check.ts +13 -2
  363. package/src/dashboard/turn-stream-formatter.ts +25 -0
  364. package/src/debug/log-diet.ts +159 -0
  365. package/src/debug/log.ts +52 -17
  366. package/src/display/coordinator.ts +25 -2
  367. package/src/domains/coo-admin-tool.ts +27 -0
  368. package/src/domains/cron-match.ts +1 -0
  369. package/src/domains/outbound-alert.ts +17 -6
  370. package/src/domains/release-tool.ts +119 -10
  371. package/src/domains/schedule-registry.ts +10 -7
  372. package/src/domains/schedule-runner.ts +1 -0
  373. package/src/elanous-config-dir.ts +2 -2
  374. package/src/elanous-daemon.ts +3 -3
  375. package/src/execution-loop/launch-gate.ts +1 -1
  376. package/src/expression/i18n/messages.en.ts +5 -0
  377. package/src/expression/i18n/messages.ja.ts +5 -0
  378. package/src/expression/i18n/messages.ko.ts +5 -0
  379. package/src/expression/i18n/messages.zh.ts +5 -0
  380. package/src/expression/i18n/types.ts +8 -0
  381. package/src/github/pr-labels.ts +1 -0
  382. package/src/graph-runner/runner.ts +54 -5
  383. package/src/harness/dispatch-backpressure.ts +259 -0
  384. package/src/harness/harness-cli-command.ts +107 -28
  385. package/src/harness/harness-seams.ts +2 -20
  386. package/src/harness/harness-substrate-default.ts +72 -1
  387. package/src/harness/nested-elanous-policy.ts +4 -2
  388. package/src/hq/fence-wrapper.ts +7 -0
  389. package/src/hq/hq.ts +33 -2
  390. package/src/hq/lease.ts +13 -0
  391. package/src/hq/seats.ts +114 -18
  392. package/src/index.ts +163 -64
  393. package/src/intake-plane/collect-github.ts +19 -1
  394. package/src/intake-plane/digest.ts +45 -20
  395. package/src/intake-plane/lens.ts +36 -8
  396. package/src/intelligence-map/model-alias.ts +6 -0
  397. package/src/intelligence-map/model-catalog.ts +41 -0
  398. package/src/knowledge/kgs/pack.ts +5 -2
  399. package/src/knowledge/kgs/sqlite-store.ts +15 -0
  400. package/src/knowledge/tools/knowledge-query.ts +20 -2
  401. package/src/knowledge/write.ts +234 -2
  402. package/src/llm/bedrock-provider.ts +175 -0
  403. package/src/llm/bedrock.ts +345 -0
  404. package/src/llm/provider-credentials.ts +3 -1
  405. package/src/llm.ts +306 -35
  406. package/src/loops/budget.ts +150 -0
  407. package/src/loops/orchestrator/seat-share.ts +6 -49
  408. package/src/loops/orchestrator/traffic.ts +5 -81
  409. package/src/market/signed-index.ts +42 -0
  410. package/src/mcp/mcp-oauth.ts +136 -17
  411. package/src/model-tier/llm-tier-map.ts +48 -4
  412. package/src/models/prompts.ts +22 -1
  413. package/src/mss/logging/horizon-watch.ts +168 -0
  414. package/src/mss/logging/log-store.ts +204 -15
  415. package/src/native-tool-catalog.ts +1 -1
  416. package/src/nexus/api/consult-requests.ts +7 -10
  417. package/src/nexus/api/discord-trigger-bot.ts +6 -3
  418. package/src/nexus/api/error-reports.ts +30 -8
  419. package/src/nexus/api/http-server.ts +8 -0
  420. package/src/nexus/api/loop-resources.ts +42 -0
  421. package/src/nexus/api/outbound-report.ts +8 -3
  422. package/src/nexus/api/telegram-trigger-bot.ts +6 -3
  423. package/src/nexus/index.ts +10 -0
  424. package/src/nexus/outbound/format.ts +2 -0
  425. package/src/nexus/outbound/router.ts +198 -13
  426. package/src/oauth/codex-account-rotation.ts +16 -18
  427. package/src/oauth/codex-account-store.ts +134 -5
  428. package/src/oauth/fallback-chain.ts +28 -6
  429. package/src/onboarding.ts +2 -1
  430. package/src/plugins/adapters/claude-package.ts +4 -12
  431. package/src/plugins/core/capability-policy.ts +141 -0
  432. package/src/plugins/core/host.ts +21 -8
  433. package/src/plugins/core/manifest.ts +19 -2
  434. package/src/plugins/core/trust-store.ts +15 -0
  435. package/src/plugins/core/types.ts +12 -0
  436. package/src/plugins/install/market-fetch.ts +68 -16
  437. package/src/plugins/install/plugin-install.ts +187 -16
  438. package/src/plugins/maker/plugin-maker.ts +25 -20
  439. package/src/plugins/maker/wizard-generate.ts +16 -2
  440. package/src/policy/llm-policy.ts +14 -1
  441. package/src/process/pid-liveness.ts +1 -1
  442. package/src/prompt-cache/openai.ts +10 -6
  443. package/src/prompt-cache/openrouter.ts +94 -0
  444. package/src/prompt-enhance/enhance.ts +46 -5
  445. package/src/prompt-library/family-prompt-coverage.ts +1 -0
  446. package/src/prompt-library/universal-preamble.ts +2 -2
  447. package/src/registry/discovery/openrouter-live-resolve.ts +122 -0
  448. package/src/registry/loader.ts +6 -1
  449. package/src/release-loop/checklist.ts +24 -9
  450. package/src/release-loop/feature-store.ts +3 -0
  451. package/src/release-loop/gate-shards.ts +48 -4
  452. package/src/release-loop/landed-to-green.ts +81 -0
  453. package/src/release-loop/light-rc.ts +11 -1
  454. package/src/release-loop/merged-pr-checklist.ts +10 -0
  455. package/src/release-loop/placement.ts +91 -22
  456. package/src/release-loop/rehearsal.ts +317 -0
  457. package/src/release-loop/release-schedule.ts +13 -1
  458. package/src/release-loop/rubric-types.ts +1 -0
  459. package/src/release-loop/rubric.ts +2 -2
  460. package/src/retro/daily.ts +23 -9
  461. package/src/seat-loop/seat-loop.ts +34 -1
  462. package/src/seat-loop/traffic-signal.ts +44 -0
  463. package/src/self-dev/ask-launch-flow.ts +2 -2
  464. package/src/self-dev/dev-cli.ts +67 -3
  465. package/src/self-dev/dev-pipeline.ts +4 -6
  466. package/src/self-dev/draft-residue.ts +463 -0
  467. package/src/self-dev/draft-sweep.ts +151 -10
  468. package/src/self-dev/draft-triage-rules.ts +27 -1
  469. package/src/self-dev/launch-preflight-text.ts +549 -0
  470. package/src/self-dev/launch-preflight.ts +10 -545
  471. package/src/self-dev/orchestrate-cli.ts +57 -2
  472. package/src/self-dev/orchestrate-summary-line.ts +13 -0
  473. package/src/self-dev/orchestrate.ts +8 -135
  474. package/src/self-dev/review-provider-fallback.ts +115 -19
  475. package/src/self-dev/run-store.ts +1 -3
  476. package/src/self-dev/run-supervisor.ts +3 -22
  477. package/src/self-dev/run-types.ts +178 -0
  478. package/src/self-dev/shard-aggregate.ts +61 -13
  479. package/src/self-dev/shard-plan.ts +5 -1
  480. package/src/self-dev/shard-run.ts +23 -5
  481. package/src/self-dev/supervisor-verdict-edges.ts +2 -4
  482. package/src/self-implement/blocked-draft-policy.ts +3 -19
  483. package/src/self-implement/draft-on-stop-mode.ts +18 -0
  484. package/src/self-implement/follow-up-goals.ts +114 -38
  485. package/src/self-implement/gate-baseline-reap.ts +157 -0
  486. package/src/self-implement/gate-baseline.ts +78 -16
  487. package/src/self-implement/gate-cli.ts +12 -2
  488. package/src/self-implement/gate-remote.ts +17 -6
  489. package/src/self-implement/goal-author.ts +24 -4
  490. package/src/self-implement/goal-digest.ts +19 -0
  491. package/src/self-implement/graph-overlay-condition.ts +4 -0
  492. package/src/self-implement/headless-elanous-driver.ts +11 -9
  493. package/src/self-implement/host-regate.ts +120 -29
  494. package/src/self-implement/l8-merge-queue.ts +67 -5
  495. package/src/self-implement/merge-train.ts +131 -0
  496. package/src/self-implement/off-diff-evidence.ts +9 -0
  497. package/src/self-implement/orchestrator.ts +133 -16
  498. package/src/self-implement/review-finding-recurrence.ts +28 -0
  499. package/src/self-implement/rework-policy.ts +2 -1
  500. package/src/self-implement/round-summary-card.ts +63 -0
  501. package/src/self-implement/run-ledger.ts +4 -3
  502. package/src/self-implement/run-step-projection.ts +147 -0
  503. package/src/self-implement/run-stop.ts +1 -1
  504. package/src/self-implement/seams.ts +36 -3
  505. package/src/self-implement/self-implement-cli.ts +3 -1
  506. package/src/self-implement/train-gate-remote.ts +158 -0
  507. package/src/session-runtime/index.ts +10 -1
  508. package/src/session-runtime/retry-api.ts +11 -1
  509. package/src/skills/gift-install.ts +119 -0
  510. package/src/skills/runner.ts +59 -2
  511. package/src/skills/skill-feedback.ts +31 -1
  512. package/src/skills/skill-tier.ts +1 -0
  513. package/src/smart-wizard/capability-index.ts +231 -0
  514. package/src/smart-wizard/rung.ts +74 -0
  515. package/src/steward/launch.ts +20 -9
  516. package/src/steward/triage.ts +4 -2
  517. package/src/storage/file-lock.ts +8 -1
  518. package/src/task-agent/actions.ts +4 -0
  519. package/src/task-agent/card-evidence.ts +59 -13
  520. package/src/task-agent/judge.ts +4 -3
  521. package/src/task-agent/land-worktree.ts +201 -0
  522. package/src/task-agent/live-move-history.ts +133 -0
  523. package/src/task-agent/live-moves.ts +106 -19
  524. package/src/task-agent/parent-host.ts +117 -8
  525. package/src/task-agent/rejudge-on-head.ts +297 -0
  526. package/src/task-agent/review-result-capture.ts +218 -0
  527. package/src/task-agent/shadow.ts +103 -7
  528. package/src/task-agent/task-hand.ts +136 -11
  529. package/src/task-orchestrator/surfaces/pod-child-providers.ts +29 -0
  530. package/src/task-orchestrator/surfaces/pod-command-job.ts +6 -3
  531. package/src/task-orchestrator/surfaces/pod-failure-reason.ts +16 -2
  532. package/src/task-orchestrator/surfaces/pod-image-source.ts +212 -0
  533. package/src/task-orchestrator/surfaces/pod-lease.ts +93 -12
  534. package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +25 -2
  535. package/src/task-orchestrator/surfaces/pod-pool.ts +66 -14
  536. package/src/task-orchestrator/surfaces/self-implement-pod.ts +259 -36
  537. package/src/task-orchestrator/surfaces/self-implement.ts +6 -2
  538. package/src/telegram-service.ts +5 -1
  539. package/src/telegram.ts +17 -4
  540. package/src/time/format.ts +13 -4
  541. package/src/tool-runtime/goal-author-runtime.ts +1 -0
  542. package/src/tool-runtime/self-cognition-catalog.ts +23 -0
  543. package/src/tool-runtime/self-cognition-runtimes.ts +2 -24
  544. package/src/user-config.ts +132 -24
  545. package/src/ux-sim/slash-menu-frames.ts +168 -0
  546. package/src/version/packed-revision.json +1 -1
  547. package/src/web-terminal/preview-tap-registry.ts +5 -2
  548. package/src/workflow-runtime/triggers/scheduler.ts +1 -0
  549. package/apps/pwa/out/_next/static/chunks/1109.78a7a3288c0259d9.js +0 -148
  550. package/apps/pwa/out/_next/static/chunks/115.1c6690b8604f81c4.js +0 -32
  551. package/apps/pwa/out/_next/static/chunks/1262.76a3f08e67c5f816.js +0 -309
  552. package/apps/pwa/out/_next/static/chunks/1307-08b716476f1d50af.js +0 -1
  553. package/apps/pwa/out/_next/static/chunks/1358d292.5c87f12cad6eaa91.js +0 -122
  554. package/apps/pwa/out/_next/static/chunks/1438.27a1a7b8f2308004.js +0 -149
  555. package/apps/pwa/out/_next/static/chunks/1442.02d85cd88f4c1eac.js +0 -79
  556. package/apps/pwa/out/_next/static/chunks/1501-1aad662d82ba43f7.js +0 -1
  557. package/apps/pwa/out/_next/static/chunks/1584.9e2dd53da6e6d4b1.js +0 -24
  558. package/apps/pwa/out/_next/static/chunks/1586.b989b0614afb77e6.js +0 -1
  559. package/apps/pwa/out/_next/static/chunks/1609.2ad355062870151f.js +0 -1
  560. package/apps/pwa/out/_next/static/chunks/1612-f6df0fc0eed7157b.js +0 -1
  561. package/apps/pwa/out/_next/static/chunks/1826-5d279da64bbbc2de.js +0 -1
  562. package/apps/pwa/out/_next/static/chunks/1923.37f1d5fe7ebe3860.js +0 -27
  563. package/apps/pwa/out/_next/static/chunks/195-fb1d7690bf30bab4.js +0 -1
  564. package/apps/pwa/out/_next/static/chunks/1986-33e13d648a9df765.js +0 -1
  565. package/apps/pwa/out/_next/static/chunks/1993.3fd074a2a246234e.js +0 -1
  566. package/apps/pwa/out/_next/static/chunks/211-9c9090c59e2c5907.js +0 -1
  567. package/apps/pwa/out/_next/static/chunks/2140.d2daf9ee96e295d1.js +0 -1
  568. package/apps/pwa/out/_next/static/chunks/2196-4700653d12a44b40.js +0 -1
  569. package/apps/pwa/out/_next/static/chunks/2212.0359851457473efc.js +0 -1
  570. package/apps/pwa/out/_next/static/chunks/224.583e562365a52f74.js +0 -1
  571. package/apps/pwa/out/_next/static/chunks/2328.0f37465646bafa7d.js +0 -36
  572. package/apps/pwa/out/_next/static/chunks/2372.bf71372184991937.js +0 -286
  573. package/apps/pwa/out/_next/static/chunks/2489-d238d3e2eb5b2bf5.js +0 -1
  574. package/apps/pwa/out/_next/static/chunks/2537-6c289376caa88ca1.js +0 -1
  575. package/apps/pwa/out/_next/static/chunks/2549-a8515c86f9e8cb96.js +0 -1
  576. package/apps/pwa/out/_next/static/chunks/2655.72cc6a97276c5acc.js +0 -1
  577. package/apps/pwa/out/_next/static/chunks/2699-325cc923ba7b8631.js +0 -1
  578. package/apps/pwa/out/_next/static/chunks/2743-ef6e8c3ced74cce5.js +0 -1
  579. package/apps/pwa/out/_next/static/chunks/2840-5458ad50c49cac6a.js +0 -1
  580. package/apps/pwa/out/_next/static/chunks/2856.f75d5baef7f49b74.js +0 -1
  581. package/apps/pwa/out/_next/static/chunks/311.e685738f92e6e2ec.js +0 -1
  582. package/apps/pwa/out/_next/static/chunks/3194-807b212fb8eae065.js +0 -1
  583. package/apps/pwa/out/_next/static/chunks/3324-d1d2979821dfa845.js +0 -1
  584. package/apps/pwa/out/_next/static/chunks/3344.0de4b0ec5a454b7a.js +0 -1
  585. package/apps/pwa/out/_next/static/chunks/335.a13f339e97893437.js +0 -94
  586. package/apps/pwa/out/_next/static/chunks/3394.646348f34b2500be.js +0 -1
  587. package/apps/pwa/out/_next/static/chunks/3443.6276909b53d72d53.js +0 -1
  588. package/apps/pwa/out/_next/static/chunks/3495.abd4254d808bf2dc.js +0 -1
  589. package/apps/pwa/out/_next/static/chunks/3558-bbb5ab553a0471d3.js +0 -1
  590. package/apps/pwa/out/_next/static/chunks/3721.ba5b82db0d9a5572.js +0 -1
  591. package/apps/pwa/out/_next/static/chunks/3857-5985236d9307f676.js +0 -1
  592. package/apps/pwa/out/_next/static/chunks/3916-3d02dd1fc257dd3c.js +0 -1
  593. package/apps/pwa/out/_next/static/chunks/3919.9ff7dc25afefee33.js +0 -74
  594. package/apps/pwa/out/_next/static/chunks/3998.bd9a69d9b50e9bd2.js +0 -1
  595. package/apps/pwa/out/_next/static/chunks/4003.ab6db1dc266576c9.js +0 -1
  596. package/apps/pwa/out/_next/static/chunks/4144-fad095b48a1862ee.js +0 -1
  597. package/apps/pwa/out/_next/static/chunks/4216.e8ac94b9fe6b96ab.js +0 -1
  598. package/apps/pwa/out/_next/static/chunks/4327.c44dc2b55cb43ee5.js +0 -34
  599. package/apps/pwa/out/_next/static/chunks/4401.94257d497b6d26bc.js +0 -1
  600. package/apps/pwa/out/_next/static/chunks/4559.17ca2cdbaa8d414f.js +0 -115
  601. package/apps/pwa/out/_next/static/chunks/4565-3d7e3fdeb43686a8.js +0 -1
  602. package/apps/pwa/out/_next/static/chunks/4706.97e734bd4c9816ca.js +0 -1
  603. package/apps/pwa/out/_next/static/chunks/4795-f372490b178583fc.js +0 -1
  604. package/apps/pwa/out/_next/static/chunks/4982.6e44352f751b9a1b.js +0 -77
  605. package/apps/pwa/out/_next/static/chunks/5342.49c5c5a530e62b50.js +0 -1
  606. package/apps/pwa/out/_next/static/chunks/5360-08a80abdf3a4da99.js +0 -1
  607. package/apps/pwa/out/_next/static/chunks/5431.66c58ab1327ee0b7.js +0 -1
  608. package/apps/pwa/out/_next/static/chunks/5633-d6852385000738ef.js +0 -1
  609. package/apps/pwa/out/_next/static/chunks/5717.bcfcb96e701b82e0.js +0 -90
  610. package/apps/pwa/out/_next/static/chunks/5755-30d102586789bf7f.js +0 -1
  611. package/apps/pwa/out/_next/static/chunks/5957.3f1a4012388dd962.js +0 -1
  612. package/apps/pwa/out/_next/static/chunks/5977-5b9e8a45a5b0c823.js +0 -1
  613. package/apps/pwa/out/_next/static/chunks/602.9bf02ac8e9502598.js +0 -1
  614. package/apps/pwa/out/_next/static/chunks/6020.3a1b347e2d2dfacc.js +0 -8
  615. package/apps/pwa/out/_next/static/chunks/6375.fce1d174ba6d0324.js +0 -64
  616. package/apps/pwa/out/_next/static/chunks/6546.92a828aef441271f.js +0 -1
  617. package/apps/pwa/out/_next/static/chunks/658.957fdb0a8c6c6c8e.js +0 -287
  618. package/apps/pwa/out/_next/static/chunks/6706.323590aabb8b1293.js +0 -226
  619. package/apps/pwa/out/_next/static/chunks/6747.b67947f293a4c02d.js +0 -1
  620. package/apps/pwa/out/_next/static/chunks/6868.000b24a3045c5574.js +0 -1
  621. package/apps/pwa/out/_next/static/chunks/6877-74399f69c7da1433.js +0 -1
  622. package/apps/pwa/out/_next/static/chunks/6946.bba74e9c96050f30.js +0 -1
  623. package/apps/pwa/out/_next/static/chunks/7033.a558f1b2f6912a53.js +0 -1
  624. package/apps/pwa/out/_next/static/chunks/7065.46029c26c53eac50.js +0 -1
  625. package/apps/pwa/out/_next/static/chunks/7087.6fae577d09249bc1.js +0 -1
  626. package/apps/pwa/out/_next/static/chunks/722.de8d9801e0f6d414.js +0 -41
  627. package/apps/pwa/out/_next/static/chunks/726.83532d69452ee24b.js +0 -1
  628. package/apps/pwa/out/_next/static/chunks/7290-f09c928a06c52e2d.js +0 -1
  629. package/apps/pwa/out/_next/static/chunks/7497.57981e9d7a75a52a.js +0 -24
  630. package/apps/pwa/out/_next/static/chunks/7672.4af122ad892117c7.js +0 -1
  631. package/apps/pwa/out/_next/static/chunks/771.6ab889a233e7dae7.js +0 -1
  632. package/apps/pwa/out/_next/static/chunks/7797-df5aa0381fdc078f.js +0 -1
  633. package/apps/pwa/out/_next/static/chunks/8060-cc008cc6fedfa818.js +0 -1
  634. package/apps/pwa/out/_next/static/chunks/8085-d065d0393c1760f5.js +0 -1
  635. package/apps/pwa/out/_next/static/chunks/8096.f7da0776cfde6300.js +0 -1
  636. package/apps/pwa/out/_next/static/chunks/8401-35e94e9091f5eb8d.js +0 -1
  637. package/apps/pwa/out/_next/static/chunks/8682-7061dd4fa7bd62ed.js +0 -1
  638. package/apps/pwa/out/_next/static/chunks/8695-846034ce44852c70.js +0 -1
  639. package/apps/pwa/out/_next/static/chunks/8769.815ca69879f608b4.js +0 -104
  640. package/apps/pwa/out/_next/static/chunks/8857-aa1cc4ac90af4d40.js +0 -1
  641. package/apps/pwa/out/_next/static/chunks/8f835b4f.048eeb02299e3e12.js +0 -1
  642. package/apps/pwa/out/_next/static/chunks/9068.5f2764fccf86c1b2.js +0 -93
  643. package/apps/pwa/out/_next/static/chunks/9103-60fbef33e66f0a46.js +0 -1
  644. package/apps/pwa/out/_next/static/chunks/9247-55bcfbb0e8057f59.js +0 -1
  645. package/apps/pwa/out/_next/static/chunks/935.4ddaa99fc1d1126c.js +0 -1
  646. package/apps/pwa/out/_next/static/chunks/9451.ebfb0dcc9c02250f.js +0 -23
  647. package/apps/pwa/out/_next/static/chunks/9559.e648369b1d57cf9a.js +0 -62
  648. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-c1a96669158471ec.js +0 -1
  649. package/apps/pwa/out/_next/static/chunks/app/approvals/page-88459e0903da2803.js +0 -1
  650. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-d85c43f46f58716b.js +0 -1
  651. package/apps/pwa/out/_next/static/chunks/app/board/page-4e645600579ae952.js +0 -1
  652. package/apps/pwa/out/_next/static/chunks/app/botlab/page-87edbfd19117823b.js +0 -1
  653. package/apps/pwa/out/_next/static/chunks/app/bots/page-7f0c2b7801ac6a54.js +0 -1
  654. package/apps/pwa/out/_next/static/chunks/app/ceo/page-bf1f030a9d1adffd.js +0 -1
  655. package/apps/pwa/out/_next/static/chunks/app/chat/page-cbaf66b1e1804adf.js +0 -1
  656. package/apps/pwa/out/_next/static/chunks/app/control/page-9f5610caaa06178c.js +0 -1
  657. package/apps/pwa/out/_next/static/chunks/app/decisions/page-ec8f3575e339de74.js +0 -1
  658. package/apps/pwa/out/_next/static/chunks/app/design-check/page-86d753e460f1a989.js +0 -1
  659. package/apps/pwa/out/_next/static/chunks/app/editor/page-4166a7d51cf0a57d.js +0 -1
  660. package/apps/pwa/out/_next/static/chunks/app/exec/page-2a19c5cd3b562580.js +0 -1
  661. package/apps/pwa/out/_next/static/chunks/app/field/page-b6a09cde2e07521a.js +0 -1
  662. package/apps/pwa/out/_next/static/chunks/app/healthz/route-c1a96669158471ec.js +0 -1
  663. package/apps/pwa/out/_next/static/chunks/app/inside/page-f7eae1518ca0c353.js +0 -1
  664. package/apps/pwa/out/_next/static/chunks/app/intake/page-456a8573ad811381.js +0 -1
  665. package/apps/pwa/out/_next/static/chunks/app/layout-8462d6a543cbb2d9.js +0 -1
  666. package/apps/pwa/out/_next/static/chunks/app/live/page-2974cdfb87c0f9c9.js +0 -1
  667. package/apps/pwa/out/_next/static/chunks/app/live-run/page-f927ffa25f3c86d1.js +0 -1
  668. package/apps/pwa/out/_next/static/chunks/app/loops/page-abd86cd832aaa011.js +0 -1
  669. package/apps/pwa/out/_next/static/chunks/app/market/page-79daf68f6256adaa.js +0 -1
  670. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-8c3f168fc3f91939.js +0 -1
  671. package/apps/pwa/out/_next/static/chunks/app/morning/page-786fb4504d0f2fa9.js +0 -1
  672. package/apps/pwa/out/_next/static/chunks/app/not-found-75209f14597dcb8f.js +0 -1
  673. package/apps/pwa/out/_next/static/chunks/app/observatory/page-bf30affe3a85af7e.js +0 -1
  674. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-1187a44b7515434b.js +0 -1
  675. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-3d0910185a66ed54.js +0 -1
  676. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-f401ee1dd5a26e25.js +0 -1
  677. package/apps/pwa/out/_next/static/chunks/app/outputs/page-5dfe7604fd842784.js +0 -1
  678. package/apps/pwa/out/_next/static/chunks/app/page-76cf838050a8d681.js +0 -1
  679. package/apps/pwa/out/_next/static/chunks/app/reflection/page-7ec8e289b4c14df6.js +0 -1
  680. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-8c8ac17d52be2a79.js +0 -1
  681. package/apps/pwa/out/_next/static/chunks/app/sessions/page-d359ab048d63a9d9.js +0 -1
  682. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-b8b345c880bf78d1.js +0 -1
  683. package/apps/pwa/out/_next/static/chunks/app/settings/page-f151cc10ad026940.js +0 -1
  684. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-9875f103057da433.js +0 -1
  685. package/apps/pwa/out/_next/static/chunks/app/setup/layout-c1a96669158471ec.js +0 -1
  686. package/apps/pwa/out/_next/static/chunks/app/setup/page-9789328af9a9aa62.js +0 -1
  687. package/apps/pwa/out/_next/static/chunks/app/share/page-bf495a1e284f4e00.js +0 -1
  688. package/apps/pwa/out/_next/static/chunks/app/showroom/page-a263d94bca4be22a.js +0 -1
  689. package/apps/pwa/out/_next/static/chunks/app/tasks/page-3962eab8ec58d16e.js +0 -1
  690. package/apps/pwa/out/_next/static/chunks/app/term/page-9abcfac07e02a542.js +0 -1
  691. package/apps/pwa/out/_next/static/chunks/app/today/page-bee49f927c333abb.js +0 -1
  692. package/apps/pwa/out/_next/static/chunks/app/trace/page-bac2d56dcb02d955.js +0 -1
  693. package/apps/pwa/out/_next/static/chunks/app/vault/page-937cbcc13176295c.js +0 -1
  694. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-d09495594f7f480d.js +0 -1
  695. package/apps/pwa/out/_next/static/chunks/app/workflows/page-69cb93d1a2e57e0e.js +0 -1
  696. package/apps/pwa/out/_next/static/chunks/app/workspace/page-72ec543928d67c3e.js +0 -1
  697. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-82906eeda0186a7b.js +0 -1
  698. package/apps/pwa/out/_next/static/chunks/f6231284-45726550aba08fca.js +0 -1
  699. package/apps/pwa/out/_next/static/chunks/main-59fe95fed40c7a01.js +0 -1
  700. package/apps/pwa/out/_next/static/chunks/main-app-4ca74dbbee7057c6.js +0 -1
  701. package/apps/pwa/out/_next/static/chunks/pages/_app-13a0a886df4d97a1.js +0 -1
  702. package/apps/pwa/out/_next/static/chunks/pages/_error-6af8591cd3a3f038.js +0 -1
  703. package/apps/pwa/out/_next/static/chunks/webpack-f2b4016b9b981bc8.js +0 -1
  704. package/apps/pwa/out/_next/static/css/f2bd5257e6af88d8.css +0 -1
  705. /package/apps/pwa/out/_next/static/{aBi6EyMtLsrl7J8eH4kF7 → rfkMm4movqkqMebBrCXiR}/_ssgManifest.js +0 -0
@@ -36,6 +36,7 @@ import { parseDiscordEndpoint, isOwnInstanceEndpoint } from '../../session/sessi
36
36
  import { createDiscordStreamSink } from '../../session/streaming/discord-stream-sink.js';
37
37
  import { splitForDiscord } from '../../discord.js';
38
38
  import { formatForDiscord } from '../../discord-markdown.js';
39
+ import { receiveChannelEvent } from '../outbound/router.js';
39
40
 
40
41
  export interface NexusDiscordTriggerBotOpts {
41
42
  /** Bot token (without `Bot ` prefix). Production reads
@@ -109,11 +110,13 @@ export function createDiscordTriggerLogger(
109
110
  * (`src/discord.ts:160-172` comment) so the wire is a near-identity
110
111
  * with `raw` carrying the full gateway payload for debugging. */
111
112
  export function toDiscordEvent(tap: DcTriggerEvent): DiscordEvent {
113
+ const envelope = receiveChannelEvent('discord', tap);
114
+ if (!envelope || envelope instanceof Promise) throw new Error('discord connector cannot normalize accepted tap');
112
115
  return {
113
116
  kind: tap.kind,
114
- channel: tap.channel,
115
- user: tap.user,
116
- body: tap.body,
117
+ channel: envelope.channel ?? tap.channel,
118
+ user: envelope.senderId ?? tap.user,
119
+ body: envelope.text,
117
120
  raw: tap,
118
121
  };
119
122
  }
@@ -25,19 +25,21 @@ function storeDir(root: string): string { return join(root, 'error-reports'); }
25
25
  /** Removes day folders older than the keep window. Returns how many days were removed. */
26
26
  export function pruneErrorReports(root: string, now: number): number {
27
27
  const base = storeDir(root);
28
- if (!existsSync(base)) return 0;
29
28
  let removed = 0;
30
- for (const year of readdirSync(base).filter((n) => /^\d{4}$/.test(n))) {
31
- for (const month of readdirSync(join(base, year)).filter((n) => /^\d{2}$/.test(n))) {
32
- for (const day of readdirSync(join(base, year, month)).filter((n) => /^\d{2}$/.test(n))) {
33
- const at = Date.parse(`${year}-${month}-${day}T00:00:00Z`);
34
- if (Number.isFinite(at) && now - at > ERROR_REPORT_KEEP_DAYS * DAY_MS + DAY_MS) {
35
- rmSync(join(base, year, month, day), { recursive: true, force: true });
36
- removed++;
29
+ if (existsSync(base)) {
30
+ for (const year of readdirSync(base).filter((n) => /^\d{4}$/.test(n))) {
31
+ for (const month of readdirSync(join(base, year)).filter((n) => /^\d{2}$/.test(n))) {
32
+ for (const day of readdirSync(join(base, year, month)).filter((n) => /^\d{2}$/.test(n))) {
33
+ const at = Date.parse(`${year}-${month}-${day}T00:00:00Z`);
34
+ if (Number.isFinite(at) && now - at > ERROR_REPORT_KEEP_DAYS * DAY_MS + DAY_MS) {
35
+ rmSync(join(base, year, month, day), { recursive: true, force: true });
36
+ removed++;
37
+ }
37
38
  }
38
39
  }
39
40
  }
40
41
  }
42
+ debug.log('error-report.prune', 'done', { pruned: removed });
41
43
  return removed;
42
44
  }
43
45
 
@@ -77,6 +79,26 @@ function count24h(root: string, dedupe: string, now: number): number {
77
79
  return n;
78
80
  }
79
81
 
82
+ /** Retention without a receipt: prune once at daemon boot and then once a day (the ingest path still prunes too). */
83
+ export function startErrorReportPruneTick(deps: {
84
+ root?: () => string;
85
+ now?: () => number;
86
+ schedule?: (tick: () => void, ms: number) => { unref?: () => void };
87
+ } = {}): { stop(): void } {
88
+ const root = deps.root ?? effectiveInstanceRoot;
89
+ const now = deps.now ?? Date.now;
90
+ const run = (trigger: 'boot' | 'tick') => {
91
+ // pruneErrorReports emits error-report.prune/done { pruned }; only a failure is logged here.
92
+ try { pruneErrorReports(root(), now()); }
93
+ catch (error) { debug.log('error-report.prune', 'failed', { trigger, reason: error instanceof Error ? error.message : String(error) }); }
94
+ };
95
+ run('boot');
96
+ let stopped = false;
97
+ const handle = (deps.schedule ?? ((tick, ms) => setInterval(tick, ms)))(() => { if (!stopped) run('tick'); }, DAY_MS);
98
+ handle.unref?.();
99
+ return { stop: () => { stopped = true; if (!deps.schedule) clearInterval(handle as ReturnType<typeof setInterval>); } };
100
+ }
101
+
80
102
  /** POST /v1/reports/ingest — the caller (router) has already checked the ingest token. */
81
103
  export async function handleErrorReportIngest(req: Request, deps: ErrorReportIngestDeps = {}): Promise<Response> {
82
104
  const root = (deps.root ?? effectiveInstanceRoot)();
@@ -170,6 +170,7 @@ import { handleBuildsGet, parseBuildsPath } from './builds-api.js';
170
170
  import { handleHarnessAskPost, handleHarnessAskStatusGet, handleHarnessRunEventsGet, handleHarnessRunScreenGet, handleHarnessRunsGet, handleHarnessStopPost } from './harness-api.js';
171
171
  import { handleHarnessRunTraversalGet } from './harness-traversal.js';
172
172
  import { handleLoopEdgesGet } from './loop-edges.js';
173
+ import { handleLoopResourcesGet } from './loop-resources.js';
173
174
  import { handleFabricPlans, type FabricPlansRouteOpts } from './fabric-plans.js';
174
175
  import { dispatchPersonaRoute } from './personas.js';
175
176
  import { handleMe, operatorSignal } from './operator.js';
@@ -498,6 +499,8 @@ export interface NexusHttpServerOpts {
498
499
  outputs?: OutputsDeps;
499
500
  /** Read-only grid measurement seam; omitted in production for live HQ and Pod pool reads. */
500
501
  grid?: GridDeps;
502
+ /** Resource observation seam; production reads the same ORCH traffic decision. */
503
+ loopResources?: Parameters<typeof handleLoopResourcesGet>[0];
501
504
  /** DRAFT-METRIC cached source seam; omitted in production for the process-wide TTL cache. */
502
505
  draftMetrics?: DraftMetricsSource;
503
506
  decisions?: Pick<DecisionsRouteDeps, 'ledger'>;
@@ -1396,6 +1399,11 @@ export async function routeRequest(
1396
1399
 
1397
1400
  // Same owner auth as the neighbouring GETs (short-lived owner tokens · same-origin PWA); #23932 compared the admin
1398
1401
  // bearer only, so a PWA on a short-lived token got 401 and the loop map stayed empty.
1402
+ if (pathname === '/v1/loops/resources' && method === 'GET') {
1403
+ if (!opts.metaApi) return jsonResponse({ error: 'meta-api-runtime-not-wired' }, 503);
1404
+ if (!checkAuth(req, opts.metaApi)) return jsonResponse({ error: 'unauthorized' }, 401);
1405
+ return handleLoopResourcesGet(opts.loopResources);
1406
+ }
1399
1407
  if (pathname === '/v1/loops/edges' && method === 'GET') {
1400
1408
  if (!opts.metaApi) return jsonResponse({ error: 'meta-api-runtime-not-wired' }, 503);
1401
1409
  if (!checkAuth(req, opts.metaApi)) return jsonResponse({ error: 'unauthorized' }, 401);
@@ -0,0 +1,42 @@
1
+ import { defaultListHarnessProcesses } from '../../harness/harness-cli-command.js';
2
+ import { devVersion, listChecklist } from '../../release-loop/checklist.js';
3
+ import { listSchedules } from '../../release-loop/release-schedule.js';
4
+ import { releaseLedgerRoot } from '../../instance/resolve.js';
5
+ import { getUserConfig, ORCHESTRATOR_DEFAULTS } from '../../user-config.js';
6
+ import { seatOfTree, trafficTick, TRAFFIC_SEATS, type TrafficCell, type TrafficResult } from '../../loops/orchestrator/traffic.js';
7
+ import { finishAdvice, measureFinish } from '../../loops/orchestrator/finish-rate.js';
8
+ import { jsonResponse } from './json-response.js';
9
+
10
+ /** Read-only snapshot of the same ORCH traffic decision used by the scheduled tick. */
11
+ export function observeLoopResources(deps: {
12
+ now?: Date;
13
+ processes?: ReturnType<typeof defaultListHarnessProcesses>;
14
+ version?: string;
15
+ openCells?: readonly TrafficCell[];
16
+ nextRound?: readonly TrafficCell[];
17
+ totalSlots?: number;
18
+ } = {}): TrafficResult {
19
+ const now = deps.now ?? new Date();
20
+ const cfg = getUserConfig().loops?.orchestrator ?? ORCHESTRATOR_DEFAULTS;
21
+ const observation = deps.processes ?? defaultListHarnessProcesses();
22
+ if (observation.status !== 'ok') throw new Error(`process observation ${observation.status}`);
23
+ const version = deps.version ?? devVersion().replace(/-dev\.\d+$/, '');
24
+ const upcoming = deps.nextRound === undefined ? listSchedules(releaseLedgerRoot()).filter(row => Date.parse(row.cutAt) > now.getTime())
25
+ .sort((a, b) => Date.parse(a.cutAt) - Date.parse(b.cutAt)) : [];
26
+ const currentIndex = upcoming.findIndex(row => row.version === version);
27
+ const nextVersion = currentIndex < 0 ? upcoming[0]?.version : upcoming[currentIndex + 1]?.version;
28
+ const totalSlots = deps.totalSlots ?? finishAdvice(measureFinish({}, now),
29
+ TRAFFIC_SEATS.reduce((sum, seat) => sum + cfg.seatCaps[seat], 0)).launchSlots;
30
+ return trafficTick({
31
+ processes: observation.records.map(process => ({ ...process, seat: seatOfTree(process.cwdStatus === 'unknown' ? undefined : process.cwd, cfg) })),
32
+ now, caps: cfg.seatCaps, openCells: deps.openCells ?? listChecklist(version).items, totalSlots,
33
+ ...(deps.nextRound !== undefined ? { nextRound: deps.nextRound }
34
+ : nextVersion ? { nextRound: listChecklist(nextVersion).items } : {}),
35
+ retainQueuedCells: true,
36
+ });
37
+ }
38
+
39
+ export function handleLoopResourcesGet(observe: () => TrafficResult = observeLoopResources): Response {
40
+ try { return jsonResponse({ resource: observe() }); }
41
+ catch { return jsonResponse({ error: 'resource-unavailable' }, 503); }
42
+ }
@@ -8,7 +8,7 @@
8
8
  // so ALL outbound routes through elanous's delivery channels. Send-only;
9
9
  // auth via bearer (acp-token).
10
10
  //
11
- // Body: { text: string, markdown?: boolean, kind?: string }
11
+ // Body: { text: string, markdown?: boolean, kind?: string, role?: string }
12
12
  // kind routes per-channel since R6 Phase 1 (2026-07-07): the body is
13
13
  // handed to routeOutbound (src/nexus/outbound/router.ts) which fans out
14
14
  // to telegram/discord/pushcut per the `outbound` user-config section.
@@ -53,6 +53,7 @@ interface OutboundBody {
53
53
  text?: string;
54
54
  markdown?: boolean;
55
55
  kind?: string;
56
+ role?: string;
56
57
  }
57
58
 
58
59
  function json(body: unknown, status: number): Response {
@@ -85,10 +86,14 @@ export async function handleOutboundReport(req: Request, metaApi: MetaApiOpts, r
85
86
  const cfg = getUserConfig();
86
87
  const kind = typeof body.kind === 'string' ? body.kind : 'report';
87
88
  try {
88
- // Router is per-channel fail-soft; `delivered` = any channel ok.
89
+ // routeOutbound dispatches via the channel adapter registry; legacy fan-out
90
+ // remains per-channel fail-soft and `delivered` = any channel ok.
89
91
  // Producers (outbound-alert.ts deliver()) gate their direct-telegram
90
92
  // fallback on this top-level boolean — keep it stable.
91
- const result = await routeOutbound(cfg, { text, markdown: body.markdown ?? true, kind }, routerDeps);
93
+ const role = typeof body.role === 'string' && /^[a-zA-Z0-9_-]{1,80}$/.test(body.role) ? body.role : undefined;
94
+ // A request that names a role intends that role's recipients — never fall back to the kind route silently.
95
+ if (body.role !== undefined && role === undefined) return json({ error: 'invalid-role' }, 400);
96
+ const result = await routeOutbound(cfg, { text, markdown: body.markdown ?? true, kind, ...(role ? { role } : {}) }, routerDeps);
92
97
  const clientFallback = req.headers.get('X-Elanous-Client-Fallback') === 'direct';
93
98
  const source = req.headers.get('X-Elanous-Outbound-Source') ?? 'daemon-http';
94
99
  if (result.delivered || !clientFallback) observeDaemonSend(cfg, text, kind, result, source);
@@ -36,6 +36,7 @@ import { parseTelegramEndpoint, isOwnInstanceEndpoint, matchesBot } from '../../
36
36
  import { createTelegramStreamSink } from '../../session/streaming/telegram-stream-sink.js';
37
37
  import { splitMarkdownForTelegram } from '../../telegram-format.js';
38
38
  import { formatTablesAndRules } from '../../discord-markdown.js';
39
+ import { receiveChannelEvent } from '../outbound/router.js';
39
40
 
40
41
  export interface NexusTelegramTriggerBotOpts {
41
42
  /** Bot token (without `Bot ` prefix). Production reads
@@ -117,11 +118,13 @@ export function createTelegramTriggerLogger(
117
118
  * comment) so the wire is a near-identity with `raw` carrying the
118
119
  * full payload (messageId · isDm) for debugging. */
119
120
  export function toTelegramEvent(tap: TgTriggerEvent): TelegramEvent {
121
+ const envelope = receiveChannelEvent('telegram', tap);
122
+ if (!envelope || envelope instanceof Promise) throw new Error('telegram connector cannot normalize accepted tap');
120
123
  return {
121
124
  kind: tap.kind,
122
- chat: tap.chat,
123
- user: tap.user,
124
- body: tap.body,
125
+ chat: envelope.channel ?? tap.chat,
126
+ user: envelope.senderId ?? tap.user,
127
+ body: envelope.text,
125
128
  ...(tap.command !== undefined ? { command: tap.command } : {}),
126
129
  raw: tap,
127
130
  };
@@ -410,6 +410,8 @@ export interface RunNexusOptions {
410
410
  * detachForTesting; production runs always start the server. Tests
411
411
  * that DO want the server can pass false explicitly. */
412
412
  skipHttpServer?: boolean;
413
+ /** Test seam: replaces the daily error-report prune timer (the boot prune still runs for real). */
414
+ errorReportPruneScheduleForTesting?: (tick: () => void, ms: number) => { unref?: () => void };
413
415
  /** Override starting port for the HTTP server. Defaults to 31415. */
414
416
  httpStartPort?: number;
415
417
  /** Control-plane port; passing 0 also enables an isolated listener in detached tests. */
@@ -1266,6 +1268,13 @@ export async function runNexus(opts: RunNexusOptions = {}): Promise<RunNexusHand
1266
1268
  // 60s 틱마다 grace 유예 초과 구독자를 left 로 이탈 확정(reconcileSessionPresence).
1267
1269
  // cleared in shutdown hook.
1268
1270
  let presenceSweepHandle: ReturnType<typeof setInterval> | undefined;
1271
+ // ERROR-REPORT-PRUNE-TICK — 30-day error-report retention must not wait for the next receipt:
1272
+ // prune once at boot and once a day (unconditional · fail-soft · unref). Cleared in shutdown hook.
1273
+ let errorReportPrune: { stop(): void } | undefined;
1274
+ try {
1275
+ const { startErrorReportPruneTick } = await import('./api/error-reports.js');
1276
+ errorReportPrune = startErrorReportPruneTick({ ...(opts.errorReportPruneScheduleForTesting ? { schedule: opts.errorReportPruneScheduleForTesting } : {}) });
1277
+ } catch (err) { console.warn(`[nexus] error-report prune wiring failed: ${(err as Error).message}`); }
1269
1278
  // ★ UR4d — 조율자 저지연 push watcher(logs.db tail edge-trigger). opt-in·기본 OFF.
1270
1279
  // cleared in shutdown hook(내부 타이머는 unref 이나 명시 정지).
1271
1280
  let coordinatorPushWatcher: { stop(): void } | undefined;
@@ -3383,6 +3392,7 @@ export async function runNexus(opts: RunNexusOptions = {}): Promise<RunNexusHand
3383
3392
  if (replayArmerHandle) { clearInterval(replayArmerHandle); replayArmerHandle = undefined; }
3384
3393
  // §P2 — stop the session presence grace sweeper.
3385
3394
  if (presenceSweepHandle) { clearInterval(presenceSweepHandle); presenceSweepHandle = undefined; }
3395
+ if (errorReportPrune) { errorReportPrune.stop(); errorReportPrune = undefined; }
3386
3396
  // ★ UR4d — stop the coordinator low-latency push watcher(logs.db tail + debounce).
3387
3397
  if (coordinatorPushWatcher) { try { coordinatorPushWatcher.stop(); } catch { /* ignore */ } coordinatorPushWatcher = undefined; }
3388
3398
  // Phase 2 (2026-05-13) — dispose the TOX boot handle. dispose()
@@ -12,6 +12,8 @@ export interface OutboundMsg {
12
12
  text: string;
13
13
  markdown: boolean;
14
14
  kind: string;
15
+ /** Optional routing role; absent preserves per-kind fan-out. */
16
+ role?: string;
15
17
  }
16
18
 
17
19
  export interface FormattedMessage {
@@ -43,8 +43,66 @@ import {
43
43
  export { chunkForDiscord } from './format.js';
44
44
  export type { OutboundChannelType, OutboundMsg } from './format.js';
45
45
 
46
+ /** The adapter's capability declaration is explicit: a feature is unavailable unless
47
+ * the connector implements it. Send-only webhooks must not claim inbound support. */
48
+ export interface ChannelCapabilities {
49
+ send: boolean;
50
+ receive: boolean;
51
+ thread: boolean;
52
+ topic: boolean;
53
+ persona: boolean;
54
+ channelLifecycle: boolean;
55
+ permissions: boolean;
56
+ commands: boolean;
57
+ attachments: boolean;
58
+ buttons: boolean;
59
+ choices: boolean;
60
+ deliveryConfirmation: boolean;
61
+ rateLimit: boolean;
62
+ }
63
+
64
+ export interface ChannelEnvelope {
65
+ text: string;
66
+ kind: string;
67
+ channel?: string;
68
+ senderId?: string;
69
+ messageId?: string | number;
70
+ threadId?: string;
71
+ topicId?: string;
72
+ persona?: { name: string; avatarUrl?: string };
73
+ attachments?: readonly { url: string; name?: string }[];
74
+ buttons?: readonly { label: string; value: string }[];
75
+ choices?: readonly { label: string; value: string }[];
76
+ }
77
+
78
+ /** Plugin contract; optional operations may only be advertised when implemented.
79
+ * `send` returns the same ChannelResult shape used by the existing delivery ledger. */
80
+ export interface ChannelAdapter {
81
+ readonly type: string;
82
+ readonly capabilities: ChannelCapabilities;
83
+ send(config: OutboundChannelConfig, message: OutboundMsg, cfg: UserConfig, deps: RouterDeps): Promise<ChannelResult>;
84
+ receive?: (payload: unknown) => ChannelEnvelope | null | Promise<ChannelEnvelope | null>;
85
+ createChannel?: (name: string) => Promise<string>;
86
+ archiveChannel?: (id: string) => Promise<void>;
87
+ checkPermission?: (action: string) => Promise<boolean>;
88
+ registerCommands?: (commands: readonly string[]) => Promise<void>;
89
+ confirmDelivery?: (messageId: string) => Promise<boolean>;
90
+ retryAfterMs?: (result: ChannelResult) => number | undefined;
91
+ /** Validate a plugin's channel entry before it becomes eligible for routing.
92
+ * Unknown or invalid entries stay disabled rather than falling back to a different channel. */
93
+ parseConfig?: (raw: Record<string, unknown>) => OutboundChannelConfig | undefined;
94
+ }
95
+
96
+ /** Existing bot receive paths can normalize an accepted event through the same
97
+ * adapter contract before dispatching to their unchanged workflow/chat handlers. */
98
+ export function receiveChannelEvent(type: string, payload: unknown): ChannelEnvelope | null | Promise<ChannelEnvelope | null> {
99
+ const adapter = outboundAdapters.get(type) ?? installedAdapters.get(type);
100
+ if (!adapter?.capabilities.receive || !adapter.receive) return null;
101
+ return adapter.receive(payload);
102
+ }
103
+
46
104
  export interface OutboundChannelConfig {
47
- type: OutboundChannelType;
105
+ type: OutboundChannelType | (string & {});
48
106
  /** discord: full webhook URL (required). pushcut: webhook URL — when
49
107
  * omitted the adapter falls back to the API-key client
50
108
  * (~/.elanous/pushcut.json) with `notification`. */
@@ -54,6 +112,24 @@ export interface OutboundChannelConfig {
54
112
  notification?: string;
55
113
  }
56
114
 
115
+ /** Connector providers register while the host is running; unregister on plugin
116
+ * deactivation. Built-ins cannot be replaced by a plugin or removed. */
117
+ const installedAdapters = new Map<string, ChannelAdapter>();
118
+ export function registerOutboundAdapter(adapter: ChannelAdapter): () => void {
119
+ if (!/^[a-z][a-z0-9-]*$/.test(adapter.type) || outboundAdapters.has(adapter.type) || installedAdapters.has(adapter.type)
120
+ || !adapter.capabilities.send || typeof adapter.send !== 'function' || typeof adapter.parseConfig !== 'function'
121
+ || (adapter.capabilities.receive && typeof adapter.receive !== 'function')
122
+ || (adapter.capabilities.channelLifecycle && (typeof adapter.createChannel !== 'function' || typeof adapter.archiveChannel !== 'function'))
123
+ || (adapter.capabilities.permissions && typeof adapter.checkPermission !== 'function')
124
+ || (adapter.capabilities.commands && typeof adapter.registerCommands !== 'function')
125
+ || (adapter.capabilities.deliveryConfirmation && typeof adapter.confirmDelivery !== 'function')
126
+ || (adapter.capabilities.rateLimit && typeof adapter.retryAfterMs !== 'function')) {
127
+ throw new Error(`invalid or duplicate outbound connector: ${adapter.type}`);
128
+ }
129
+ installedAdapters.set(adapter.type, adapter);
130
+ return () => { if (installedAdapters.get(adapter.type) === adapter) installedAdapters.delete(adapter.type); };
131
+ }
132
+
57
133
  export interface OutboundFanoutConfig {
58
134
  channels: OutboundChannelConfig[];
59
135
  /** kind → channel types. A kind missing here falls back to
@@ -62,6 +138,11 @@ export interface OutboundFanoutConfig {
62
138
  routes?: Record<string, string[]>;
63
139
  /** 외출 중 kind → channel types. 미지정 kind는 awayRoutes.default, 이것도 없으면 기존 routes를 유지한다. */
64
140
  awayRoutes?: Record<string, string[]>;
141
+ /** Optional primary/fallback per message kind or role; legacy routes remain fan-out. */
142
+ primary?: Record<string, string>;
143
+ fallback?: Record<string, string>;
144
+ /** Optional role → eligible channel types; no role retains kind routes. */
145
+ roleRoutes?: Record<string, string[]>;
65
146
  }
66
147
 
67
148
  export interface ChannelResult {
@@ -95,6 +176,8 @@ export interface RouterDeps {
95
176
  /** 롱콘텐츠 spill 주입(테스트/대체). 기본 = spillLongContent(S3 게이트·fail-soft).
96
177
  * 긴 본문을 S3 업로드+링크로 대체 → 전 메신저가 짧은 링크 버전을 받음. */
97
178
  spill?: (text: string) => { text: string; spilled: boolean; url?: string };
179
+ /** Test/host supplied connector registry; built-ins remain the default. */
180
+ adapters?: ReadonlyMap<string, ChannelAdapter>;
98
181
  }
99
182
 
100
183
  /** Parse the sparse `outbound` section. Malformed input → undefined
@@ -109,7 +192,16 @@ export function normalizeOutbound(raw: unknown): OutboundFanoutConfig | undefine
109
192
  if (!entry || typeof entry !== 'object') continue;
110
193
  const ch = entry as Record<string, unknown>;
111
194
  const type = ch.type;
112
- if (type !== 'telegram' && type !== 'discord' && type !== 'pushcut') continue;
195
+ if (type !== 'telegram' && type !== 'discord' && type !== 'pushcut') {
196
+ if (typeof type === 'string') {
197
+ const connector = installedAdapters.get(type);
198
+ try {
199
+ const parsed = connector?.parseConfig?.(ch);
200
+ if (parsed?.type === type) channels.push(parsed);
201
+ } catch { /* invalid plugin config is not a routable channel */ }
202
+ }
203
+ continue;
204
+ }
113
205
  const webhookUrl = typeof ch.webhookUrl === 'string' && ch.webhookUrl.trim()
114
206
  ? ch.webhookUrl.trim() : undefined;
115
207
  const notification = typeof ch.notification === 'string' && ch.notification.trim()
@@ -132,7 +224,18 @@ export function normalizeOutbound(raw: unknown): OutboundFanoutConfig | undefine
132
224
  };
133
225
  const routes = parseRoutes(o.routes);
134
226
  const awayRoutes = parseRoutes(o.awayRoutes);
135
- return { channels, ...(routes ? { routes } : {}), ...(awayRoutes ? { awayRoutes } : {}) };
227
+ const roleRoutes = parseRoutes(o.roleRoutes);
228
+ const parseSelection = (value: unknown): Record<string, string> | undefined => {
229
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return undefined;
230
+ return Object.fromEntries(Object.entries(value as Record<string, unknown>)
231
+ // Keep a selection even when its channel is not configured: an explicit main/backup must
232
+ // never silently turn back into legacy fan-out (it is reported as not-routed instead).
233
+ .filter((entry): entry is [string, string] => typeof entry[1] === 'string' && entry[1].trim().length > 0));
234
+ };
235
+ const primary = parseSelection(o.primary);
236
+ const fallback = parseSelection(o.fallback);
237
+ return { channels, ...(routes ? { routes } : {}), ...(awayRoutes ? { awayRoutes } : {}),
238
+ ...(roleRoutes ? { roleRoutes } : {}), ...(primary ? { primary } : {}), ...(fallback ? { fallback } : {}) };
136
239
  }
137
240
 
138
241
  /** Resolve the channel list for one message kind. No/empty config →
@@ -148,14 +251,14 @@ export function resolveChannels(
148
251
  return outbound.channels.filter(ch => route.includes(ch.type));
149
252
  }
150
253
 
151
- async function deliverTelegram(cfg: UserConfig, msg: OutboundMsg, deps: RouterDeps): Promise<ChannelResult> {
254
+ async function deliverTelegram(_ch: OutboundChannelConfig, msg: OutboundMsg, cfg: UserConfig, deps: RouterDeps): Promise<ChannelResult> {
152
255
  const fmt = formatForChannel('telegram', msg);
153
256
  const send = deps.telegramSend ?? sendTelegramReport;
154
257
  const ok = await send(cfg, fmt.text, { markdown: fmt.markdown ?? msg.markdown, fetchImpl: deps.fetchImpl, kind: msg.kind });
155
258
  return ok ? { type: 'telegram', ok: true } : { type: 'telegram', ok: false, error: 'not-configured' };
156
259
  }
157
260
 
158
- async function deliverDiscord(ch: OutboundChannelConfig, msg: OutboundMsg, deps: RouterDeps): Promise<ChannelResult> {
261
+ async function deliverDiscord(ch: OutboundChannelConfig, msg: OutboundMsg, _cfg: UserConfig, deps: RouterDeps): Promise<ChannelResult> {
159
262
  const fetchImpl = deps.fetchImpl ?? fetch;
160
263
  // webhookUrl guaranteed by normalizeOutbound; guard for direct callers.
161
264
  if (!ch.webhookUrl) return { type: 'discord', ok: false, error: 'missing-webhook-url' };
@@ -172,7 +275,7 @@ async function deliverDiscord(ch: OutboundChannelConfig, msg: OutboundMsg, deps:
172
275
  return { type: 'discord', ok: true };
173
276
  }
174
277
 
175
- async function deliverPushcut(ch: OutboundChannelConfig, msg: OutboundMsg, deps: RouterDeps): Promise<ChannelResult> {
278
+ async function deliverPushcut(ch: OutboundChannelConfig, msg: OutboundMsg, _cfg: UserConfig, deps: RouterDeps): Promise<ChannelResult> {
176
279
  const fmt = formatForChannel('pushcut', msg);
177
280
  const title = fmt.title ?? `elanous ${msg.kind}`;
178
281
  if (ch.webhookUrl) {
@@ -194,6 +297,40 @@ async function deliverPushcut(ch: OutboundChannelConfig, msg: OutboundMsg, deps:
194
297
  return r.ok ? { type: 'pushcut', ok: true } : { type: 'pushcut', ok: false, error: r.reason ?? 'pushcut-failed' };
195
298
  }
196
299
 
300
+ /** Built-in channel connectors. Telegram and Discord receive adapters normalize
301
+ * already-authorized trigger taps; the webhook and Pushcut senders do not poll. */
302
+ export const outboundAdapters: ReadonlyMap<string, ChannelAdapter> = new Map<string, ChannelAdapter>([
303
+ ['telegram', { type: 'telegram', capabilities: {
304
+ send: true, receive: true, thread: false, topic: false, persona: false,
305
+ channelLifecycle: false, permissions: false, commands: false, attachments: false,
306
+ buttons: false, choices: false, deliveryConfirmation: false, rateLimit: false,
307
+ }, send: deliverTelegram, receive: (payload): ChannelEnvelope | null => {
308
+ if (!payload || typeof payload !== 'object') return null;
309
+ const tap = payload as Record<string, unknown>;
310
+ if (typeof tap.body !== 'string' || typeof tap.kind !== 'string' || typeof tap.chat !== 'string') return null;
311
+ return { text: tap.body, kind: tap.kind, channel: tap.chat,
312
+ ...(typeof tap.user === 'string' ? { senderId: tap.user } : {}),
313
+ ...(typeof tap.messageId === 'number' ? { messageId: tap.messageId } : {}) };
314
+ } }],
315
+ ['discord', { type: 'discord', capabilities: {
316
+ send: true, receive: true, thread: false, topic: false, persona: false,
317
+ channelLifecycle: false, permissions: false, commands: false, attachments: false,
318
+ buttons: false, choices: false, deliveryConfirmation: false, rateLimit: false,
319
+ }, send: deliverDiscord, receive: (payload): ChannelEnvelope | null => {
320
+ if (!payload || typeof payload !== 'object') return null;
321
+ const tap = payload as Record<string, unknown>;
322
+ if (typeof tap.body !== 'string' || typeof tap.kind !== 'string' || typeof tap.channel !== 'string') return null;
323
+ return { text: tap.body, kind: tap.kind, channel: tap.channel,
324
+ ...(typeof tap.user === 'string' ? { senderId: tap.user } : {}),
325
+ ...(typeof tap.messageId === 'string' ? { messageId: tap.messageId } : {}) };
326
+ } }],
327
+ ['pushcut', { type: 'pushcut', capabilities: {
328
+ send: true, receive: false, thread: false, topic: false, persona: false,
329
+ channelLifecycle: false, permissions: false, commands: false, attachments: false,
330
+ buttons: false, choices: false, deliveryConfirmation: false, rateLimit: false,
331
+ }, send: deliverPushcut }],
332
+ ]);
333
+
197
334
  /** Fan one message out to every routed channel. Per-channel fail-soft —
198
335
  * a throwing adapter records `{ok:false}` and never blocks siblings.
199
336
  * 구조: 채널별 포맷(format.ts) + 중복 발사 제어·배송 원장(delivery-ledger.ts).
@@ -228,27 +365,75 @@ export async function routeOutbound(cfg: UserConfig, msg: OutboundMsg, deps: Rou
228
365
 
229
366
  // 상태 파일이 없거나 읽기에 실패하면 기존 kind 경로를 그대로 사용한다.
230
367
  let list = resolveChannels(outbound, msg.kind);
368
+ // Whether the route that decided `list` is explicitly empty — that means «suppress», which is
369
+ // different from a route whose channels are simply not configured.
370
+ const kindRoute = outbound ? (outbound.routes?.[msg.kind] ?? outbound.routes?.default) : undefined;
371
+ let explicitlyEmpty = kindRoute !== undefined && kindRoute.length === 0;
372
+ const role = msg.role;
373
+ const roleRoute = role ? outbound?.roleRoutes?.[role] : undefined;
374
+ if (roleRoute && outbound) {
375
+ list = outbound.channels.filter(ch => roleRoute.includes(ch.type));
376
+ explicitlyEmpty = roleRoute.length === 0;
377
+ }
231
378
  if (outbound?.awayRoutes) {
232
379
  try {
233
380
  if (readPresence().away) {
234
381
  const awayRoute = outbound.awayRoutes[msg.kind] ?? outbound.awayRoutes.default;
235
382
  // kind 도 default 도 없으면 기존 경로로 · 명시적인 빈 배열은 기존 routes 와 같은 규칙(채널 0)을 따른다.
236
383
  if (awayRoute !== undefined) {
237
- list = outbound.channels.filter(ch => awayRoute.includes(ch.type));
384
+ // Away mode narrows the route; it never widens a role's allowed channels.
385
+ list = outbound.channels.filter(ch => awayRoute.includes(ch.type) && (!roleRoute || roleRoute.includes(ch.type)));
386
+ // A role's explicitly empty route stays suppressed while away.
387
+ explicitlyEmpty = awayRoute.length === 0 || roleRoute?.length === 0;
238
388
  try { debug.log('outbound.send', 'away-route', { kind: msg.kind, channels: list.map(ch => ch.type) }); } catch { /* 관측 실패는 발송 결과에 영향 없음 */ }
239
389
  }
240
390
  }
241
391
  } catch { /* 상태 조회 실패 — 기존 경로 유지 */ }
242
392
  }
243
- const channels = await Promise.all(list.map(async (ch): Promise<ChannelResult> => {
393
+ const send = async (ch: OutboundChannelConfig): Promise<ChannelResult> => {
244
394
  try {
245
- if (ch.type === 'telegram') return await deliverTelegram(cfg, outMsg, deps);
246
- if (ch.type === 'discord') return await deliverDiscord(ch, outMsg, deps);
247
- return await deliverPushcut(ch, outMsg, deps);
395
+ const adapter = deps.adapters?.get(ch.type) ?? outboundAdapters.get(ch.type) ?? installedAdapters.get(ch.type);
396
+ if (!adapter?.capabilities.send) return { type: ch.type, ok: false, error: 'unsupported-channel' };
397
+ const result = await adapter.send(ch, outMsg, cfg, deps);
398
+ // A connector may not impersonate another channel or echo credentials
399
+ // through the response. Legacy built-in result fields stay unchanged.
400
+ if (!outboundAdapters.has(ch.type)) return { type: ch.type, ok: result.ok,
401
+ ...(result.ok ? {} : { error: 'delivery-failed' }) };
402
+ return { ...result, type: ch.type };
248
403
  } catch (e) {
249
- return { type: ch.type, ok: false, error: e instanceof Error ? e.message : String(e) };
404
+ // Connector exceptions may include secret-bearing URLs or configuration.
405
+ // Keep the old built-in error reasons unless they expose the webhook;
406
+ // a plugin's arbitrary configuration cannot safely be echoed at all.
407
+ const reason = e instanceof Error ? e.message : String(e);
408
+ const plugin = !outboundAdapters.has(ch.type);
409
+ return { type: ch.type, ok: false,
410
+ error: plugin || (ch.webhookUrl && reason.includes(ch.webhookUrl)) ? 'delivery-failed' : reason };
250
411
  }
251
- }));
412
+ };
413
+ const primaryType = (role ? outbound?.primary?.[role] : undefined) ?? outbound?.primary?.[msg.kind] ?? outbound?.primary?.default;
414
+ const fallbackType = (role ? outbound?.fallback?.[role] : undefined) ?? outbound?.fallback?.[msg.kind] ?? outbound?.fallback?.default;
415
+ // Legacy routes still fan out. An explicit primary switches only this kind/role to ordered
416
+ // failover: main, then the configured backup — never the rest of the route. A selected channel
417
+ // that the current route does not carry is recorded as not-routed and the backup is tried.
418
+ // An explicitly empty route stays suppressed (no rows).
419
+ // Only an explicitly empty route suppresses the selection entirely (no rows).
420
+ const suppressed = list.length === 0 && explicitlyEmpty;
421
+ let channels: ChannelResult[];
422
+ if (suppressed) {
423
+ channels = [];
424
+ } else if (primaryType) {
425
+ channels = [];
426
+ for (const type of fallbackType && fallbackType !== primaryType ? [primaryType, fallbackType] : [primaryType]) {
427
+ const ch = list.find(candidate => candidate.type === type);
428
+ const result = ch ? await send(ch) : { type, ok: false, error: 'not-routed' };
429
+ channels.push(result);
430
+ if (result.ok) break;
431
+ }
432
+ } else {
433
+ // A backup without a main has nothing to back up: the allowed route already fans out to every
434
+ // channel it may reach, and a backup outside that route must not bypass role/away limits.
435
+ channels = await Promise.all(list.map(send));
436
+ }
252
437
 
253
438
  // 배송 기록(리드 동기화·중복 제어 토대) — fail-soft.
254
439
  if (ledger) {
@@ -4,7 +4,7 @@
4
4
  // 주간 창이 찬 계정으로 계속 쏘면 런이 「구현 결손」으로 죽는다(그 어휘는 `#7123` 이 냈다).
5
5
  //
6
6
  // ⛔⭐⭐ **결정 다섯** (대표):
7
- // ① 계정 «전환»만 자동이다 — 리셋 크레딧의 실제 소비는 이 판정 밖의 기존 경로가 맡는다.
7
+ // ① 리셋권 소비는 순수 판정 밖의 적용 경로가 기존 소비 연산으로 맡는다.
8
8
  // ② 기본 ON. config `llm.codexAccountRotation: false` 로 끈다.
9
9
  // ③ 사람이 «명시»한 계정은 전환하지 않는다 — 의도가 이긴다.
10
10
  // ④ 현재 사용률을 모르면 안전한 기본 계정에 고정하지 않는다 — 쓸 후보가 있으면 넘긴다.
@@ -185,23 +185,6 @@ export function decideCodexRotation(input: RotationInput): RotationDecision {
185
185
  });
186
186
  if (input.explicit) return { reason: 'explicit', candidateCount, accountThresholds };
187
187
  if (!input.enabled) return { reason: 'disabled', candidateCount, accountThresholds, ...(input.disabledProvenance ? { disabledProvenance: input.disabledProvenance } : {}) };
188
- if (input.creditPace?.active) {
189
- const self = input.candidates.find((candidate) => candidate.name === input.current.name);
190
- const eligible = (name: string, balance: number | undefined, hasCredits: boolean | undefined, home: string | undefined): boolean =>
191
- !!home?.trim() && typeof balance === 'number' && Number.isFinite(balance) && balance > 0
192
- && hasCredits !== false && accountThresholds.some((row) => row.name === name
193
- && (row.status === 'reached' || row.status === 'threshold-reached'));
194
- const currentBalance = input.currentCreditBalance ?? self?.creditBalance;
195
- const currentHas = input.currentHasCredits ?? self?.hasCredits;
196
- const to = otherCandidates.filter((candidate) => eligible(candidate.name, candidate.creditBalance, candidate.hasCredits, candidate.home))
197
- .sort((a, b) => (b.creditBalance! - a.creditBalance!) || (a.name < b.name ? -1 : a.name > b.name ? 1 : 0))[0];
198
- if (eligible(input.current.name, currentBalance, currentHas, self?.home ?? input.current.home)) {
199
- return { reason: 'credit-pace', candidateCount, accountThresholds };
200
- }
201
- if (to) {
202
- return { reason: 'credit-pace', candidateCount, accountThresholds, to };
203
- }
204
- }
205
188
  const currentUsageUnknown = input.currentReached === undefined && input.currentUsedPercent == null;
206
189
  if (input.currentReached !== true && !currentUsageUnknown && accountThresholds[0]?.status !== 'threshold-reached') {
207
190
  return { reason: 'not-reached', candidateCount, accountThresholds };
@@ -232,6 +215,21 @@ export function decideCodexRotation(input: RotationInput): RotationDecision {
232
215
  const to = usable[0];
233
216
  if (!to) {
234
217
  if (input.resetCreditAvailability === 'available') return { reason: 'reset-credit-available', candidateCount, accountThresholds };
218
+ if (input.creditPace?.active) {
219
+ const self = input.candidates.find((candidate) => candidate.name === input.current.name);
220
+ const eligible = (name: string, balance: number | undefined, hasCredits: boolean | undefined, home: string | undefined): boolean =>
221
+ !!home?.trim() && typeof balance === 'number' && Number.isFinite(balance) && balance > 0
222
+ && hasCredits !== false && accountThresholds.some((row) => row.name === name
223
+ && (row.status === 'reached' || row.status === 'threshold-reached'));
224
+ const currentBalance = input.currentCreditBalance ?? self?.creditBalance;
225
+ const currentHas = input.currentHasCredits ?? self?.hasCredits;
226
+ const richest = otherCandidates.filter((candidate) => eligible(candidate.name, candidate.creditBalance, candidate.hasCredits, candidate.home))
227
+ .sort((a, b) => (b.creditBalance! - a.creditBalance!) || (a.name < b.name ? -1 : a.name > b.name ? 1 : 0))[0];
228
+ if (eligible(input.current.name, currentBalance, currentHas, self?.home ?? input.current.home)) {
229
+ return { reason: 'credit-pace', candidateCount, accountThresholds };
230
+ }
231
+ if (richest) return { reason: 'credit-pace', candidateCount, accountThresholds, to: richest };
232
+ }
235
233
  // 대표 크레딧 허가: 구독 잔량이 남은 계정이 없으면 크레딧으로 계속 — 한도가 찬 요청은 서버가 선불 크레딧으로 넘긴다.
236
234
  // 🩸 09-28 실측: default 크레딧만 시간당 ~5,200 씩 줄고 team·third(합 ~5.5만)는 0 — «지금 계정에 머문다»만으로는
237
235
  // 한 계정 크레딧이 바닥나면 남은 크레딧을 두고 폴백(grok)으로 갔다. ⇒ 크레딧이 «더 많이» 남은 계정으로 옮긴다.