elanous 0.2.0 → 0.2.5

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 (1249) hide show
  1. package/.env.example +6 -0
  2. package/README.md +17 -15
  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/06ef2eba-d1b99ac6d2ea663c.js +1 -0
  6. package/apps/pwa/out/_next/static/chunks/1413-7179a49332db5a80.js +1 -0
  7. package/apps/pwa/out/_next/static/chunks/150-25c277137a9638ab.js +1 -0
  8. package/apps/pwa/out/_next/static/chunks/1640-bc99ec23bbe29d12.js +1 -0
  9. package/apps/pwa/out/_next/static/chunks/1751.50b96ea9c37b49b6.js +1 -0
  10. package/apps/pwa/out/_next/static/chunks/2300-de059d0ab2a16630.js +1 -0
  11. package/apps/pwa/out/_next/static/chunks/2684-05b0a81e75298c16.js +1 -0
  12. package/apps/pwa/out/_next/static/chunks/3026-2ed7e317d9b02102.js +25 -0
  13. package/apps/pwa/out/_next/static/chunks/3611-77496ada38cd6358.js +1 -0
  14. package/apps/pwa/out/_next/static/chunks/3717-68957c4dc9ceec90.js +1 -0
  15. package/apps/pwa/out/_next/static/chunks/3929-46ba9af6f9d895cc.js +1 -0
  16. package/apps/pwa/out/_next/static/chunks/4015-62c5a3744698a8da.js +1 -0
  17. package/apps/pwa/out/_next/static/chunks/4217-3c4317a7905f9eeb.js +1 -0
  18. package/apps/pwa/out/_next/static/chunks/4240-3230f230fae2d7d5.js +1 -0
  19. package/apps/pwa/out/_next/static/chunks/4271-e53e075ebd48593b.js +1 -0
  20. package/apps/pwa/out/_next/static/chunks/4377.3b125fef79576dfd.js +5 -0
  21. package/apps/pwa/out/_next/static/chunks/4440-18bb160d95819f4e.js +1 -0
  22. package/apps/pwa/out/_next/static/chunks/4853-35fdbe6bdbcdc0fc.js +1 -0
  23. package/apps/pwa/out/_next/static/chunks/{2a79f15a-2013e4d421d6df88.js → 4daa2798-1363d725c79f3593.js} +1 -1
  24. package/apps/pwa/out/_next/static/chunks/5007-6a3b30e23ff68a6e.js +1 -0
  25. package/apps/pwa/out/_next/static/chunks/5244-12b424c42070f5ff.js +1 -0
  26. package/apps/pwa/out/_next/static/chunks/5348-bbf838e119661eee.js +1 -0
  27. package/apps/pwa/out/_next/static/chunks/5391-8793f88b03b30161.js +1 -0
  28. package/apps/pwa/out/_next/static/chunks/5611-df00af3e4e4a61bb.js +1 -0
  29. package/apps/pwa/out/_next/static/chunks/5890-dca2321773be829a.js +1 -0
  30. package/apps/pwa/out/_next/static/chunks/6123-37c9dc57ddaa7ab0.js +1 -0
  31. package/apps/pwa/out/_next/static/chunks/6588-b7975304a302ab91.js +1 -0
  32. package/apps/pwa/out/_next/static/chunks/6604-c03961962b6273b6.js +1 -0
  33. package/apps/pwa/out/_next/static/chunks/7109-cf8c87e968e83a65.js +1 -0
  34. package/apps/pwa/out/_next/static/chunks/7178-30f77877525a2efb.js +1 -0
  35. package/apps/pwa/out/_next/static/chunks/7262-871563fc9663730b.js +1 -0
  36. package/apps/pwa/out/_next/static/chunks/7294-f0eeebb92aa93459.js +1 -0
  37. package/apps/pwa/out/_next/static/chunks/7429-881af44deed6958f.js +1 -0
  38. package/apps/pwa/out/_next/static/chunks/{5927.8378481a12cc8044.js → 7527.943753b6ed829f09.js} +1 -1
  39. package/apps/pwa/out/_next/static/chunks/764-a01e858525a190c0.js +1 -0
  40. package/apps/pwa/out/_next/static/chunks/7868-e35d5f7985ecd3f1.js +1 -0
  41. package/apps/pwa/out/_next/static/chunks/8320-51484af7b01fdfa5.js +1 -0
  42. package/apps/pwa/out/_next/static/chunks/{6804.8ed6ab492bc5fc0a.js → 8617.465ca22a319c95b3.js} +1 -1
  43. package/apps/pwa/out/_next/static/chunks/{320-d7013ec072863e5e.js → 8918-482b30ce2a8ebf4c.js} +4 -4
  44. package/apps/pwa/out/_next/static/chunks/9131-52f5fad51cceca04.js +1 -0
  45. package/apps/pwa/out/_next/static/chunks/{2598.814f64e3268634e7.js → 9238.2cd5f47f4bf7a2c3.js} +1 -1
  46. package/apps/pwa/out/_next/static/chunks/9704-3221c102623c8d73.js +1 -0
  47. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-70c404c7bb6d7f0f.js +1 -0
  48. package/apps/pwa/out/_next/static/chunks/app/approvals/page-3ffd80166b1050aa.js +1 -0
  49. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-f89e9a303515a033.js +1 -0
  50. package/apps/pwa/out/_next/static/chunks/app/board/page-4f403cd060442c95.js +1 -0
  51. package/apps/pwa/out/_next/static/chunks/app/botlab/page-0ac5d053f0444d21.js +1 -0
  52. package/apps/pwa/out/_next/static/chunks/app/bots/page-4bcc63e272ae8205.js +1 -0
  53. package/apps/pwa/out/_next/static/chunks/app/chat/page-880734bd07d62882.js +1 -0
  54. package/apps/pwa/out/_next/static/chunks/app/control/page-72bd384898d88955.js +1 -0
  55. package/apps/pwa/out/_next/static/chunks/app/design-check/page-1018bbc9586ecae5.js +1 -0
  56. package/apps/pwa/out/_next/static/chunks/app/editor/page-de86f8c38076dd7b.js +1 -0
  57. package/apps/pwa/out/_next/static/chunks/app/healthz/route-70c404c7bb6d7f0f.js +1 -0
  58. package/apps/pwa/out/_next/static/chunks/app/intake/page-cd6447196d8bafe6.js +1 -0
  59. package/apps/pwa/out/_next/static/chunks/app/layout-e733630c47fa102b.js +1 -0
  60. package/apps/pwa/out/_next/static/chunks/app/live/page-d1c1a8357b161875.js +1 -0
  61. package/apps/pwa/out/_next/static/chunks/app/market/page-60c514128e81d787.js +1 -0
  62. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-8b637b7e325e13c1.js +1 -0
  63. package/apps/pwa/out/_next/static/chunks/app/morning/page-bccce0babceae30f.js +1 -0
  64. package/apps/pwa/out/_next/static/chunks/app/not-found-63301d11dbe73836.js +1 -0
  65. package/apps/pwa/out/_next/static/chunks/app/observatory/page-a8cf0dc472060db2.js +1 -0
  66. package/apps/pwa/out/_next/static/chunks/app/page-02fba54881ccfa49.js +1 -0
  67. package/apps/pwa/out/_next/static/chunks/app/reflection/page-d5e8e90f0038f8de.js +1 -0
  68. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-b1ef6824f494466a.js +1 -0
  69. package/apps/pwa/out/_next/static/chunks/app/sessions/page-d589bb0f233b1009.js +1 -0
  70. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-c9bbaebef4cda059.js +1 -0
  71. package/apps/pwa/out/_next/static/chunks/app/settings/page-3639ce7916b92fed.js +1 -0
  72. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-7450ae7478955653.js +1 -0
  73. package/apps/pwa/out/_next/static/chunks/app/setup/layout-70c404c7bb6d7f0f.js +1 -0
  74. package/apps/pwa/out/_next/static/chunks/app/setup/page-b41ca4abfd618452.js +1 -0
  75. package/apps/pwa/out/_next/static/chunks/app/share/page-b333dd79ebee3fa9.js +1 -0
  76. package/apps/pwa/out/_next/static/chunks/app/showroom/page-8be1227492793655.js +1 -0
  77. package/apps/pwa/out/_next/static/chunks/app/tasks/page-6e03e9371b552e0d.js +1 -0
  78. package/apps/pwa/out/_next/static/chunks/app/term/page-911411e9fca18d03.js +1 -0
  79. package/apps/pwa/out/_next/static/chunks/app/trace/page-792170e649b37088.js +1 -0
  80. package/apps/pwa/out/_next/static/chunks/app/vault/page-c40463466937bc91.js +1 -0
  81. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-509c798b80384536.js +1 -0
  82. package/apps/pwa/out/_next/static/chunks/app/workflows/page-1cac2aa21d77c417.js +1 -0
  83. package/apps/pwa/out/_next/static/chunks/app/workspace/page-9626799d9b4a2cd1.js +1 -0
  84. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-84cd419494e16b0f.js +1 -0
  85. package/apps/pwa/out/_next/static/chunks/f6f3850d-7f324c6566002113.js +1 -0
  86. package/apps/pwa/out/_next/static/chunks/framework-361487d2da603917.js +1 -0
  87. package/apps/pwa/out/_next/static/chunks/main-app-607f5beb94cb2e07.js +1 -0
  88. package/apps/pwa/out/_next/static/chunks/main-d0222d219e29dddf.js +1 -0
  89. package/apps/pwa/out/_next/static/chunks/pages/_app-a26980a31126decc.js +1 -0
  90. package/apps/pwa/out/_next/static/chunks/pages/_error-edb2b37decf23e4d.js +1 -0
  91. package/apps/pwa/out/_next/static/chunks/webpack-2bf59c720fe9542c.js +1 -0
  92. package/apps/pwa/out/_next/static/css/eaa74e8bb2f62269.css +1 -0
  93. package/apps/pwa/out/_next/static/ktMqfrBbf8BMGac0jqLrb/_buildManifest.js +1 -0
  94. package/apps/pwa/out/approvals/index.html +1 -0
  95. package/apps/pwa/out/approvals/index.txt +32 -0
  96. package/apps/pwa/out/autopilot/index.html +1 -1
  97. package/apps/pwa/out/autopilot/index.txt +29 -26
  98. package/apps/pwa/out/board/index.html +1 -0
  99. package/apps/pwa/out/board/index.txt +32 -0
  100. package/apps/pwa/out/botlab/index.html +1 -1
  101. package/apps/pwa/out/botlab/index.txt +26 -23
  102. package/apps/pwa/out/bots/index.html +1 -1
  103. package/apps/pwa/out/bots/index.txt +26 -23
  104. package/apps/pwa/out/chat/index.html +1 -2
  105. package/apps/pwa/out/chat/index.txt +26 -23
  106. package/apps/pwa/out/control/index.html +1 -1
  107. package/apps/pwa/out/control/index.txt +29 -26
  108. package/apps/pwa/out/design-check/index.html +1 -1
  109. package/apps/pwa/out/design-check/index.txt +29 -26
  110. package/apps/pwa/out/editor/index.html +1 -0
  111. package/apps/pwa/out/editor/index.txt +33 -0
  112. package/apps/pwa/out/healthz +1 -1
  113. package/apps/pwa/out/icons/elanous-16.png +0 -0
  114. package/apps/pwa/out/icons/elanous-180.png +0 -0
  115. package/apps/pwa/out/icons/elanous-192.png +0 -0
  116. package/apps/pwa/out/icons/elanous-32.png +0 -0
  117. package/apps/pwa/out/icons/elanous-512.png +0 -0
  118. package/apps/pwa/out/index.html +1 -1
  119. package/apps/pwa/out/index.txt +26 -23
  120. package/apps/pwa/out/intake/index.html +1 -1
  121. package/apps/pwa/out/intake/index.txt +29 -26
  122. package/apps/pwa/out/live/index.html +1 -0
  123. package/apps/pwa/out/live/index.txt +32 -0
  124. package/apps/pwa/out/manifest.webmanifest +5 -2
  125. package/apps/pwa/out/market/index.html +1 -0
  126. package/apps/pwa/out/market/index.txt +29 -0
  127. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  128. package/apps/pwa/out/missions/placeholder/index.txt +26 -23
  129. package/apps/pwa/out/morning/index.html +1 -1
  130. package/apps/pwa/out/morning/index.txt +29 -26
  131. package/apps/pwa/out/observatory/index.html +1 -1
  132. package/apps/pwa/out/observatory/index.txt +29 -26
  133. package/apps/pwa/out/reflection/index.html +1 -1
  134. package/apps/pwa/out/reflection/index.txt +27 -24
  135. package/apps/pwa/out/scheduler/index.html +1 -1
  136. package/apps/pwa/out/scheduler/index.txt +29 -26
  137. package/apps/pwa/out/sessions/index.html +1 -1
  138. package/apps/pwa/out/sessions/index.txt +26 -23
  139. package/apps/pwa/out/settings/devices/index.html +1 -1
  140. package/apps/pwa/out/settings/devices/index.txt +29 -26
  141. package/apps/pwa/out/settings/index.html +1 -1
  142. package/apps/pwa/out/settings/index.txt +29 -26
  143. package/apps/pwa/out/setup/done/index.html +1 -1
  144. package/apps/pwa/out/setup/done/index.txt +29 -26
  145. package/apps/pwa/out/setup/index.html +1 -1
  146. package/apps/pwa/out/setup/index.txt +29 -26
  147. package/apps/pwa/out/share/index.html +1 -1
  148. package/apps/pwa/out/share/index.txt +29 -26
  149. package/apps/pwa/out/showroom/index.html +1 -1
  150. package/apps/pwa/out/showroom/index.txt +26 -23
  151. package/apps/pwa/out/sw-strategies.js +44 -0
  152. package/apps/pwa/out/sw.js +6 -44
  153. package/apps/pwa/out/tasks/index.html +1 -1
  154. package/apps/pwa/out/tasks/index.txt +29 -26
  155. package/apps/pwa/out/term/index.html +1 -1
  156. package/apps/pwa/out/term/index.txt +29 -26
  157. package/apps/pwa/out/trace/index.html +1 -0
  158. package/apps/pwa/out/trace/index.txt +32 -0
  159. package/apps/pwa/out/vault/index.html +1 -1
  160. package/apps/pwa/out/vault/index.txt +29 -26
  161. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  162. package/apps/pwa/out/workflows/chat-ui/index.txt +29 -26
  163. package/apps/pwa/out/workflows/index.html +1 -1
  164. package/apps/pwa/out/workflows/index.txt +29 -27
  165. package/apps/pwa/out/workspace/index.html +1 -1
  166. package/apps/pwa/out/workspace/index.txt +29 -26
  167. package/apps/pwa/out/worktrees/index.html +1 -1
  168. package/apps/pwa/out/worktrees/index.txt +29 -26
  169. package/catalog/resources.yaml +14 -17
  170. package/docs/design/systems/LICENSE +201 -0
  171. package/docs/design/systems/NOTICE.md +29 -0
  172. package/docs/design/systems/SOURCE.json +291 -0
  173. package/docs/design/systems/application/DESIGN.md +71 -0
  174. package/docs/design/systems/application/manifest.json +54 -0
  175. package/docs/design/systems/application/tokens.css +63 -0
  176. package/docs/design/systems/artistic/DESIGN.md +71 -0
  177. package/docs/design/systems/artistic/manifest.json +54 -0
  178. package/docs/design/systems/artistic/tokens.css +63 -0
  179. package/docs/design/systems/bento/DESIGN.md +71 -0
  180. package/docs/design/systems/bento/manifest.json +54 -0
  181. package/docs/design/systems/bento/tokens.css +63 -0
  182. package/docs/design/systems/bold/DESIGN.md +71 -0
  183. package/docs/design/systems/bold/manifest.json +54 -0
  184. package/docs/design/systems/bold/tokens.css +63 -0
  185. package/docs/design/systems/brutalism/DESIGN.md +71 -0
  186. package/docs/design/systems/brutalism/manifest.json +54 -0
  187. package/docs/design/systems/brutalism/tokens.css +63 -0
  188. package/docs/design/systems/cafe/DESIGN.md +71 -0
  189. package/docs/design/systems/cafe/manifest.json +54 -0
  190. package/docs/design/systems/cafe/tokens.css +63 -0
  191. package/docs/design/systems/claymorphism/DESIGN.md +71 -0
  192. package/docs/design/systems/claymorphism/manifest.json +54 -0
  193. package/docs/design/systems/claymorphism/tokens.css +63 -0
  194. package/docs/design/systems/clean/DESIGN.md +71 -0
  195. package/docs/design/systems/clean/manifest.json +54 -0
  196. package/docs/design/systems/clean/tokens.css +63 -0
  197. package/docs/design/systems/colorful/DESIGN.md +71 -0
  198. package/docs/design/systems/colorful/manifest.json +54 -0
  199. package/docs/design/systems/colorful/tokens.css +63 -0
  200. package/docs/design/systems/contemporary/DESIGN.md +71 -0
  201. package/docs/design/systems/contemporary/manifest.json +54 -0
  202. package/docs/design/systems/contemporary/tokens.css +63 -0
  203. package/docs/design/systems/corporate/DESIGN.md +71 -0
  204. package/docs/design/systems/corporate/manifest.json +54 -0
  205. package/docs/design/systems/corporate/tokens.css +63 -0
  206. package/docs/design/systems/cosmic/DESIGN.md +71 -0
  207. package/docs/design/systems/cosmic/manifest.json +54 -0
  208. package/docs/design/systems/cosmic/tokens.css +63 -0
  209. package/docs/design/systems/creative/DESIGN.md +71 -0
  210. package/docs/design/systems/creative/manifest.json +54 -0
  211. package/docs/design/systems/creative/tokens.css +63 -0
  212. package/docs/design/systems/dashboard/DESIGN.md +71 -0
  213. package/docs/design/systems/dashboard/manifest.json +54 -0
  214. package/docs/design/systems/dashboard/tokens.css +63 -0
  215. package/docs/design/systems/default/DESIGN.md +62 -0
  216. package/docs/design/systems/default/manifest.json +69 -0
  217. package/docs/design/systems/default/tokens.css +200 -0
  218. package/docs/design/systems/dithered/DESIGN.md +71 -0
  219. package/docs/design/systems/dithered/manifest.json +54 -0
  220. package/docs/design/systems/dithered/tokens.css +63 -0
  221. package/docs/design/systems/doodle/DESIGN.md +71 -0
  222. package/docs/design/systems/doodle/manifest.json +54 -0
  223. package/docs/design/systems/doodle/tokens.css +63 -0
  224. package/docs/design/systems/dramatic/DESIGN.md +71 -0
  225. package/docs/design/systems/dramatic/manifest.json +54 -0
  226. package/docs/design/systems/dramatic/tokens.css +63 -0
  227. package/docs/design/systems/editorial/DESIGN.md +71 -0
  228. package/docs/design/systems/editorial/manifest.json +54 -0
  229. package/docs/design/systems/editorial/tokens.css +63 -0
  230. package/docs/design/systems/elegant/DESIGN.md +71 -0
  231. package/docs/design/systems/elegant/manifest.json +54 -0
  232. package/docs/design/systems/elegant/tokens.css +63 -0
  233. package/docs/design/systems/energetic/DESIGN.md +72 -0
  234. package/docs/design/systems/energetic/manifest.json +54 -0
  235. package/docs/design/systems/energetic/tokens.css +63 -0
  236. package/docs/design/systems/enterprise/DESIGN.md +71 -0
  237. package/docs/design/systems/enterprise/manifest.json +54 -0
  238. package/docs/design/systems/enterprise/tokens.css +63 -0
  239. package/docs/design/systems/expressive/DESIGN.md +71 -0
  240. package/docs/design/systems/expressive/manifest.json +54 -0
  241. package/docs/design/systems/expressive/tokens.css +63 -0
  242. package/docs/design/systems/fantasy/DESIGN.md +71 -0
  243. package/docs/design/systems/fantasy/manifest.json +54 -0
  244. package/docs/design/systems/fantasy/tokens.css +63 -0
  245. package/docs/design/systems/flat/DESIGN.md +71 -0
  246. package/docs/design/systems/flat/manifest.json +54 -0
  247. package/docs/design/systems/flat/tokens.css +63 -0
  248. package/docs/design/systems/friendly/DESIGN.md +71 -0
  249. package/docs/design/systems/friendly/manifest.json +54 -0
  250. package/docs/design/systems/friendly/tokens.css +63 -0
  251. package/docs/design/systems/glassmorphism/DESIGN.md +71 -0
  252. package/docs/design/systems/glassmorphism/manifest.json +54 -0
  253. package/docs/design/systems/glassmorphism/tokens.css +63 -0
  254. package/docs/design/systems/gradient/DESIGN.md +71 -0
  255. package/docs/design/systems/gradient/manifest.json +54 -0
  256. package/docs/design/systems/gradient/tokens.css +63 -0
  257. package/docs/design/systems/luxury/DESIGN.md +71 -0
  258. package/docs/design/systems/luxury/manifest.json +54 -0
  259. package/docs/design/systems/luxury/tokens.css +63 -0
  260. package/docs/design/systems/minimal/DESIGN.md +71 -0
  261. package/docs/design/systems/minimal/manifest.json +54 -0
  262. package/docs/design/systems/minimal/tokens.css +63 -0
  263. package/docs/design/systems/modern/DESIGN.md +71 -0
  264. package/docs/design/systems/modern/manifest.json +54 -0
  265. package/docs/design/systems/modern/tokens.css +63 -0
  266. package/docs/design/systems/mono/DESIGN.md +71 -0
  267. package/docs/design/systems/mono/manifest.json +54 -0
  268. package/docs/design/systems/mono/tokens.css +63 -0
  269. package/docs/design/systems/neobrutalism/DESIGN.md +71 -0
  270. package/docs/design/systems/neobrutalism/manifest.json +54 -0
  271. package/docs/design/systems/neobrutalism/tokens.css +63 -0
  272. package/docs/design/systems/neon/DESIGN.md +71 -0
  273. package/docs/design/systems/neon/manifest.json +54 -0
  274. package/docs/design/systems/neon/tokens.css +63 -0
  275. package/docs/design/systems/neumorphism/DESIGN.md +71 -0
  276. package/docs/design/systems/neumorphism/manifest.json +54 -0
  277. package/docs/design/systems/neumorphism/tokens.css +63 -0
  278. package/docs/design/systems/paper/DESIGN.md +71 -0
  279. package/docs/design/systems/paper/manifest.json +54 -0
  280. package/docs/design/systems/paper/tokens.css +63 -0
  281. package/docs/design/systems/perspective/DESIGN.md +71 -0
  282. package/docs/design/systems/perspective/manifest.json +54 -0
  283. package/docs/design/systems/perspective/tokens.css +63 -0
  284. package/docs/design/systems/premium/DESIGN.md +71 -0
  285. package/docs/design/systems/premium/manifest.json +54 -0
  286. package/docs/design/systems/premium/tokens.css +63 -0
  287. package/docs/design/systems/professional/DESIGN.md +71 -0
  288. package/docs/design/systems/professional/manifest.json +54 -0
  289. package/docs/design/systems/professional/tokens.css +63 -0
  290. package/docs/design/systems/publication/DESIGN.md +71 -0
  291. package/docs/design/systems/publication/manifest.json +54 -0
  292. package/docs/design/systems/publication/tokens.css +63 -0
  293. package/docs/design/systems/refined/DESIGN.md +71 -0
  294. package/docs/design/systems/refined/manifest.json +54 -0
  295. package/docs/design/systems/refined/tokens.css +63 -0
  296. package/docs/design/systems/retro/DESIGN.md +71 -0
  297. package/docs/design/systems/retro/manifest.json +54 -0
  298. package/docs/design/systems/retro/tokens.css +63 -0
  299. package/docs/design/systems/shadcn/DESIGN.md +71 -0
  300. package/docs/design/systems/shadcn/manifest.json +54 -0
  301. package/docs/design/systems/shadcn/tokens.css +255 -0
  302. package/docs/design/systems/simple/DESIGN.md +71 -0
  303. package/docs/design/systems/simple/manifest.json +54 -0
  304. package/docs/design/systems/simple/tokens.css +63 -0
  305. package/docs/design/systems/skeumorphism/DESIGN.md +71 -0
  306. package/docs/design/systems/skeumorphism/manifest.json +54 -0
  307. package/docs/design/systems/skeumorphism/tokens.css +63 -0
  308. package/docs/design/systems/sleek/DESIGN.md +71 -0
  309. package/docs/design/systems/sleek/manifest.json +54 -0
  310. package/docs/design/systems/sleek/tokens.css +63 -0
  311. package/docs/design/systems/spacious/DESIGN.md +71 -0
  312. package/docs/design/systems/spacious/manifest.json +54 -0
  313. package/docs/design/systems/spacious/tokens.css +63 -0
  314. package/docs/design/systems/storytelling/DESIGN.md +71 -0
  315. package/docs/design/systems/storytelling/manifest.json +54 -0
  316. package/docs/design/systems/storytelling/tokens.css +63 -0
  317. package/docs/design/systems/tom-modern/DESIGN.md +361 -0
  318. package/docs/design/systems/tom-modern/manifest.json +16 -0
  319. package/docs/design/systems/tom-modern/tokens.css +99 -0
  320. package/docs/design/systems/vibrant/DESIGN.md +71 -0
  321. package/docs/design/systems/vibrant/manifest.json +54 -0
  322. package/docs/design/systems/vibrant/tokens.css +63 -0
  323. package/docs/design/systems/vintage/DESIGN.md +71 -0
  324. package/docs/design/systems/vintage/manifest.json +54 -0
  325. package/docs/design/systems/vintage/tokens.css +63 -0
  326. package/docs/design/systems/warm-editorial/DESIGN.md +65 -0
  327. package/docs/design/systems/warm-editorial/manifest.json +54 -0
  328. package/docs/design/systems/warm-editorial/tokens.css +63 -0
  329. package/docs/goal-context/01-goal-authoring-checks.md +18 -0
  330. package/docs/goal-context/steward-directives-2026-09-28.md +42 -0
  331. package/graphs/catalog/catalog.yaml +615 -0
  332. package/graphs/default-loop.yaml +1 -1
  333. package/graphs/document-loop.yaml +1 -1
  334. package/graphs/examples/stale-draft-digest/digest.ts +26 -0
  335. package/graphs/examples/stale-draft-digest/recipes.yaml +8 -0
  336. package/graphs/examples/stale-draft-digest/stale-draft-digest.yaml +22 -0
  337. package/graphs/heal/heal-loop.yaml +155 -0
  338. package/graphs/heal/recipes.yaml +19 -0
  339. package/graphs/implement-loop.yaml +2 -2
  340. package/graphs/intake/intake-daily.yaml +55 -0
  341. package/graphs/intake/recipes.yaml +16 -0
  342. package/graphs/landing/landing-heal.yaml +22 -0
  343. package/graphs/landing/recipes.yaml +8 -0
  344. package/graphs/launch/launch-head.yaml +66 -0
  345. package/graphs/launch/recipes.yaml +8 -0
  346. package/graphs/operate-loop.yaml +1 -1
  347. package/graphs/ops/docs-publish.yaml +31 -0
  348. package/graphs/ops/recipes.yaml +8 -0
  349. package/graphs/release/recipes.yaml +45 -0
  350. package/graphs/release/release-loop.yaml +45 -0
  351. package/graphs/research-loop.yaml +1 -1
  352. package/graphs/steward/recipes.yaml +12 -0
  353. package/graphs/steward/steward.yaml +45 -0
  354. package/graphs/video/README.md +1 -0
  355. package/graphs/video/broll-line.yaml +54 -0
  356. package/graphs/video/explainer-line.yaml +58 -0
  357. package/package.json +9 -5
  358. package/plugins/job-coach/.agents/plugins/marketplace.json +12 -0
  359. package/plugins/job-coach/.codex-plugin/plugin.json +23 -0
  360. package/plugins/job-coach/.mcp.json +9 -0
  361. package/plugins/job-coach/README.md +32 -0
  362. package/plugins/job-coach/connectors/ncs/server.ts +90 -0
  363. package/plugins/job-coach/examples/interview-sample.md +11 -0
  364. package/plugins/job-coach/graphs/course-links.ts +46 -0
  365. package/plugins/job-coach/graphs/gap-judge.ts +44 -0
  366. package/plugins/job-coach/graphs/recipes.yaml +45 -0
  367. package/plugins/job-coach/graphs/report-enterprise.yaml +28 -0
  368. package/plugins/job-coach/graphs/report.yaml +20 -0
  369. package/plugins/job-coach/graphs/run-step.ts +302 -0
  370. package/plugins/job-coach/plugin.json +26 -0
  371. package/plugins/job-coach/plugin.ts +6 -0
  372. package/plugins/job-coach/skills/career-report/SKILL.md +6 -0
  373. package/plugins/job-coach/skills/enterprise-ai-fit/SKILL.md +8 -0
  374. package/plugins/job-coach/skills/enterprise-competency/SKILL.md +8 -0
  375. package/plugins/job-coach/skills/enterprise-needs/SKILL.md +8 -0
  376. package/plugins/job-coach/skills/enterprise-roadmap/SKILL.md +8 -0
  377. package/plugins/job-coach/skills/enterprise-task-analysis/SKILL.md +8 -0
  378. package/plugins/job-coach/skills/interview-to-profile/SKILL.md +23 -0
  379. package/plugins/job-coach/skills/run-report/SKILL.md +15 -0
  380. package/scripts/backup/elanous-backup.sh +143 -14
  381. package/scripts/backup/elanous-restore.sh +122 -0
  382. package/scripts/build-docs-site.ts +4 -3
  383. package/scripts/ci-daemon-port-gate.ts +164 -0
  384. package/scripts/ci-typecheck-changed.ts +15 -1
  385. package/scripts/cli-json-exit-scan.ts +126 -0
  386. package/scripts/codex-quota-alert-format.ts +52 -0
  387. package/scripts/codex-quota-alert.ts +63 -13
  388. package/scripts/continuation-dogfood.ts +2 -1
  389. package/scripts/coord-channel-watch.sh +28 -9
  390. package/scripts/coord-post.sh +49 -12
  391. package/scripts/coord-tracks.json +6 -5
  392. package/scripts/cron-run.ts +37 -16
  393. package/scripts/daemon-port-baseline.txt +9 -0
  394. package/scripts/design/extract-corpus-bench.ts +469 -0
  395. package/scripts/design/extract-corpus-score.ts +510 -0
  396. package/scripts/design/vendor-open-design-systems.ts +100 -0
  397. package/scripts/docs-lint.ts +1 -1
  398. package/scripts/eval-bot-vs-grok.ts +79 -0
  399. package/scripts/eval-grok-live-baseline.ts +36 -0
  400. package/scripts/fixtures/front-route-corpus.sample.json +17 -0
  401. package/scripts/fleet/node-setup.sh +80 -7
  402. package/scripts/heal/run-role.ts +328 -0
  403. package/scripts/install-role-watch.sh +95 -0
  404. package/scripts/install.ps1 +11 -1
  405. package/scripts/install.sh +81 -9
  406. package/scripts/isolation-hardcode-baseline.txt +1 -1
  407. package/scripts/landing-heal/must-fix-match.ts +63 -0
  408. package/scripts/landing-heal/run-mission.ts +195 -0
  409. package/scripts/lib/front-route-measurement.ts +131 -0
  410. package/scripts/lib/pwa-scenarios/acp-replay.ts +79 -0
  411. package/scripts/lib/pwa-scenarios/cdp-driver.ts +67 -0
  412. package/scripts/lib/pwa-scenarios/runner.ts +200 -0
  413. package/scripts/lib/pwa-scenarios/scenarios.ts +202 -0
  414. package/scripts/lib/tui-sim-keys.ts +42 -0
  415. package/scripts/market/db-check.ts +212 -0
  416. package/scripts/market-posture-cycle.ts +63 -4
  417. package/scripts/measure-front-route-classifier.ts +56 -0
  418. package/scripts/media/ocr-leak-scan.sh +21 -0
  419. package/scripts/nightly-docops.ts +1 -1
  420. package/scripts/ops/channel-bot-token-migrate.ts +146 -0
  421. package/scripts/ops-health-check.ts +15 -3
  422. package/scripts/pty-scenarios/run.ts +272 -0
  423. package/scripts/public-export.ts +2 -3
  424. package/scripts/public-import-pr.ts +80 -6
  425. package/scripts/public-leak-baseline.txt +0 -7
  426. package/scripts/publish-internal-dist.ts +118 -0
  427. package/scripts/pwa-scenarios.ts +106 -0
  428. package/scripts/rebrand-elanous.ts +1 -1
  429. package/scripts/red-sweep-diff.ts +1 -1
  430. package/scripts/release-loop/auto-approve-node.ts +72 -0
  431. package/scripts/release-loop/cutoff.ts +139 -0
  432. package/scripts/release-loop/docs-land-node.ts +65 -0
  433. package/scripts/release-loop/docs-node.ts +172 -0
  434. package/scripts/release-loop/gate-diff.ts +29 -0
  435. package/scripts/release-loop/gate-node.ts +641 -0
  436. package/scripts/release-loop/node-verdict.ts +85 -0
  437. package/scripts/release-loop/prepare-node.ts +31 -0
  438. package/scripts/release-loop/publish-node.ts +61 -0
  439. package/scripts/release-loop/pwa-node-verdict.ts +45 -0
  440. package/scripts/release-loop/pwa-scenarios-node.ts +140 -0
  441. package/scripts/release-loop/release-docs.ts +109 -0
  442. package/scripts/release-loop/test-durations.ts +63 -0
  443. package/scripts/release-loop/tui-node-checks.ts +29 -0
  444. package/scripts/release-loop/tui-sim-node.ts +113 -0
  445. package/scripts/release-loop/upgrade-node.ts +26 -0
  446. package/scripts/release-loop/verify-node.ts +21 -0
  447. package/scripts/release-loop/version-node.ts +147 -0
  448. package/scripts/release-upgrade-check.sh +53 -0
  449. package/scripts/se-mission-prepare.ts +9 -15
  450. package/scripts/test-audit-coverage-per-file.sh +16 -0
  451. package/scripts/test-audit-meaningless.ts +88 -0
  452. package/scripts/test-audit-overlap-and-weak.ts +73 -0
  453. package/scripts/test-deterministic.ts +29 -6
  454. package/scripts/tui-sim.ts +59 -14
  455. package/scripts/uninstall.sh +10 -3
  456. package/scripts/ux/bump-installed.sh +20 -0
  457. package/scripts/ux/lib/cdp.ts +72 -0
  458. package/scripts/ux/record-page.ts +50 -0
  459. package/scripts/video-broll-line.ts +89 -0
  460. package/scripts/waitlist-daily-digest.ts +108 -0
  461. package/scripts/window-landings.sh +1 -1
  462. package/skills/explainer-video/SKILL.md +99 -0
  463. package/skills/explainer-video/engine/brand/elanous-mark-on-dark.svg +1 -0
  464. package/skills/explainer-video/engine/build.mjs +431 -0
  465. package/skills/explainer-video/engine/vo.ts +37 -0
  466. package/skills/explainer-video/examples/elanous-0.2.5/script.json +155 -0
  467. package/skills/motion-broll/LICENSE +25 -0
  468. package/skills/motion-broll/SKILL.md +150 -0
  469. package/skills/motion-broll/SOURCE.md +6 -0
  470. package/skills/motion-broll/engine/base.css +19 -0
  471. package/skills/motion-broll/engine/beats.js +11 -0
  472. package/skills/motion-broll/engine/build.py +21 -0
  473. package/skills/motion-broll/engine/fonts/Geist-Variable.woff2 +0 -0
  474. package/skills/motion-broll/engine/fonts/GeistMono-Medium.woff2 +0 -0
  475. package/skills/motion-broll/engine/fonts/OFL-Geist.txt +92 -0
  476. package/skills/motion-broll/engine/motion.js +146 -0
  477. package/skills/motion-broll/engine/render.js +22 -0
  478. package/skills/motion-broll/examples/opus-aoe2/01-opus-drop.html +41 -0
  479. package/skills/motion-broll/examples/opus-aoe2/02-pip-builds.html +110 -0
  480. package/skills/motion-broll/examples/opus-aoe2/03-effort-slider.html +38 -0
  481. package/skills/motion-broll/examples/opus-aoe2/04-master-prompt.html +56 -0
  482. package/skills/motion-broll/examples/opus-aoe2/05-low-vs-max.html +49 -0
  483. package/skills/motion-broll/examples/opus-aoe2/06-chapter.html +18 -0
  484. package/skills/motion-broll/examples/opus-aoe2/plan.json +14 -0
  485. package/skills/motion-broll/reference/engine-api.md +106 -0
  486. package/skills/motion-broll/scripts/composite.py +14 -0
  487. package/skills/motion-broll/scripts/inspect_video.py +34 -0
  488. package/skills/motion-broll/scripts/make_pages.py +34 -0
  489. package/skills/motion-broll/scripts/setup.sh +16 -0
  490. package/skills/motion-broll/scripts/words.py +13 -0
  491. package/skills/motion-broll/templates/compare.html +135 -0
  492. package/skills/motion-broll/templates/viewer.html +97 -0
  493. package/skills/omni-digest/SKILL.md +260 -0
  494. package/skills/omni-digest/digest.md +197 -0
  495. package/skills/omni-digest/docs/diagrams/noncode-terminal-promotion-path-2026-08-13.mmd +32 -0
  496. package/skills/omni-digest/package-lock.json +558 -0
  497. package/skills/omni-digest/package.json +13 -0
  498. package/skills/omni-digest/scripts/main.ts +404 -0
  499. package/skills/omni-digest/src/delegate.ts +34 -0
  500. package/skills/omni-digest/src/detect.ts +46 -0
  501. package/skills/omni-digest/src/diagram.ts +72 -0
  502. package/skills/omni-digest/src/domain-staging.ts +50 -0
  503. package/skills/omni-digest/src/env.ts +38 -0
  504. package/skills/omni-digest/src/fetch.ts +237 -0
  505. package/skills/omni-digest/src/media.ts +180 -0
  506. package/skills/omni-digest/src/obsidian.ts +69 -0
  507. package/skills/omni-digest/src/ocr.ts +149 -0
  508. package/skills/omni-digest/src/router.ts +60 -0
  509. package/skills/omni-digest/src/summarize.ts +87 -0
  510. package/skills/omni-digest/src/types.ts +124 -0
  511. package/skills/omni-digest/src/util.ts +34 -0
  512. package/skills/omni-digest/src/visual.ts +129 -0
  513. package/skills/omni-digest/src/x-api.ts +151 -0
  514. package/skills/omni-digest/tsconfig.json +13 -0
  515. package/src/a2a/agent-card.ts +29 -0
  516. package/src/a2a/server.ts +173 -0
  517. package/src/acp/auto-persist.ts +3 -6
  518. package/src/acp/backend-registry.ts +6 -10
  519. package/src/acp/channel-browser-shell.ts +1 -535
  520. package/src/acp/client.ts +106 -36
  521. package/src/acp/fs-roots.ts +20 -4
  522. package/src/acp/obsidian-tags.ts +6 -3
  523. package/src/acp/server.ts +189 -32
  524. package/src/agent/nested-agent-env.ts +1 -0
  525. package/src/agent/pty-binding.ts +1 -1
  526. package/src/agent/spawn-embodied-agent-in-vw.ts +5 -336
  527. package/src/agent/subagent-callable.ts +39 -1
  528. package/src/agent-mission/browser-login.ts +241 -0
  529. package/src/agent-mission/capabilities-cli.ts +33 -0
  530. package/src/agent-mission/capability-matrix.ts +31 -0
  531. package/src/agent-mission/capability-readers.ts +79 -0
  532. package/src/agent-mission/capability-types.ts +15 -0
  533. package/src/agent-mission/claude-headless.ts +131 -0
  534. package/src/agent-mission/claude-subscription.ts +57 -0
  535. package/src/agent-mission/doc-guided-install.ts +185 -0
  536. package/src/agent-mission/driver.ts +900 -69
  537. package/src/agent-mission/env-profile.ts +68 -0
  538. package/src/agent-mission/handoff.ts +128 -0
  539. package/src/agent-mission/in-agent-plugin.ts +311 -0
  540. package/src/agent-mission/mission-cli.ts +25 -1
  541. package/src/agent-mission/parse.ts +9 -4
  542. package/src/agent-mission/pr-review-watch.ts +62 -13
  543. package/src/agent-mission/resource-ladder.ts +235 -0
  544. package/src/agent-mission/tool-install.ts +249 -0
  545. package/src/agent-mission/tool-ladder.ts +37 -0
  546. package/src/agent-room/room-builder.ts +6 -600
  547. package/src/agent-substrate/review-intent.ts +43 -1
  548. package/src/auth/acp-token.ts +16 -0
  549. package/src/auth/github-app-token.ts +112 -0
  550. package/src/auth/scope.ts +8 -4
  551. package/src/auth/temp-tokens.ts +131 -0
  552. package/src/auto-research/obsidian-bridge.ts +2 -0
  553. package/src/autopilot/build/integrity-gate.ts +27 -4
  554. package/src/autopilot/build/isolated-instance.ts +2 -1
  555. package/src/autopilot/build/llm-conflict-merge.ts +27 -0
  556. package/src/autopilot/gh-rest-fallback.ts +177 -0
  557. package/src/autopilot/llm-control-brain.ts +54 -12
  558. package/src/autopilot/mission-engine.ts +15 -1
  559. package/src/autopilot/mission-notify.ts +4 -2
  560. package/src/autopilot/mission-origin.ts +5 -5
  561. package/src/autopilot/mission-phase-track.ts +52 -0
  562. package/src/autopilot/mission-tool.ts +1 -1
  563. package/src/autopilot/pr-manager.ts +51 -4
  564. package/src/autopilot/pty-control-loop.ts +94 -2
  565. package/src/autopilot/track-agent.ts +138 -0
  566. package/src/autopilot/track-courier.ts +153 -0
  567. package/src/boot/acp-server.ts +18 -18
  568. package/src/boot/check-same-origin.ts +19 -5
  569. package/src/boot/daemon-multi-llm-runtime.ts +14 -8
  570. package/src/boot/daemon-prompt-turn.ts +2 -0
  571. package/src/boot/daemon-runtime.ts +36 -14
  572. package/src/boot/daemon-tools/index.ts +16 -0
  573. package/src/boot/daemon-tools/self-implement.ts +4 -0
  574. package/src/boot/daemon-tools/types.ts +3 -0
  575. package/src/boot/daemon-tools/web-search.ts +2 -1
  576. package/src/boot/ws-bridge.ts +11 -0
  577. package/src/browser-cdp/chrome-flags.ts +7 -0
  578. package/src/browser-cdp/client.ts +2 -0
  579. package/src/browser-pane/mount.ts +1 -246
  580. package/src/budget/codex-reset-credit-state.ts +15 -0
  581. package/src/budget/fetchers/codex.ts +2 -1
  582. package/src/budget/run-usage-rollup.ts +11 -4
  583. package/src/budget/unified-usage.ts +2 -1
  584. package/src/capture/tui-self-report.ts +10 -0
  585. package/src/channel-bot-token.ts +126 -0
  586. package/src/chat/index.ts +57 -52
  587. package/src/chat/input-text-key.ts +2 -2
  588. package/src/chat/log-scroll-key.ts +12 -0
  589. package/src/chat/mode/manual.ts +2 -4
  590. package/src/chat/pickers/state.ts +10 -27
  591. package/src/chat/search/modal.ts +14 -2
  592. package/src/chat/submit-trace.ts +30 -0
  593. package/src/chat/tool-render/types.ts +1 -1
  594. package/src/chat/turn-typeahead.ts +2 -1
  595. package/src/chat/wrap.ts +6 -8
  596. package/src/cli/__fixtures__/agent-help-before/agent.txt +18 -0
  597. package/src/cli/__fixtures__/agent-help-before/registry.txt +13 -0
  598. package/src/cli/__fixtures__/agent-help-before/root.txt +100 -0
  599. package/src/cli/__fixtures__/agent-help-before/tier.txt +15 -0
  600. package/src/cli/a2a-cli.ts +52 -0
  601. package/src/cli/agent-cli.ts +726 -0
  602. package/src/cli/autopilot-cli.ts +739 -0
  603. package/src/cli/bg-launch.ts +20 -4
  604. package/src/cli/claude-plugin-setup.ts +199 -0
  605. package/src/cli/config-test-sync.ts +94 -10
  606. package/src/cli/connector-cli.ts +204 -0
  607. package/src/cli/control-cli.ts +220 -0
  608. package/src/cli/control-join-cli.ts +142 -0
  609. package/src/cli/decisions-cli.ts +94 -0
  610. package/src/cli/docs-cli.ts +38 -0
  611. package/src/cli/doctor-cli.ts +262 -77
  612. package/src/cli/doctor-distro.ts +17 -1
  613. package/src/cli/doctor-fix.ts +145 -17
  614. package/src/cli/doctor-llm-assist.ts +71 -0
  615. package/src/cli/doctor-private-files.ts +66 -0
  616. package/src/cli/doctor-readiness.ts +23 -28
  617. package/src/cli/fleet-cli.ts +63 -0
  618. package/src/cli/g6a-registration-probe.ts +16 -0
  619. package/src/cli/git-install-plan.ts +51 -0
  620. package/src/cli/hooks-cli.ts +44 -0
  621. package/src/cli/ingest-token-cli.ts +21 -0
  622. package/src/cli/intake-cli.ts +416 -0
  623. package/src/cli/labels-cli.ts +74 -0
  624. package/src/cli/leader-cli.ts +10 -159
  625. package/src/cli/live-cli.ts +48 -0
  626. package/src/cli/llm-detect-cli.ts +130 -0
  627. package/src/cli/logs-cli.ts +231 -7
  628. package/src/cli/machine-cli.ts +38 -0
  629. package/src/cli/mcp-call.ts +52 -13
  630. package/src/cli/mcp-gateway-cli.ts +94 -0
  631. package/src/cli/mcp-login.ts +116 -8
  632. package/src/cli/mcp-reload.ts +20 -3
  633. package/src/cli/nexus-dist.ts +18 -13
  634. package/src/cli/nexus-ios-bind.ts +20 -7
  635. package/src/cli/ops-cli.ts +225 -0
  636. package/src/cli/plugin-cli.ts +201 -0
  637. package/src/cli/pod-cli.ts +83 -0
  638. package/src/cli/port-lease-local.ts +124 -0
  639. package/src/cli/pr-cli.ts +12 -1
  640. package/src/cli/pr-lineage.ts +2 -1
  641. package/src/cli/provider-cli.ts +720 -0
  642. package/src/cli/pty-record-cli.ts +63 -0
  643. package/src/cli/pty-takeover-cli.ts +57 -40
  644. package/src/cli/pty-web-address.ts +52 -0
  645. package/src/cli/publish-cli.ts +44 -0
  646. package/src/cli/pwa-build.ts +18 -12
  647. package/src/cli/pwa-dev.ts +13 -8
  648. package/src/cli/pwa-registry.ts +6 -0
  649. package/src/cli/pwa-share.ts +65 -22
  650. package/src/cli/pwa-show.ts +20 -4
  651. package/src/cli/pwa-start.ts +102 -27
  652. package/src/cli/pwa-test.ts +201 -96
  653. package/src/cli/release-cli.ts +177 -7
  654. package/src/cli/remotes-cli.ts +20 -1
  655. package/src/cli/remotes.ts +2 -1
  656. package/src/cli/repo-cli.ts +315 -36
  657. package/src/cli/research-cli.ts +66 -0
  658. package/src/cli/role-cli.ts +320 -0
  659. package/src/cli/schedule-cli.ts +246 -0
  660. package/src/cli/self-update.ts +103 -12
  661. package/src/cli/setup-cli.ts +64 -2
  662. package/src/cli/shadow-cli.ts +188 -0
  663. package/src/cli/share-auto-mount.ts +12 -8
  664. package/src/cli/start-cli.ts +240 -0
  665. package/src/cli/stdout-flush.ts +8 -0
  666. package/src/cli/storage-cli.ts +334 -0
  667. package/src/cli/tailscale-serve.ts +34 -19
  668. package/src/cli/tasks-cli.ts +176 -0
  669. package/src/cli/test-state-dir-flag.ts +1 -1
  670. package/src/cli/usage-cli.ts +15 -4
  671. package/src/cli/where-cli.ts +22 -55
  672. package/src/code-edit/diff-render.ts +6 -32
  673. package/src/code-edit/source-delta-browser.ts +0 -1
  674. package/src/codex/models.ts +1 -1
  675. package/src/config.ts +8 -13
  676. package/src/connectors/event-ledger.ts +41 -0
  677. package/src/connectors/linear-projector.ts +88 -0
  678. package/src/connectors/linear.ts +143 -0
  679. package/src/connectors/types.ts +19 -0
  680. package/src/control-plane/lease-holder.ts +41 -0
  681. package/src/control-plane/ledger.ts +146 -0
  682. package/src/control-plane/light-member.ts +164 -0
  683. package/src/control-plane/member-tokens.ts +88 -0
  684. package/src/control-plane/member.ts +159 -0
  685. package/src/control-plane/port-lease-heartbeat.ts +101 -0
  686. package/src/control-plane/ports.ts +40 -0
  687. package/src/control-plane/primary.ts +169 -0
  688. package/src/control-plane/server.ts +216 -0
  689. package/src/dashboard/acp-boot.ts +14 -38
  690. package/src/dashboard/assistant-render-state-runtime.ts +2 -2
  691. package/src/dashboard/assistant-sample-runtime.ts +2 -2
  692. package/src/dashboard/auto-compact-runtime.ts +11 -16
  693. package/src/dashboard/browser-help-runtime.ts +2 -3
  694. package/src/dashboard/chat/acp-chat.ts +2 -5
  695. package/src/dashboard/code-edit-post-turn-runtime.ts +1 -2
  696. package/src/dashboard/compact-surface-assembly.ts +0 -14
  697. package/src/dashboard/compact-surface-dispatch.ts +1 -38
  698. package/src/dashboard/compact-surface-inventory.ts +0 -8
  699. package/src/dashboard/compact-surface-runtime.ts +0 -14
  700. package/src/dashboard/context-menu-provider-boot.ts +0 -9
  701. package/src/dashboard/context-window-runtime-boot.ts +0 -37
  702. package/src/dashboard/index.ts +331 -2562
  703. package/src/dashboard/input/chat-main-plain-turn-runtime.ts +0 -6
  704. package/src/dashboard/input/chat-main-slash-command.ts +2 -2
  705. package/src/dashboard/input/dashboard-priority-key-route.ts +1 -43
  706. package/src/dashboard/input/mouse-modal-surface-runtime.ts +1 -5
  707. package/src/dashboard/input/mouse-wiring.ts +2 -3
  708. package/src/dashboard/input/no-provider-reply.ts +13 -0
  709. package/src/dashboard/layout-display-runtime-registration.ts +0 -10
  710. package/src/dashboard/modals/pane-multi.ts +1 -1
  711. package/src/dashboard/replay-preview.ts +1 -1
  712. package/src/dashboard/runtime/approvers.ts +0 -1
  713. package/src/dashboard/runtime/state-snapshot.ts +0 -90
  714. package/src/dashboard/scenario-runtime-boot.ts +16 -16
  715. package/src/dashboard/slash-runtime/dashboard-handlers.ts +134 -1456
  716. package/src/dashboard/slash-runtime/design-pick.ts +96 -0
  717. package/src/dashboard/slash-runtime/help-from-registry.ts +77 -0
  718. package/src/dashboard/slash-runtime/unknown-slash-reply.ts +78 -0
  719. package/src/dashboard/stale-install-check.ts +77 -0
  720. package/src/dashboard/surface-registry-wiring.ts +2 -15
  721. package/src/dashboard/turn-message-runtime.ts +0 -4
  722. package/src/dashboard/turn-preamble.ts +1 -1
  723. package/src/dashboard/turn-stream-formatter.ts +2 -2
  724. package/src/dashboard/turn-stream-runtime.ts +2 -2
  725. package/src/dashboard-pane-context-menu.ts +0 -14
  726. package/src/debug/log.ts +7 -0
  727. package/src/decide/boundary-shadow.ts +131 -0
  728. package/src/decisions/decision-ledger.ts +404 -0
  729. package/src/design/apply-direction.ts +209 -0
  730. package/src/design/css-root-tokens.ts +89 -0
  731. package/src/design/custom-system-create.ts +230 -0
  732. package/src/design/design-directions.ts +61 -41
  733. package/src/design/design-gate.ts +241 -0
  734. package/src/design/design-library.ts +57 -0
  735. package/src/design/design-previews.ts +138 -0
  736. package/src/design/design-systems.ts +61 -0
  737. package/src/design/lint-artifact-run.ts +46 -6
  738. package/src/design/lint-artifact.ts +5 -1
  739. package/src/design/open-design-client.ts +247 -0
  740. package/src/design/system-from-extract.ts +767 -0
  741. package/src/design/system-schema.ts +123 -0
  742. package/src/discord-slash-wire.ts +2 -1
  743. package/src/discord-test-runner.ts +2 -1
  744. package/src/discord-voice-wire.ts +2 -1
  745. package/src/discord.ts +1 -1
  746. package/src/dispatch/opportunistic-launcher.ts +24 -1
  747. package/src/domains/ops-status.ts +35 -2
  748. package/src/domains/outbound-alert.ts +24 -6
  749. package/src/domains/repeated-failure.ts +89 -0
  750. package/src/domains/schedule-manage-tool.ts +28 -8
  751. package/src/domains/schedule-migrate.ts +22 -4
  752. package/src/domains/schedule-registry.ts +88 -33
  753. package/src/domains/schedule-runs.ts +132 -0
  754. package/src/domains/schedule-state.ts +152 -0
  755. package/src/domains/telegram-kind-route.ts +21 -0
  756. package/src/element-registry/address.ts +1 -3
  757. package/src/execution-loop/dispatch-task.ts +139 -0
  758. package/src/execution-loop/gate-decision-summary.ts +29 -0
  759. package/src/execution-loop/gates.ts +197 -0
  760. package/src/git-fs/gh-cli.ts +47 -17
  761. package/src/git-fs/retry.ts +1 -0
  762. package/src/git-fs/runner.ts +1 -0
  763. package/src/git-fs/worktree.ts +6 -5
  764. package/src/github/pr-labels.ts +68 -0
  765. package/src/graph-kinds/installed-plugin-nodes.ts +95 -0
  766. package/src/graph-kinds/plugin-nodes.ts +68 -0
  767. package/src/graph-kinds/registry.ts +118 -0
  768. package/src/graph-runner/graph-cli.ts +103 -0
  769. package/src/graph-runner/graph-notify.ts +220 -0
  770. package/src/graph-runner/graph-tick.ts +119 -0
  771. package/src/graph-runner/release-manifest.ts +93 -0
  772. package/src/graph-runner/runner.ts +487 -0
  773. package/src/grok/credential.ts +158 -5
  774. package/src/grounding/ensure-grounding.ts +131 -0
  775. package/src/grounding/mirror-export.ts +89 -0
  776. package/src/grounding/mirror-manifest.ts +252 -0
  777. package/src/grounding/sources-cli.ts +209 -0
  778. package/src/grounding/sources.ts +333 -0
  779. package/src/grounding/token.ts +121 -0
  780. package/src/harness/harness-cli-command.ts +415 -23
  781. package/src/harness/harness-pod-dispatch.ts +116 -0
  782. package/src/harness/harness-stop.ts +260 -0
  783. package/src/harness/harness-substrate-default.ts +40 -0
  784. package/src/harness/harness-worktrees.ts +1 -1
  785. package/src/harness/harness-write-boundary.ts +2 -0
  786. package/src/harness/llm-decompose.ts +2 -1
  787. package/src/harness/self-send-decide.ts +233 -0
  788. package/src/harness/self-send-target.ts +22 -1
  789. package/src/harness/shell-relay.ts +5 -0
  790. package/src/harness/worktree-branch-prefix.ts +4 -3
  791. package/src/hooks/providers.ts +77 -0
  792. package/src/hooks/queue.ts +73 -0
  793. package/src/hooks/receiver.ts +133 -0
  794. package/src/index.ts +712 -3409
  795. package/src/input-core/dispatcher.ts +1 -1
  796. package/src/input-core/drag-esc-interceptor.ts +1 -1
  797. package/src/instance/current.ts +5 -5
  798. package/src/instance/leader.ts +10 -326
  799. package/src/instance/nexus-run-refusal.ts +27 -92
  800. package/src/instance/project-local-exclude.ts +27 -0
  801. package/src/instance/resolve.ts +46 -175
  802. package/src/intake-plane/capture.ts +14 -1
  803. package/src/intake-plane/check.ts +34 -14
  804. package/src/intake-plane/collect-github.ts +258 -0
  805. package/src/intake-plane/collect-pod.ts +100 -0
  806. package/src/intake-plane/collect-telegram-saved.ts +91 -0
  807. package/src/intake-plane/digest.ts +111 -0
  808. package/src/intake-plane/enrich-plugins.ts +1 -1
  809. package/src/intake-plane/front-classifier.ts +56 -0
  810. package/src/intake-plane/front-route-classifier.ts +80 -0
  811. package/src/intake-plane/front-route-rules.ts +152 -0
  812. package/src/intake-plane/grounding-docs.ts +185 -0
  813. package/src/intake-plane/harness-target.ts +20 -0
  814. package/src/intake-plane/intake-to-tasks.ts +303 -0
  815. package/src/intake-plane/items.ts +247 -0
  816. package/src/intake-plane/link-ledger.ts +27 -0
  817. package/src/intake-plane/route.ts +104 -0
  818. package/src/intake-plane/runtime-callables.ts +3 -1
  819. package/src/intake-plane/sketch-ledger.ts +34 -0
  820. package/src/intake-plane/slash.ts +30 -2
  821. package/src/intake-plane/submit-intake-work.ts +160 -0
  822. package/src/intake-plane/telegram-work.ts +56 -0
  823. package/src/iul/sidebar-shell.ts +0 -165
  824. package/src/launch-head/capability-check.ts +162 -0
  825. package/src/launch-head/launch-head-cli.ts +242 -0
  826. package/src/launch-head/load-balance.ts +81 -0
  827. package/src/live/detail-switch.ts +180 -0
  828. package/src/llm/provider-detect.ts +247 -0
  829. package/src/llm/provider-probe.ts +57 -0
  830. package/src/llm/usable-llm.ts +32 -0
  831. package/src/llm.ts +55 -23
  832. package/src/log-entry.ts +1 -1
  833. package/src/log-pane/attachment-popup.ts +1 -2
  834. package/src/loops/loop-cli.ts +37 -0
  835. package/src/loops/registry.ts +196 -0
  836. package/src/market/official-keys.ts +5 -0
  837. package/src/market/publish.ts +329 -0
  838. package/src/market/signed-index.ts +156 -0
  839. package/src/market/tgz.ts +121 -0
  840. package/src/mcp/client.ts +1 -1
  841. package/src/mcp/mcp-oauth.ts +40 -5
  842. package/src/mcp/server.ts +75 -7
  843. package/src/mcp-gateway/gateway.ts +128 -0
  844. package/src/mcp-gateway/pat-store.ts +124 -0
  845. package/src/mss/logging/log-store.ts +91 -2
  846. package/src/mss/logging/scoped-level.ts +5 -9
  847. package/src/native-tool-catalog.ts +66 -345
  848. package/src/nexus/api/connect-info.ts +23 -25
  849. package/src/nexus/api/context-url.ts +1 -1
  850. package/src/nexus/api/design-check.ts +90 -59
  851. package/src/nexus/api/design-previews-route.ts +182 -0
  852. package/src/nexus/api/design-system-create-route.ts +123 -0
  853. package/src/nexus/api/graph-approvals.ts +125 -0
  854. package/src/nexus/api/graph-kinds.ts +69 -0
  855. package/src/nexus/api/graphs-api.ts +221 -0
  856. package/src/nexus/api/harness-api.ts +31 -2
  857. package/src/nexus/api/health.ts +15 -0
  858. package/src/nexus/api/http-server.ts +328 -43
  859. package/src/nexus/api/image-generate.ts +3 -0
  860. package/src/nexus/api/ingest-token.ts +175 -0
  861. package/src/nexus/api/intake-ledger-api.ts +54 -0
  862. package/src/nexus/api/intake-route.ts +111 -0
  863. package/src/nexus/api/json-response.ts +33 -0
  864. package/src/nexus/api/live-detail.ts +47 -0
  865. package/src/nexus/api/live-shipped.ts +78 -0
  866. package/src/nexus/api/log-fabric.ts +42 -2
  867. package/src/nexus/api/markdown-publish.ts +3 -2
  868. package/src/nexus/api/mcp-http.ts +14 -9
  869. package/src/nexus/api/mcp-oauth-hint.ts +31 -0
  870. package/src/nexus/api/mcp-public-allowlist.ts +33 -0
  871. package/src/nexus/api/merge-approvals-gate.ts +79 -0
  872. package/src/nexus/api/merge-approvals.ts +307 -0
  873. package/src/nexus/api/meta-api.ts +18 -0
  874. package/src/nexus/api/next-fluent-wiring.ts +7 -9
  875. package/src/nexus/api/plugins-credentials-api.ts +47 -0
  876. package/src/nexus/api/plugins-market.ts +209 -0
  877. package/src/nexus/api/pod-credential-api.ts +187 -0
  878. package/src/nexus/api/public-routes.ts +87 -0
  879. package/src/nexus/api/rest-route-paths.ts +15 -0
  880. package/src/nexus/api/schedules-action.ts +12 -3
  881. package/src/nexus/api/schedules-read.ts +228 -0
  882. package/src/nexus/api/sessions-store.ts +8 -2
  883. package/src/nexus/api/setup-answer-priority.ts +130 -0
  884. package/src/nexus/api/setup-channel-bot.ts +56 -0
  885. package/src/nexus/api/setup-child-llm.ts +228 -0
  886. package/src/nexus/api/setup-obsidian-skills.ts +93 -0
  887. package/src/nexus/api/task-cards-api.ts +34 -0
  888. package/src/nexus/api/tasks-create.ts +136 -0
  889. package/src/nexus/api/telegram-trigger-bot.ts +3 -3
  890. package/src/nexus/api/terminals.ts +204 -18
  891. package/src/nexus/api/trace.ts +405 -0
  892. package/src/nexus/api/vault-api.ts +140 -7
  893. package/src/nexus/api/worktrees.ts +18 -2
  894. package/src/nexus/boot/register-mcp-clients.ts +135 -16
  895. package/src/nexus/daemon-endpoint.ts +85 -0
  896. package/src/nexus/default-port.ts +1 -0
  897. package/src/nexus/index.ts +362 -143
  898. package/src/nexus/onboarding/pwa-share-prompt.ts +31 -8
  899. package/src/nexus/onboarding/tailscale-probe.ts +9 -1
  900. package/src/nexus/outbound/router.ts +1 -1
  901. package/src/nexus/setup-status.ts +58 -0
  902. package/src/nexus/supervisor/lock.ts +8 -0
  903. package/src/nexus/webterm/pty.ts +15 -0
  904. package/src/oauth/codex-account-rotation.ts +122 -16
  905. package/src/oauth/codex-account-store.ts +88 -19
  906. package/src/oauth/codex-quota-policy.ts +55 -0
  907. package/src/oauth/store.ts +28 -25
  908. package/src/obsidian/default-vault.ts +17 -0
  909. package/src/obsidian/vault-write-guard.ts +46 -0
  910. package/src/onboarding/help/llm.md +2 -2
  911. package/src/onboarding.ts +27 -4
  912. package/src/panes/alt-skip-predicate.ts +4 -3
  913. package/src/panes/external-terminal-pane.ts +3 -4
  914. package/src/panes/factory.ts +1 -18
  915. package/src/panes/index.ts +1 -5
  916. package/src/panes/pane-cycle.ts +1 -1
  917. package/src/panes/types.ts +1 -1
  918. package/src/platform/env-literal.ts +4 -0
  919. package/src/plugin-hooks/dispatcher.ts +10 -4
  920. package/src/plugins/core/host.ts +108 -4
  921. package/src/plugins/core/manifest.ts +104 -7
  922. package/src/plugins/install/market-fetch.ts +250 -0
  923. package/src/plugins/install/plugin-credentials.ts +106 -0
  924. package/src/plugins/install/plugin-install.ts +518 -0
  925. package/src/policy/claude-subscription-guard.ts +25 -0
  926. package/src/preview/terminal.ts +24 -1
  927. package/src/preview-pane/mount.ts +1 -142
  928. package/src/prompt/references.ts +4 -51
  929. package/src/prompt-library/universal-preamble.ts +8 -2
  930. package/src/pty-shell/pty-control-ipc.ts +57 -3
  931. package/src/pty-shell/pty-mouse.ts +75 -0
  932. package/src/pty-shell/pty-recording.ts +108 -0
  933. package/src/pty-shell/registry.ts +53 -3
  934. package/src/python/resolve-python.ts +1 -1
  935. package/src/release-loop/checklist.ts +191 -0
  936. package/src/release-loop/manifest.ts +65 -0
  937. package/src/release-loop/release-note.ts +177 -0
  938. package/src/roles/lease-cas.ts +52 -0
  939. package/src/roles/machine-name.ts +48 -0
  940. package/src/roles/machine-profile.ts +65 -0
  941. package/src/roles/role-lease-read.ts +81 -0
  942. package/src/roles/role-lease.ts +63 -0
  943. package/src/roles/role-watch.ts +69 -0
  944. package/src/self-dev/ask-launch-flow.ts +39 -22
  945. package/src/self-dev/ask-launch-io.ts +1 -1
  946. package/src/self-dev/dev-cli.ts +290 -159
  947. package/src/self-dev/dev-pipeline.ts +157 -9
  948. package/src/self-dev/draft-sweep.ts +231 -0
  949. package/src/self-dev/draft-triage-rules.ts +42 -0
  950. package/src/self-dev/entrance-baseline.ts +156 -4
  951. package/src/self-dev/fabric-decompose-adapter.ts +10 -0
  952. package/src/self-dev/launch-preflight.ts +1 -1
  953. package/src/self-dev/orchestrate.ts +297 -20
  954. package/src/self-dev/repair-signals.ts +6 -0
  955. package/src/self-dev/run-store.ts +20 -5
  956. package/src/self-dev/run-supervisor.ts +43 -3
  957. package/src/self-implement/abandoned-classification.ts +15 -1
  958. package/src/self-implement/blocked-draft-policy.ts +6 -0
  959. package/src/self-implement/budget-gate.ts +236 -0
  960. package/src/self-implement/child-llm-preference.ts +84 -0
  961. package/src/self-implement/decision-events.ts +57 -0
  962. package/src/self-implement/decision-signal-press.ts +5 -2
  963. package/src/self-implement/dependency-change-gate.ts +90 -0
  964. package/src/self-implement/gate-baseline.ts +35 -2
  965. package/src/self-implement/gate-cli.ts +29 -7
  966. package/src/self-implement/gate-scope.ts +7 -1
  967. package/src/self-implement/goal-author-decompose.ts +22 -4
  968. package/src/self-implement/goal-author.ts +41 -3
  969. package/src/self-implement/goal-boundary.ts +65 -0
  970. package/src/self-implement/goal-clarification-escalation.ts +1 -0
  971. package/src/self-implement/goal-documents-dir.ts +6 -1
  972. package/src/self-implement/goal-execution-records.ts +58 -0
  973. package/src/self-implement/goal-run-store.ts +42 -24
  974. package/src/self-implement/graph-authority.ts +13 -28
  975. package/src/self-implement/graph-catalog.ts +48 -0
  976. package/src/self-implement/graph-run-contract.ts +127 -0
  977. package/src/self-implement/graph-templates.ts +14 -7
  978. package/src/self-implement/graph-yaml.ts +35 -6
  979. package/src/self-implement/harness-policy-gates.ts +42 -0
  980. package/src/self-implement/headless-elanous-driver.ts +30 -6
  981. package/src/self-implement/heal-triage.ts +229 -0
  982. package/src/self-implement/host-regate.ts +244 -0
  983. package/src/self-implement/non-git-consent.ts +23 -0
  984. package/src/self-implement/orchestrator.ts +488 -134
  985. package/src/self-implement/pipeline-shape.ts +1 -1
  986. package/src/self-implement/pr-evidence-artifact.ts +6 -19
  987. package/src/self-implement/prior-draft-findings.ts +128 -0
  988. package/src/self-implement/pwa-import-graph.ts +65 -0
  989. package/src/self-implement/repo-provision.ts +18 -21
  990. package/src/self-implement/report-origin.ts +39 -0
  991. package/src/self-implement/rework-policy.ts +21 -0
  992. package/src/self-implement/run-ledger-compact.ts +438 -0
  993. package/src/self-implement/run-ledger.ts +212 -29
  994. package/src/self-implement/run-report.ts +24 -0
  995. package/src/self-implement/run-status-mapping.ts +2 -0
  996. package/src/self-implement/running-runs.ts +112 -14
  997. package/src/self-implement/scoped-typecheck-config.ts +49 -0
  998. package/src/self-implement/seams.ts +207 -40
  999. package/src/self-implement/self-implement-cli.ts +17 -2
  1000. package/src/self-implement/shadow-repo.ts +350 -0
  1001. package/src/self-implement/supervision-vocabulary.ts +7 -0
  1002. package/src/shell-runner/dispatch.ts +3 -0
  1003. package/src/shell-runner/index.ts +0 -7
  1004. package/src/skills/runner.ts +0 -69
  1005. package/src/skills/tools/agent-room-slash.ts +2 -1
  1006. package/src/skills/tools/agent-room.ts +8 -140
  1007. package/src/skills/tools/agent.ts +5 -3
  1008. package/src/skills/tools/context.ts +5 -140
  1009. package/src/skills/tools/control.ts +4 -188
  1010. package/src/skills/tools/dashboard-slash.ts +3 -11
  1011. package/src/skills/tools/dashboard-state.ts +3 -5
  1012. package/src/skills/tools/dev-harness.ts +45 -2
  1013. package/src/skills/tools/omni-search.ts +29 -1
  1014. package/src/skills/tools/shell.ts +11 -29
  1015. package/src/skills/tools/spawn-coding-agent-headless.ts +6 -9
  1016. package/src/ssh/ssh-picker-modal.ts +1 -1
  1017. package/src/steward/directive.ts +99 -0
  1018. package/src/steward/steward-cards.ts +45 -0
  1019. package/src/steward/triage.ts +239 -0
  1020. package/src/storage/cli-bin.ts +67 -0
  1021. package/src/storage/object-store-visibility.ts +169 -0
  1022. package/src/storage/object-store.ts +452 -0
  1023. package/src/storage/s3.ts +53 -10
  1024. package/src/storage/storage-detect.ts +337 -0
  1025. package/src/surface/address.ts +3 -5
  1026. package/src/surface/index.ts +0 -6
  1027. package/src/surface/llm-tools.ts +1 -2
  1028. package/src/surface/shell-runner-boot.ts +4 -89
  1029. package/src/task-cards/card-cli.ts +64 -0
  1030. package/src/task-cards/card-store.ts +241 -0
  1031. package/src/task-orchestrator/boot.ts +27 -1
  1032. package/src/task-orchestrator/dispatcher.ts +49 -7
  1033. package/src/task-orchestrator/external-fingerprint.ts +10 -0
  1034. package/src/task-orchestrator/external-policy.ts +86 -0
  1035. package/src/task-orchestrator/feedback-loop.ts +4 -2
  1036. package/src/task-orchestrator/graph.ts +18 -1
  1037. package/src/task-orchestrator/runtimes/create.ts +128 -0
  1038. package/src/task-orchestrator/store.ts +78 -18
  1039. package/src/task-orchestrator/surface-registry.ts +5 -2
  1040. package/src/task-orchestrator/surfaces/external-exec.ts +180 -0
  1041. package/src/task-orchestrator/surfaces/idea-approval-pr.ts +118 -0
  1042. package/src/task-orchestrator/surfaces/index.ts +4 -3
  1043. package/src/task-orchestrator/surfaces/pod-account-broker.ts +60 -5
  1044. package/src/task-orchestrator/surfaces/pod-artifact-return.ts +194 -0
  1045. package/src/task-orchestrator/surfaces/pod-command-job.ts +369 -0
  1046. package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +207 -0
  1047. package/src/task-orchestrator/surfaces/pod-pool.ts +128 -12
  1048. package/src/task-orchestrator/surfaces/pod-skills.ts +11 -3
  1049. package/src/task-orchestrator/surfaces/pod-source-bundle.ts +147 -0
  1050. package/src/task-orchestrator/surfaces/pod-source-receive.ts +129 -0
  1051. package/src/task-orchestrator/surfaces/pod-source-spec.ts +77 -0
  1052. package/src/task-orchestrator/surfaces/self-implement-pod.ts +930 -45
  1053. package/src/task-orchestrator/surfaces/self-implement.ts +8 -1
  1054. package/src/task-orchestrator/tox-loop.ts +204 -0
  1055. package/src/task-orchestrator/types.ts +21 -1
  1056. package/src/telegram-commands.ts +8 -0
  1057. package/src/telegram-report.ts +14 -7
  1058. package/src/telegram-run.ts +18 -5
  1059. package/src/telegram.ts +92 -22
  1060. package/src/tool-runtime/acp-session-runtime.ts +28 -7
  1061. package/src/tool-runtime/agent-runtime.ts +1 -0
  1062. package/src/tool-runtime/context-runtime.ts +18 -35
  1063. package/src/tool-runtime/control-runtime.ts +8 -21
  1064. package/src/tool-runtime/dashboard-state-runtime.ts +1 -5
  1065. package/src/tool-runtime/elanous-asana-runtimes.ts +205 -0
  1066. package/src/tool-runtime/elanous-control-runtimes.ts +16 -2
  1067. package/src/tool-runtime/elanous-task-submit-runtime.ts +124 -0
  1068. package/src/tool-runtime/index.ts +4 -1
  1069. package/src/tool-runtime/scenario-runtimes.ts +22 -21
  1070. package/src/tool-runtime/scenario-target-mount.ts +6 -32
  1071. package/src/tool-runtime/types.ts +8 -0
  1072. package/src/tui/chat/attachment-summary.ts +4 -4
  1073. package/src/tui-client/acp-transport-ws-client.ts +72 -5
  1074. package/src/tui-client/headless-core-guard.ts +0 -1
  1075. package/src/tui.ts +11 -7
  1076. package/src/typecheck-ratchet.ts +15 -0
  1077. package/src/ui/hover-presenter.ts +3 -7
  1078. package/src/user-config.ts +453 -190
  1079. package/src/util/spawn-sync-output.ts +15 -0
  1080. package/src/version/code-revision.ts +34 -9
  1081. package/src/version/packed-revision.json +1 -1
  1082. package/src/video-pipeline/broll-agent.ts +102 -0
  1083. package/src/video-pipeline/recipes/broll.ts +263 -0
  1084. package/src/video-pipeline/walk-line.ts +2 -1
  1085. package/src/views/scratch-image-size.ts +10 -0
  1086. package/src/voice/channel-adapters/pwa-voice-adapter.ts +2 -18
  1087. package/src/voice/channel-adapters/telegram-voice-adapter.ts +1 -6
  1088. package/src/voice/voice-tts-singleton.ts +46 -8
  1089. package/src/web-push/vapid-keys.ts +1 -1
  1090. package/src/web-search/free.ts +77 -0
  1091. package/src/web-search/index.ts +7 -5
  1092. package/src/web-terminal/preview-tap-registry.ts +152 -43
  1093. package/src/workflow-runtime/deps-bridge.ts +4 -3
  1094. package/src/workflow-runtime/executor.ts +18 -22
  1095. package/src/workflow-runtime/node-catalog.ts +20 -1
  1096. package/src/workflow-runtime/nodes/bash.ts +1 -0
  1097. package/src/workflow-runtime/nodes/task.ts +74 -0
  1098. package/src/workflow-runtime/nodes/triggers.ts +24 -1
  1099. package/src/workflow-runtime/plugin-kind-node.ts +166 -0
  1100. package/src/workflow-runtime/schema.ts +98 -2
  1101. package/src/workflow-runtime/triggers/webhook-router.ts +43 -3
  1102. package/src/workflow-runtime/triggers/webhook-source.ts +19 -1
  1103. package/src/workflow-runtime/types.ts +76 -5
  1104. package/apps/pwa/out/_next/static/Ksn99fVf2HUWhSyV1-FC8/_buildManifest.js +0 -1
  1105. package/apps/pwa/out/_next/static/chunks/1176-4943bd83b00e53a2.js +0 -25
  1106. package/apps/pwa/out/_next/static/chunks/1380-9c652e1002032c86.js +0 -1
  1107. package/apps/pwa/out/_next/static/chunks/1863-2ea170dfec26b88b.js +0 -1
  1108. package/apps/pwa/out/_next/static/chunks/2091-2d6b7f4f16300f83.js +0 -1
  1109. package/apps/pwa/out/_next/static/chunks/2347-366617ec73babe3b.js +0 -1
  1110. package/apps/pwa/out/_next/static/chunks/3089-e60cc8f5d35ee239.js +0 -1
  1111. package/apps/pwa/out/_next/static/chunks/3158-c77ed9ea34f9cc9f.js +0 -1
  1112. package/apps/pwa/out/_next/static/chunks/3607-f0b5c5ee18fcf56a.js +0 -1
  1113. package/apps/pwa/out/_next/static/chunks/3659-05b2d5d971b42dbc.js +0 -1
  1114. package/apps/pwa/out/_next/static/chunks/3736-b6a7bfdcb8f56ec6.js +0 -1
  1115. package/apps/pwa/out/_next/static/chunks/3839-eebf298e89a7bb77.js +0 -1
  1116. package/apps/pwa/out/_next/static/chunks/4012.832a2824b978e7ee.js +0 -5
  1117. package/apps/pwa/out/_next/static/chunks/4706-d85d7f3345f3ff91.js +0 -1
  1118. package/apps/pwa/out/_next/static/chunks/526-17a6a2e0e6dc4498.js +0 -1
  1119. package/apps/pwa/out/_next/static/chunks/5508-676ac75f2757ad40.js +0 -1
  1120. package/apps/pwa/out/_next/static/chunks/5583-8a0317f4b8e68db9.js +0 -1
  1121. package/apps/pwa/out/_next/static/chunks/6258-b58f722b10e8d31d.js +0 -1
  1122. package/apps/pwa/out/_next/static/chunks/6820-32fd097e6186fdc9.js +0 -1
  1123. package/apps/pwa/out/_next/static/chunks/6852-983db7ca958634b7.js +0 -1
  1124. package/apps/pwa/out/_next/static/chunks/7235-e583e4a015e159b9.js +0 -1
  1125. package/apps/pwa/out/_next/static/chunks/7482-7b417330b24a996a.js +0 -1
  1126. package/apps/pwa/out/_next/static/chunks/7701-99b7d4dedb955bf8.js +0 -1
  1127. package/apps/pwa/out/_next/static/chunks/77f1928d-bd3777589042d3ea.js +0 -1
  1128. package/apps/pwa/out/_next/static/chunks/8366-36956cafc5e33614.js +0 -1
  1129. package/apps/pwa/out/_next/static/chunks/8370-23d0de995be666f8.js +0 -1
  1130. package/apps/pwa/out/_next/static/chunks/8617-307421f3b0e8b2a2.js +0 -1
  1131. package/apps/pwa/out/_next/static/chunks/8837-e45de46a9fedf1f5.js +0 -1
  1132. package/apps/pwa/out/_next/static/chunks/9058-0e8055a5ea3ebdd6.js +0 -1
  1133. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-84b84eca12acada4.js +0 -1
  1134. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-1f23568c29e88f7a.js +0 -1
  1135. package/apps/pwa/out/_next/static/chunks/app/botlab/page-bf6fc9aafd6135a7.js +0 -1
  1136. package/apps/pwa/out/_next/static/chunks/app/bots/page-3f8539ae4b719d2e.js +0 -1
  1137. package/apps/pwa/out/_next/static/chunks/app/chat/page-d2900febb74ecf32.js +0 -1
  1138. package/apps/pwa/out/_next/static/chunks/app/control/page-e82dec647dd79c36.js +0 -1
  1139. package/apps/pwa/out/_next/static/chunks/app/dashboard/page-096ec743a8ee955a.js +0 -1
  1140. package/apps/pwa/out/_next/static/chunks/app/design-check/page-255c2ceded4b3493.js +0 -1
  1141. package/apps/pwa/out/_next/static/chunks/app/healthz/route-84b84eca12acada4.js +0 -1
  1142. package/apps/pwa/out/_next/static/chunks/app/intake/page-2db61b2f9efba61f.js +0 -1
  1143. package/apps/pwa/out/_next/static/chunks/app/layout-e11ee17f221aaa9b.js +0 -1
  1144. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-e075974e029ce4c4.js +0 -1
  1145. package/apps/pwa/out/_next/static/chunks/app/morning/page-ec5b68a9af3366a0.js +0 -1
  1146. package/apps/pwa/out/_next/static/chunks/app/not-found-986c177afb4adae6.js +0 -1
  1147. package/apps/pwa/out/_next/static/chunks/app/observatory/page-a72781461af2f2a3.js +0 -1
  1148. package/apps/pwa/out/_next/static/chunks/app/page-92781cbaafc487ad.js +0 -1
  1149. package/apps/pwa/out/_next/static/chunks/app/reflection/page-2c14d2a1f2930f33.js +0 -1
  1150. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-22ee4bf8070b2b4d.js +0 -1
  1151. package/apps/pwa/out/_next/static/chunks/app/sessions/page-9c06e4a8297942a8.js +0 -1
  1152. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-d0cd32f6d373798f.js +0 -1
  1153. package/apps/pwa/out/_next/static/chunks/app/settings/page-0c1642018d87ebb8.js +0 -1
  1154. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-93e5e97d614df487.js +0 -1
  1155. package/apps/pwa/out/_next/static/chunks/app/setup/layout-84b84eca12acada4.js +0 -1
  1156. package/apps/pwa/out/_next/static/chunks/app/setup/page-b1ce871940d7393e.js +0 -1
  1157. package/apps/pwa/out/_next/static/chunks/app/share/page-ae154addf345febe.js +0 -1
  1158. package/apps/pwa/out/_next/static/chunks/app/showroom/page-90af3aa813d2053c.js +0 -1
  1159. package/apps/pwa/out/_next/static/chunks/app/tasks/page-f3f962fda428a738.js +0 -1
  1160. package/apps/pwa/out/_next/static/chunks/app/term/page-1c1f7cc1d5def06d.js +0 -1
  1161. package/apps/pwa/out/_next/static/chunks/app/vault/page-09d4cbdaaacf06c1.js +0 -1
  1162. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-e877b8e8ca90b27f.js +0 -1
  1163. package/apps/pwa/out/_next/static/chunks/app/workflows/page-71d902f463dea526.js +0 -1
  1164. package/apps/pwa/out/_next/static/chunks/app/workspace/page-40ea576cefa25fe6.js +0 -1
  1165. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-a56a3f178c3831f6.js +0 -1
  1166. package/apps/pwa/out/_next/static/chunks/bcc499d1-804a270d878e0842.js +0 -1
  1167. package/apps/pwa/out/_next/static/chunks/framework-b25b7c5501c59b6d.js +0 -1
  1168. package/apps/pwa/out/_next/static/chunks/main-a9ec9da43387afff.js +0 -1
  1169. package/apps/pwa/out/_next/static/chunks/main-app-aa2c05954aaa6754.js +0 -1
  1170. package/apps/pwa/out/_next/static/chunks/pages/_app-bb3768fb4b84aebb.js +0 -1
  1171. package/apps/pwa/out/_next/static/chunks/pages/_error-2820ba75b87b2f5e.js +0 -1
  1172. package/apps/pwa/out/_next/static/chunks/webpack-58c73b145f189b1c.js +0 -1
  1173. package/apps/pwa/out/_next/static/css/effec8adf767aa75.css +0 -1
  1174. package/apps/pwa/out/dashboard/index.html +0 -1
  1175. package/apps/pwa/out/dashboard/index.txt +0 -26
  1176. package/src/acp/resident-shell.ts +0 -244
  1177. package/src/agent/spawn-local-llm-in-vw.ts +0 -240
  1178. package/src/dashboard/agent-spawn-tools-boot.ts +0 -14
  1179. package/src/dashboard/dock-picker-inventory.ts +0 -41
  1180. package/src/dashboard/goal-plan-hud-bridge.ts +0 -112
  1181. package/src/dashboard/hud-mirror.ts +0 -207
  1182. package/src/dashboard/input/mouse-dock-runtime.ts +0 -156
  1183. package/src/dashboard/input/mouse-hover-runtime.ts +0 -46
  1184. package/src/dashboard/input/mouse-pill-context-runtime.ts +0 -74
  1185. package/src/dashboard/input/mouse-workspace-restore-runtime.ts +0 -64
  1186. package/src/dashboard/input/scratch-actions.ts +0 -36
  1187. package/src/dashboard/input/virtual-window-key-router.ts +0 -24
  1188. package/src/dashboard/rich-scratch-viewers.ts +0 -77
  1189. package/src/dashboard/scratch-slash-runtime.ts +0 -36
  1190. package/src/dashboard/scratch-state-runtime.ts +0 -42
  1191. package/src/dashboard/scratch-widget-runtime.ts +0 -37
  1192. package/src/dashboard/sim-shell.ts +0 -313
  1193. package/src/dashboard/virtual-window-control-runtime.ts +0 -57
  1194. package/src/dashboard/virtual-window-help-runtime.ts +0 -113
  1195. package/src/dashboard/virtual-window-input-runtime.ts +0 -99
  1196. package/src/dashboard/virtual-window-mutation-runtime.ts +0 -102
  1197. package/src/dashboard/virtual-window-split-runtime.ts +0 -70
  1198. package/src/dashboard/vw-popup-openers.ts +0 -126
  1199. package/src/dashboard/window-picker-popup.ts +0 -74
  1200. package/src/dashboard/window-slash-runtime.ts +0 -108
  1201. package/src/dashboard/windowing/companion-slash.ts +0 -78
  1202. package/src/dashboard/windowing/fast-switch.ts +0 -138
  1203. package/src/dashboard/windowing/lifecycle.ts +0 -185
  1204. package/src/dashboard/windowing/virtual-windows.ts +0 -259
  1205. package/src/dashboard/windowing/visibility-chord.ts +0 -77
  1206. package/src/iul/resident-vw.ts +0 -55
  1207. package/src/panes/content-adapter.ts +0 -162
  1208. package/src/prompt/vw-reference.ts +0 -153
  1209. package/src/shell-runner/external-terminal-pane.ts +0 -157
  1210. package/src/sim/resident-vw.ts +0 -48
  1211. package/src/skills/tools/spawn-coding-agent-vw.ts +0 -181
  1212. package/src/skills/tools/virtual-windows.ts +0 -632
  1213. package/src/surface/adapters/window-surface-adapter.ts +0 -141
  1214. package/src/tool-runtime/layout-runtimes.ts +0 -81
  1215. package/src/tool-runtime/scenario-window-mount.ts +0 -207
  1216. package/src/views/ui-mode.ts +0 -100
  1217. package/src/virtual-window-context-menu.ts +0 -97
  1218. package/src/virtual-windows/addressing.ts +0 -195
  1219. package/src/virtual-windows/benchmark-preset.ts +0 -119
  1220. package/src/virtual-windows/event-bus.ts +0 -202
  1221. package/src/virtual-windows/layout/apply-planner.ts +0 -189
  1222. package/src/virtual-windows/layout/index.ts +0 -68
  1223. package/src/virtual-windows/layout/layout-commands.ts +0 -186
  1224. package/src/virtual-windows/layout/layout-tools.ts +0 -262
  1225. package/src/virtual-windows/layout/persistence.ts +0 -219
  1226. package/src/virtual-windows/layout/presets.ts +0 -114
  1227. package/src/virtual-windows/layout/restore-planner.ts +0 -223
  1228. package/src/virtual-windows/layout/serializer.ts +0 -197
  1229. package/src/virtual-windows/layout/tree.ts +0 -147
  1230. package/src/virtual-windows/layout/types.ts +0 -63
  1231. package/src/virtual-windows/layout-tree.ts +0 -389
  1232. package/src/virtual-windows/navigation.ts +0 -251
  1233. package/src/virtual-windows/pane-capture.ts +0 -142
  1234. package/src/virtual-windows/pane-content.ts +0 -611
  1235. package/src/virtual-windows/pane-spawner.ts +0 -278
  1236. package/src/virtual-windows/pty-tail-content.ts +0 -291
  1237. package/src/virtual-windows/resident-window.ts +0 -59
  1238. package/src/virtual-windows/secondary-action.ts +0 -25
  1239. package/src/virtual-windows/terminal-slot-content.ts +0 -165
  1240. package/src/virtual-windows/virtual-window.ts +0 -1301
  1241. package/src/virtual-windows/vw-local-input-target-popup.ts +0 -110
  1242. package/src/virtual-windows/vw-rename-modal.ts +0 -77
  1243. package/src/virtual-windows/vw-selector-popup.ts +0 -177
  1244. package/src/virtual-windows/window-registry.ts +0 -519
  1245. package/src/voice/channel-adapters/discord-voice-adapter.ts +0 -202
  1246. package/src/voice/voice-pwa-dispatch.ts +0 -245
  1247. package/src/window-picker-modal.ts +0 -135
  1248. /package/apps/pwa/out/_next/static/{Ksn99fVf2HUWhSyV1-FC8 → ktMqfrBbf8BMGac0jqLrb}/_ssgManifest.js +0 -0
  1249. /package/src/dashboard/{companion-slash-runtime.ts → companion-messages-runtime.ts} +0 -0
