elanous 0.2.0 → 0.2.4

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