@@ -2,7 +2,7 @@
2
2
  //
3
3
  // 계약은 로컬 spawn(`defaultSelfImplementSpawn`)과 «같다»: 입력 → { address, done }. 그래서 분해·동시 실행 상한·
4
4
  // 감독 루프·원장이 수정 없이 따라온다(RFC-elanous-on-docker-and-kubernetes-isolation-ladder · ROADMAP C2).
5
- // Pod 안에서는 같은 명령 `elanous self implement "<feature>" --json …` 을 치고, 마지막 줄 JSON 을 Job 로그로 읽는다.
5
+ // Pod 안에서는 일반 골은 `self implement`, 전달된 ask 골 문서는 `harness ask` 로 실행하고 마지막 줄 JSON 을 Job 로그로 읽는다.
6
6
  //
7
7
  // 📏 2026-09-25 실측 근거(docker/harness · docker/runner):
8
8
  // · 자격 = 호스트 계정의 «refresh 없는» 사본(1회용 refresh 보호) — Secret(읽기 전용) → 쓰기 가능한 홈으로 복사.
@@ -12,24 +12,62 @@
12
12
  // 부작용(kubectl·파일)은 주입받는다 — 시험은 가짜 kubectl 로 누른다.
13
13
 
14
14
  import { podSkillsDigest, readSkillEnvFiles, resolvePodSkills } from './pod-skills.js';
15
+ import { podSourceScript, type PodSource } from './pod-source-receive.js';
16
+ import { GROUNDING_TOKEN_ENV, GROUNDING_URL_ENV, mintGroundingToken, revokeGroundingRun, type GroundingTokenScope } from '../../grounding/token.js';
17
+ import { POD_CREDENTIAL_GROK_PATH, POD_CREDENTIAL_GITHUB_PATH, POD_GITHUB_CREDENTIAL_TOKEN_ENV, POD_GITHUB_CREDENTIAL_URL_ENV, installationRepositories } from '../../nexus/api/pod-credential-api.js';
18
+ import { githubInstallationCredential } from '../../auth/github-app-token.js';
19
+ import { POD_CREDENTIAL_TOKEN_ENV, POD_CREDENTIAL_URL_ENV } from '../../grok/credential.js';
20
+ import { collectPodLedgers, createPodLedgerFollower } from './pod-ledger-collect.js';
21
+ import { collectPodArtifacts } from './pod-artifact-return.js';
22
+ import { isValidAccountName, resolveCodexAccount } from '../../oauth/codex-account.js';
23
+ import { effectiveInstanceRoot } from '../../instance/resolve.js';
15
24
  import { controlInboxEnv } from '../../harness/control-inbox.js';
16
25
  import { finishPodFragment, writePodFragment } from '../../harness/self-send-target.js';
26
+ import { mintRunId, normalizeRunId } from '../../harness/harness-space.js';
27
+ import { appendRunLedgerEntry, runLedgerDir, runLedgerPath } from '../../self-implement/run-ledger.js';
28
+ import type { GoalExecutionRecord } from '../../self-implement/orchestrator.js';
29
+ import { runHostRegate, type HostRegateResult } from '../../self-implement/host-regate.js';
17
30
 
18
31
  export const POD_CONTROL_INBOX_DIR = '/tmp/elanous-control.inbox';
32
+ /** Pod Job 수명 상한(`activeDeadlineSeconds`) — 대표 2026-09-26 90분 → 180분(90분에 `DeadlineExceeded` 로 죽은 런 둘). */
33
+ /** Newest pod log bytes returned when the export passes the 5MB artifact limit (headroom under it). */
34
+ export const POD_LOGS_KEEP_BYTES = 4_500_000;
35
+
36
+ export const POD_JOB_DEADLINE_SECONDS = 10_800;
37
+
38
+ /** 자식 컨테이너 «요청» — 🩸 2026-09-27: limits 만 두면 k8s 가 requests=limits(cpu 4)로 잡아, 32코어 노드에
39
+ * «자리 요청»이 88% 차서 잡이 Pending 인데 실사용은 14%였다(풀 25 자리 중 ~8 만 떴다). 상한(limits)은 그대로 두고
40
+ * 예약만 실사용에 맞춘다. */
41
+ export const POD_CHILD_REQUESTS = { cpu: '1', memory: '4Gi' } as const;
19
42
  import type { PodPoolMember, PodPoolScheduler } from './pod-pool.js';
43
+ import { ACTUAL_SUBSTRATE_ENV, RUN_CONTRACT_ENV, carryRunContract, completionFloorFor } from '../../self-implement/graph-run-contract.js';
20
44
  import { spawnSync } from 'node:child_process';
21
45
  import { createHash } from 'node:crypto';
22
- import { readFileSync } from 'node:fs';
46
+ import { existsSync, readFileSync, realpathSync } from 'node:fs';
23
47
  import { homedir } from 'node:os';
24
- import { join } from 'node:path';
48
+ import { isAbsolute, join, normalize, relative, resolve, sep } from 'node:path';
49
+ import { findGitDir } from '../../git-fs/locate.js';
25
50
  import { debug } from '../../debug/log.js';
51
+ import { selectLiveDetail } from '../../live/detail-switch.js';
26
52
  import { authStorePath } from '../../oauth/store.js';
53
+ import { grokAuthFilePath, resolveGrokCredential } from '../../grok/credential.js';
54
+ import { defaultGrokModel } from '../../grok/models.js';
27
55
  import { resolveHostId } from '../../platform/host-id.js';
56
+ import { envLiteral } from '../../platform/env-literal.js';
28
57
  import { LLM_TIER_MAP_BY_PROVIDER, lookupLlmTierSpec, type LlmTierProvider } from '../../model-tier/llm-tier-map.js';
29
58
  import { parseSelfImplementJson, type SelfImplementJobDone, type SelfImplementJobSpawn } from './self-implement.js';
59
+ import { codexQuotaPolicyFromConfig } from '../../oauth/codex-account-store.js';
30
60
 
31
61
  export type Kubectl = (args: readonly string[], input?: string) => { status: number | null; stdout: string; stderr: string };
32
62
 
63
+ // gh auth login may reject installation tokens when validating /user. Its config is
64
+ // still the credential source for gh and setup-git's helper. Install it privately
65
+ // on that failure; rename keeps concurrent readers from seeing a partial token.
66
+ const APP_GH_CONFIG_PATH = '$HOME/.config/gh';
67
+ const APP_GH_AUTH_SCRIPT = 'const fs=require("node:fs"),path=require("node:path"),crypto=require("node:crypto");const token=fs.readFileSync(0,"utf8").trim();if(!/^[A-Za-z0-9_]+$/.test(token))process.exit(1);const dir=process.env.GH_CONFIG_DIR||path.join(process.env.HOME,".config/gh");fs.mkdirSync(dir,{recursive:true,mode:0o700});const dest=path.join(dir,"hosts.yml"),temp=path.join(dir,".hosts.yml."+crypto.randomUUID());try{fs.writeFileSync(temp,"github.com:\\n oauth_token: "+token+"\\n git_protocol: https\\n",{mode:0o600,flag:"wx"});fs.renameSync(temp,dest);fs.chmodSync(dest,0o600)}finally{try{fs.unlinkSync(temp)}catch{}}';
68
+ const APP_GH_AUTH_COMMAND = `bun -e '${APP_GH_AUTH_SCRIPT}'`;
69
+ const APP_GH_EXEC = `unset GH_TOKEN GITHUB_TOKEN; GH_CONFIG_DIR="${APP_GH_CONFIG_PATH}" exec "$@"`;
70
+
33
71
  export interface PodSpawnOptions {
34
72
  /** 이미지 판(라벨 elanous.commit) — 주입(시험·감독기가 이미 잰 값). 없으면 docker 로 잰다. */
35
73
  imageCommit?: string | null;
@@ -37,12 +75,26 @@ export interface PodSpawnOptions {
37
75
  account?: string;
38
76
  /** Job 마다 계정을 고른다(pod-account-broker) — 있으면 `account` 보다 먼저. */
39
77
  accountBroker?: () => string;
78
+ /** Usable codex accounts from the broker; the allocated account is moved to the front per Job. */
79
+ rotationAccounts?: readonly string[];
80
+ /** 미지정이면 종전 Codex Job. */
81
+ provider?: 'openai-codex' | 'grok';
82
+ /** 🔐 API 키 과금 허용은 명시 true 뿐. */
83
+ grokApiKeyOptIn?: boolean;
40
84
  namespace?: string;
41
85
  /** 클러스터 안 이미지(`docker/harness/run.sh` 가 만드는 `elanous-harness:local`). */
42
86
  image?: string;
43
87
  repoUrl?: string;
88
+ /** 호스트 Git 미러 디렉터리 — 명시 옵션 > 환경변수 > pod.hostMirror. */
89
+ hostMirror?: string;
90
+ /** 설정 읽기(시험 주입). kubectl 주입 시험에서는 기본 사용자 설정을 읽지 않는다. */
91
+ configHostMirror?: () => string | undefined;
92
+ /** 원천 — 없으면 종전 `git clone --depth 50`. bundle 이면 apply 뒤 kubectl cp 로 싣는다. */
93
+ source?: PodSource;
44
94
  /** 호스트 환경에서 읽어 Pod env 로 넣을 키 이름(예: OPENROUTER_API_KEY · ANTHROPIC_API_KEY) — 벤치마크 과금 경로. */
45
95
  passEnv?: readonly string[];
96
+ /** 호스트가 재발사를 소유하면 Pod 자식의 자체 감독을 끈다. 독립 호출 기본 true; 실제 CLI 호스트는 자기 감독 상태를 명시한다. */
97
+ hostSupervised?: boolean;
46
98
  /** 자식 `self implement` 에 덧붙일 인자. */
47
99
  extraArgs?: readonly string[];
48
100
  /** Pod 에 그대로 넣을 «비밀 아닌» env — 벤치 팔의 `ELANOUS_LLM_PROVIDER`·`ELANOUS_LLM_MODEL`·`ELANOUS_ARM_ID`. */
@@ -51,17 +103,34 @@ export interface PodSpawnOptions {
51
103
  deadlineSeconds?: number;
52
104
  pollMs?: number;
53
105
  kubectl?: Kubectl;
106
+ /** Host-side regate (injected for Pod tests). */
107
+ hostRegate?: (input: { prNumber: number; headCommit: string; repoRoot: string }) => Promise<HostRegateResult>;
108
+ /** PR comment for host-regate failures that never reached runHostRegate (injectable for tests). */
109
+ ghComment?: (prNumber: number, body: string) => void;
54
110
  /** 🔑 Pod 필수 스킬의 키(.env)를 이 런의 Secret 으로 넘긴다 — 명시 opt-in(유료 크레딧을 쓴다). */
55
111
  skillEnv?: boolean;
56
112
  /** 스킬 키 읽기(시험 주입) — `{ <스킬>: <.env 내용> }`. */
57
113
  readSkillEnv?: () => Record<string, string>;
58
114
  /** ☸️ 여러 클러스터 풀(pod-pool.ts) — Job 마다 우선순위 순 첫 빈 자리로. 없으면 현재 컨텍스트 하나. */
59
115
  pool?: PodPoolScheduler;
116
+ /** ☸️ 원격 그라운딩 주소(P13) — 없으면 호스트 env ELANOUS_GROUNDING_URL → 설정 pod.groundingUrl. 셋 다 없으면 토큰을 안 만든다. */
117
+ groundingUrl?: string;
118
+ /** 시험 심 — 토큰 발급·회수. GitHub credential 토큰에는 호스트가 검증한 저장소를 서명한다. */
119
+ mintGrounding?: (claims: { runId: string; job: string; ttlMs: number; scope?: GroundingTokenScope; repository?: string }) => Promise<{ token: string; exp: number }>;
120
+ revokeGrounding?: (runId: string) => void;
121
+ /** Test seam for the host's repository-scoped GitHub App installation token. */
122
+ githubInstallation?: (repository: string) => { token: string; expires_at?: string; expiresAt?: string | number } | null;
123
+ /** GitHub installation repository lookup; required to prove the scoped token matches owner/name. */
124
+ githubRepositories?: (token: string) => Promise<readonly string[] | null>;
125
+ now?: () => number;
60
126
  sleep?: (ms: number) => Promise<void>;
61
- credentials?: () => { elanousAuth: string; codexAuth: string; ghToken: string };
127
+ credentials?: (account: string) => { elanousAuth: string; codexAuth: string; ghToken: string };
128
+ grokCredentials?: () => { grokAuth?: string; grokApiKey?: string; ghToken: string };
62
129
  /** 호스트 키 캐시(`~/.cache/<소문자 이름>`)에서 키를 읽는다(시험 주입) — env 에 없을 때. */
63
130
  readKeyCache?: (name: string) => string | undefined;
64
131
  env?: NodeJS.ProcessEnv;
132
+ /** Detail switch file (test injection; defaults to the host's local/production selection). */
133
+ liveDetailFile?: string;
65
134
  }
66
135
 
67
136
  export function defaultKubectl(args: readonly string[], input?: string): { status: number | null; stdout: string; stderr: string } {
@@ -83,31 +152,137 @@ export function podJobName(spaceId: string): string {
83
152
  export function hostCredentials(account: string, storePath: string = authStorePath(), ghToken: () => string = defaultGhToken): { elanousAuth: string; codexAuth: string; ghToken: string } {
84
153
  // 경로는 해석기로(격리 게이트 · 2026-09-25) — 손으로 `~/.elanous/auth.json` 을 조립하면 test↔prod 격리가 새는 자리가 된다.
85
154
  const store = JSON.parse(readFileSync(storePath, 'utf8')) as { version?: number; providers: Record<string, Record<string, unknown>> };
86
- const entry = store.providers[`openai-codex:${account}`] as { tokens: Record<string, unknown>; lastRefresh?: string; authMode?: string; codexHome?: string } | undefined;
87
- if (!entry?.codexHome) throw new Error(`openai-codex:${account} 계정이 없거나 codexHome 을 모른다`);
88
- const codex = JSON.parse(readFileSync(join(entry.codexHome, 'auth.json'), 'utf8')) as { tokens: Record<string, unknown> };
155
+ // ⭐ 기본 계정은 정본 키가 `openai-codex`(이름 없음)이고 홈을 안 적는다 — 기본 위치로 푼다(회전 후보와 같은 해석).
156
+ const storeKey = account === 'default' ? 'openai-codex' : `openai-codex:${account}`;
157
+ const entry = store.providers[storeKey] as { tokens: Record<string, unknown>; lastRefresh?: string; authMode?: string; codexHome?: string } | undefined;
158
+ const codexHome = entry?.codexHome ?? (entry && account === 'default' ? resolveCodexAccount(envLiteral({ CODEX_HOME: process.env.CODEX_HOME })).home : undefined);
159
+ if (!entry || !codexHome) throw new Error(`${storeKey} 계정이 없거나 codexHome 을 모른다`);
160
+ const codex = JSON.parse(readFileSync(join(codexHome, 'auth.json'), 'utf8')) as { tokens: Record<string, unknown> };
89
161
  const access = String(codex.tokens.access_token ?? '');
90
162
  const exp = Number(JSON.parse(Buffer.from(access.split('.')[1] ?? '', 'base64url').toString('utf8') || '{}').exp ?? 0);
91
163
  if (exp * 1000 - Date.now() < 3 * 3600_000) throw new Error(`openai-codex:${account} access token 이 3시간 안에 만료 — 호스트에서 먼저 갱신(컨테이너는 갱신 못 한다)`);
92
164
  const elanousAuth = JSON.stringify({
93
165
  version: store.version ?? 1,
94
- providers: { 'openai-codex': { tokens: { ...entry.tokens, refreshToken: '' }, lastRefresh: entry.lastRefresh, authMode: entry.authMode, chatGPT: { accountId: codex.tokens.account_id } } },
166
+ // ⭐ P4(2026-09-26): elanous 사본도 «방금 검사한» codex 홈의 토큰으로 싣는다 — 두 저장소는 따로 갱신된다.
167
+ // 🩸 실측: third 는 elanous 쪽 토큰이 4시간 전에 만료됐고 codex 홈 쪽은 235시간 남아 있었다 → Pod 가 갱신 400 으로 첫 호출 전에 죽었다(할당량 소진으로 오분류).
168
+ providers: { 'openai-codex': { tokens: { ...entry.tokens, accessToken: access, expiresAt: exp * 1000, refreshToken: '' }, lastRefresh: entry.lastRefresh, authMode: entry.authMode, chatGPT: { accountId: codex.tokens.account_id } } },
95
169
  });
96
170
  const codexAuth = JSON.stringify({ ...codex, tokens: { ...codex.tokens, refresh_token: '' } });
97
171
  return { elanousAuth, codexAuth, ghToken: ghToken() };
98
172
  }
99
173
 
174
+ /** resolveGrokCredential 과 동일한 출처를 읽되 Pod 로는 refresh 없는 access 사본만 보낸다. */
175
+ export function hostGrokCredentials(opts: {
176
+ home?: string; env?: NodeJS.ProcessEnv; apiKeyOptIn?: boolean; ghToken?: () => string;
177
+ } = {}): { grokAuth?: string; grokApiKey?: string; ghToken: string } {
178
+ const credential = resolveGrokCredential({ home: opts.home, env: opts.env });
179
+ if (credential?.kind === 'subscription') {
180
+ const scopes = JSON.parse(readFileSync(grokAuthFilePath(opts.home), 'utf8')) as Record<string, unknown>;
181
+ const redacted: Record<string, { key: string; expires_at?: string; user_id?: string }> = {};
182
+ for (const [name, value] of Object.entries(scopes)) {
183
+ if (!value || typeof value !== 'object' || Array.isArray(value)) continue;
184
+ const scope = value as Record<string, unknown>;
185
+ if (scope.key !== credential.token) continue;
186
+ redacted[name] = {
187
+ key: scope.key,
188
+ ...(typeof scope.expires_at === 'string' ? { expires_at: scope.expires_at } : {}),
189
+ ...(typeof scope.user_id === 'string' ? { user_id: scope.user_id } : {}),
190
+ };
191
+ }
192
+ if (!Object.keys(redacted).length) throw new Error('grok: 구독 access 토큰 없음');
193
+ return { grokAuth: JSON.stringify(redacted), ghToken: (opts.ghToken ?? defaultGhToken)() };
194
+ }
195
+ if (credential?.kind === 'api_key' && opts.apiKeyOptIn === true) {
196
+ return { grokApiKey: credential.token, ghToken: (opts.ghToken ?? defaultGhToken)() };
197
+ }
198
+ throw new Error('grok: 구독 자격 없음 · API 키 opt-in 꺼짐 또는 키 없음');
199
+ }
200
+
100
201
  /** 키 캐시 관례: `~/.cache/<env 이름 소문자>`(예: OPENROUTER_API_KEY → ~/.cache/openrouter_api_key · src/config.ts 와 같다). */
101
202
  function defaultReadKeyCache(name: string): string | undefined {
102
203
  try { const v = readFileSync(join(homedir(), '.cache', name.toLowerCase()), 'utf8').trim(); return v || undefined; } catch { return undefined; }
103
204
  }
104
205
 
105
- function defaultGhToken(): string {
206
+ export function defaultGhToken(): string {
106
207
  const r = spawnSync('gh', ['auth', 'token'], { encoding: 'utf8' });
107
208
  if (r.status !== 0 || !r.stdout.trim()) throw new Error('gh auth token 실패 — 호스트에서 gh auth login');
108
209
  return r.stdout.trim();
109
210
  }
110
211
 
212
+ /** Bind credential issuance to the repository the host actually supplied to the Pod. */
213
+ function githubRepositoryFromUrl(repoUrl: string): string | null {
214
+ const match = /^(?:https:\/\/github\.com\/|git@github\.com:)([a-zA-Z0-9_.-]+)\/([a-zA-Z0-9_.-]+?)(?:\.git)?\/?$/.exec(repoUrl);
215
+ const owner = match?.[1];
216
+ const name = match?.[2];
217
+ return owner && name && ![owner, name].some((part) => part === '.' || part === '..') ? `${owner}/${name}` : null;
218
+ }
219
+
220
+ /** Job 스크립트의 salvage 단계 — 자식 rc 가 0 이 아닐 때만, 변경·미푸시 커밋이 있는 격리 워크트리를 `salvage/<job>/<worktree>` 로 push.
221
+ * main·self-impl/ 로는 절대 안 민다. 실패해도 제어 흐름을 바꾸지 않는다(호출자는 이 뒤에 `exit $rc`). */
222
+ export function podSalvageScript(): string {
223
+ return [
224
+ 'if [ "${rc:-0}" -ne 0 ]; then',
225
+ ' job_name="${ELANOUS_POD_NAME:-unknown-job}"',
226
+ ' salvage_any=0',
227
+ ' while IFS= read -r wt; do',
228
+ ' [ -n "$wt" ] || continue',
229
+ ' case "$wt" in',
230
+ ' /*) ;;',
231
+ " *) printf 'ELANOUS_POD_SALVAGE_NONE bad-worktree-path\\n'; continue ;;",
232
+ ' esac',
233
+ ' wt_name=$(basename -- "$wt")',
234
+ ' case "$wt_name" in',
235
+ " ''|.*|*/*|*'..'*) printf 'ELANOUS_POD_SALVAGE_NONE bad-worktree-name\\n'; continue ;;",
236
+ ' esac',
237
+ ' branch="salvage/${job_name}/${wt_name}"',
238
+ ' case "$branch" in',
239
+ ' salvage/*) ;;',
240
+ " *) printf 'ELANOUS_POD_SALVAGE_NONE refused-prefix\\n'; continue ;;",
241
+ ' esac',
242
+ ' case "$branch" in',
243
+ " main|self-impl|self-impl/*) printf 'ELANOUS_POD_SALVAGE_NONE refused-prefix\\n'; continue ;;",
244
+ ' esac',
245
+ ' (',
246
+ ' set +e',
247
+ ' cd -- "$wt" || { printf \'ELANOUS_POD_SALVAGE_NONE cd-failed\\n\'; exit 0; }',
248
+ ' git rev-parse --is-inside-work-tree >/dev/null 2>&1 || { printf \'ELANOUS_POD_SALVAGE_NONE not-a-worktree\\n\'; exit 0; }',
249
+ ' dirty=0',
250
+ ' git diff --quiet || dirty=1',
251
+ ' git diff --cached --quiet || dirty=1',
252
+ ' if [ -n "$(git ls-files --others --exclude-standard)" ]; then dirty=1; fi',
253
+ ' ahead=0',
254
+ ' if git rev-parse --verify HEAD >/dev/null 2>&1; then',
255
+ " if git rev-parse --abbrev-ref --symbolic-full-name '@{u}' >/dev/null 2>&1; then",
256
+ " if [ \"$(git rev-list --count '@{u}..HEAD' 2>/dev/null || true)\" != 0 ]; then ahead=1; fi",
257
+ ' else',
258
+ ' if git rev-parse --verify origin/HEAD >/dev/null 2>&1; then',
259
+ ' base=$(git rev-parse origin/HEAD)',
260
+ ' elif git rev-parse --verify origin/main >/dev/null 2>&1; then',
261
+ ' base=$(git rev-parse origin/main)',
262
+ ' else',
263
+ ' base=$(git rev-list --max-parents=0 HEAD 2>/dev/null | head -n 1)',
264
+ ' fi',
265
+ ' if [ -n "$base" ] && [ "$(git rev-list --count "${base}..HEAD" 2>/dev/null || true)" != 0 ]; then ahead=1; fi',
266
+ ' fi',
267
+ ' fi',
268
+ ' if [ "$dirty" -eq 0 ] && [ "$ahead" -eq 0 ]; then',
269
+ " printf 'ELANOUS_POD_SALVAGE_NONE clean\\n'",
270
+ ' exit 0',
271
+ ' fi',
272
+ ' if [ "$dirty" -eq 1 ]; then',
273
+ " git add -A -- . || { printf 'ELANOUS_POD_SALVAGE_NONE add-failed\\n'; exit 0; }",
274
+ ' git commit -m "salvage: ${job_name} rc=${rc}" || { printf \'ELANOUS_POD_SALVAGE_NONE commit-failed\\n\'; exit 0; }',
275
+ ' fi',
276
+ ' git push origin "HEAD:refs/heads/${branch}" || { printf \'ELANOUS_POD_SALVAGE_NONE push-failed\\n\'; exit 0; }',
277
+ ' commit=$(git rev-parse HEAD) || { printf \'ELANOUS_POD_SALVAGE_NONE rev-parse-failed\\n\'; exit 0; }',
278
+ " printf 'ELANOUS_POD_SALVAGE %s %s\\n' \"$branch\" \"$commit\"",
279
+ ' )',
280
+ ' salvage_any=1',
281
+ " done < <(git worktree list --porcelain | awk '/^worktree / { sub(/^worktree /, \"\"); print }')",
282
+ " if [ \"$salvage_any\" -eq 0 ]; then printf 'ELANOUS_POD_SALVAGE_NONE no-worktree\\n'; fi",
283
+ 'fi',
284
+ ].join('\n');
285
+ }
111
286
  const GATE = `ok=0
112
287
  for i in $(seq 1 60); do
113
288
  if curl -s -m 1 -o /dev/null http://host.orb.internal:31415/health || curl -s -m 1 -o /dev/null http://core.elanous-prod:8080/; then ok=0; else ok=$((ok+1)); fi
@@ -116,48 +291,199 @@ for i in $(seq 1 60); do
116
291
  done
117
292
  echo "[gate] ISOLATION NOT ENFORCED within 30s"; exit 1`;
118
293
 
294
+ /** Only the allocated account is active; each candidate has its own read-only Secret files and writable Codex home. */
295
+ /** The host's codex quota policy, exported into the Pod so the child rotates onto credits like the host does. */
296
+ function podQuotaPolicyExport(policy: string = codexQuotaPolicyFromConfig()): string {
297
+ return `export ELANOUS_CODEX_QUOTA_POLICY='${policy.replace(/[^a-z-]/g, '')}'`;
298
+ }
299
+
300
+ function podCodexAccountScript(accounts: readonly string[]): string {
301
+ if (!accounts.length || new Set(accounts).size !== accounts.length || accounts.some((name) => !isValidAccountName(name))) throw new Error('pod: 유효하고 서로 다른 codex 계정이 필요하다');
302
+ return [
303
+ 'mkdir -p "$HOME/.elanous" "$HOME/.codex"',
304
+ // Names travel as JSON data, never as shell syntax. Reconstruct the account-store keys that elanous resolves at runtime.
305
+ `POD_CODEX_ACCOUNTS='${JSON.stringify(accounts)}' bun -e 'const fs=require("node:fs"),path=require("node:path");const names=JSON.parse(process.env.POD_CODEX_ACCOUNTS),home=process.env.HOME;const providers={};let version;for(let i=0;i<names.length;i++){const payload=JSON.parse(fs.readFileSync("/creds/codex-"+i+".json","utf8"));const row=JSON.parse(payload.elanousAuth);if(i===0)version=row.version;const entry=row.providers["openai-codex"];if(!entry)throw Error("missing codex account credential "+i);const codexHome=path.join(home,".elanous/codex-accounts",String(i));fs.mkdirSync(codexHome,{recursive:true});fs.writeFileSync(path.join(codexHome,"auth.json"),payload.codexAuth,{mode:0o600});fs.chmodSync(path.join(codexHome,"auth.json"),0o600);providers[names[i]==="default"?"openai-codex":"openai-codex:"+names[i]]={...entry,codexHome}}if(!providers["openai-codex"])providers["openai-codex"]=providers["openai-codex:"+names[0]];fs.writeFileSync(path.join(home,".elanous/auth.json"),JSON.stringify({version,providers}),{mode:0o600});fs.chmodSync(path.join(home,".elanous/auth.json"),0o600);fs.copyFileSync(path.join(home,".elanous/codex-accounts/0/auth.json"),path.join(home,".codex/auth.json"));fs.chmodSync(path.join(home,".codex/auth.json"),0o600)' || exit 5`,
306
+ `export ELANOUS_CODEX_ACCOUNT='${accounts[0]!.replace(/'/g, `'\\''`)}'`,
307
+ 'export ELANOUS_CODEX_ACCOUNT_HOME="$HOME/.elanous/codex-accounts/0" CODEX_HOME="$HOME/.elanous/codex-accounts/0"',
308
+ podQuotaPolicyExport(),
309
+ ].join('\n');
310
+ }
311
+
119
312
  /** Job 매니페스트(JSON) — docker/harness/job.yaml 과 같은 격리(관문 ⊕ 읽기 전용 Secret ⊕ 한도). */
120
- export function podJobManifest(o: { name: string; namespace: string; image: string; repoUrl: string; args: readonly string[]; passEnv: readonly string[]; deadlineSeconds: number; runId?: string; armEnv?: Readonly<Record<string, string>>; hostId?: string; imageCommit?: string | null; skillEnvs?: readonly string[]; memoryLimit?: string }): Record<string, unknown> {
313
+ /** k8s 라벨 값 규칙(63자 이하 · 영숫자로 시작·끝 · 가운데 `-_.`) 밖은 정리한다. 비면 undefined. */
314
+ export function k8sLabelValue(value: string | undefined): string | undefined {
315
+ const cleaned = (value ?? '').replace(/[^A-Za-z0-9._-]+/g, '-').slice(0, 63).replace(/^[^A-Za-z0-9]+|[^A-Za-z0-9]+$/g, '');
316
+ return cleaned || undefined;
317
+ }
318
+
319
+ /** `elanous.run` = 사람이 보는 하니스 런(오케스트레이터 · `ELANOUS_RUN_ID`) — `harness stop <runId>` 와 오케스트레이터의 신호 정리(#21171)가
320
+ * 이 라벨로 Job 을 고른다. 자식 런 id 는 `elanous.child-run`. */
321
+ export function podRunLabels(o: { runId?: string; parentRunId?: string }): Record<string, string> {
322
+ const run = k8sLabelValue(o.parentRunId ?? o.runId);
323
+ const child = k8sLabelValue(o.runId);
324
+ return { ...(run ? { 'elanous.run': run } : {}), ...(child && child !== run ? { 'elanous.child-run': child } : {}) };
325
+ }
326
+
327
+ /** Pod 메모리 «등급» — 대표 09-27: PWA Pod 골이 16Gi 에서 OOM 으로 죽는다 ⇒ «high» 등급을 옵션으로 고른다.
328
+ * 등급 = `standard`(`ELANOUS_POD_MEMORY` 또는 16Gi) · `high`(`ELANOUS_POD_MEMORY_HIGH` 또는 32Gi · standard 보다 작아지지 않는다).
329
+ * 고르는 순서(앞이 이긴다): ① 발사 옵션 `--pod-memory <등급>`(→ `ELANOUS_POD_MEMORY_TIER`) ② 조각 문면 한 줄 `Pod 메모리: high|standard` ③ 조각의 `apps/pwa/` ④ 부모 골 본문의 같은 두 규칙 ⑤ standard.
330
+ * node-b 노드 할당 가능 약 251Gi. */
331
+ export const POD_MEMORY_DEFAULT = '16Gi';
332
+ export const POD_MEMORY_HIGH_DEFAULT = '32Gi';
333
+ export const POD_MEMORY_TIERS = ['standard', 'high'] as const;
334
+ export type PodMemoryTier = (typeof POD_MEMORY_TIERS)[number];
335
+ export type PodMemorySource = 'option' | 'goal-line' | 'pwa-auto' | 'default' | 'parent-goal-line' | 'parent-pwa-auto';
336
+ const PWA_PATH = /(?:^|[\s`'"(,·])apps\/pwa\//m;
337
+ const GOAL_LINE = /^\s*(?:Pod 메모리|pod-memory)\s*:\s*(standard|high)\s*$/im;
338
+
339
+ export function goalTouchesPwa(feature: string): boolean {
340
+ return PWA_PATH.test(feature);
341
+ }
342
+
343
+ export function parsePodMemoryTier(v: string | undefined): PodMemoryTier | null {
344
+ const t = v?.trim().toLowerCase();
345
+ return t && (POD_MEMORY_TIERS as readonly string[]).includes(t) ? (t as PodMemoryTier) : null;
346
+ }
347
+
348
+ function memoryGi(v: string): number | null {
349
+ const m = /^(\d+(?:\.\d+)?)(Gi|Mi)$/.exec(v);
350
+ if (!m) return null;
351
+ return m[2] === 'Gi' ? Number(m[1]) : Number(m[1]) / 1024;
352
+ }
353
+
354
+ export function podMemoryLimitFor(feature: string, env: NodeJS.ProcessEnv | Readonly<Record<string, string | undefined>>, parentGoal?: string): { limit: string; tier: PodMemoryTier; source: PodMemorySource } {
355
+ const option = parsePodMemoryTier(env.ELANOUS_POD_MEMORY_TIER);
356
+ const line = parsePodMemoryTier(GOAL_LINE.exec(feature)?.[1]);
357
+ const parentLine = !option && !line && !goalTouchesPwa(feature) ? parsePodMemoryTier(GOAL_LINE.exec(parentGoal ?? '')?.[1]) : null;
358
+ const [tier, source]: [PodMemoryTier, PodMemorySource] = option ? [option, 'option']
359
+ : line ? [line, 'goal-line']
360
+ : goalTouchesPwa(feature) ? ['high', 'pwa-auto']
361
+ : parentLine ? [parentLine, 'parent-goal-line']
362
+ : parentGoal && goalTouchesPwa(parentGoal) ? ['high', 'parent-pwa-auto']
363
+ : ['standard', 'default'];
364
+ const base = env.ELANOUS_POD_MEMORY?.trim() || POD_MEMORY_DEFAULT;
365
+ if (tier === 'standard') return { limit: base, tier, source };
366
+ const high = env.ELANOUS_POD_MEMORY_HIGH?.trim() || POD_MEMORY_HIGH_DEFAULT;
367
+ const a = memoryGi(base), b = memoryGi(high);
368
+ return { limit: a !== null && b !== null && a > b ? base : high, tier, source };
369
+ }
370
+
371
+ export function podJobManifest(o: { name: string; namespace: string; image: string; /** 레지스트리 이미지면 IfNotPresent(노드가 pull) · 반입 이미지면 Never. */ imagePullPolicy?: 'Never' | 'IfNotPresent'; repoUrl: string; source?: PodSource; hostMirror?: string; args: readonly string[]; passEnv: readonly string[]; deadlineSeconds: number; runId?: string; parentRunId?: string; armEnv?: Readonly<Record<string, string>>; hostId?: string; imageCommit?: string | null; skillEnvs?: readonly string[]; memoryLimit?: string; goalDoc?: string; grokCredential?: 'subscription' | 'api_key'; codexAccounts?: readonly string[]; appCredential?: boolean }): Record<string, unknown> {
121
372
  const quoted = o.args.map((a) => `'${a.replace(/'/g, `'\\''`)}'`).join(' ');
373
+ const goalPath = o.goalDoc ? `'${(o.goalDoc.startsWith('-') ? `./${o.goalDoc}` : o.goalDoc).replace(/'/g, `'\\''`)}'` : undefined;
374
+ const askBaseIndex = o.goalDoc ? o.args.indexOf('--base') : -1;
375
+ const quotedAsk = (askBaseIndex >= 0 && o.args[askBaseIndex + 1]
376
+ ? ` '--base' '${o.args[askBaseIndex + 1]!.replace(/'/g, `'\\''`)}'`
377
+ : '') + (o.goalDoc && o.args.includes('--merge-by-host') ? ' --merge-by-host' : o.goalDoc && o.args.includes('--open-pr') && !o.args.includes('--auto-merge') ? ' --no-auto-merge' : '')
378
+ + (o.goalDoc && o.args.includes('--no-supervise') ? ' --no-supervise' : '');
122
379
  const script = [
123
380
  'set -u',
124
- 'mkdir -p ~/.elanous ~/.codex && cp /creds/elanous-auth.json ~/.elanous/auth.json && cp /creds/codex-auth.json ~/.codex/auth.json && chmod 600 ~/.elanous/auth.json ~/.codex/auth.json',
125
- 'export GH_TOKEN="$(cat /creds/gh-token)"',
381
+ o.grokCredential === 'subscription'
382
+ ? 'mkdir -p ~/.grok && install -m 600 /creds/grok-auth.json ~/.grok/auth.json && export ELANOUS_LLM_PROVIDER=grok'
383
+ : o.grokCredential === 'api_key'
384
+ ? 'mkdir -p ~/.grok && install -m 600 /creds/grok-api-key ~/.grok/api-key && export XAI_API_KEY="$(cat ~/.grok/api-key)" && export ELANOUS_LLM_PROVIDER=grok'
385
+ : o.codexAccounts ? podCodexAccountScript(o.codexAccounts) : `mkdir -p ~/.elanous ~/.codex && cp /creds/elanous-auth.json ~/.elanous/auth.json && cp /creds/codex-auth.json ~/.codex/auth.json && chmod 600 ~/.elanous/auth.json ~/.codex/auth.json\n${podQuotaPolicyExport()}`,
386
+ o.appCredential ? `unset GH_TOKEN GITHUB_TOKEN; export GH_CONFIG_DIR="${APP_GH_CONFIG_PATH}"; mkdir -p "$GH_CONFIG_DIR" && chmod 700 "$GH_CONFIG_DIR" && { gh auth login --with-token < /creds/gh-token >/dev/null 2>&1 || ${APP_GH_AUTH_COMMAND} < /creds/gh-token; } && chmod 600 "$GH_CONFIG_DIR/hosts.yml" && chmod 700 "$GH_CONFIG_DIR" || exit 7` : 'export GH_TOKEN="$(cat /creds/gh-token)"',
126
387
  // 🔑 스킬 키(.env) — 이미지엔 없다. 이 런의 Secret 에서 각 스킬 폴더로 0600 복사(값은 로그에 안 나온다).
127
388
  ...(o.skillEnvs?.length ? [`for n in ${o.skillEnvs.join(' ')}; do [ -d ~/.claude/skills/$n ] && install -m 600 /creds/skillenv-$n ~/.claude/skills/$n/.env; done; echo "[pod] skill env: ${o.skillEnvs.join(',')}"`] : []),
128
389
  'git config --global user.name "elanous pod child" && git config --global user.email "noreply@anthropic.com" && gh auth setup-git',
129
390
  'curl -s -m 3 -o /dev/null http://host.orb.internal:31415/health && { echo "[pod] ISOLATION FAIL"; exit 3; }',
130
- `git clone -q --depth 50 '${o.repoUrl}' repo && cd repo || exit 5`,
131
- // self implement 의 마지막 줄 JSON 이 «맨 끝»이어야 한다(parseSelfImplementJson) — rollup 은 그 앞에.
132
- `elanous self implement "$(cat /creds/feature)" --json ${quoted} > /tmp/si.out 2>&1; rc=$?`,
391
+ podSourceScript(o.source ?? { kind: 'default' }, o.repoUrl),
392
+ ...(goalPath ? [
393
+ `mkdir -p -- "$(dirname -- ${goalPath})" && cp -- /creds/goal-doc ${goalPath} || exit 6`,
394
+ ] : []),
395
+ // Only fixed command shapes leave the Pod; arbitrary argv (including shell -c text) is never emitted.
396
+ `(while :; do { mem=$(if [ -r /sys/fs/cgroup/memory.current ]; then cat /sys/fs/cgroup/memory.current; elif [ -r /sys/fs/cgroup/memory/memory.usage_in_bytes ]; then cat /sys/fs/cgroup/memory/memory.usage_in_bytes; else printf -- -; fi); top=$(ps -eo rss=,comm=,args= --sort=-rss | head -5 | awk 'function encode(s) { gsub(/%/, "%25", s); gsub(/:/, "%3A", s); gsub(/ /, "%20", s); gsub(/\\t/, "%09", s); return s } { rss=$1; name=$2; sub(/^[[:space:]]*[0-9]+[[:space:]]+[^[:space:]]+[[:space:]]*/, ""); n=split($0, a, /[[:space:]]+/); cmd="<redacted>"; if (name=="sleep" && a[2]=="30") cmd="sleep 30" (n>2 ? " <redacted>" : ""); else if (name=="bun") { cmd="bun <redacted>"; if (a[2]=="test") cmd="bun test <redacted>"; else if (a[2]=="run") cmd="bun run <redacted>"; else if ((a[2]=="x" || a[2]=="exec") && a[3]=="tsc") cmd="bun x tsc <redacted>" } else if (name=="tsc") cmd="tsc <redacted>"; else if (name=="elanous") { cmd="elanous <redacted>"; if (a[2]=="self") cmd="elanous self <redacted>"; else if (a[2]=="harness") cmd="elanous harness <redacted>" } else if (name=="node") cmd="node <redacted>"; if (name=="sleep" && cmd=="<redacted>") cmd="sleep <redacted>"; else if (cmd=="<redacted>" && name!="bash" && name!="sh") name="other"; printf " %s:%s:%s", rss, encode(name), encode(substr(cmd,1,120)) }'); printf "ELANOUS_MEM %s %s%s\\n" "$(date +%s)" "$mem" "$top"; } || true; sleep 15 || break; done) & mem_sampler_pid=$!`,
397
+ // 마지막 줄 JSON 이 «맨 끝»이어야 한다(parseSelfImplementJson) — rollup 은 그 앞에.
398
+ goalPath
399
+ ? `elanous harness ask ${goalPath} --json${quotedAsk} > /tmp/si.out 2>&1; rc=$?; bun -e 'const fs=require("fs");const p="/tmp/si.out";const s=fs.readFileSync(p,"utf8");const line=s.trimEnd().split("\\n").at(-1);try{const o=JSON.parse(line);if(o.kind==="self"&&o.result&&typeof o.result==="object")fs.appendFileSync(p,"\\n"+JSON.stringify({...o.result,...(typeof o.ok==="boolean"?{ok:o.ok}:{})})+"\\n")}catch{}'`
400
+ : `elanous self implement "$(cat /creds/feature)" --json ${quoted} > /tmp/si.out 2>&1; rc=$?`,
133
401
  'cat /tmp/si.out',
134
402
  '[ -f scripts/usage-rollup.ts ] && bun scripts/usage-rollup.ts --since 12h || echo "ELANOUS_USAGE_ROLLUP {\"measured\":false,\"reason\":\"no rollup script\"}"',
403
+ // A long run's log export can pass the 5MB artifact limit; skipping it whole left no window into why a
404
+ // 2h+ child failed (🅢 09-27). Keep the newest lines under the limit instead, and say so.
405
+ `if mkdir -p "$HOME/outbox/pod-logs" && elanous logs --all --include-test --since 12h --limit 20000 --json > "$HOME/outbox/pod-logs/logs.jsonl"; then
406
+ logs_size=$(( $(wc -c < "$HOME/outbox/pod-logs/logs.jsonl") ))
407
+ if [ "$logs_size" -gt ${POD_LOGS_KEEP_BYTES} ]; then
408
+ tail -c ${POD_LOGS_KEEP_BYTES} "$HOME/outbox/pod-logs/logs.jsonl" | tail -n +2 > "$HOME/outbox/pod-logs/logs.jsonl.tail" && mv -f "$HOME/outbox/pod-logs/logs.jsonl.tail" "$HOME/outbox/pod-logs/logs.jsonl"
409
+ printf 'ELANOUS_POD_LOGS_TRUNCATED %s %s\\n' "$logs_size" "$(( $(wc -c < "$HOME/outbox/pod-logs/logs.jsonl") ))"
410
+ fi
411
+ else
412
+ logs_rc=$?
413
+ rm -f "$HOME/outbox/pod-logs/logs.jsonl"
414
+ printf 'ELANOUS_POD_LOGS_UNAVAILABLE export-exit-%s\\n' "$logs_rc"
415
+ fi`,
416
+ `set -o pipefail
417
+ artifact_bytes=0
418
+ if [ -d "$HOME/outbox" ]; then
419
+ while IFS= read -r -d '' file; do
420
+ [ -f "$file" ] && [ ! -L "$file" ] || continue
421
+ relative=\${file#"$HOME/outbox/"}
422
+ size=$(( $(wc -c < "$file") ))
423
+ if [ "$size" -gt 5242880 ] || [ $((artifact_bytes + size)) -gt 20971520 ]; then
424
+ printf 'ELANOUS_POD_ARTIFACT_SKIPPED %s %s\\n' "$relative" "$size"
425
+ continue
426
+ fi
427
+ path_token=$(printf '%s' "$relative" | base64 | tr -d '\\n' | tr '+/' '-_' | tr -d '=')
428
+ if encoded=$(gzip -c "$file" | base64 | tr -d '\\n'); then
429
+ artifact_bytes=$((artifact_bytes + size))
430
+ total=$(( (\${#encoded} + 7999) / 8000 ))
431
+ for ((n=1; n<=total; n++)); do
432
+ chunk=\${encoded:$(( (n-1)*8000 )):8000}
433
+ printf 'ELANOUS_POD_ARTIFACT %s %s/%s %s\\n' "$path_token" "$n" "$total" "$chunk"
434
+ done
435
+ else
436
+ printf 'ELANOUS_POD_ARTIFACT_SKIPPED %s %s\\n' "$relative" "$size"
437
+ fi
438
+ done < <(find "$HOME/outbox" -type f -print0)
439
+ fi
440
+ found=0
441
+ for ledger in "\${ELANOUS_STATE_DIR:-$HOME/.elanous}"/run-ledger/*.jsonl; do
442
+ [ -f "$ledger" ] || continue
443
+ found=1
444
+ run_id=\${ledger##*/}; run_id=\${run_id%.jsonl}
445
+ if encoded=$(gzip -c "$ledger" | base64 | tr -d '\\n'); then
446
+ total=$(( (\${#encoded} + 7999) / 8000 ))
447
+ for ((n=1; n<=total; n++)); do
448
+ chunk=\${encoded:$(( (n-1)*8000 )):8000}
449
+ printf 'ELANOUS_RUN_LEDGER %s %s/%s %s\\n' "$run_id" "$n" "$total" "$chunk"
450
+ done
451
+ else
452
+ echo "[pod] ledger transfer failed: $run_id" >&2
453
+ fi
454
+ done
455
+ if [ "$found" -eq 0 ]; then echo ELANOUS_RUN_LEDGER_NONE; fi`,
456
+ 'kill "$mem_sampler_pid" 2>/dev/null || true',
457
+ 'wait "$mem_sampler_pid" 2>/dev/null || true',
135
458
  'tail -n 1 /tmp/si.out',
459
+ podSalvageScript(),
136
460
  'exit $rc',
137
461
  ].join('\n');
138
462
  return {
139
463
  apiVersion: 'batch/v1',
140
464
  kind: 'Job',
141
- metadata: { name: o.name, namespace: o.namespace, labels: { 'elanous.substrate': 'pod', 'elanous.job': o.name } },
465
+ metadata: { name: o.name, namespace: o.namespace, labels: { 'elanous.substrate': 'pod', 'elanous.job': o.name, ...podRunLabels(o) } },
142
466
  spec: {
143
467
  backoffLimit: 0,
144
468
  ttlSecondsAfterFinished: 7200,
145
469
  activeDeadlineSeconds: o.deadlineSeconds,
146
470
  template: {
147
471
  // ⭐ Pod 라벨 — local 팔만 `elanous.egress/local-llm` 을 단다. docker/h1/policy-local-llm.yaml 이 그 라벨에만 호스트 LLM 포트 «하나»를 연다.
148
- metadata: { labels: { 'elanous.job': o.name, ...(o.armEnv?.ELANOUS_LLM_PROVIDER === 'local' ? { 'elanous.egress/local-llm': 'true' } : {}) } },
472
+ metadata: { labels: { 'elanous.job': o.name, ...podRunLabels(o), ...(o.armEnv?.ELANOUS_LLM_PROVIDER === 'local' ? { 'elanous.egress/local-llm': 'true' } : {}) } },
149
473
  spec: {
150
474
  restartPolicy: 'Never',
151
475
  securityContext: { runAsUser: 1000, fsGroup: 1000 },
152
- initContainers: [{ name: 'isolation-gate', image: o.image, imagePullPolicy: 'Never', command: ['bash', '-c'], args: [GATE] }],
476
+ initContainers: [{ name: 'isolation-gate', image: o.image, imagePullPolicy: o.imagePullPolicy ?? 'Never', command: ['bash', '-c'], args: [GATE] }],
153
477
  containers: [{
154
- name: 'child', image: o.image, imagePullPolicy: 'Never',
155
- // 📏 09-25: 6Gi 는 빠듯했다 — 자식이 6,127Mi 에 붙어 OOMKilled(137). 기본 12Gi · ELANOUS_POD_MEMORY 로 조정.
156
- resources: { limits: { memory: o.memoryLimit ?? '12Gi', cpu: '4' } },
478
+ name: 'child', image: o.image, imagePullPolicy: o.imagePullPolicy ?? 'Never',
479
+ // 📏 09-25: 6Gi 는 빠듯했다 — 자식이 6,127Mi 에 붙어 OOMKilled(137) → 12Gi.
480
+ // 📏 09-27: 12Gi 에서도 OOMKilled 셋(구현 노드 도중) — 같은 골을 16Gi 로 다시 쏘니 끝까지 갔다(#20930) → 기본 16Gi.
481
+ // 요청(4Gi · POD_CHILD_REQUESTS)과 따로라 자리 계산은 안 바뀐다. ELANOUS_POD_MEMORY 로 조정.
482
+ resources: { requests: { ...POD_CHILD_REQUESTS }, limits: { memory: o.memoryLimit ?? POD_MEMORY_DEFAULT, cpu: '4' } },
157
483
  command: ['bash', '-c'], args: [script],
158
484
  env: [
159
- // 토큰 관측(`llm.usage`)이 부모 런에 묶이게 — debug.log 는 ELANOUS_RUN_ID 를 data.runId 로 붙인다.
160
485
  ...(o.runId ? [{ name: 'ELANOUS_RUN_ID', value: o.runId }] : []),
486
+ ...(o.parentRunId ? [{ name: 'ELANOUS_PARENT_RUN_ID', value: o.parentRunId }] : []),
161
487
  { name: 'ELANOUS_SUBSTRATE', value: 'pod' },
162
488
  ...Object.entries(controlInboxEnv(POD_CONTROL_INBOX_DIR)).map(([name, value]) => ({ name, value })),
163
489
  // ⭐ 런 출처(🅣 RFC run-origin §A3 · #20457/#20468 칸 이름) — Pod 는 자기 elanous_id 를 쓰지 않고 «띄운 감독기»의 hostId 를 물려받는다.
@@ -166,36 +492,86 @@ export function podJobManifest(o: { name: string; namespace: string; image: stri
166
492
  { name: 'ELANOUS_POD_NAMESPACE', valueFrom: { fieldRef: { fieldPath: 'metadata.namespace' } } },
167
493
  ...(o.hostId ? [{ name: 'ELANOUS_HOST_ID', value: o.hostId }] : []),
168
494
  ...(o.imageCommit ? [{ name: 'ELANOUS_IMAGE_COMMIT', value: o.imageCommit }] : []),
495
+ // ⭐ 런 계약(graph-run-contract.ts) — 이 칸이 «실제로» Pod 이고, 부모가 정한 계약도 Pod 다. 안의 그래프가 노드마다 대조한다.
496
+ { name: ACTUAL_SUBSTRATE_ENV, value: 'pod' }, { name: RUN_CONTRACT_ENV, value: carryRunContract({ substrate: 'pod' }) },
169
497
  ...Object.entries(o.armEnv ?? {}).map(([name, value]) => ({ name, value })),
170
498
  ...o.passEnv.map((key) => ({ name: key, valueFrom: { secretKeyRef: { name: `${o.name}-creds`, key: `env-${key}` } } })),
171
499
  ],
172
- volumeMounts: [{ name: 'creds', mountPath: '/creds', readOnly: true }],
500
+ volumeMounts: [{ name: 'creds', mountPath: '/creds', readOnly: true }, ...(o.hostMirror ? [{ name: 'host-mirror', mountPath: '/host-mirror', readOnly: true }] : [])],
173
501
  }],
174
- volumes: [{ name: 'creds', secret: { secretName: `${o.name}-creds`, defaultMode: 0o400 } }],
502
+ volumes: [{ name: 'creds', secret: { secretName: `${o.name}-creds`, defaultMode: 0o400 } }, ...(o.hostMirror ? [{ name: 'host-mirror', hostPath: { path: o.hostMirror, type: 'Directory' } }] : [])],
175
503
  },
176
504
  },
177
505
  },
178
506
  };
179
507
  }
180
508
 
509
+ async function waitForRunningPod(kubectl: Kubectl, namespace: string, job: string, sleep: (ms: number) => Promise<void>): Promise<string | null> {
510
+ for (let i = 0; i < 60; i++) {
511
+ const listed = kubectl(['-n', namespace, 'get', 'pods', '-l', `job-name=${job}`, '-o', 'jsonpath={range .items[*]}{.metadata.name} {.status.phase}{"\\n"}{end}']);
512
+ const running = listed.stdout.split('\n').map((line) => line.trim().split(/\s+/)).find((parts) => parts[1] === 'Running');
513
+ if (running?.[0]) return running[0];
514
+ await sleep(250);
515
+ }
516
+ return null;
517
+ }
518
+
519
+ function defaultGhComment(prNumber: number, body: string): void {
520
+ const r = spawnSync('gh', ['pr', 'comment', String(prNumber), '--body', body], { cwd: findGitDir(process.cwd())?.root ?? process.cwd(), encoding: 'utf8', timeout: 60_000 });
521
+ if (r.status !== 0) throw new Error((r.stderr || r.error?.message || `gh exited ${r.status}`).trim().slice(0, 200));
522
+ }
523
+
181
524
  export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplementJobSpawn {
182
525
  const baseKubectl = options.kubectl ?? defaultKubectl;
183
526
  const sleep = options.sleep ?? ((ms: number) => new Promise<void>((r) => setTimeout(r, ms)));
184
527
  const namespace = options.namespace ?? 'elanous-test';
185
528
  const image = options.image ?? 'elanous-harness:local';
186
- const repoUrl = options.repoUrl ?? 'https://github.com/ElanvitalAI/monad';
529
+ const repoUrl = options.repoUrl ?? 'https://github.com/ElanvitalAI/elanous';
530
+ const repository = githubRepositoryFromUrl(repoUrl);
187
531
  const env = options.env ?? process.env;
532
+ const hostMirror = (options.hostMirror ?? env.ELANOUS_POD_HOST_MIRROR ?? (options.configHostMirror ?? (options.kubectl ? () => undefined : configHostMirror))())?.trim() || undefined;
533
+ if (hostMirror && !isAbsolute(hostMirror)) throw new Error('pod.hostMirror must be an absolute directory path');
534
+ const hostSupervised = options.hostSupervised ?? true;
188
535
  return (input) => {
189
536
  const name = podJobName(input.spaceId);
537
+ const featureMemory = podMemoryLimitFor(input.feature, env);
538
+ let parentGoal: string | undefined;
539
+ let parentGoalUnreadable = false;
540
+ let inheritedFrom: string | undefined;
541
+ if (featureMemory.source === 'default' && env.ELANOUS_POD_GOAL_DOC) {
542
+ try {
543
+ const requestedPath = normalize(env.ELANOUS_POD_GOAL_DOC);
544
+ if (isAbsolute(requestedPath) || requestedPath === '..' || requestedPath.startsWith(`..${sep}`) || requestedPath === '.') throw new Error('invalid goal path');
545
+ const root = findGitDir(process.cwd())?.root;
546
+ if (!root) throw new Error('no repository');
547
+ const realRoot = realpathSync(root);
548
+ const goalFile = realpathSync(resolve(realRoot, requestedPath));
549
+ const withinRoot = relative(realRoot, goalFile);
550
+ if (withinRoot === '..' || withinRoot.startsWith(`..${sep}`) || isAbsolute(withinRoot)) throw new Error('outside repository');
551
+ parentGoal = readFileSync(goalFile, 'utf8').replace(/\r\n/g, '\n').split(/^## 실행 기록(?:\n|$)/m, 1)[0];
552
+ inheritedFrom = withinRoot;
553
+ } catch { parentGoalUnreadable = true; }
554
+ }
555
+ const { limit: memoryLimit, tier: memoryTier, source: memorySource } = parentGoal === undefined ? featureMemory : podMemoryLimitFor(input.feature, env, parentGoal);
556
+ debug.log('self-implement.pod', 'memory-limit', { spaceId: input.spaceId, memoryLimit, tier: memoryTier, source: memorySource,
557
+ ...(memorySource.startsWith('parent-') ? { inheritedFrom } : {}), ...(parentGoalUnreadable ? { parentGoal: 'unreadable' } : {}) });
558
+ const childRunId = mintRunId();
559
+ const parentRunId = env.ELANOUS_RUN_ID?.trim();
190
560
  const address = `self-impl:${input.spaceId}`;
561
+ const ledgerDir = runLedgerDir(env.ELANOUS_STATE_DIR);
191
562
  const args = [
192
563
  ...(input.base ? ['--base', input.base] : []),
193
- ...(input.autoMerge ? ['--auto-merge'] : []),
564
+ ...(input.autoMerge ? ['--merge-by-host'] : []),
194
565
  ...(input.autoReview ? ['--auto-review'] : []),
195
- ...(input.openPr ? ['--open-pr'] : []),
566
+ // ⭐ 계약이 미래 노드를 바꾼다(첫 규칙): Pod 는 끝나면 사라지므로 완료 하한 = PR. 작업 트리로만 끝나면 결과가 Pod 와 함께 없어진다.
567
+ // ⚠️ 임시 안전망(🅢 2026-09-26) — 🅣 의 «종결 하한 오버레이»(그래프 쪽)가 서면 이 줄을 지우고 그것으로 바꾼다(채널 합의).
568
+ ...(input.openPr || (!input.autoMerge && completionFloorFor({ substrate: 'pod' }) === 'pr') ? ['--open-pr'] : []),
196
569
  ...(input.draft === false ? ['--no-draft'] : []),
570
+ ...(hostSupervised ? ['--no-supervise'] : []),
197
571
  ...(options.extraArgs ?? []),
572
+ ...(options.provider === 'grok' ? ['--child-llm-provider', 'grok', '--child-llm-model', defaultGrokModel().id] : []),
198
573
  ];
574
+ debug.log('pod.self-implement', 'child-supervise', { hostSupervised, childSupervise: !hostSupervised });
199
575
  const done = (async (): Promise<SelfImplementJobDone> => {
200
576
  // ☸️ 풀이면 자리를 잡는다(우선순위 순 · 다 차면 기다린다) — 그 노드의 컨텍스트로 모든 호출을 묶는다.
201
577
  let member: PodPoolMember | null = null;
@@ -212,40 +588,315 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
212
588
  const context = member?.context ?? (currentContext?.status === 0 ? currentContext.stdout.trim() : '');
213
589
  const kubectl: Kubectl = (args, stdin) => baseKubectl(['--context', context, ...args], stdin);
214
590
  let recorded = false;
591
+ let groundingMinted: string | null = null;
215
592
  try {
216
593
  if (!context) return { exitCode: 1, output: '', error: { code: 'pod-context', message: 'Pod Job context를 확인할 수 없다' } };
217
594
  const cleanupSecret = () => { kubectl(['-n', namespace, 'delete', 'secret', `${name}-creds`, '--ignore-not-found']); };
595
+ const collectFullLogs = (unavailableEvent: string, replace?: ReadonlySet<string>, onEvent?: (event: string, data: Record<string, unknown>) => void, childId?: string): { status: 'ok' | 'unavailable' | 'incomplete'; childLedgerIncomplete: boolean; samples: ReturnType<typeof parseMemSamples> } => {
596
+ let full: ReturnType<Kubectl>;
597
+ try {
598
+ full = kubectl(['-n', namespace, 'logs', `job/${name}`, '-c', 'child']);
599
+ if (full.status !== 0) throw new Error(full.stderr || `kubectl logs exited ${full.status}`);
600
+ if (!full.stdout && unavailableEvent === 'failed-job-logs-unavailable') throw new Error('empty child logs');
601
+ } catch (error) {
602
+ debug.log('self-implement.pod', unavailableEvent, { job: name, reason: error instanceof Error ? error.message : String(error) });
603
+ return { status: 'unavailable', childLedgerIncomplete: false, samples: [] };
604
+ }
605
+ const via = /^ELANOUS_POD_SOURCE_VIA (mirror|github)\r?$/m.exec(full.stdout)?.[1];
606
+ if (via) debug.log('self-implement.pod', 'source-via', { job: name, via });
607
+ let ledgerIncomplete = false;
608
+ let childLedgerIncomplete = false;
609
+ let artifactIncomplete = false;
610
+ const record = (category: string, event: string, data: Record<string, unknown>) => {
611
+ if (event === 'ledger-collect-incomplete' || event === 'ledger-collect-skipped') {
612
+ ledgerIncomplete = true;
613
+ if (childId && (data.runId === childId || !data.runId)) childLedgerIncomplete = true;
614
+ }
615
+ if (event === 'artifact-collect-incomplete' || event === 'artifact-collect-skipped') artifactIncomplete = true;
616
+ debug.log(category, event, data);
617
+ onEvent?.(event, data);
618
+ };
619
+ try { collectPodLedgers(full.stdout, { dir: ledgerDir, ...(replace ? { replace } : {}), log: record }); }
620
+ catch (error) { record('self-implement.pod', 'ledger-collect-incomplete', { job: name, reason: error instanceof Error ? error.message : String(error) }); }
621
+ try {
622
+ collectPodArtifacts(full.stdout, { dir: join(effectiveInstanceRoot(), 'pod-artifacts'), job: name, log: record });
623
+ }
624
+ catch (error) { record('self-implement.pod', 'artifact-collect-incomplete', { job: name, reason: error instanceof Error ? error.message : String(error) }); }
625
+ return { status: ledgerIncomplete || artifactIncomplete ? 'incomplete' : 'ok', childLedgerIncomplete, samples: parseMemSamples(full.stdout) };
626
+ };
627
+ let retryAccount: string | undefined;
628
+ let failedAccount: string | undefined;
629
+ let usageLimitRetried = false;
630
+ for (;;) {
218
631
  try {
219
- const account = options.accountBroker?.() ?? options.account ?? 'team';
632
+ const grok = options.provider === 'grok';
633
+ const account = retryAccount ?? (grok ? 'grok' : options.accountBroker?.() ?? options.account ?? 'team');
220
634
  debug.log('self-implement.pod', 'account', { spaceId: input.spaceId, account, brokered: Boolean(options.accountBroker) });
221
- const creds = (options.credentials ?? (() => hostCredentials(account)))();
635
+ const codexAccounts = grok || !options.accountBroker || !options.rotationAccounts ? undefined : [account, ...options.rotationAccounts.filter((candidate) => candidate !== account && candidate !== failedAccount)];
636
+ if (codexAccounts && !options.rotationAccounts!.includes(account)) throw new Error(`pod: 배분 계정 ${account} 이 회전 계획에 없다`);
637
+ if (codexAccounts && new Set(options.rotationAccounts).size !== options.rotationAccounts!.length) throw new Error('pod: 유효하고 서로 다른 codex 계정이 필요하다');
638
+ const accountCredentials = options.credentials ?? hostCredentials;
639
+ if (codexAccounts) podCodexAccountScript(codexAccounts);
640
+ const codexCredentials = codexAccounts?.map((candidate) => {
641
+ const credential = accountCredentials(candidate);
642
+ const elanous = JSON.parse(credential.elanousAuth) as { providers?: Record<string, { tokens?: { refreshToken?: unknown } }> };
643
+ const codex = JSON.parse(credential.codexAuth) as { tokens?: { refresh_token?: unknown } };
644
+ if (!elanous.providers?.['openai-codex']?.tokens || elanous.providers['openai-codex'].tokens.refreshToken || !codex.tokens || codex.tokens.refresh_token) {
645
+ throw new Error(`pod: ${candidate} 자격에 refresh 토큰이 있거나 인증 파일이 없다`);
646
+ }
647
+ return credential;
648
+ });
649
+ const creds = grok
650
+ ? (options.grokCredentials ?? (() => hostGrokCredentials({ env, apiKeyOptIn: options.grokApiKeyOptIn })))()
651
+ : codexCredentials?.[0] ?? accountCredentials(account);
652
+ if (grok && !('grokAuth' in creds && creds.grokAuth) && !('grokApiKey' in creds && creds.grokApiKey && options.grokApiKeyOptIn === true)) {
653
+ throw new Error('grok: 구독 자격 없음 · API 키 opt-in 꺼짐 또는 키 없음');
654
+ }
655
+ if (grok && 'grokAuth' in creds && creds.grokAuth) {
656
+ let parsed: unknown;
657
+ try { parsed = JSON.parse(creds.grokAuth); } catch { throw new Error('grok: Pod 구독 자격 JSON 이 아니다'); }
658
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed) || !Object.values(parsed).some((scope) => scope && typeof scope === 'object' && typeof (scope as { key?: unknown }).key === 'string') || /"[^\"]*refresh[^\"]*"\s*:/i.test(creds.grokAuth)) {
659
+ throw new Error('grok: Pod 구독 자격에 access 토큰이 없거나 refresh 필드가 있다');
660
+ }
661
+ }
662
+ // Grok 자격은 전용 Secret 키 하나로만 보낸다 — passEnv 의 API 키 중복 전달은 막는다.
663
+ const passKeys = (options.passEnv ?? []).filter((key) => key !== 'ELANOUS_LIVE_DETAIL_UNTIL'
664
+ && key !== POD_GITHUB_CREDENTIAL_TOKEN_ENV && key !== POD_GITHUB_CREDENTIAL_URL_ENV
665
+ && (!grok || !['XAI_API_KEY', 'GROK_API_KEY', 'GROK_CODE_XAI_API_KEY'].includes(key)));
222
666
  const skillEnvs: Record<string, string> = options.skillEnv
223
667
  ? (options.readSkillEnv ?? (() => readSkillEnvFiles(resolvePodSkills(env).skills)))()
224
668
  : {};
225
669
  if (options.skillEnv) debug.log('self-implement.pod', 'skill-env', { job: name, skills: Object.keys(skillEnvs) }); // ⛔ 이름만 — 값은 안 싣는다
670
+ // ⭐ 원격 그라운딩(P13): 주소가 설정돼 있으면 이 런 전용·짧은 수명 토큰을 발급해 Secret 으로만 싣는다(값은 로그에 안 싣는다).
671
+ const groundingUrl = (options.groundingUrl ?? env[GROUNDING_URL_ENV] ?? (options.kubectl ? undefined : configGroundingUrl()))?.trim() || undefined; // kubectl 주입(=시험)이면 사용자 설정을 읽지 않는다
672
+ const groundingRunId = env.ELANOUS_RUN_ID?.trim() || input.spaceId;
673
+ const groundingTtlMs = (options.deadlineSeconds ?? POD_JOB_DEADLINE_SECONDS) * 1000;
674
+ const mint = options.mintGrounding ?? mintGroundingToken;
675
+ const grounding = groundingUrl
676
+ ? await mint({ runId: groundingRunId, job: name, ttlMs: groundingTtlMs })
677
+ : null;
678
+ groundingMinted = grounding ? groundingRunId : null;
679
+ debug.log('self-implement.pod', grounding ? 'grounding-token-issued' : 'grounding-token-skipped', { job: name, ...(grounding ? { runId: groundingRunId, exp: grounding.exp } : { reason: 'no-grounding-url' }) });
680
+ // grok 구독 access(~2시간) < Job 수명. 중계 토큰은 그라운딩과 같은 runId 로만 발급하고, 회수는 아래 finally 의 revokeGroundingRun 한 경로가 닫는다.
681
+ const grokSubscription = grok && 'grokAuth' in creds && Boolean(creds.grokAuth);
682
+ const credentialRelay = grokSubscription && groundingUrl
683
+ ? await mint({ runId: groundingRunId, job: name, ttlMs: groundingTtlMs, scope: 'llm-credential' })
684
+ : null;
685
+ if (credentialRelay) {
686
+ groundingMinted = groundingRunId;
687
+ debug.log('self-implement.pod', 'credential-relay', { job: name, runId: groundingRunId, exp: credentialRelay.exp });
688
+ } else if (grokSubscription && !groundingUrl) {
689
+ debug.log('self-implement.pod', 'credential-relay-skipped', { reason: 'no-host-url' });
690
+ }
691
+ const now = options.now ?? Date.now;
692
+ const issueVerifiedAppCredential = async (): Promise<{ token: string; expiresAt: number } | null> => {
693
+ if (!repository) return null;
694
+ // This is the same repository-scoped, contents+pull_requests installation request on launch and refresh.
695
+ const candidate = (options.githubInstallation ?? ((repo) => githubInstallationCredential({ scope: { repository: repo.split('/')[1]! } })))(repository);
696
+ if (!candidate?.token) return null;
697
+ const expiry = candidate.expires_at ?? ('expiresAt' in candidate ? candidate.expiresAt : undefined);
698
+ const expiresAt = typeof expiry === 'number' ? expiry : Date.parse(expiry ?? '');
699
+ if (!Number.isFinite(expiresAt) || expiresAt <= now()) return null;
700
+ const available = await (options.githubRepositories ?? installationRepositories)(candidate.token);
701
+ return available?.length === 1 && available[0]?.toLowerCase() === repository.toLowerCase()
702
+ ? { token: candidate.token, expiresAt } : null;
703
+ };
704
+ let appCredential: Awaited<ReturnType<typeof issueVerifiedAppCredential>> = null;
705
+ try { appCredential = await issueVerifiedAppCredential(); }
706
+ catch { debug.log('self-implement.pod', 'github-app-unavailable', { job: name, reason: 'mint-or-verification-failed' }); }
707
+ const githubRelay = appCredential && groundingUrl && repository
708
+ ? await mint({ runId: groundingRunId, job: name, ttlMs: groundingTtlMs, scope: 'gh-credential', repository })
709
+ : null;
710
+ if (githubRelay) {
711
+ groundingMinted = groundingRunId;
712
+ debug.log('self-implement.pod', 'github-credential-relay', { job: name, runId: groundingRunId, exp: githubRelay.exp });
713
+ }
714
+ // The host polling loop is a refresh path even when the Pod cannot reach the relay.
715
+ const hostRefresh = Boolean(appCredential);
716
+ const podGhToken = hostRefresh ? appCredential!.token : creds.ghToken;
717
+ const githubPassKeys = hostRefresh ? passKeys.filter((key) => !['GH_TOKEN', 'GITHUB_TOKEN', 'GH_CONFIG_DIR'].includes(key)) : passKeys;
718
+ let ghTokenExpiresAt = hostRefresh ? appCredential!.expiresAt : null;
719
+ const requestedGoalDoc = env.ELANOUS_POD_GOAL_DOC;
720
+ const requestedPath = requestedGoalDoc ? normalize(requestedGoalDoc) : undefined;
721
+ if (requestedPath && (isAbsolute(requestedPath) || requestedPath === '..' || requestedPath.startsWith(`..${sep}`) || requestedPath === '.')) {
722
+ throw new Error(`ELANOUS_POD_GOAL_DOC must be a repository-relative file: ${requestedGoalDoc}`);
723
+ }
724
+ const repoRoot = requestedPath ? findGitDir(process.cwd())?.root : undefined;
725
+ if (requestedPath && !repoRoot) throw new Error('ELANOUS_POD_GOAL_DOC requires a Git repository');
726
+ const goalFile = requestedPath ? realpathSync(resolve(repoRoot!, requestedPath)) : undefined;
727
+ const relativeGoalFile = goalFile ? relative(realpathSync(repoRoot!), goalFile) : undefined;
728
+ if (relativeGoalFile && (relativeGoalFile === '..' || relativeGoalFile.startsWith(`..${sep}`) || isAbsolute(relativeGoalFile))) throw new Error('ELANOUS_POD_GOAL_DOC outside repository');
729
+ const originalGoal = goalFile ? readFileSync(goalFile, 'utf8') : undefined;
730
+ // The orchestrator appends its identity after the executable feature, before
731
+ // dependency handoffs. A quoted heading or pieceIndex example is not a stamp.
732
+ // The stamp is the LAST identity heading followed only by the end or by the
733
+ // handoffs the orchestrator appends after it. The shard body itself may quote
734
+ // those handoff headings, so splitting at the first one would hide the stamp.
735
+ const normalizedFeature = input.feature.replace(/\r\n/g, '\n');
736
+ const stampPattern = /\n\n## Shard identity\n(\{[^\n]*\})(?=$|\n\n## (?:Dependency outputs|Dependency handoff|Working-memory handoff)\n)/g;
737
+ let stamp: RegExpExecArray | null = null;
738
+ for (const match of normalizedFeature.matchAll(stampPattern)) stamp = match as RegExpExecArray;
739
+ const executableFeature = stamp
740
+ ? normalizedFeature.slice(0, stamp.index + stamp[0].length)
741
+ : normalizedFeature.split(/\n\n## (?:Dependency outputs|Dependency handoff|Working-memory handoff)\n/, 1)[0]!;
742
+ const goalBody = originalGoal?.replace(/\r\n/g, '\n').split(/^## 실행 기록(?:\n|$)/m, 1)[0]?.trim();
743
+ // A stamp already present in the goal document is an example, not a new
744
+ // orchestration identity. Compare the entire input, not the unstamped body:
745
+ // a real shard can have precisely the same body as the parent goal.
746
+ const isOriginalDocument = [originalGoal?.replace(/\r\n/g, '\n').trim(), goalBody]
747
+ .some((text) => text !== undefined && (executableFeature.trim() === text || executableFeature.trim() === text.replace(/;;/gu, '; ;')));
748
+ let shard = false;
749
+ if (originalGoal !== undefined && stamp && !isOriginalDocument) {
750
+ try {
751
+ const parsed: unknown = JSON.parse(stamp[1]!);
752
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
753
+ const id = parsed as Record<string, unknown>;
754
+ const count = id.totalShards;
755
+ const prefix = executableFeature.slice(0, stamp.index);
756
+ const summary = prefix.trim().replace(/\s+/g, ' ');
757
+ const expectedSummary = !summary ? '(empty shard)' : summary.length <= 240 ? summary : `${summary.slice(0, 239)}…`;
758
+ shard = Number.isInteger(count) && (count as number) >= 1
759
+ && typeof id.orchestrationId === 'string' && /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(id.orchestrationId)
760
+ && typeof id.shardId === 'string' && /^task:[0-9a-f-]+$/i.test(id.shardId)
761
+ && Number.isInteger(id.position) && (id.position as number) >= 1 && (id.position as number) <= (count as number)
762
+ && id.summary === expectedSummary && Array.isArray(id.siblings)
763
+ && id.siblings.length === (count as number) - 1
764
+ && id.siblings.every((sibling: unknown) => sibling && typeof sibling === 'object' && !Array.isArray(sibling)
765
+ && typeof (sibling as { shardId?: unknown }).shardId === 'string'
766
+ && /^task:[0-9a-f-]+$/i.test((sibling as { shardId: string }).shardId)
767
+ && (sibling as { shardId: string }).shardId !== id.shardId
768
+ && typeof (sibling as { summary?: unknown }).summary === 'string');
769
+ }
770
+ } catch { /* Invalid stamp: preserve the goal-document route. */ }
771
+ }
772
+ const mode = shard ? 'shard-feature' : requestedPath ? 'goal-doc' : 'feature';
773
+ debug.log('self-implement.pod', 'goal-doc-mode', { spaceId: input.spaceId, mode, reason: shard ? 'shard-identity' : requestedPath ? 'goal-doc-env' : 'no-goal-doc' });
774
+ const goalDoc = shard ? undefined : requestedPath;
775
+ const goalDocument = goalDoc ? originalGoal : undefined;
226
776
  const secret = {
227
777
  apiVersion: 'v1', kind: 'Secret', type: 'Opaque',
228
778
  metadata: { name: `${name}-creds`, namespace, labels: { 'elanous.job': name } },
229
779
  stringData: {
230
- 'elanous-auth.json': creds.elanousAuth, 'codex-auth.json': creds.codexAuth, 'gh-token': creds.ghToken, feature: input.feature,
780
+ ...(grok
781
+ ? ('grokAuth' in creds && creds.grokAuth
782
+ ? { 'grok-auth.json': creds.grokAuth }
783
+ : { 'grok-api-key': (creds as { grokApiKey: string }).grokApiKey })
784
+ : codexCredentials
785
+ ? Object.fromEntries(codexCredentials.map((candidate, i) => [`codex-${i}.json`, JSON.stringify({ elanousAuth: candidate.elanousAuth, codexAuth: candidate.codexAuth })]))
786
+ : { 'elanous-auth.json': (creds as ReturnType<typeof hostCredentials>).elanousAuth, 'codex-auth.json': (creds as ReturnType<typeof hostCredentials>).codexAuth }),
787
+ 'gh-token': podGhToken, feature: input.feature,
788
+ ...(goalDocument !== undefined ? { 'goal-doc': goalDocument } : {}),
231
789
  ...Object.fromEntries(Object.entries(skillEnvs).map(([n, text]) => [`skillenv-${n}`, text])),
232
- ...Object.fromEntries((options.passEnv ?? []).map((k) => [k, env[k] ?? (options.readKeyCache ?? defaultReadKeyCache)(k)] as const).filter(([, v]) => v).map(([k, v]) => [`env-${k}`, v!])),
790
+ ...(grounding ? { [`env-${GROUNDING_TOKEN_ENV}`]: grounding.token } : {}),
791
+ ...(credentialRelay ? { [`env-${POD_CREDENTIAL_TOKEN_ENV}`]: credentialRelay.token } : {}),
792
+ ...(githubRelay ? { [`env-${POD_GITHUB_CREDENTIAL_TOKEN_ENV}`]: githubRelay.token } : {}),
793
+ ...Object.fromEntries(githubPassKeys.map((k) => [k, env[k] ?? (options.readKeyCache ?? defaultReadKeyCache)(k)] as const).filter(([, v]) => v).map(([k, v]) => [`env-${k}`, v!])),
233
794
  },
234
795
  };
235
796
  const hostKey = (k: string): string | undefined => env[k] ?? (options.readKeyCache ?? defaultReadKeyCache)(k);
236
- const passEnv = (options.passEnv ?? []).filter((k) => hostKey(k));
237
- const missing = (options.passEnv ?? []).filter((k) => !hostKey(k));
797
+ const passEnv = githubPassKeys.filter((k) => hostKey(k));
798
+ const missing = githubPassKeys.filter((k) => !hostKey(k));
238
799
  if (missing.length) debug.log('self-implement.pod', 'pass-env-missing', { job: name, missing }, { level: 'warn' });
800
+ // ☸️ 재개(P3 · 2026-09-26): 같은 이름의 Job 이 이미 있으면 «지우지 않고» 붙는다 — 호스트가 끊긴 동안에도 원격은 계속 돌았다.
801
+ // 🩸 종전엔 여기서 delete 부터 해서, 재개가 원격에서 멀쩡히 돌던 Job 을 죽였다. 실패로 끝난 Job 만 지우고 다시 만든다.
802
+ // ☸️ 노드 레지스트리에 이 판이 있으면 그것을 pull 한다(델타 · 대표 2026-09-26) — 라벨 판정은 로컬 이미지 이름으로 한다.
803
+ const jobImage = member?.imageRef ?? image;
804
+ const launchRunId = retryAccount ? mintRunId() : childRunId;
805
+ let liveChildRunId = launchRunId;
806
+ const existing = kubectl(['-n', namespace, 'get', 'job', name, '-o', 'jsonpath={.metadata.uid} {.status.conditions[*].type}']);
807
+ const [existingUid = '', ...existingConditions] = existing.status === 0 ? existing.stdout.trim().split(/\s+/u) : [];
808
+ const reattach = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/iu.test(existingUid) && // k8s uid 는 UUID — 다른 산출을 «있음»으로 읽지 않는다
809
+ !/Failed|FailureTarget/.test(existingConditions.join(' '));
810
+ if (reattach) {
811
+ const existingJob = kubectl(['-n', namespace, 'get', 'job', name, '-o', 'json']);
812
+ if (existingJob.status === 0) {
813
+ try {
814
+ const job = JSON.parse(existingJob.stdout) as { spec?: { template?: { spec?: { containers?: Array<{ name?: string; args?: string[]; env?: Array<{ name: string; value?: string }> }> } } } };
815
+ const child = job.spec?.template?.spec?.containers?.find((c) => c.name === 'child');
816
+ const inherited = child?.env?.find((e) => e.name === 'ELANOUS_RUN_ID')?.value;
817
+ if (inherited && inherited !== parentRunId && inherited === normalizeRunId(inherited)) liveChildRunId = inherited;
818
+ // A resumed App Job has an unknown remaining lifetime; refresh its gh config on the first live poll.
819
+ ghTokenExpiresAt = child?.args?.[0]?.includes('gh auth login --with-token < /creds/gh-token') ? now() : null;
820
+ } catch { /* The existing Job's identity must be verified before following it. */ }
821
+ }
822
+ if (liveChildRunId === launchRunId) {
823
+ debug.log('self-implement.pod', 'job-reattach-id-unavailable', { job: name });
824
+ return { exitCode: 1, output: '', error: { code: 'pod-child-run-id', message: `Job ${name} child runId를 확인할 수 없다` } };
825
+ }
826
+ }
827
+ if (reattach) debug.log('self-implement.pod', 'job-reattach', { job: name, conditions: existingConditions.join(' ') || 'running', spaceId: input.spaceId });
828
+ if (!hostRefresh && ghTokenExpiresAt === null && !githubRelay) debug.log('self-implement.pod', 'gh-token-app-skipped', { job: name, reason: 'no-verified-app-or-refresh-path' });
829
+ if (!reattach) {
830
+ if (/Failed|FailureTarget/.test(existingConditions.join(' ')) && collectFullLogs('failed-job-logs-unavailable').status === 'incomplete') {
831
+ return { exitCode: 1, output: '', error: { code: 'pod-collection-incomplete', message: `Job ${name} recovery incomplete — old Job retained` } };
832
+ }
239
833
  const s = kubectl(['apply', '-f', '-'], JSON.stringify(secret));
240
834
  if (s.status !== 0) return { exitCode: 1, output: s.stderr, error: { code: 'pod-secret', message: s.stderr.trim() } };
241
835
  kubectl(['-n', namespace, 'delete', 'job', name, '--ignore-not-found']);
242
- const job = podJobManifest({ name, namespace, image, repoUrl, args, passEnv, deadlineSeconds: options.deadlineSeconds ?? 5400, ...(env.ELANOUS_RUN_ID ? { runId: env.ELANOUS_RUN_ID } : {}), ...(options.armEnv ? { armEnv: options.armEnv } : {}), hostId: resolveHostId(env), skillEnvs: Object.keys(skillEnvs), ...(env.ELANOUS_POD_MEMORY?.trim() ? { memoryLimit: env.ELANOUS_POD_MEMORY.trim() } : {}), imageCommit: options.imageCommit !== undefined ? options.imageCommit : options.kubectl ? null : podImageFreshness({ image }).imageCommit }); // kubectl 주입(=시험)이면 docker 를 부르지 않는다
836
+ const jobPassEnv = [
837
+ ...passEnv,
838
+ ...(grounding ? [GROUNDING_TOKEN_ENV] : []),
839
+ ...(credentialRelay ? [POD_CREDENTIAL_TOKEN_ENV] : []),
840
+ ...(githubRelay ? [POD_GITHUB_CREDENTIAL_TOKEN_ENV] : []),
841
+ ];
842
+ const detail = selectLiveDetail(options.liveDetailFile ? { path: options.liveDetailFile } : {}).state;
843
+ const detailUntil = detail && (detail.scope === 'all' || detail.scope === parentRunId || detail.scope === launchRunId) && detail.until > Date.now()
844
+ ? String(detail.until) : undefined;
845
+ const armEnv = Object.fromEntries(Object.entries(options.armEnv ?? {}).filter(([key]) => key !== 'ELANOUS_LIVE_DETAIL_UNTIL' && key !== POD_GITHUB_CREDENTIAL_TOKEN_ENV && key !== POD_GITHUB_CREDENTIAL_URL_ENV && (!hostRefresh || !['GH_TOKEN', 'GITHUB_TOKEN', 'GH_CONFIG_DIR'].includes(key))));
846
+ const jobArmEnv = grounding || credentialRelay || githubRelay || env.ELANOUS_DISPATCH_RECORDED === '1' || options.armEnv || detailUntil
847
+ ? {
848
+ ...armEnv,
849
+ ...(detailUntil ? { ELANOUS_LIVE_DETAIL_UNTIL: detailUntil } : {}),
850
+ ...(env.ELANOUS_DISPATCH_RECORDED === '1' ? { ELANOUS_DISPATCH_RECORDED: '1' } : {}),
851
+ ...(grounding ? { [GROUNDING_URL_ENV]: groundingUrl! } : {}),
852
+ ...(credentialRelay ? { [POD_CREDENTIAL_URL_ENV]: `${new URL(groundingUrl!).origin}${POD_CREDENTIAL_GROK_PATH}` } : {}),
853
+ ...(githubRelay ? { [POD_GITHUB_CREDENTIAL_URL_ENV]: `${new URL(groundingUrl!).origin}${POD_CREDENTIAL_GITHUB_PATH}` } : {}),
854
+ }
855
+ : undefined;
856
+ const job = podJobManifest({ name, namespace, image: jobImage, ...(member?.imageRef ? { imagePullPolicy: 'IfNotPresent' as const } : {}), repoUrl, ...(options.source ? { source: options.source } : {}), ...(hostMirror ? { hostMirror } : {}), args, passEnv: jobPassEnv, deadlineSeconds: options.deadlineSeconds ?? POD_JOB_DEADLINE_SECONDS, ...(goalDoc ? { goalDoc } : {}), runId: launchRunId, ...(parentRunId ? { parentRunId } : {}), ...(jobArmEnv ? { armEnv: jobArmEnv } : {}), hostId: resolveHostId(env), skillEnvs: Object.keys(skillEnvs), memoryLimit, imageCommit: options.imageCommit !== undefined ? options.imageCommit : options.kubectl ? null : podImageFreshness({ image }).imageCommit, ...(grok ? { grokCredential: 'grokAuth' in creds && creds.grokAuth ? 'subscription' as const : 'api_key' as const } : {}), ...(codexAccounts ? { codexAccounts } : {}), ...(hostRefresh ? { appCredential: true } : {}) }); // kubectl 주입(=시험)이면 docker 를 부르지 않는다
243
857
  const a = kubectl(['apply', '-f', '-'], JSON.stringify(job));
244
858
  if (a.status !== 0) { cleanupSecret(); return { exitCode: 1, output: a.stderr, error: { code: 'pod-apply', message: a.stderr.trim() } }; }
245
- writePodFragment({ spaceId: input.spaceId, context, namespace, job: name, inboxDir: POD_CONTROL_INBOX_DIR }, env);
859
+ if (options.source?.kind === 'bundle') {
860
+ const podName = await waitForRunningPod(kubectl, namespace, name, sleep);
861
+ if (!podName) {
862
+ debug.log('self-implement.pod', 'source-mismatch', { job: name, kind: 'bundle', headCommit: options.source.headCommit });
863
+ cleanupSecret();
864
+ return { exitCode: 1, output: '', error: { code: 'pod-source', message: `Pod for ${name} did not become Running` } };
865
+ }
866
+ const copied = kubectl(['cp', options.source.bundlePath, `${podName}:/tmp/source.bundle`, '-c', 'child', '-n', namespace]);
867
+ if (copied.status !== 0) {
868
+ debug.log('self-implement.pod', 'source-mismatch', { job: name, kind: 'bundle', headCommit: options.source.headCommit });
869
+ cleanupSecret();
870
+ return { exitCode: 1, output: copied.stderr, error: { code: 'pod-source', message: copied.stderr.trim() || 'kubectl cp failed' } };
871
+ }
872
+ const ready = kubectl(['-n', namespace, 'exec', podName, '-c', 'child', '--', 'touch', '/tmp/source.ready']);
873
+ if (ready.status !== 0) {
874
+ debug.log('self-implement.pod', 'source-mismatch', { job: name, kind: 'bundle', headCommit: options.source.headCommit });
875
+ cleanupSecret();
876
+ return { exitCode: 1, output: ready.stderr, error: { code: 'pod-source', message: ready.stderr.trim() || 'kubectl exec touch failed' } };
877
+ }
878
+ debug.log('self-implement.pod', 'source-delivered', { job: name, kind: 'bundle', headCommit: options.source.headCommit });
879
+ }
880
+ // ☸️ P2: 비밀의 소유자 = Job — 호스트가 죽어도 Job 의 TTL 삭제와 함께 k8s 가 비밀을 거둔다(자격 사본이 클러스터에 남지 않는다).
881
+ const uid = kubectl(['-n', namespace, 'get', 'job', name, '-o', 'jsonpath={.metadata.uid}']).stdout.trim();
882
+ if (uid) {
883
+ const owned = kubectl(['-n', namespace, 'patch', 'secret', `${name}-creds`, '--type=merge', '-p', JSON.stringify({ metadata: { ownerReferences: [{ apiVersion: 'batch/v1', kind: 'Job', name, uid }] } })]);
884
+ if (owned.status !== 0) debug.log('self-implement.pod', 'secret-owner-failed', { job: name, stderr: owned.stderr.trim() }, { level: 'warn' });
885
+ }
886
+ }
887
+ if (parentRunId) {
888
+ try { appendRunLedgerEntry({ runId: parentRunId, event: 'pod-child-run', data: { childRunId: liveChildRunId, job: name, ...(goalFile ? { goalFile } : {}) } }, ledgerDir); }
889
+ catch (error) { debug.log('self-implement.pod', 'parent-ledger-unavailable', { job: name, reason: error instanceof Error ? error.message : String(error) }); }
890
+ }
891
+ writePodFragment({ spaceId: input.spaceId, context, namespace, job: name, inboxDir: POD_CONTROL_INBOX_DIR, ...(liveChildRunId === normalizeRunId(liveChildRunId) ? { runId: liveChildRunId } : {}), ...(parentRunId && normalizeRunId(parentRunId) === parentRunId ? { parentRunId } : {}) }, env);
246
892
  recorded = true;
247
- debug.log('self-implement.pod', 'job-applied', { job: name, namespace, ...(member ? { context: member.context } : {}), image, spaceId: input.spaceId, passEnv, extraArgs: options.extraArgs ?? [], ...(options.armEnv?.ELANOUS_ARM_ID ? { armId: options.armEnv.ELANOUS_ARM_ID } : {}) });
893
+ debug.log('self-implement.pod', 'job-applied', { job: name, namespace, ...(member ? { context: member.context } : {}), image: jobImage, spaceId: input.spaceId, passEnv, extraArgs: options.extraArgs ?? [], ...(options.armEnv?.ELANOUS_ARM_ID ? { armId: options.armEnv.ELANOUS_ARM_ID } : {}) });
248
894
  let state: 'complete' | 'failed' | 'aborted' = 'failed';
895
+ let failedReason = '';
896
+ let containerReason: string | null = null;
897
+ let containerExitCode: number | null = null;
898
+ // ⭐ 런 «도중» 원장 증분 회수 — 호스트 슈퍼바이저가 Pod 걸음을 실시간으로 본다(🅣 요청 · 힐 루프 runtime 성형의 전제).
899
+ const follower = createPodLedgerFollower({ runId: liveChildRunId, dir: ledgerDir, exec: (script) => kubectl(['-n', namespace, 'exec', `job/${name}`, '-c', 'child', '--', 'sh', '-c', script]) });
249
900
  for (;;) {
250
901
  if (input.signal?.aborted) {
251
902
  kubectl(['-n', namespace, 'delete', 'job', name, '--ignore-not-found', '--wait=false']);
@@ -255,25 +906,166 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
255
906
  const g = kubectl(['-n', namespace, 'get', 'job', name, '-o', 'jsonpath={.status.conditions[*].type}']);
256
907
  const types = g.stdout;
257
908
  if (/Complete|SuccessCriteriaMet/.test(types)) { state = 'complete'; break; }
258
- if (/Failed|FailureTarget/.test(types)) { state = 'failed'; break; }
909
+ if (/Failed|FailureTarget/.test(types)) {
910
+ state = 'failed';
911
+ const reason = kubectl(['-n', namespace, 'get', 'job', name, '-o', 'jsonpath={.status.conditions[?(@.type=="Failed")].reason}']);
912
+ failedReason = reason.status === 0 ? reason.stdout.trim() : '';
913
+ try {
914
+ const pods = kubectl(['-n', namespace, 'get', 'pods', '-l', `job-name=${name}`, '-o', 'jsonpath={range .items[*]}{.metadata.creationTimestamp}{"\\t"}{range .status.containerStatuses[?(@.name=="child")]}{.state.terminated.reason}{"\\t"}{.state.terminated.exitCode}{end}{"\\n"}{end}']);
915
+ if (pods.status === 0 && pods.stdout) {
916
+ const rows = pods.stdout.trimEnd().split('\n').map((line) => line.split('\t'));
917
+ const latest = rows.filter(([created]) => created && !Number.isNaN(Date.parse(created)))
918
+ .sort((a, b) => Date.parse(b[0]!) - Date.parse(a[0]!))[0];
919
+ if (latest) {
920
+ const [, reasonText, exitText] = latest;
921
+ containerReason = reasonText?.trim() || null;
922
+ containerExitCode = exitText?.trim() && /^\d+$/.test(exitText.trim()) ? Number(exitText.trim()) : null;
923
+ }
924
+ }
925
+ } catch { /* Pod 가 사라졌거나 조회 불가 — 종료 사유는 미상. */ }
926
+ debug.log('self-implement.pod', 'container-terminated', { job: name, container: 'child', reason: containerReason, exitCode: containerExitCode, jobReason: failedReason || null, memoryLimit });
927
+ break;
928
+ }
929
+ if (ghTokenExpiresAt !== null && ghTokenExpiresAt - now() <= 10 * 60_000) {
930
+ try {
931
+ const fresh = await issueVerifiedAppCredential();
932
+ if (!fresh) throw new Error('mint-or-verification-failed');
933
+ // No bearer in argv, stdout, stderr, or the run ledger. kubectl receives it only over stdin.
934
+ const installed = kubectl(['-n', namespace, 'exec', '-i', `job/${name}`, '-c', 'child', '--', 'sh', '-c', APP_GH_EXEC, 'sh', 'gh', 'auth', 'login', '--with-token'], `${fresh.token}\n`);
935
+ if (installed.status !== 0) {
936
+ const fallback = kubectl(['-n', namespace, 'exec', '-i', `job/${name}`, '-c', 'child', '--', 'sh', '-c', APP_GH_EXEC, 'sh', 'bun', '-e', APP_GH_AUTH_SCRIPT], `${fresh.token}\n`);
937
+ if (fallback.status !== 0) throw new Error('exec-failed');
938
+ }
939
+ ghTokenExpiresAt = fresh.expiresAt;
940
+ debug.log('self-implement.pod', 'gh-token-refreshed', { job: name, expiresAt: new Date(fresh.expiresAt).toISOString() });
941
+ } catch {
942
+ debug.log('self-implement.pod', 'gh-token-refresh-failed', { job: name, reason: 'mint-verification-or-exec-failed' });
943
+ }
944
+ }
945
+ try { follower?.poll(); } catch (e) { debug.log('self-implement.pod', 'ledger-live-unavailable', { job: name, reason: e instanceof Error ? e.message : String(e) }); }
259
946
  await sleep(options.pollMs ?? 15_000);
260
947
  }
261
- const logs = kubectl(['-n', namespace, 'logs', `job/${name}`, '-c', 'child', '--tail=400']).stdout;
948
+ let logs = '';
949
+ let logTailReason: string | undefined;
950
+ try {
951
+ const fetched = kubectl(['-n', namespace, 'logs', `job/${name}`, '-c', 'child', '--tail=400']);
952
+ if (fetched.status !== 0) logTailReason = fetched.stderr.trim() || `kubectl logs exited ${fetched.status}`;
953
+ else if (!fetched.stdout) logTailReason = 'empty child logs';
954
+ else logs = fetched.stdout;
955
+ } catch (error) { logTailReason = error instanceof Error ? error.message : String(error); }
956
+ let ledgerReason = '';
957
+ let ledgerCollected = false;
958
+ const collection = collectFullLogs('ledger-collect-incomplete', follower.owned ? new Set([liveChildRunId]) : undefined, (event, data) => {
959
+ if ((event === 'ledger-collect-incomplete' || event === 'ledger-collect-skipped') && (data.runId === liveChildRunId || !data.runId)) ledgerReason = String(data.reason);
960
+ if ((event === 'ledger-collected' || event === 'ledger-collect-already-complete') && data.runId === liveChildRunId) ledgerCollected = true;
961
+ }, liveChildRunId);
962
+ const samples = collection.status === 'unavailable' ? parseMemSamples(logs) : collection.samples;
963
+ if (state === 'failed' && containerReason === 'OOMKilled') {
964
+ // 자식 stdout 원문은 싣지 않는다 — 임의 출력에 든 비밀이 진단 경로로 새지 않도록 «모양»(줄·바이트)만 남긴다.
965
+ const childLines = logs ? logs.replace(/\n$/, '').split('\n').filter((line) => !line.startsWith('ELANOUS_MEM ')) : [];
966
+ const logShape = logs ? { lines: childLines.length, bytes: Buffer.byteLength(childLines.join('\n')) } : null;
967
+ debug.log('self-implement.pod', 'oom-evidence', { job: name, memoryLimit, samples: samples.slice(-3), logShape, ...(logShape === null ? { logTailReason: logTailReason ?? 'empty child logs' } : {}) }, { compact: { maxDepth: 6 } });
968
+ } else if (state === 'complete') {
969
+ debug.log('self-implement.pod', 'memory-last', { job: name, sample: samples.at(-1) ?? null }, { compact: { maxDepth: 6 } });
970
+ }
971
+ const ledgerExists = existsSync(runLedgerPath(liveChildRunId, ledgerDir));
972
+ const ledgerCompleteness = collection.childLedgerIncomplete || (follower.owned && !ledgerCollected) ? 'incomplete' : ledgerCollected ? 'complete' : 'missing';
973
+ if (ledgerCompleteness === 'incomplete' || (state !== 'complete' && !ledgerExists)) {
974
+ try { appendRunLedgerEntry({ runId: liveChildRunId, event: 'pod-ledger-incomplete', data: { job: name, reason: ledgerReason || (collection.status === 'unavailable' ? 'logs-unavailable' : ledgerExists ? 'collection-incomplete' : 'child-ledger-missing') } }, ledgerDir); }
975
+ catch (error) { debug.log('self-implement.pod', 'ledger-marker-unavailable', { job: name, childRunId: liveChildRunId, reason: error instanceof Error ? error.message : String(error) }); }
976
+ }
977
+ const salvage = recordPodSalvage(logs, name);
262
978
  cleanupSecret();
263
979
  // ⭐ 호스트 단가로 다시 매긴다(BACKLOG C1b) — Pod 엔 레지스트리 스냅숏이 없다.
264
980
  const { estimateLlmCost } = await import('../../budget/llm-cost.js');
265
981
  reemitPodUsage(logs, name, undefined, (u) => estimateLlmCost(u) as { kind: string; usd?: number });
266
982
  const parsed = parseSelfImplementJson(logs);
983
+ const terminal = parseSelfImplementJson(logs.trimEnd().split('\n').at(-1) ?? '');
984
+ const nextAccount = codexAccounts?.find((candidate) => candidate !== account);
985
+ if (!usageLimitRetried && !input.signal?.aborted && state !== 'aborted' && terminal?.ok === false
986
+ && /\b429\b/.test(terminal.error ?? '') && /\busage_limit_reached\b/.test(terminal.error ?? '') && nextAccount) {
987
+ debug.log('self-implement.pod', 'usage-limit-retry', { job: name, spaceId: input.spaceId, from: account, to: nextAccount });
988
+ const deleted = kubectl(['-n', namespace, 'delete', 'job', name, '--ignore-not-found', '--wait=true']);
989
+ if (deleted.status !== 0) throw new Error(`pod: 사용량 초과 Job 삭제 실패: ${deleted.stderr.trim()}`);
990
+ failedAccount = account;
991
+ retryAccount = nextAccount;
992
+ usageLimitRetried = true;
993
+ continue;
994
+ }
267
995
  // Pod 안 경로는 호스트에서 쓸 수 없다.
268
- const disposition = parsed ? { ...parsed, worktreePath: undefined } : undefined;
269
- debug.log('self-implement.pod', 'job-finished', { job: name, ...(member ? { context: member.context } : {}), state, stage: disposition?.stage ?? null, prUrl: disposition?.prUrl ?? null });
270
- const tail = logs.slice(-4000);
996
+ let disposition = parsed ? { ...parsed, worktreePath: undefined } : undefined;
997
+ if (input.autoMerge && state === 'complete' && disposition?.stage === 'merge-ready') {
998
+ const prNumber = disposition.prNumber;
999
+ const headCommit = disposition.checkedHeadCommit;
1000
+ let regate: HostRegateResult;
1001
+ if (!Number.isSafeInteger(prNumber) || !prNumber || !headCommit || !/^[0-9a-f]{40}$/i.test(headCommit)) {
1002
+ debug.log('harness.host-regate', 'unmeasured', { pr: prNumber ?? null, files: [], os: process.platform, failedStep: 'pod-result' });
1003
+ regate = { passed: false, failures: [{ step: 'pod-result', detail: 'missing PR number or checked head commit' }], os: process.platform };
1004
+ } else {
1005
+ try {
1006
+ const hostRoot = findGitDir(process.cwd())?.root;
1007
+ if (!hostRoot) throw new Error('host repository unavailable');
1008
+ regate = await (options.hostRegate ?? runHostRegate)({ prNumber, headCommit, repoRoot: hostRoot });
1009
+ } catch (error) {
1010
+ debug.log('harness.host-regate', 'unmeasured', { pr: prNumber, files: [], os: process.platform, failedStep: 'host-regate' });
1011
+ regate = { passed: false, failures: [{ step: 'host-regate', detail: error instanceof Error ? error.message : String(error) }], os: process.platform };
1012
+ }
1013
+ }
1014
+ // runHostRegate comments on its own failures; these two paths never reached it.
1015
+ const failedStep = regate.failures[0]?.step;
1016
+ if (!regate.passed && (failedStep === 'pod-result' || failedStep === 'host-regate') && Number.isSafeInteger(prNumber) && prNumber) {
1017
+ const detail = (regate.failures[0]?.detail ?? 'unknown').replace(/\s+/g, ' ').slice(0, 180);
1018
+ try { (options.ghComment ?? defaultGhComment)(prNumber, `호스트 재게이트 실패(${process.platform}): ${failedStep} — ${detail}`); }
1019
+ catch (error) { debug.log('harness.host-regate', 'unmeasured', { pr: prNumber, files: [], os: process.platform, failedStep: 'comment', detail: error instanceof Error ? error.message : String(error) }); }
1020
+ }
1021
+ disposition = { ...disposition, stage: regate.passed ? 'merged' : 'host-regate-failed', merged: regate.passed, hostRegate: regate, ok: regate.passed };
1022
+ }
1023
+ if (goalFile) {
1024
+ const record: GoalExecutionRecord = {
1025
+ runId: liveChildRunId,
1026
+ stage: (state === 'aborted' || !disposition ? 'aborted' : disposition.stage) as GoalExecutionRecord['stage'],
1027
+ outcome: (!disposition ? 'pod-no-result' : state === 'complete' && disposition.merged ? 'merged' : state === 'complete' && disposition.ok ? 'completed' : 'abandoned') as GoalExecutionRecord['outcome'],
1028
+ ok: state === 'complete' && (disposition?.ok ?? false),
1029
+ ...(disposition?.prNumber !== undefined ? { prNumber: disposition.prNumber } : {}),
1030
+ completedAt: new Date().toISOString(),
1031
+ };
1032
+ try { const { appendGoalExecutionRecord } = await import('../../self-implement/orchestrator.js'); appendGoalExecutionRecord(goalFile, record); }
1033
+ catch (error) { debug.log('self-implement.pod', 'goal-record-unavailable', { job: name, childRunId: liveChildRunId, reason: error instanceof Error ? error.message : String(error) }); }
1034
+ }
1035
+ const childFailure = state === 'failed' && containerReason !== 'OOMKilled' && failedReason !== 'DeadlineExceeded'
1036
+ ? lastPodChildFailure(logs) : null;
1037
+ if (childFailure) debug.log('self-implement.pod', 'child-error', { job: name, stage: childFailure.stage, error: childFailure.error }, { compact: { stringMax: 500 } });
1038
+ debug.log('self-implement.pod', 'job-finished', { job: name, ...(member ? { context: member.context } : {}), state, containerReason, stage: disposition?.stage ?? null, prUrl: disposition?.prUrl ?? null, childRunId: liveChildRunId, ledgerCompleteness,
1039
+ ...(state === 'failed' && containerReason !== 'OOMKilled' && failedReason !== 'DeadlineExceeded'
1040
+ ? { ...(childFailure ? { childStage: childFailure.stage } : {}), childError: childFailure?.error ?? 'no-result-line' } : {}) },
1041
+ state === 'failed' && containerReason !== 'OOMKilled' && failedReason !== 'DeadlineExceeded' ? { compact: { stringMax: 500 } } : undefined);
1042
+ const tail = podRunResultLine(logs, salvage);
271
1043
  if (state === 'aborted') return { exitCode: null, output: tail, error: { code: 'aborted', message: 'aborted — Job deleted' }, ...(disposition ? { disposition } : {}) };
1044
+ const deadlineExceeded = state === 'failed' && failedReason === 'DeadlineExceeded';
1045
+ const deadlineSeconds = options.deadlineSeconds ?? POD_JOB_DEADLINE_SECONDS;
1046
+ if (deadlineExceeded) {
1047
+ debug.log('self-implement.pod', 'deadline-exceeded', {
1048
+ job: name,
1049
+ deadlineSeconds,
1050
+ childClassification: disposition?.failureClassification ?? null,
1051
+ prUrl: disposition?.prUrl ?? null,
1052
+ branch: disposition?.branch ?? null,
1053
+ });
1054
+ }
1055
+ const finishedDisposition = deadlineExceeded && disposition
1056
+ ? { ...disposition, failureClassification: 'run-deadline-exceeded' as const }
1057
+ : disposition;
272
1058
  return {
273
- exitCode: state === 'complete' ? 0 : 1,
1059
+ exitCode: state === 'complete' && disposition?.stage !== 'host-regate-failed' && disposition?.ok !== false ? 0 : 1,
274
1060
  output: tail,
275
- ...(state === 'failed' ? { error: { code: 'pod-job-failed', message: `Job ${name} failed` } } : {}),
276
- ...(disposition ? { disposition } : {}),
1061
+ ...(deadlineExceeded
1062
+ ? { error: { code: 'pod-deadline-exceeded', message: `Job ${name} 이 수명 상한 ${deadlineSeconds}초에 닿았다` } }
1063
+ : state === 'failed'
1064
+ ? containerReason === 'OOMKilled'
1065
+ ? { error: { code: 'pod-oom-killed', message: `Job ${name} failed (OOMKilled/${containerExitCode ?? 'unknown'}, memoryLimit=${memoryLimit})\n${formatLastMemSample(samples.at(-1))}` } }
1066
+ : { error: { code: 'pod-job-failed', message: `Job ${name} failed${failedReason || containerReason ? ` (${[failedReason, containerReason ? `container=${containerReason}/${containerExitCode ?? 'unknown'}` : ''].filter(Boolean).join(', ')})` : ''}${childFailure ? ` — childStage=${childFailure.stage} · childError=${childFailure.error}` : ' — childError=no-result-line'}` } }
1067
+ : {}),
1068
+ ...(finishedDisposition ? { disposition: finishedDisposition } : {}),
277
1069
  };
278
1070
  } catch (err) {
279
1071
  cleanupSecret();
@@ -281,8 +1073,10 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
281
1073
  debug.log('self-implement.pod', 'job-error', { job: name, message }, { level: 'error' });
282
1074
  return { exitCode: 1, output: message, error: { code: 'pod-error', message } };
283
1075
  }
1076
+ }
284
1077
  } finally {
285
1078
  if (recorded) finishPodFragment(input.spaceId, env);
1079
+ if (groundingMinted) { try { (options.revokeGrounding ?? revokeGroundingRun)(groundingMinted); } catch (e) { debug.log('self-implement.pod', 'grounding-revoke-failed', { job: name, reason: e instanceof Error ? e.message : String(e) }, { level: 'warn' }); } }
286
1080
  if (member) options.pool!.release(member);
287
1081
  }
288
1082
  })();
@@ -311,6 +1105,81 @@ export function podRowCost(r: Record<string, unknown>, reprice?: PodRowReprice):
311
1105
  return { kind: 'partial', usd: usdKnown, unknownCostCalls: unknown };
312
1106
  }
313
1107
 
1108
+ const SALVAGE_LINE = /^ELANOUS_POD_SALVAGE (\S+) (\S+)$/;
1109
+
1110
+ /** Job 로그의 `ELANOUS_POD_SALVAGE <branch> <commit>` — 호스트가 거둘 브랜치. 접두가 salvage/ 가 아니면 버린다. */
1111
+ export function parsePodSalvageLines(logs: string): Array<{ branch: string; commit: string }> {
1112
+ const out: Array<{ branch: string; commit: string }> = [];
1113
+ for (const line of logs.split('\n')) {
1114
+ const m = SALVAGE_LINE.exec(line.trim());
1115
+ if (!m || !m[1]!.startsWith('salvage/')) continue;
1116
+ out.push({ branch: m[1]!, commit: m[2]! });
1117
+ }
1118
+ return out;
1119
+ }
1120
+
1121
+ /** 기존 Job 로그 소비 경로가 부르는 salvage 기록. 사람용 런 결과 줄에 붙일 브랜치 목록을 돌려준다. */
1122
+ export function recordPodSalvage(logs: string, job: string, log: (category: string, event: string, data: Record<string, unknown>) => void = (c, e, d) => debug.log(c, e, d)): string[] {
1123
+ const rows = parsePodSalvageLines(logs);
1124
+ for (const row of rows) log('self-implement.pod', 'salvage-pushed', { job, branch: row.branch, commit: row.commit });
1125
+ return rows.map((row) => row.branch);
1126
+ }
1127
+
1128
+ function lastPodChildFailure(logs: string): { stage: string; error: string } | null {
1129
+ for (const line of logs.split('\n').reverse()) {
1130
+ if (!line.trimStart().startsWith('{')) continue;
1131
+ let result: unknown;
1132
+ try { result = JSON.parse(line); } catch { continue; }
1133
+ if (!result || typeof result !== 'object' || Array.isArray(result)) continue;
1134
+ const row = result as Record<string, unknown>;
1135
+ if (typeof row.stage !== 'string' || typeof row.ok !== 'boolean') continue;
1136
+ if (row.ok !== false || typeof row.error !== 'string') return null;
1137
+ const lines = row.error.replace(/\/home\/[^/\s]+\//g, '~/').split(/\r\n|\n|\r/);
1138
+ const first = lines[0]!.slice(0, 240);
1139
+ const last = lines.at(-1)!.slice(0, 240);
1140
+ return { stage: row.stage, error: first === last ? first : `${first}\n${last}` };
1141
+ }
1142
+ return null;
1143
+ }
1144
+
1145
+ /** 사람용 런 결과 한 줄 — 수확 브랜치가 있으면 «수확할 브랜치: …» 를 붙인다. */
1146
+ export function podRunResultLine(logs: string, branches: readonly string[]): string {
1147
+ const result = logs.split('\n').filter((line) => !line.startsWith('ELANOUS_MEM ')).join('\n').slice(-4000);
1148
+ if (!branches.length) return result;
1149
+ const note = branches.map((branch) => `수확할 브랜치: ${branch}`).join('\n');
1150
+ return result ? `${result}\n${note}` : note;
1151
+ }
1152
+
1153
+ export function parseMemSamples(logs: string): Array<{ at: number; cgroupBytes: number | null; top: Array<{ rssKb: number; name: string; cmd?: string }> }> {
1154
+ const samples: Array<{ at: number; cgroupBytes: number | null; top: Array<{ rssKb: number; name: string; cmd?: string }> }> = [];
1155
+ for (const line of logs.split('\n')) {
1156
+ const match = /^ELANOUS_MEM (\d+) (\d+|-)(?: (.*))?\r?$/.exec(line);
1157
+ if (!match) continue;
1158
+ const at = Number(match[1]);
1159
+ const cgroupBytes = match[2] === '-' ? null : Number(match[2]);
1160
+ if (!Number.isSafeInteger(at) || (cgroupBytes !== null && !Number.isSafeInteger(cgroupBytes))) continue;
1161
+ const top: Array<{ rssKb: number; name: string; cmd?: string }> = [];
1162
+ let valid = true;
1163
+ for (const token of (match[3] ?? '').split(' ').filter(Boolean)) {
1164
+ const entry = /^(\d+):([^\s:]+)(?::([^\s:]+))?$/.exec(token);
1165
+ if (!entry || !Number.isSafeInteger(Number(entry[1])) || top.length >= 5) { valid = false; break; }
1166
+ let name: string;
1167
+ let cmd: string | undefined;
1168
+ try { name = decodeURIComponent(entry[2]!); if (entry[3] !== undefined) cmd = decodeURIComponent(entry[3]); }
1169
+ catch { valid = false; break; }
1170
+ top.push({ rssKb: Number(entry[1]), name, ...(cmd !== undefined ? { cmd } : {}) });
1171
+ }
1172
+ if (valid) samples.push({ at, cgroupBytes, top });
1173
+ }
1174
+ return samples;
1175
+ }
1176
+
1177
+ function formatLastMemSample(sample: ReturnType<typeof parseMemSamples>[number] | undefined): string {
1178
+ if (!sample) return '마지막 샘플: 샘플 없음';
1179
+ const gib = (bytes: number) => `${(bytes / 1024 ** 3).toFixed(2)}GiB`;
1180
+ return `마지막 샘플: cgroup ${sample.cgroupBytes === null ? '-' : gib(sample.cgroupBytes)} · 상위 ${sample.top.length ? sample.top.map((p) => `${p.name} ${gib(p.rssKb * 1024)}`).join(' · ') : '없음'}`;
1181
+ }
1182
+
314
1183
  export function reemitPodUsage(logs: string, job: string, log: (category: string, event: string, data: Record<string, unknown>) => void = (c, e, d) => debug.log(c, e, d), reprice?: PodRowReprice): number {
315
1184
  const line = logs.split('\n').reverse().find((l) => l.startsWith('ELANOUS_USAGE_ROLLUP '));
316
1185
  if (!line) { log('self-implement.pod', 'usage-rollup-missing', { job }); return 0; }
@@ -334,7 +1203,7 @@ export function reemitPodUsage(logs: string, job: string, log: (category: string
334
1203
  /** 이 기계가 Pod 칸을 쓸 수 있나(클러스터 · 이미지) — 발사 전 한 번. */
335
1204
  export function podSubstrateReady(kubectl: Kubectl = defaultKubectl, image = 'elanous-harness:local'): { ok: boolean; reason: string } {
336
1205
  const ctx = kubectl(['config', 'current-context']);
337
- if (ctx.status !== 0) return { ok: false, reason: 'kubectl context 없음 — k3d cluster create elanous-h1 --no-lb' };
1206
+ if (ctx.status !== 0) return { ok: false, reason: 'kubectl context 없음 — k3d cluster create elanous-h1 --no-lb (이미 있는 k3d 클러스터면 kubectl config use-context k3d-<이름>)' };
338
1207
  const ns = kubectl(['get', 'ns', 'elanous-test']);
339
1208
  if (ns.status !== 0) return { ok: false, reason: 'elanous-test 네임스페이스 없음 — kubectl apply -f docker/h1/base.yaml -f docker/h1/policy-internet.yaml' };
340
1209
  void image;
@@ -449,3 +1318,19 @@ export function podImageFreshness(deps: {
449
1318
  ? { imageCommit, headCommit, fresh: true, reason: 'HEAD 와 같다' }
450
1319
  : { imageCommit, headCommit, fresh: false, reason: `이미지 ${imageCommit.slice(0, 12)} ≠ HEAD ${headCommit.slice(0, 12)}` };
451
1320
  }
1321
+
1322
+ function configHostMirror(): string | undefined {
1323
+ try {
1324
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
1325
+ const { getUserConfig } = require('../../user-config.js') as typeof import('../../user-config.js');
1326
+ return getUserConfig().pod?.hostMirror;
1327
+ } catch { return undefined; }
1328
+ }
1329
+
1330
+ function configGroundingUrl(): string | undefined {
1331
+ try {
1332
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
1333
+ const { getUserConfig } = require('../../user-config.js') as typeof import('../../user-config.js');
1334
+ return getUserConfig().pod?.groundingUrl;
1335
+ } catch { return undefined; }
1336
+ }