elanous 0.2.14 → 0.2.15

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 (293) hide show
  1. package/apps/pwa/out/404/index.html +1 -1
  2. package/apps/pwa/out/404.html +1 -1
  3. package/apps/pwa/out/_next/static/LZcstaO9_NXypPFmujolv/_buildManifest.js +1 -0
  4. package/apps/pwa/out/_next/static/chunks/{1307-6e7e0377c1db0bdc.js → 1307-33ffcc49ab45123b.js} +1 -1
  5. package/apps/pwa/out/_next/static/chunks/5977-c952712c87a1f757.js +1 -0
  6. package/apps/pwa/out/_next/static/chunks/899-46946fe8363e1262.js +1 -0
  7. package/apps/pwa/out/_next/static/chunks/app/approvals/{page-74383f890f075511.js → page-0e2c57fe6add4b2a.js} +1 -1
  8. package/apps/pwa/out/_next/static/chunks/app/autopilot/{page-7b2b4f378354869a.js → page-e05a77df7e005b04.js} +1 -1
  9. package/apps/pwa/out/_next/static/chunks/app/board/page-f544338193a9a762.js +1 -0
  10. package/apps/pwa/out/_next/static/chunks/app/botlab/page-5491c115b1f9a923.js +1 -0
  11. package/apps/pwa/out/_next/static/chunks/app/bots/{page-1512d91c7491ed62.js → page-b8c49f6b4fa2df91.js} +1 -1
  12. package/apps/pwa/out/_next/static/chunks/app/ceo/page-96398e364fbd4548.js +1 -0
  13. package/apps/pwa/out/_next/static/chunks/app/chat/{page-a627db94ec6904a0.js → page-976813c91d7394ea.js} +1 -1
  14. package/apps/pwa/out/_next/static/chunks/app/consult/{page-fb260b9283dbf646.js → page-28b9448091776c06.js} +1 -1
  15. package/apps/pwa/out/_next/static/chunks/app/control/page-1d4901eb7c2d8822.js +1 -0
  16. package/apps/pwa/out/_next/static/chunks/app/design-check/page-72d3e7ff35a8c954.js +1 -0
  17. package/apps/pwa/out/_next/static/chunks/app/editor/{page-fed69f9759d248d0.js → page-60c7a8e1a4fe62ce.js} +1 -1
  18. package/apps/pwa/out/_next/static/chunks/app/exec/{page-15374909d8f1aebd.js → page-2a52b316cb143d9c.js} +1 -1
  19. package/apps/pwa/out/_next/static/chunks/app/field/{page-012a742f81218b01.js → page-c3ce483c8d5db370.js} +1 -1
  20. package/apps/pwa/out/_next/static/chunks/app/inside/page-13c7b53e0c2519a9.js +1 -0
  21. package/apps/pwa/out/_next/static/chunks/app/intake/{page-99effd9d893a43dc.js → page-96f074f6538e8a2c.js} +1 -1
  22. package/apps/pwa/out/_next/static/chunks/app/{layout-938de57ae0315e46.js → layout-e97be5334d83a9f5.js} +1 -1
  23. package/apps/pwa/out/_next/static/chunks/app/live/page-65e484302921a932.js +1 -0
  24. package/apps/pwa/out/_next/static/chunks/app/loops/page-34d08facaf08f556.js +1 -0
  25. package/apps/pwa/out/_next/static/chunks/app/market/{page-629e1ede482ec6eb.js → page-955ec2da3be4c560.js} +1 -1
  26. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-9f88fbc15d9058d1.js +1 -0
  27. package/apps/pwa/out/_next/static/chunks/app/morning/{page-27e99b4e54a849d4.js → page-c1343f9499d86f3f.js} +1 -1
  28. package/apps/pwa/out/_next/static/chunks/app/not-found-8739a2e0ae793130.js +1 -0
  29. package/apps/pwa/out/_next/static/chunks/app/observatory/page-c9fc5706b8abaa11.js +1 -0
  30. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/{page-32cde713be47cddb.js → page-d23b12bb92242558.js} +1 -1
  31. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-8d47f45a0dc044ac.js +1 -0
  32. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-c7fea475488201a8.js +1 -0
  33. package/apps/pwa/out/_next/static/chunks/app/outputs/{page-04250f0e2c0d03df.js → page-e2ee2896811e98d0.js} +1 -1
  34. package/apps/pwa/out/_next/static/chunks/app/page-83b19dae8619de05.js +1 -0
  35. package/apps/pwa/out/_next/static/chunks/app/reflection/{page-e64caff084f7ce21.js → page-5efbf81124c474ae.js} +1 -1
  36. package/apps/pwa/out/_next/static/chunks/app/scheduler/{page-db07eaffba0df7e3.js → page-85f1a1fc0a82738c.js} +1 -1
  37. package/apps/pwa/out/_next/static/chunks/app/sessions/{page-8ac8d71c2adf9c8a.js → page-da6c08aae0f92768.js} +1 -1
  38. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-06ee266953d9c879.js +1 -0
  39. package/apps/pwa/out/_next/static/chunks/app/settings/{page-86f91034bd5cabe5.js → page-153c2b3bcc89e8de.js} +1 -1
  40. package/apps/pwa/out/_next/static/chunks/app/setup/done/{page-94dc383d3d54db1a.js → page-87a11e6a05f15199.js} +1 -1
  41. package/apps/pwa/out/_next/static/chunks/app/setup/{page-b3759cc14da80908.js → page-7d4182964407c09c.js} +1 -1
  42. package/apps/pwa/out/_next/static/chunks/app/share/{page-ce4d8a1215ce53a3.js → page-0d6d23b40263a7ea.js} +1 -1
  43. package/apps/pwa/out/_next/static/chunks/app/showroom/page-7e8c39241f8eb6d0.js +1 -0
  44. package/apps/pwa/out/_next/static/chunks/app/tasks/page-008c1a96618a4887.js +1 -0
  45. package/apps/pwa/out/_next/static/chunks/app/term/{page-274703bc41b71333.js → page-f5597e894c02cd33.js} +1 -1
  46. package/apps/pwa/out/_next/static/chunks/app/today/page-0337fdf7fb5e37b6.js +1 -0
  47. package/apps/pwa/out/_next/static/chunks/app/trace/{page-35bdeae2e2c17147.js → page-73735b64bfe1ae67.js} +1 -1
  48. package/apps/pwa/out/_next/static/chunks/app/vault/{page-3b7e821dc492bdfd.js → page-8fcec4c9ea2e7f70.js} +1 -1
  49. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-f7e631e9b4daf66e.js +1 -0
  50. package/apps/pwa/out/_next/static/chunks/app/workflows/page-4881b8f9f63694b4.js +1 -0
  51. package/apps/pwa/out/_next/static/chunks/app/workspace/{page-2f810b18d89ecba6.js → page-9817c7f9007466cc.js} +1 -1
  52. package/apps/pwa/out/_next/static/chunks/app/worktrees/{page-134c1bc762aee62c.js → page-97f9f43172a3a90e.js} +1 -1
  53. package/apps/pwa/out/_next/static/css/1f7c958c41ffa3b9.css +1 -0
  54. package/apps/pwa/out/approvals/index.html +1 -1
  55. package/apps/pwa/out/approvals/index.txt +14 -14
  56. package/apps/pwa/out/autopilot/index.html +1 -1
  57. package/apps/pwa/out/autopilot/index.txt +14 -14
  58. package/apps/pwa/out/board/index.html +1 -1
  59. package/apps/pwa/out/board/index.txt +14 -14
  60. package/apps/pwa/out/botlab/index.html +1 -1
  61. package/apps/pwa/out/botlab/index.txt +14 -14
  62. package/apps/pwa/out/bots/index.html +1 -1
  63. package/apps/pwa/out/bots/index.txt +14 -14
  64. package/apps/pwa/out/ceo/index.html +1 -0
  65. package/apps/pwa/out/ceo/index.txt +33 -0
  66. package/apps/pwa/out/chat/index.html +1 -1
  67. package/apps/pwa/out/chat/index.txt +14 -14
  68. package/apps/pwa/out/consult/index.html +1 -1
  69. package/apps/pwa/out/consult/index.txt +14 -14
  70. package/apps/pwa/out/control/index.html +1 -1
  71. package/apps/pwa/out/control/index.txt +14 -14
  72. package/apps/pwa/out/design-check/index.html +1 -1
  73. package/apps/pwa/out/design-check/index.txt +14 -14
  74. package/apps/pwa/out/editor/index.html +1 -1
  75. package/apps/pwa/out/editor/index.txt +14 -14
  76. package/apps/pwa/out/exec/index.html +1 -1
  77. package/apps/pwa/out/exec/index.txt +14 -14
  78. package/apps/pwa/out/field/index.html +1 -1
  79. package/apps/pwa/out/field/index.txt +14 -14
  80. package/apps/pwa/out/healthz +1 -1
  81. package/apps/pwa/out/index.html +1 -1
  82. package/apps/pwa/out/index.txt +14 -14
  83. package/apps/pwa/out/inside/index.html +1 -1
  84. package/apps/pwa/out/inside/index.txt +14 -14
  85. package/apps/pwa/out/intake/index.html +1 -1
  86. package/apps/pwa/out/intake/index.txt +14 -14
  87. package/apps/pwa/out/live/index.html +1 -1
  88. package/apps/pwa/out/live/index.txt +14 -14
  89. package/apps/pwa/out/loops/index.html +1 -1
  90. package/apps/pwa/out/loops/index.txt +14 -14
  91. package/apps/pwa/out/market/index.html +1 -1
  92. package/apps/pwa/out/market/index.txt +14 -14
  93. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  94. package/apps/pwa/out/missions/placeholder/index.txt +14 -14
  95. package/apps/pwa/out/morning/index.html +1 -1
  96. package/apps/pwa/out/morning/index.txt +14 -14
  97. package/apps/pwa/out/observatory/index.html +1 -1
  98. package/apps/pwa/out/observatory/index.txt +14 -14
  99. package/apps/pwa/out/ops/checklist/index.html +1 -1
  100. package/apps/pwa/out/ops/checklist/index.txt +14 -14
  101. package/apps/pwa/out/ops/release/index.html +1 -1
  102. package/apps/pwa/out/ops/release/index.txt +14 -14
  103. package/apps/pwa/out/ops/seats/index.html +1 -1
  104. package/apps/pwa/out/ops/seats/index.txt +14 -14
  105. package/apps/pwa/out/outputs/index.html +1 -1
  106. package/apps/pwa/out/outputs/index.txt +14 -14
  107. package/apps/pwa/out/reflection/index.html +1 -1
  108. package/apps/pwa/out/reflection/index.txt +15 -15
  109. package/apps/pwa/out/scheduler/index.html +1 -1
  110. package/apps/pwa/out/scheduler/index.txt +14 -14
  111. package/apps/pwa/out/sessions/index.html +1 -1
  112. package/apps/pwa/out/sessions/index.txt +14 -14
  113. package/apps/pwa/out/settings/devices/index.html +1 -1
  114. package/apps/pwa/out/settings/devices/index.txt +14 -14
  115. package/apps/pwa/out/settings/index.html +1 -1
  116. package/apps/pwa/out/settings/index.txt +14 -14
  117. package/apps/pwa/out/setup/done/index.html +1 -1
  118. package/apps/pwa/out/setup/done/index.txt +14 -14
  119. package/apps/pwa/out/setup/index.html +1 -1
  120. package/apps/pwa/out/setup/index.txt +14 -14
  121. package/apps/pwa/out/share/index.html +1 -1
  122. package/apps/pwa/out/share/index.txt +14 -14
  123. package/apps/pwa/out/showroom/index.html +1 -1
  124. package/apps/pwa/out/showroom/index.txt +14 -14
  125. package/apps/pwa/out/tasks/index.html +1 -1
  126. package/apps/pwa/out/tasks/index.txt +14 -14
  127. package/apps/pwa/out/term/index.html +1 -1
  128. package/apps/pwa/out/term/index.txt +14 -14
  129. package/apps/pwa/out/today/index.html +1 -1
  130. package/apps/pwa/out/today/index.txt +14 -14
  131. package/apps/pwa/out/trace/index.html +1 -1
  132. package/apps/pwa/out/trace/index.txt +14 -14
  133. package/apps/pwa/out/vault/index.html +1 -1
  134. package/apps/pwa/out/vault/index.txt +14 -14
  135. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  136. package/apps/pwa/out/workflows/chat-ui/index.txt +14 -14
  137. package/apps/pwa/out/workflows/index.html +1 -1
  138. package/apps/pwa/out/workflows/index.txt +14 -14
  139. package/apps/pwa/out/workspace/index.html +1 -1
  140. package/apps/pwa/out/workspace/index.txt +14 -14
  141. package/apps/pwa/out/worktrees/index.html +1 -1
  142. package/apps/pwa/out/worktrees/index.txt +14 -14
  143. package/graphs/heal/heal-loop.yaml +1 -1
  144. package/graphs/implement-loop.yaml +1 -1
  145. package/graphs/release/recipes.yaml +3 -0
  146. package/graphs/release/release-loop.yaml +4 -2
  147. package/package.json +1 -1
  148. package/scripts/brand/check.ts +7 -5
  149. package/scripts/ci-typecheck-changed.ts +50 -18
  150. package/scripts/docs/generate-graph-anatomy.ts +315 -0
  151. package/scripts/expose-rubric-suggest.ts +18 -12
  152. package/scripts/lecture-note/node.ts +31 -11
  153. package/scripts/lessons/handbook-page.ts +30 -3
  154. package/scripts/marketing/generate-ax-brief.ts +140 -0
  155. package/scripts/marketing/generate-feature-map.ts +118 -0
  156. package/scripts/release-loop/checklist-gate-node.ts +53 -14
  157. package/scripts/release-loop/gate-node.ts +80 -14
  158. package/scripts/release-loop/vault-note-node.ts +240 -8
  159. package/scripts/release-story/draft.ts +57 -9
  160. package/scripts/rhythm/daily.ts +263 -76
  161. package/scripts/rhythm/weekly.ts +30 -9
  162. package/scripts/smart-merge/resync-shadow.ts +147 -0
  163. package/src/agent-env/profile.ts +161 -0
  164. package/src/agent-substrate/pr-reviewer.ts +2 -0
  165. package/src/ask-user-question/pending-questions.ts +5 -0
  166. package/src/auth/github-app-token.ts +112 -13
  167. package/src/autopilot/build/llm-conflict-merge.ts +32 -6
  168. package/src/briefing/brief-items.ts +121 -0
  169. package/src/claims/claims-render.ts +14 -5
  170. package/src/cli/agent-env-cli.ts +21 -0
  171. package/src/cli/decisions-cli.ts +9 -0
  172. package/src/cli/doctor-cli.ts +23 -1
  173. package/src/cli/doctor-node-modules.ts +147 -0
  174. package/src/cli/intake-cli.ts +46 -17
  175. package/src/cli/pod-cli.ts +2 -0
  176. package/src/cli/release-cli.ts +61 -27
  177. package/src/cli/seat-requests-cli.ts +52 -0
  178. package/src/context-bus/context-now-public.ts +16 -6
  179. package/src/context-bus/context-now.ts +3 -1
  180. package/src/context-bus/coord-events.ts +6 -4
  181. package/src/decisions/decision-cards.ts +33 -7
  182. package/src/decisions/proact-meter.ts +286 -0
  183. package/src/decisions/proact1-lite.ts +86 -0
  184. package/src/decisions/web-push-decision-cards.ts +1 -1
  185. package/src/domains/alert-audit.ts +271 -0
  186. package/src/domains/core-tools.ts +4 -3
  187. package/src/domains/ops-facts-tool.ts +31 -1
  188. package/src/harness/harness-cli-command.ts +111 -15
  189. package/src/harness/harness-incidents.ts +2 -0
  190. package/src/harness/harness-pod-dispatch.ts +100 -11
  191. package/src/harness/harness-queue.ts +380 -50
  192. package/src/harness/helper-repair.ts +87 -0
  193. package/src/harness/helper-scan.ts +16 -4
  194. package/src/harness/launch-stamp.ts +236 -0
  195. package/src/harness/ledger-write-guard-child.fixture.ts +12 -0
  196. package/src/harness/ledger-write-guard.ts +35 -0
  197. package/src/harness/pod-memory-policy.ts +15 -0
  198. package/src/harness/repository-name.ts +55 -0
  199. package/src/hq/fence-audit.ts +90 -0
  200. package/src/hq/hq.ts +62 -4
  201. package/src/hq/lease.ts +4 -4
  202. package/src/hq/move-plan.ts +126 -0
  203. package/src/hq/promote.ts +31 -0
  204. package/src/index.ts +77 -7
  205. package/src/intake-plane/check.ts +63 -0
  206. package/src/intake-plane/digest.ts +37 -15
  207. package/src/intake-plane/front-classifier.ts +6 -0
  208. package/src/intake-plane/items.ts +3 -2
  209. package/src/intake-plane/lens.ts +177 -0
  210. package/src/intake-plane/route.ts +145 -1
  211. package/src/launch-head/seat-goal-queue-lock.fixture.ts +15 -0
  212. package/src/launch-head/seat-goal-queue.ts +198 -0
  213. package/src/loops/budget.ts +36 -5
  214. package/src/loops/checker.ts +2 -2
  215. package/src/loops/orchestrator/author-depth.ts +65 -9
  216. package/src/loops/orchestrator/author-input-check.ts +8 -3
  217. package/src/loops/orchestrator/author-ledger.ts +3 -1
  218. package/src/loops/orchestrator/seat-share.ts +56 -0
  219. package/src/loops/orchestrator/tick.ts +11 -4
  220. package/src/loops/orchestrator/traffic.ts +117 -11
  221. package/src/loops/registry.ts +142 -61
  222. package/src/maturity/feature-maturity.ts +2 -1
  223. package/src/maturity/tui-slash-availability.ts +31 -0
  224. package/src/mss/logging/log-store.ts +35 -4
  225. package/src/nexus/api/http-server.ts +28 -14
  226. package/src/nexus/api/loop-edges.ts +173 -0
  227. package/src/nexus/api/schedules-read.ts +13 -0
  228. package/src/nexus/api/seat-requests.ts +79 -56
  229. package/src/prompt-enhance/enhance.ts +6 -1
  230. package/src/release-loop/auto-start.ts +6 -3
  231. package/src/release-loop/checklist.ts +13 -4
  232. package/src/release-loop/feature-store.ts +58 -31
  233. package/src/release-loop/merged-pr-checklist.ts +125 -0
  234. package/src/release-loop/placement.ts +86 -26
  235. package/src/seat-dispatch/seat-request-eligibility.ts +19 -0
  236. package/src/seat-dispatch/seat-request-ledger.ts +133 -0
  237. package/src/seat-loop/seat-loop.ts +126 -10
  238. package/src/self-dev/dev-pipeline.ts +8 -4
  239. package/src/self-dev/draft-sweep.ts +90 -10
  240. package/src/self-dev/draft-triage-rules.ts +25 -8
  241. package/src/self-dev/orchestrate.ts +15 -1
  242. package/src/self-dev/review-provider-fallback.ts +6 -1
  243. package/src/self-dev/run-supervisor.ts +49 -2
  244. package/src/self-implement/frozen-merges.ts +3 -2
  245. package/src/self-implement/goal-author.ts +43 -5
  246. package/src/self-implement/goal-clarification-escalation.ts +5 -0
  247. package/src/self-implement/heal-intake.ts +120 -2
  248. package/src/self-implement/host-regate.ts +9 -2
  249. package/src/self-implement/l8-merge-queue.ts +132 -0
  250. package/src/self-implement/orchestrator.ts +187 -40
  251. package/src/self-implement/pipeline-shape.ts +1 -1
  252. package/src/self-implement/revert-guard.ts +42 -7
  253. package/src/self-implement/run-ledger.ts +2 -0
  254. package/src/self-implement/run-status-mapping.ts +2 -0
  255. package/src/self-implement/seams.ts +1 -1
  256. package/src/self-implement/self-implement-cli.ts +1 -0
  257. package/src/self-implement/speed-claims.ts +338 -0
  258. package/src/steward/triage.ts +74 -3
  259. package/src/steward/working-backwards.ts +4 -1
  260. package/src/task-orchestrator/surfaces/pod-artifact-return.ts +63 -48
  261. package/src/task-orchestrator/surfaces/pod-command-job.ts +84 -9
  262. package/src/task-orchestrator/surfaces/pod-failure-reason.ts +48 -0
  263. package/src/task-orchestrator/surfaces/pod-pool.ts +60 -8
  264. package/src/task-orchestrator/surfaces/self-implement-pod.ts +147 -36
  265. package/src/task-orchestrator/surfaces/self-implement.ts +5 -1
  266. package/src/telegram.ts +25 -4
  267. package/src/user-config.ts +29 -5
  268. package/src/version/packed-revision.json +1 -1
  269. package/src/workflow-runtime/nodes/bash.ts +9 -1
  270. package/apps/pwa/out/_next/static/chunks/4562-071d8bac44a01069.js +0 -1
  271. package/apps/pwa/out/_next/static/chunks/5977-2df489622a52a33a.js +0 -1
  272. package/apps/pwa/out/_next/static/chunks/app/board/page-c496bb16da85385f.js +0 -1
  273. package/apps/pwa/out/_next/static/chunks/app/botlab/page-1abfe3aec3be6bdc.js +0 -1
  274. package/apps/pwa/out/_next/static/chunks/app/control/page-e6b84ab07d3567d5.js +0 -1
  275. package/apps/pwa/out/_next/static/chunks/app/design-check/page-61ec1d2e92bdd50a.js +0 -1
  276. package/apps/pwa/out/_next/static/chunks/app/inside/page-fd7e200d4d83fbd7.js +0 -1
  277. package/apps/pwa/out/_next/static/chunks/app/live/page-d47266766766d227.js +0 -1
  278. package/apps/pwa/out/_next/static/chunks/app/loops/page-160089123a40dd91.js +0 -1
  279. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-2ce5471926761fdd.js +0 -1
  280. package/apps/pwa/out/_next/static/chunks/app/not-found-e995d30f6800b6bf.js +0 -1
  281. package/apps/pwa/out/_next/static/chunks/app/observatory/page-39f89c53038a51d4.js +0 -1
  282. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-e7e10cd3386222e5.js +0 -1
  283. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-5a753bb4ec0cc511.js +0 -1
  284. package/apps/pwa/out/_next/static/chunks/app/page-341480ed8e3c1ea8.js +0 -1
  285. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-e1e47d8d940aba3d.js +0 -1
  286. package/apps/pwa/out/_next/static/chunks/app/showroom/page-58043db78ba519ae.js +0 -1
  287. package/apps/pwa/out/_next/static/chunks/app/tasks/page-357750dfb91bf960.js +0 -1
  288. package/apps/pwa/out/_next/static/chunks/app/today/page-194dce4a83143fce.js +0 -1
  289. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-16bef3da05a900c3.js +0 -1
  290. package/apps/pwa/out/_next/static/chunks/app/workflows/page-f8da5dc6f3752276.js +0 -1
  291. package/apps/pwa/out/_next/static/css/2e33713c444d5b39.css +0 -1
  292. package/apps/pwa/out/_next/static/x6mjI6vqOkVVu0jFuDkz1/_buildManifest.js +0 -1
  293. /package/apps/pwa/out/_next/static/{x6mjI6vqOkVVu0jFuDkz1 → LZcstaO9_NXypPFmujolv}/_ssgManifest.js +0 -0
@@ -15,10 +15,10 @@ import { podSkillsDigest, readSkillEnvFiles, resolvePodSkills } from './pod-skil
15
15
  import { podSourceScript, type PodSource } from './pod-source-receive.js';
16
16
  import { GROUNDING_TOKEN_ENV, GROUNDING_URL_ENV, mintGroundingToken, revokeGroundingRun, type GroundingTokenScope } from '../../grounding/token.js';
17
17
  import { POD_CREDENTIAL_GROK_PATH, POD_CREDENTIAL_GITHUB_PATH, POD_GITHUB_CREDENTIAL_TOKEN_ENV, POD_GITHUB_CREDENTIAL_URL_ENV, installationRepositories } from '../../nexus/api/pod-credential-api.js';
18
- import { githubInstallationCredential } from '../../auth/github-app-token.js';
18
+ import { coalescedInstallationCredential } from '../../auth/github-app-token.js';
19
19
  import { POD_CREDENTIAL_TOKEN_ENV, POD_CREDENTIAL_URL_ENV } from '../../grok/credential.js';
20
20
  import { collectPodLedgers, createPodLedgerFollower } from './pod-ledger-collect.js';
21
- import { collectPodArtifacts } from './pod-artifact-return.js';
21
+ import { collectPodArtifacts, podNoResultDiagnostic } from './pod-artifact-return.js';
22
22
  import { isValidAccountName, resolveCodexAccount } from '../../oauth/codex-account.js';
23
23
  import { effectiveInstanceRoot } from '../../instance/resolve.js';
24
24
  import { controlInboxEnv } from '../../harness/control-inbox.js';
@@ -59,8 +59,10 @@ import { LLM_TIER_MAP_BY_PROVIDER, lookupLlmTierSpec, type LlmTierProvider } fro
59
59
  import { parseSelfImplementJson, type SelfImplementJobDone, type SelfImplementJobSpawn } from './self-implement.js';
60
60
  import { codexQuotaPolicyFromConfig } from '../../oauth/codex-account-store.js';
61
61
  import { goalTypeOf, type GoalType } from '../../../scripts/measure-pod-memory-by-goal.js';
62
+ import { declaredGoalType, type GoalType as DeclaredGoalType } from '../../self-implement/goal-author.js';
62
63
  import { readPodMemoryAdvice, type PodMemoryAdvice } from '../../cli/pod-memory-advice.js';
63
64
  import { getUserConfig } from '../../user-config.js';
65
+ import { extractPodFailureReason } from './pod-failure-reason.js';
64
66
 
65
67
  export type Kubectl = (args: readonly string[], input?: string) => { status: number | null; stdout: string; stderr: string };
66
68
 
@@ -83,6 +85,11 @@ export interface PodSpawnOptions {
83
85
  rotationAccounts?: readonly string[];
84
86
  /** 미지정이면 종전 Codex Job. */
85
87
  provider?: 'openai-codex' | 'grok';
88
+ /** Child model/effort chosen at launch (`--child-llm-model/--child-llm-effort`) — carried into the Pod child. */
89
+ childModel?: string;
90
+ childEffort?: string;
91
+ /** True when the child provider was named at launch — the child gets explicit flags even for codex. */
92
+ childProviderExplicit?: boolean;
86
93
  /** 🔐 API 키 과금 허용은 명시 true 뿐. */
87
94
  grokApiKeyOptIn?: boolean;
88
95
  namespace?: string;
@@ -108,7 +115,7 @@ export interface PodSpawnOptions {
108
115
  pollMs?: number;
109
116
  kubectl?: Kubectl;
110
117
  /** Host-side regate (injected for Pod tests). */
111
- hostRegate?: (input: { prNumber: number; headCommit: string; repoRoot: string }) => Promise<HostRegateResult>;
118
+ hostRegate?: (input: { prNumber: number; headCommit: string; repoRoot: string; goalFile?: string }) => Promise<HostRegateResult>;
112
119
  /** PR comment for host-regate failures that never reached runHostRegate (injectable for tests). */
113
120
  ghComment?: (prNumber: number, body: string) => void;
114
121
  /** 🔑 Pod 필수 스킬의 키(.env)를 이 런의 Secret 으로 넘긴다 — 명시 opt-in(유료 크레딧을 쓴다). */
@@ -123,7 +130,8 @@ export interface PodSpawnOptions {
123
130
  mintGrounding?: (claims: { runId: string; job: string; ttlMs: number; scope?: GroundingTokenScope; repository?: string }) => Promise<{ token: string; exp: number }>;
124
131
  revokeGrounding?: (runId: string) => void;
125
132
  /** Test seam for the host's repository-scoped GitHub App installation token. */
126
- githubInstallation?: (repository: string) => { token: string; expires_at?: string; expiresAt?: string | number } | null;
133
+ /** `fresh` = do not reuse a coalesced token (the exit-7 retry must not get the token that just failed back). */
134
+ githubInstallation?: (repository: string, opts?: { fresh?: boolean }) => { token: string; expires_at?: string; expiresAt?: string | number } | null;
127
135
  /** GitHub installation repository lookup; required to prove the scoped token matches owner/name. */
128
136
  githubRepositories?: (token: string) => Promise<readonly string[] | null>;
129
137
  now?: () => number;
@@ -184,6 +192,17 @@ export function hostCredentials(account: string, storePath: string = authStorePa
184
192
 
185
193
  /** A Pod gets a refresh-less access copy, so the copy must outlive the Job: same rule as the Codex path (3 h). */
186
194
  export const POD_GROK_MIN_VALIDITY_MS = 3 * 3600_000;
195
+ /** PODCRED1 (10-05): an App installation token lives ~60 min and the host renews it in the Pod at the ten-minute
196
+ * boundary; below this remaining life at launch the token is observed (`github-app-short-lived`). */
197
+ export const POD_APP_TOKEN_MIN_START_MS = 20 * 60_000;
198
+ /** Exit 7 (in-Pod GitHub login failed) is retried once after this spread backoff — bursts were simultaneous launches. */
199
+ export const POD_GH_LOGIN_RETRY_BACKOFF_MS = 60_000;
200
+ /** Base backoff plus a 0-59 s spread derived from the job name, so jobs that failed together retry apart. */
201
+ export function podGhLoginRetryBackoffMs(job: string): number {
202
+ let spread = 0;
203
+ for (const ch of job) spread = (spread * 31 + ch.charCodeAt(0)) % 60;
204
+ return POD_GH_LOGIN_RETRY_BACKOFF_MS + spread * 1000;
205
+ }
187
206
 
188
207
  /** resolveGrokCredential 과 동일한 출처를 읽되 Pod 로는 refresh 없는 access 사본만 보낸다.
189
208
  * POD4 (10-01): the copy used to ship whatever the host had — an AUTH1 shard copied a token that had expired
@@ -453,11 +472,11 @@ export function podRunLabels(o: { runId?: string; parentRunId?: string }): Recor
453
472
 
454
473
  /** Pod 메모리 «등급» — 대표 09-27: PWA Pod 골이 16Gi 에서 OOM 으로 죽는다 ⇒ «high» 등급을 옵션으로 고른다.
455
474
  * 등급 = `standard`(`ELANOUS_POD_MEMORY` 또는 16Gi) · `high`(`ELANOUS_POD_MEMORY_HIGH` 또는 32Gi · standard 보다 작아지지 않는다).
456
- * 고르는 순서(앞이 이긴다): ① 발사 옵션 `--pod-memory <등급>`(→ `ELANOUS_POD_MEMORY_TIER`) ② 조각 문면 한 줄 `Pod 메모리: high|standard` ③ 조각의 `apps/pwa/` ④ 부모 골 본문의 같은 두 규칙 ⑤ standard.
475
+ * 고르는 순서(앞이 이긴다): ① 발사 옵션 `--pod-memory <등급>`(→ `ELANOUS_POD_MEMORY_TIER`) ② 조각 문면 `Pod 메모리: lite|standard|high` ③ 조각의 `apps/pwa/` ④ 부모의 명시 메모리 줄·PWA 경로 ⑤ 실측 권고 ⑥ 권고 없는 research/document 는 lite ⑦ standard.
457
476
  * node-b 노드 할당 가능 약 251Gi. */
458
477
  export const POD_MEMORY_DEFAULT = '16Gi';
459
478
  export const POD_MEMORY_HIGH_DEFAULT = '32Gi';
460
- /** POD7 — `lite`(`ELANOUS_POD_MEMORY_LITE` 또는 2Gi): 명시할 때만(인자·문면 줄) · 자동 판정은 없다(성공 런 피크 기록 0 — 설계 §②). OOM 이면 POD9 가 high 로 한 번 다시 띄운다. */
479
+ /** POD7 — `lite`(`ELANOUS_POD_MEMORY_LITE` 또는 2Gi): research/document 기본값; OOM 시 standard 로 한 단계 승급한다. */
461
480
  export const POD_MEMORY_LITE_DEFAULT = '2Gi';
462
481
 
463
482
  /** Reserve from measured per-run peaks, never from the advisory limit tier. Sparse or missing measurements keep the old reservation. */
@@ -475,7 +494,7 @@ export function podMemoryRequestFor(kind: GoalType | null, advice?: PodMemoryAdv
475
494
  }
476
495
  export const POD_MEMORY_TIERS = ['lite', 'standard', 'high'] as const;
477
496
  export type PodMemoryTier = (typeof POD_MEMORY_TIERS)[number];
478
- export type PodMemorySource = 'option' | 'goal-line' | 'pwa-auto' | 'default' | 'parent-goal-line' | 'parent-pwa-auto' | 'advise';
497
+ export type PodMemorySource = 'option' | 'goal-line' | 'pwa-auto' | 'default' | 'parent-goal-line' | 'parent-pwa-auto' | 'advise' | 'goal-type-auto';
479
498
  const PWA_PATH = /(?:^|[\s`'"(,·])apps\/pwa\//m;
480
499
  const GOAL_LINE = /^[ \t]*(?:Pod 메모리|pod-memory)[ \t]*:[ \t]*(lite|standard|high)[ \t]*\r?$/im;
481
500
 
@@ -511,6 +530,10 @@ export function parsePodMemoryTier(v: string | undefined): PodMemoryTier | null
511
530
  return t && (POD_MEMORY_TIERS as readonly string[]).includes(t) ? (t as PodMemoryTier) : null;
512
531
  }
513
532
 
533
+ function parsePodGoalType(value: string | undefined): DeclaredGoalType | null {
534
+ return value === 'implement' || value === 'research' || value === 'document' || value === 'operate' ? value : null;
535
+ }
536
+
514
537
  function memoryGi(v: string): number | null {
515
538
  const m = /^(\d+(?:\.\d+)?)(Gi|Mi)$/.exec(v);
516
539
  if (!m) return null;
@@ -521,9 +544,13 @@ export function podMemoryLimitFor(feature: string, env: NodeJS.ProcessEnv | Read
521
544
  const option = parsePodMemoryTier(env.ELANOUS_POD_MEMORY_TIER);
522
545
  const line = parsePodMemoryTier(GOAL_LINE.exec(feature)?.[1]);
523
546
  const parentLine = !option && !line ? parsePodMemoryTier(GOAL_LINE.exec(parentGoal ?? '')?.[1]) : null;
547
+ const declared = parsePodGoalType(env.ELANOUS_POD_GOAL_TYPE)
548
+ ?? declaredGoalType(feature) ?? declaredGoalType(parentGoal ?? '');
549
+ // 선언 «document» 는 실측 분류의 «docs» 다 — 낱말 판정(goalTypeOf)이 «document» 를 못 읽어 권고가 안 이어지던 구멍
524
550
  const goalType = advice && !option && !line && !parentLine
525
551
  ? (broadPodTestWarning(feature) ? 'test' : goalTypeOf(feature))
526
552
  ?? (parentGoal ? (broadPodTestWarning(parentGoal) ? 'test' : goalTypeOf(parentGoal)) : null)
553
+ ?? (declared === 'document' ? 'docs' : null)
527
554
  : null;
528
555
  const recommendation = advice?.byGoalType.find((entry) => entry.goalType === goalType && entry.evidence.runs > 0)?.recommended;
529
556
  const [tier, source]: [PodMemoryTier, PodMemorySource] = option ? [option, 'option']
@@ -531,8 +558,11 @@ export function podMemoryLimitFor(feature: string, env: NodeJS.ProcessEnv | Read
531
558
  // 조각 자신의 PWA 경로가 부모 줄·실측 권고보다 먼저다 — 부모 «standard» 가 PWA 조각을 16Gi 로 내리면 OOM(POD7 · #23569 순서 회귀)
532
559
  : goalTouchesPwa(feature) ? ['high', 'pwa-auto']
533
560
  : parentLine ? [parentLine, 'parent-goal-line']
534
- : recommendation ? [recommendation, 'advise']
535
561
  : parentGoal && goalTouchesPwa(parentGoal) ? ['high', 'parent-pwa-auto']
562
+ // 실측이 high(OOM 증거)를 권하면 선언 종류의 자동 lite 보다 먼저다 — 문서 골도 OOM 이 났으면 다시 2Gi 로 띄우지 않는다
563
+ : recommendation === 'high' ? ['high', 'advise']
564
+ : declared === 'research' || declared === 'document' ? ['lite', 'goal-type-auto']
565
+ : recommendation ? [recommendation, 'advise']
536
566
  : ['standard', 'default'];
537
567
  if (tier === 'lite') return { limit: env.ELANOUS_POD_MEMORY_LITE?.trim() || POD_MEMORY_LITE_DEFAULT, tier, source };
538
568
  const base = env.ELANOUS_POD_MEMORY?.trim() || POD_MEMORY_DEFAULT;
@@ -542,6 +572,28 @@ export function podMemoryLimitFor(feature: string, env: NodeJS.ProcessEnv | Read
542
572
  return { limit: a !== null && b !== null && a > b ? base : high, tier, source };
543
573
  }
544
574
 
575
+ /** A named child provider decides the Pod provider (10-05 PODPROVIDER) — grok needs a usable credential, other names are refused. */
576
+ export function podNamedChildProvider(named: string | undefined, credential: { grokSubscription: boolean; grokApiKey: boolean }): { provider?: 'openai-codex' | 'grok'; refuse?: string } {
577
+ if (!named) return {};
578
+ if (named === 'openai-codex' || named === 'codex') return { provider: 'openai-codex' };
579
+ if (named === 'grok') return credential.grokSubscription || credential.grokApiKey ? { provider: 'grok' } : { refuse: 'pod: Pod 에 grok 자격 없음 — 구독 자격이 쓸 수 없거나(만료 임박·갱신 불가) API 키 과금 동의(harness.pod.grokApiKeyOptIn)가 없다 · codex 로 조용히 돌리지 않는다' };
580
+ return { refuse: `pod: Pod 자식 provider 는 openai-codex|grok 만 — 받음 ${named}` };
581
+ }
582
+
583
+ /** Fallback credentials for the codex plan — a launch that named openai-codex never falls back to grok (post-review must-fix). */
584
+ export function podFallbackCredentials(named: 'openai-codex' | 'grok' | undefined, fallback: { grokSubscription: boolean; grokApiKey: boolean }): { grokSubscription: boolean; grokApiKey: boolean } {
585
+ return named === 'openai-codex' ? { grokSubscription: false, grokApiKey: false } : fallback;
586
+ }
587
+
588
+ /** Child LLM flags for the Pod child — grok always names its model; codex only when the launch named it (10-05 PODPROVIDER). */
589
+ export function podChildLlmArgs(options: Pick<PodSpawnOptions, 'provider' | 'childModel' | 'childEffort' | 'childProviderExplicit'>): string[] {
590
+ const provider = options.provider === 'grok' ? 'grok' : options.childProviderExplicit ? 'openai-codex' : undefined;
591
+ if (!provider) return [];
592
+ const model = options.childModel?.trim() || (provider === 'grok' ? defaultGrokModel().id : undefined);
593
+ // `self implement` registers no --child-llm-effort — passing it kills the Pod at argument parsing, so effort stays host-only.
594
+ return ['--child-llm-provider', provider, ...(model ? ['--child-llm-model', model] : [])];
595
+ }
596
+
545
597
  export function podJobManifest(o: { name: string; namespace: string; image: string; /** 레지스트리 이미지면 IfNotPresent(노드가 pull) · 반입 이미지면 Never. */ imagePullPolicy?: 'Never' | 'IfNotPresent'; repoUrl: string; source?: PodSource; hostMirror?: string; args: readonly string[]; passEnv: readonly string[]; deadlineSeconds: number; runId?: string; parentRunId?: string; armEnv?: Readonly<Record<string, string>>; hostId?: string; imageCommit?: string | null; skillEnvs?: readonly string[]; memoryLimit?: string; memoryRequest?: string; goalDoc?: string; grokCredential?: 'subscription' | 'api_key'; codexAccounts?: readonly string[]; appCredential?: boolean; /** Test seam — default POD_GH_STALE_SECONDS. */ githubStaleSeconds?: number; /** Which goal execution and attempt this Job is — a resumed host verifies it before following the Job (POD9). */ execution?: { key: string; attempt: number }; hostLeaseAdmitted?: boolean }): Record<string, unknown> {
546
598
  const quoted = o.args.map((a) => `'${a.replace(/'/g, `'\\''`)}'`).join(' ');
547
599
  const goalPath = o.goalDoc ? `'${(o.goalDoc.startsWith('-') ? `./${o.goalDoc}` : o.goalDoc).replace(/'/g, `'\\''`)}'` : undefined;
@@ -735,7 +787,8 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
735
787
  catch (error) { debug.log('self-implement.pod', 'memory-advice-unavailable', { spaceId: input.spaceId, reason: error instanceof Error ? error.message : String(error) }, { level: 'warn' }); }
736
788
  const { limit: selectedMemoryLimit, tier: memoryTier, source: memorySource } = podMemoryLimitFor(input.feature, env, parentGoal, useAdvice && !explicitMemory ? advice : undefined);
737
789
  const requestKind = broadPodTestWarning(input.feature) ? 'test' : goalTypeOf(input.feature)
738
- ?? (parentGoal ? (broadPodTestWarning(parentGoal) ? 'test' : goalTypeOf(parentGoal)) : null);
790
+ ?? (parentGoal ? (broadPodTestWarning(parentGoal) ? 'test' : goalTypeOf(parentGoal)) : null)
791
+ ?? ((parsePodGoalType(env.ELANOUS_POD_GOAL_TYPE) ?? declaredGoalType(input.feature) ?? declaredGoalType(parentGoal ?? '')) === 'document' ? 'docs' : null);
739
792
  for (const [where, text] of [['feature', input.feature], ['parent-goal', parentGoal]] as const) {
740
793
  if (text && text.split(/\r?\n/u).some((line) => /Pod 메모리/u.test(line) && !GOAL_LINE.test(line))) {
741
794
  const reason = '한 줄 단독 `Pod 메모리: high|standard` 형식이 아님';
@@ -749,9 +802,14 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
749
802
  const after = input.after ?? podPredecessorFor(input.feature, env, parentGoal);
750
803
  if (after !== undefined) debug.log('self-implement.pod', 'predecessor', { spaceId: input.spaceId, after, source: input.after !== undefined ? 'input' : 'goal-or-option' });
751
804
  let oomRetried = false;
805
+ let retryTier: PodMemoryTier = memoryTier === 'lite' ? 'standard' : 'high';
752
806
  let retryFromChildRunId: string | undefined;
753
807
  let retryFromMemoryLimit: string | undefined;
754
- debug.log('self-implement.pod', 'memory-limit', { spaceId: input.spaceId, goalType: requestKind, memoryLimit, tier: memoryTier, source: memorySource,
808
+ const selectedGoalType = parsePodGoalType(env.ELANOUS_POD_GOAL_TYPE) ?? declaredGoalType(input.feature) ?? declaredGoalType(parentGoal ?? '');
809
+ const memoryReason = memorySource === 'option' && ['explicit-option', 'goal-type-default', 'existing-tier'].includes(env.ELANOUS_POD_MEMORY_REASON ?? '')
810
+ ? env.ELANOUS_POD_MEMORY_REASON!
811
+ : memorySource === 'goal-type-auto' ? `declared goal type ${selectedGoalType}` : memorySource;
812
+ debug.log('self-implement.pod', 'memory-limit', { spaceId: input.spaceId, goalType: requestKind, declaredGoalType: selectedGoalType, memoryLimit, tier: memoryTier, source: memorySource, reason: memoryReason,
755
813
  ...(memorySource.startsWith('parent-') ? { inheritedFrom } : {}), ...(parentGoalUnreadable ? { parentGoal: 'unreadable' } : {}) });
756
814
  const childRunId = mintRunId();
757
815
  const executionKey = createHash('sha256').update(JSON.stringify({ spaceId: input.spaceId, feature: input.feature, base: input.base, autoMerge: input.autoMerge, autoReview: input.autoReview, openPr: input.openPr, draft: input.draft })).digest('hex');
@@ -769,6 +827,8 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
769
827
  if (typeof intent.data.to !== 'string' || memoryGi(intent.data.to) === null) throw new Error('invalid OOM retry memory limit');
770
828
  oomRetried = true;
771
829
  memoryLimit = intent.data.to;
830
+ retryTier = parsePodMemoryTier(typeof intent.data.tier === 'string' ? intent.data.tier : undefined)
831
+ ?? ((memoryGi(intent.data.to) ?? 0) >= (memoryGi(podMemoryLimitFor(input.feature, { ...env, ELANOUS_POD_MEMORY_TIER: 'high' }).limit) ?? Infinity) ? 'high' : 'standard');
772
832
  retryFromChildRunId = typeof intent.data.fromChildRunId === 'string' ? intent.data.fromChildRunId : undefined;
773
833
  retryFromMemoryLimit = typeof intent.data.from === 'string' ? intent.data.from : undefined;
774
834
  }
@@ -783,7 +843,7 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
783
843
  ...(input.draft === false ? ['--no-draft'] : []),
784
844
  ...(hostSupervised ? ['--no-supervise'] : []),
785
845
  ...(options.extraArgs ?? []),
786
- ...(options.provider === 'grok' ? ['--child-llm-provider', 'grok', '--child-llm-model', defaultGrokModel().id] : []),
846
+ ...podChildLlmArgs(options),
787
847
  ];
788
848
  debug.log('pod.self-implement', 'child-supervise', { hostSupervised, childSupervise: !hostSupervised });
789
849
  const done = (async (): Promise<SelfImplementJobDone> => {
@@ -800,7 +860,7 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
800
860
  if (!options.pool) return null;
801
861
  for (;;) {
802
862
  if (input.signal?.aborted) return null;
803
- const acquired = options.pool.tryAcquire();
863
+ const acquired = await options.pool.tryAcquire();
804
864
  if (acquired) {
805
865
  debug.log('self-implement.pod', 'pool-slot', { spaceId: input.spaceId, context: acquired.context, inflight: options.pool.snapshot() });
806
866
  return acquired;
@@ -868,6 +928,7 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
868
928
  let retrySamples: ReturnType<typeof parseMemSamples> = [];
869
929
  let failedAccount: string | undefined;
870
930
  let usageLimitRetried = false;
931
+ let ghLoginRetried = false;
871
932
  for (;;) {
872
933
  try {
873
934
  const grok = options.provider === 'grok';
@@ -930,10 +991,10 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
930
991
  debug.log('self-implement.pod', 'credential-relay-skipped', { reason: 'no-host-url' });
931
992
  }
932
993
  const now = options.now ?? Date.now;
933
- const issueVerifiedAppCredential = async (): Promise<{ token: string; expiresAt: number } | null> => {
994
+ const issueVerifiedAppCredential = async (fresh = false): Promise<{ token: string; expiresAt: number } | null> => {
934
995
  if (!repository) return null;
935
996
  // This is the same repository-scoped, contents+pull_requests installation request on launch and refresh.
936
- const candidate = (options.githubInstallation ?? ((repo) => githubInstallationCredential({ scope: { repository: repo.split('/')[1]! } })))(repository);
997
+ const candidate = (options.githubInstallation ?? ((repo, o) => coalescedInstallationCredential({ repository: repo.split('/')[1]! }, { fresh: o?.fresh })))(repository, { fresh });
937
998
  if (!candidate?.token) return null;
938
999
  const expiry = candidate.expires_at ?? ('expiresAt' in candidate ? candidate.expiresAt : undefined);
939
1000
  const expiresAt = typeof expiry === 'number' ? expiry : Date.parse(expiry ?? '');
@@ -943,8 +1004,13 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
943
1004
  ? { token: candidate.token, expiresAt } : null;
944
1005
  };
945
1006
  let appCredential: Awaited<ReturnType<typeof issueVerifiedAppCredential>> = null;
946
- try { appCredential = await issueVerifiedAppCredential(); }
1007
+ try { appCredential = await issueVerifiedAppCredential(ghLoginRetried); }
947
1008
  catch { debug.log('self-implement.pod', 'github-app-unavailable', { job: name, reason: 'mint-or-verification-failed' }); }
1009
+ // PODCRED1: a short token is still shipped — the host renews it in the Pod on the first live poll — but it is
1010
+ // observed, so an exit-7 burst can be checked against the remaining life the Pods actually started with.
1011
+ if (appCredential && appCredential.expiresAt - now() < POD_APP_TOKEN_MIN_START_MS) {
1012
+ debug.log('self-implement.pod', 'github-app-short-lived', { job: name, expiresAt: new Date(appCredential.expiresAt).toISOString(), requiredSeconds: POD_APP_TOKEN_MIN_START_MS / 1000 });
1013
+ }
948
1014
  const githubRelay = appCredential && groundingUrl && repository
949
1015
  ? await mint({ runId: groundingRunId, job: name, ttlMs: groundingTtlMs, scope: 'gh-credential', repository })
950
1016
  : null;
@@ -1042,7 +1108,7 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
1042
1108
  // 🩸 종전엔 여기서 delete 부터 해서, 재개가 원격에서 멀쩡히 돌던 Job 을 죽였다. 실패로 끝난 Job 만 지우고 다시 만든다.
1043
1109
  // ☸️ 노드 레지스트리에 이 판이 있으면 그것을 pull 한다(델타 · 대표 2026-09-26) — 라벨 판정은 로컬 이미지 이름으로 한다.
1044
1110
  const jobImage = member?.imageRef ?? image;
1045
- const launchRunId = retryAccount || oomRetried ? mintRunId() : childRunId;
1111
+ const launchRunId = retryAccount || oomRetried || ghLoginRetried ? mintRunId() : childRunId;
1046
1112
  let liveChildRunId = launchRunId;
1047
1113
  let reattachedMemoryLimit: string | undefined;
1048
1114
  let existingExecutionKey: string | undefined;
@@ -1083,7 +1149,10 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
1083
1149
  }
1084
1150
  if (failedExisting) {
1085
1151
  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}']);
1086
- const failedOom = pods.status === 0 && pods.stdout.split('\n').some((line) => { const fields = line.split('\t'); return fields[1] === 'OOMKilled' && fields[2]?.trim() === '137'; });
1152
+ const latestPod = pods.status === 0 ? pods.stdout.trimEnd().split('\n').map((line) => line.split('\t'))
1153
+ .filter(([created]) => created && !Number.isNaN(Date.parse(created)))
1154
+ .sort((a, b) => Date.parse(b[0]!) - Date.parse(a[0]!))[0] : undefined;
1155
+ const failedOom = latestPod?.[1]?.trim() === 'OOMKilled' && latestPod[2]?.trim() === '137';
1087
1156
  // Once attempt 2 exists, consume its terminal result even if it failed for a non-OOM reason.
1088
1157
  // Only the old, lower-memory attempt may be removed to make room for high.
1089
1158
  if (failedOom || oomRetried) reattach = !oomRetried || (memoryGi(reattachedMemoryLimit ?? '') ?? 0) >= (memoryGi(memoryLimit) ?? Infinity);
@@ -1096,21 +1165,22 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
1096
1165
  debug.log('self-implement.pod', 'job-reattach', { job: name, conditions: existingConditions.join(' ') || 'running', spaceId: input.spaceId });
1097
1166
  }
1098
1167
  if (!hostRefresh && ghTokenExpiresAt === null && !githubRelay) debug.log('self-implement.pod', 'gh-token-app-skipped', { job: name, reason: 'no-verified-app-or-refresh-path' });
1168
+ let appliedMemoryRequest: string | undefined;
1099
1169
  if (!reattach) {
1100
1170
  if (oomRetried && hasExistingJob && !failedExisting && memoryGi(reattachedMemoryLimit ?? '') === null) {
1101
1171
  return { exitCode: 1, output: '', error: { code: 'pod-memory-unavailable', message: `Job ${name} 실제 메모리 한도를 확인할 수 없다` } };
1102
1172
  }
1103
1173
  if (oomRetried && !retryAccount) {
1104
- const high = podMemoryLimitFor(input.feature, { ...env, ELANOUS_POD_MEMORY_TIER: 'high' }).limit;
1105
- if ((memoryGi(memoryLimit) ?? 0) < (memoryGi(high) ?? Infinity)) throw new Error(`pod: OOM 재시도 한도가 high 와 다름: ${memoryLimit}`);
1174
+ const retryLimit = podMemoryLimitFor(input.feature, { ...env, ELANOUS_POD_MEMORY_TIER: retryTier }).limit;
1175
+ if ((memoryGi(memoryLimit) ?? 0) < (memoryGi(retryLimit) ?? Infinity)) throw new Error(`pod: OOM 재시도 한도가 ${retryTier} 와 다름: ${memoryLimit}`);
1106
1176
  const leaseMembers = member ? [member] : [{ context, capacity: 1, k3dCluster: '' }];
1107
1177
  const lease = recommendConcurrency(measurePoolLease(leaseMembers, { kubectl: (args) => baseKubectl(args) }), {
1108
- capacity: leaseMembers.reduce((sum, item) => sum + item.capacity, 0), perGoalMemory: high, accounts: 0, perAccount: 0,
1178
+ capacity: leaseMembers.reduce((sum, item) => sum + item.capacity, 0), perGoalMemory: retryLimit, accounts: 0, perAccount: 0,
1109
1179
  });
1110
1180
  if (lease.recommended === null || lease.placeableSlots === null || lease.placeableSlots < 1 || lease.memorySlots === null || lease.memorySlots < 1) {
1111
- return { exitCode: 1, output: '', error: { code: 'pod-lease', message: `OOM 재시도 high 자리 판정 실패: ${lease.reason ?? lease.limitedBy ?? 'no capacity'}\n${formatLastMemSample(retrySamples.at(-1))}` } };
1181
+ return { exitCode: 1, output: '', error: { code: 'pod-lease', message: `OOM 재시도 ${retryTier} 자리 판정 실패: ${lease.reason ?? lease.limitedBy ?? 'no capacity'}\n${formatLastMemSample(retrySamples.at(-1))}` } };
1112
1182
  }
1113
- debug.log('self-implement.pod', 'oom-retry-lease', { job: name, context, memoryLimit: high, recommended: lease.recommended });
1183
+ debug.log('self-implement.pod', 'oom-retry-lease', { job: name, context, memoryLimit: retryLimit, tier: retryTier, recommended: lease.recommended });
1114
1184
  }
1115
1185
  if (failedExisting && !reattach && !oomRetried && collectFullLogs('failed-job-logs-unavailable').status === 'incomplete') {
1116
1186
  return { exitCode: 1, output: '', error: { code: 'pod-collection-incomplete', message: `Job ${name} recovery incomplete — old Job retained` } };
@@ -1146,7 +1216,14 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
1146
1216
  const measuredRequest = podMemoryRequestFor(requestKind, advice);
1147
1217
  const requestGi = memoryGi(measuredRequest);
1148
1218
  const limitGi = memoryGi(memoryLimit);
1149
- const memoryRequest = requestGi !== null && limitGi !== null && requestGi > limitGi ? memoryLimit : measuredRequest;
1219
+ const documentDefaultLite = !oomRetried && selectedGoalType === 'document' && memoryTier === 'lite'
1220
+ && (memorySource === 'goal-type-auto' || memoryReason === 'goal-type-default');
1221
+ const memoryRequest = documentDefaultLite ? memoryLimit
1222
+ : requestGi !== null && limitGi !== null && requestGi > limitGi ? memoryLimit : measuredRequest;
1223
+ appliedMemoryRequest = memoryRequest;
1224
+ debug.log('self-implement.pod', 'memory-request', { spaceId: input.spaceId, job: name,
1225
+ tier: oomRetried ? retryTier : memoryTier, reason: oomRetried ? 'OOMKilled' : memoryReason,
1226
+ memoryLimit, memoryRequest });
1150
1227
  const job = podJobManifest({ name, namespace, image: jobImage, ...(member?.imageRef ? { imagePullPolicy: 'IfNotPresent' as const } : {}), repoUrl, ...(options.source ? { source: options.source } : {}), ...(hostMirror ? { hostMirror } : {}), args, passEnv: jobPassEnv, deadlineSeconds: options.deadlineSeconds ?? POD_JOB_DEADLINE_SECONDS, ...(goalDoc ? { goalDoc } : {}), runId: launchRunId, ...(parentRunId ? { parentRunId } : {}), ...(jobArmEnv ? { armEnv: jobArmEnv } : {}), hostId: resolveHostId(env), skillEnvs: Object.keys(skillEnvs), memoryLimit, memoryRequest, imageCommit: options.imageCommit !== undefined ? options.imageCommit : options.kubectl ? null : podImageFreshness({ image }).imageCommit, ...(grok ? { grokCredential: 'grokAuth' in creds && creds.grokAuth ? 'subscription' as const : 'api_key' as const } : {}), ...(codexAccounts ? { codexAccounts } : {}), ...(hostRefresh ? { appCredential: true } : {}), execution: { key: executionKey, attempt: oomRetried ? 2 : 1 }, hostLeaseAdmitted: !!releaseAdmission }); // kubectl 주입(=시험)이면 docker 를 부르지 않는다
1151
1228
  const a = kubectl(['apply', '-f', '-'], JSON.stringify(job));
1152
1229
  if (a.status !== 0) { cleanupSecret(); return { exitCode: 1, output: a.stderr, error: { code: 'pod-apply', message: a.stderr.trim() } }; }
@@ -1183,7 +1260,8 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
1183
1260
  }
1184
1261
  try {
1185
1262
  appendRunLedgerEntry({ runId: ledgerRunId, event: 'pod-child-run', data: { childRunId: liveChildRunId, job: name, attempt: oomRetried ? 2 : 1, ...(goalFile ? { goalFile } : {}) } }, ledgerDir);
1186
- if (oomRetried && retryFromChildRunId) appendRunLedgerEntry({ runId: ledgerRunId, event: 'pod-oom-retry', data: { attempt: 2, from: retryFromMemoryLimit ?? selectedMemoryLimit, to: memoryLimit, job: name, childRunId: liveChildRunId, fromChildRunId: retryFromChildRunId } }, ledgerDir);
1263
+ appendRunLedgerEntry({ runId: ledgerRunId, event: 'pod-memory-selected', data: { job: name, childRunId: liveChildRunId, attempt: oomRetried ? 2 : 1, limit: memoryLimit, ...(appliedMemoryRequest ? { request: appliedMemoryRequest } : {}), tier: oomRetried ? retryTier : memoryTier, source: oomRetried ? 'oom-retry' : memorySource, reason: oomRetried ? 'OOMKilled' : memoryReason, declaredGoalType: selectedGoalType } }, ledgerDir);
1264
+ if (oomRetried && retryFromChildRunId) appendRunLedgerEntry({ runId: ledgerRunId, event: 'pod-oom-retry', data: { attempt: 2, from: retryFromMemoryLimit ?? selectedMemoryLimit, to: memoryLimit, tier: retryTier, reason: 'OOMKilled', job: name, childRunId: liveChildRunId, fromChildRunId: retryFromChildRunId } }, ledgerDir);
1187
1265
  } catch (error) {
1188
1266
  const reason = error instanceof Error ? error.message : String(error);
1189
1267
  debug.log('self-implement.pod', 'parent-ledger-unavailable', { job: name, reason, attempt: oomRetried ? 2 : 1 });
@@ -1293,6 +1371,21 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
1293
1371
  const parsed = parseSelfImplementJson(logs);
1294
1372
  const terminal = parseSelfImplementJson(logs.trimEnd().split('\n').at(-1) ?? '');
1295
1373
  const nextAccount = codexAccounts?.find((candidate) => candidate !== account);
1374
+ if (state === 'failed' && containerExitCode === 7 && !input.signal?.aborted) {
1375
+ debug.log('self-implement.pod', 'gh-login-failed', { job: name, attempt: ghLoginRetried ? 2 : 1, tokenExpiresInSec: ghTokenExpiresAt === null ? null : Math.floor((ghTokenExpiresAt - now()) / 1000) });
1376
+ if (!ghLoginRetried) {
1377
+ const deleted = kubectl(['-n', namespace, 'delete', 'job', name, '--ignore-not-found', '--wait=true']);
1378
+ if (deleted.status !== 0) throw new Error(`pod: GitHub 로그인 실패 Job 삭제 실패: ${deleted.stderr.trim()}`);
1379
+ // Bursts came from simultaneous launches (10-05 12:49Z · 5 deaths in 2 s) — wait, spread by job name, then
1380
+ // retry once with a newly issued token so the retries do not collide again.
1381
+ const backoffMs = podGhLoginRetryBackoffMs(name);
1382
+ debug.log('self-implement.pod', 'gh-login-retry-backoff', { job: name, backoffMs });
1383
+ await sleep(backoffMs);
1384
+ if (input.signal?.aborted) return { exitCode: null, output: '', error: { code: 'aborted', message: 'aborted before GitHub login retry' } };
1385
+ ghLoginRetried = true;
1386
+ continue;
1387
+ }
1388
+ }
1296
1389
  if (!usageLimitRetried && !input.signal?.aborted && state !== 'aborted' && terminal?.ok === false
1297
1390
  && /\b429\b/.test(terminal.error ?? '') && /\busage_limit_reached\b/.test(terminal.error ?? '') && nextAccount) {
1298
1391
  debug.log('self-implement.pod', 'usage-limit-retry', { job: name, spaceId: input.spaceId, from: account, to: nextAccount });
@@ -1316,7 +1409,7 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
1316
1409
  try {
1317
1410
  const hostRoot = findGitDir(process.cwd())?.root;
1318
1411
  if (!hostRoot) throw new Error('host repository unavailable');
1319
- regate = await (options.hostRegate ?? runHostRegate)({ prNumber, headCommit, repoRoot: hostRoot });
1412
+ regate = await (options.hostRegate ?? runHostRegate)({ prNumber, headCommit, repoRoot: hostRoot, ...(goalFile ? { goalFile } : {}) });
1320
1413
  } catch (error) {
1321
1414
  debug.log('harness.host-regate', 'unmeasured', { pr: prNumber, files: [], os: process.platform, failedStep: 'host-regate' });
1322
1415
  regate = { passed: false, failures: [{ step: 'host-regate', detail: error instanceof Error ? error.message : String(error) }], os: process.platform };
@@ -1335,7 +1428,16 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
1335
1428
  }
1336
1429
  const childFailure = state === 'failed' && !oomKilled && failedReason !== 'DeadlineExceeded'
1337
1430
  ? lastPodChildFailure(logs) : null;
1431
+ let noResultDiagnostic: string | null = null;
1432
+ if (state === 'failed' && containerExitCode !== null && containerExitCode !== 0 && !disposition && !oomKilled && failedReason !== 'DeadlineExceeded' && failedReason !== 'BackoffLimitExceeded') {
1433
+ try { noResultDiagnostic = podNoResultDiagnostic(readFileSync(runLedgerPath(liveChildRunId, ledgerDir), 'utf8')); }
1434
+ catch { /* ledger not returned: do not invent a stage */ }
1435
+ noResultDiagnostic ??= 'child terminal result missing; last ledger stage=unknown; round=unknown; mustFix=unknown';
1436
+ }
1338
1437
  if (childFailure) debug.log('self-implement.pod', 'child-error', { job: name, stage: childFailure.stage, error: childFailure.error }, { compact: { stringMax: 500 } });
1438
+ if (noResultDiagnostic && !noResultDiagnostic.includes('stage=unknown')) {
1439
+ debug.log('self-implement.pod', 'no-result-ledger-diagnostic', { job: name, childRunId: liveChildRunId, diagnostic: noResultDiagnostic }, { compact: { stringMax: 500 } });
1440
+ }
1339
1441
  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,
1340
1442
  ...(state === 'failed' && !oomKilled && failedReason !== 'DeadlineExceeded'
1341
1443
  ? { ...(childFailure ? { childStage: childFailure.stage } : {}), childError: childFailure?.error ?? 'no-result-line' } : {}) },
@@ -1344,22 +1446,25 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
1344
1446
  return { exitCode: 1, output: '', error: { code: 'pod-memory-unavailable', message: `Job ${name} 실제 메모리 한도를 확인할 수 없다` } };
1345
1447
  }
1346
1448
  if (oomKilled && failedReason !== 'DeadlineExceeded' && !input.signal?.aborted && !oomRetried) {
1347
- const high = podMemoryLimitFor(input.feature, { ...env, ELANOUS_POD_MEMORY_TIER: 'high' }).limit;
1348
1449
  const actual = memoryGi(reattachedMemoryLimit ?? memoryLimit);
1450
+ const standardLimit = podMemoryLimitFor(input.feature, { ...env, ELANOUS_POD_MEMORY_TIER: 'standard' }).limit;
1451
+ const nextTier: PodMemoryTier = actual !== null && actual < (memoryGi(standardLimit) ?? 0) ? 'standard' : 'high';
1452
+ const nextLimit = podMemoryLimitFor(input.feature, { ...env, ELANOUS_POD_MEMORY_TIER: nextTier }).limit;
1349
1453
  if (actual === null) {
1350
1454
  return { exitCode: 1, output: '', error: { code: 'pod-memory-unavailable', message: `Job ${name} 실제 메모리 한도를 판정할 수 없다: ${memoryLimit}` } };
1351
1455
  }
1352
- if (actual < (memoryGi(high) ?? actual)) {
1456
+ if (actual < (memoryGi(nextLimit) ?? actual)) {
1353
1457
  const from = reattachedMemoryLimit ?? memoryLimit;
1354
- appendRunLedgerEntry({ runId: ledgerRunId, event: 'pod-oom-retry-intent', data: { attempt: 2, from, to: high, job: name, executionKey, fromChildRunId: liveChildRunId } }, ledgerDir);
1355
- debug.log('self-implement.pod', 'oom-retry', { from, to: high, job: name });
1458
+ appendRunLedgerEntry({ runId: ledgerRunId, event: 'pod-oom-retry-intent', data: { attempt: 2, from, to: nextLimit, tier: nextTier, reason: 'OOMKilled', job: name, executionKey, fromChildRunId: liveChildRunId } }, ledgerDir);
1459
+ debug.log('self-implement.pod', 'oom-retry', { from, to: nextLimit, tier: nextTier, reason: 'OOMKilled', job: name });
1356
1460
  const deleted = kubectl(['-n', namespace, 'delete', 'job', name, '--ignore-not-found', '--wait=true']);
1357
1461
  if (deleted.status !== 0) throw new Error(`pod: OOM Job 삭제 실패: ${deleted.stderr.trim()}`);
1358
1462
  oomRetried = true;
1359
1463
  retryFromChildRunId = liveChildRunId;
1360
1464
  retryFromMemoryLimit = from;
1361
1465
  retrySamples = samples;
1362
- memoryLimit = high;
1466
+ memoryLimit = nextLimit;
1467
+ retryTier = nextTier;
1363
1468
  if (member) { options.pool!.release(member); member = null; }
1364
1469
  releaseAdmission?.();
1365
1470
  releaseAdmission = undefined;
@@ -1378,7 +1483,7 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
1378
1483
  const record: GoalExecutionRecord = {
1379
1484
  runId: liveChildRunId,
1380
1485
  stage: (state === 'aborted' || !disposition ? 'aborted' : disposition.stage) as GoalExecutionRecord['stage'],
1381
- outcome: (!disposition ? 'pod-no-result' : state === 'complete' && disposition.merged ? 'merged' : state === 'complete' && disposition.ok ? 'completed' : 'abandoned') as GoalExecutionRecord['outcome'],
1486
+ outcome: (!disposition ? noResultDiagnostic ? 'abandoned' : 'pod-no-result' : state === 'complete' && disposition.merged ? 'merged' : state === 'complete' && disposition.ok ? 'completed' : 'abandoned') as GoalExecutionRecord['outcome'],
1382
1487
  ok: state === 'complete' && (disposition?.ok ?? false),
1383
1488
  ...(disposition?.prNumber !== undefined ? { prNumber: disposition.prNumber } : {}),
1384
1489
  completedAt: new Date().toISOString(),
@@ -1402,16 +1507,22 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
1402
1507
  const finishedDisposition = deadlineExceeded && disposition
1403
1508
  ? { ...disposition, failureClassification: 'run-deadline-exceeded' as const }
1404
1509
  : disposition;
1510
+ const podExitCode = state === 'complete' && disposition?.stage !== 'host-regate-failed' && disposition?.ok !== false ? 0 : 1;
1511
+ // SCHED-CONTRACT: a failed child must say why outside the Pod, not «no error diagnostic».
1512
+ const podReason = podExitCode === 1 ? extractPodFailureReason({ logs, logTailReason, containerReason, jobReason: failedReason || undefined, deadlineSeconds }) : undefined;
1513
+ if (podReason) debug.log('self-implement.pod', 'failure-reason', { job: name, reason: podReason });
1405
1514
  return {
1406
- exitCode: state === 'complete' && disposition?.stage !== 'host-regate-failed' && disposition?.ok !== false ? 0 : 1,
1515
+ exitCode: podExitCode,
1407
1516
  output: tail,
1408
1517
  ...(deadlineExceeded
1409
1518
  ? { error: { code: 'pod-deadline-exceeded', message: `Job ${name} 이 수명 상한 ${deadlineSeconds}초에 닿았다` } }
1410
1519
  : state === 'failed'
1411
- ? oomKilled
1412
- ? { error: { code: 'pod-oom-killed', message: `Job ${name} failed (OOMKilled/${containerExitCode ?? 'unknown'}, memoryLimit=${memoryLimit})${oomRetried || (memoryGi(reattachedMemoryLimit ?? memoryLimit) ?? 0) >= (memoryGi(podMemoryLimitFor(input.feature, { ...env, ELANOUS_POD_MEMORY_TIER: 'high' }).limit) ?? Infinity) ? ' — OOM · high 에서도' : ''}\n${formatLastMemSample(samples.at(-1))}` } }
1413
- : { error: { code: 'pod-job-failed', message: `Job ${name} failed${failedReason || containerReason ? ` (${[failedReason, containerReason ? `container=${containerReason}/${containerExitCode ?? 'unknown'}` : ''].filter(Boolean).join(', ')})` : ''}${childFailure ? ` — childStage=${childFailure.stage} · childError=${childFailure.error}` : ' — childError=no-result-line'}` } }
1414
- : {}),
1520
+ ? containerExitCode === 7
1521
+ ? { error: { code: 'pod-gh-login-failed', message: `Job ${name} GitHub 앱 로그인 실패 (container=${containerReason ?? 'unknown'}/7, attempt=${ghLoginRetried ? 2 : 1}) — 새 토큰으로 ${ghLoginRetried ? '1회 재시도했으나 다시 실패' : '재시도 불가'}` } }
1522
+ : oomKilled
1523
+ ? { error: { code: 'pod-oom-killed', message: `Job ${name} failed (OOMKilled/${containerExitCode ?? 'unknown'}, memoryLimit=${memoryLimit})${oomRetried || (memoryGi(reattachedMemoryLimit ?? memoryLimit) ?? 0) >= (memoryGi(podMemoryLimitFor(input.feature, { ...env, ELANOUS_POD_MEMORY_TIER: 'high' }).limit) ?? Infinity) ? ` — OOM · ${oomRetried ? retryTier : 'high'} 에서도` : ''}\n${formatLastMemSample(samples.at(-1))}` } }
1524
+ : { error: { code: 'pod-job-failed', message: `Job ${name} failed${failedReason || containerReason ? ` (${[failedReason, containerReason ? `container=${containerReason}/${containerExitCode ?? 'unknown'}` : ''].filter(Boolean).join(', ')})` : ''}${childFailure ? ` — childStage=${childFailure.stage} · childError=${childFailure.error}` : ` — childError=no-result-line · reason=${podReason}`}${noResultDiagnostic ? ` · ${noResultDiagnostic}` : ''}` } }
1525
+ : podReason && !childFailure?.error ? { error: { code: 'pod-child-failed', message: podReason } } : {}),
1415
1526
  ...(finishedDisposition ? { disposition: finishedDisposition } : {}),
1416
1527
  };
1417
1528
  } catch (err) {
@@ -49,6 +49,7 @@ export interface SelfImplementDisposition {
49
49
  ok?: boolean;
50
50
  worktreePath?: string;
51
51
  branch?: string;
52
+ harvestable?: true;
52
53
  prUrl?: string;
53
54
  prNumber?: number;
54
55
  /** Head SHA checked by the Pod before host regate. */
@@ -84,6 +85,7 @@ export interface SelfImplementDisposition {
84
85
  reviewVerdict?: 'pass' | 'warn' | 'fail';
85
86
  /** 마지막 리뷰의 must-fix 수(`review.mustFix.length`). 같은 조건. */
86
87
  reviewMustFixCount?: number;
88
+ mustFix?: string[];
87
89
  }
88
90
 
89
91
  /**
@@ -265,7 +267,7 @@ export function createSelfImplementAdapter(opts: SelfImplementAdapterOptions) {
265
267
  : r.output.length === 0
266
268
  ? `${exitMessage} (child produced no output)`
267
269
  : r.disposition
268
- ? `${exitMessage} (child terminal result contained no error diagnostic)`
270
+ ? `${exitMessage} (child stage=${r.disposition.stage ?? 'unknown'}; ${r.disposition.detail ?? `mustFix=${JSON.stringify(r.disposition.mustFix ?? 'unknown')}`})`
269
271
  : `${exitMessage} (child output did not contain a parseable terminal JSON diagnostic)`;
270
272
  return {
271
273
  ...exec,
@@ -461,6 +463,7 @@ export function parseSelfImplementJson(stdout: string): SelfImplementDisposition
461
463
  ...(typeof o.ok === 'boolean' ? { ok: o.ok } : {}),
462
464
  ...(typeof o.worktreePath === 'string' ? { worktreePath: o.worktreePath } : {}),
463
465
  ...(typeof o.branch === 'string' ? { branch: o.branch } : {}),
466
+ ...(o.harvestable === true && typeof o.branch === 'string' && o.branch.trim() ? { harvestable: true } : {}),
464
467
  ...(typeof o.prUrl === 'string' ? { prUrl: o.prUrl } : {}),
465
468
  ...(typeof o.prNumber === 'number' ? { prNumber: o.prNumber } : {}),
466
469
  ...(typeof o.checkedHeadCommit === 'string' ? { checkedHeadCommit: o.checkedHeadCommit } : {}),
@@ -483,6 +486,7 @@ export function parseSelfImplementJson(stdout: string): SelfImplementDisposition
483
486
  ? { providerErrors: o.providerErrors as SelfImplementDisposition['providerErrors'] } : {}),
484
487
  ...(typeof o.runId === 'string' && o.runId.trim() ? { runId: o.runId, childRunId: o.runId } : {}),
485
488
  ...reviewGateFields(o),
489
+ ...(Array.isArray(o.mustFix) && o.mustFix.every((item) => typeof item === 'string') ? { mustFix: o.mustFix as string[] } : {}),
486
490
  };
487
491
  reportUnmappedDispositionFields(o, mapped);
488
492
  return mapped;
package/src/telegram.ts CHANGED
@@ -66,7 +66,9 @@ import { detectUrlRoute, type UrlRouteDecision } from './skills/url-router.js';
66
66
  import { observeDevRequestRouteFailSoft } from './skills/dev-request-router.js';
67
67
  import { runUrlRoute } from './skills/url-route-exec.js';
68
68
  import { recordRoutedLinkAbsorbed } from './intake-plane/link-ledger.js';
69
- import { classifyFrontInput } from './intake-plane/front-classifier.js';
69
+ import { classifyFrontInput, telegramAbsorbUrls } from './intake-plane/front-classifier.js';
70
+ import { scheduleIntakeEvent } from './intake-plane/route.js';
71
+ import { dispatchTaskCreate } from './task-orchestrator/runtimes/create.js';
70
72
  import { handleTelegramSeatWork, type TelegramSeatWorkDeps } from './intake-plane/telegram-seat-work.js';
71
73
  import { deliverSeatAnswers } from './seat-dispatch/seat-ask.js';
72
74
  import { effectiveInstanceRoot } from './instance/resolve.js';
@@ -1919,11 +1921,30 @@ export class TelegramBot {
1919
1921
  ? getActiveDelegation(delegationChatKey(this.botId, ctx.chatId, ctx.threadId))
1920
1922
  : null;
1921
1923
  if (!activeDeleg) {
1922
- try {
1923
- classifyFrontInput({ text: ctx.text, surface: 'telegram' }, { urlRouting: getUserConfig().skills.urlRouting });
1924
- } catch { /* observation must not interrupt URL routing */ }
1925
1924
  const urlDec = detectUrlRoute(ctx.text, getUserConfig().skills.urlRouting);
1926
1925
  if (urlDec) {
1926
+ try {
1927
+ const frontInput = { text: ctx.text, surface: 'telegram' as const };
1928
+ const decision = classifyFrontInput(frontInput, { urlRouting: getUserConfig().skills.urlRouting });
1929
+ const absorbUrls = telegramAbsorbUrls(frontInput, decision);
1930
+ for (const url of absorbUrls) {
1931
+ const result = await scheduleIntakeEvent(effectiveInstanceRoot(), url, {
1932
+ eventMax: getUserConfig().intake.eventMax,
1933
+ post: async ({ url: link }) => {
1934
+ const posted = await dispatchTaskCreate({
1935
+ title: `흡수: ${link}`.slice(0, 80),
1936
+ surface: { kind: 'llm-direct', prompt: `이 링크를 흡수하고 노트로 저장하세요: ${link}` },
1937
+ });
1938
+ if (!posted.taskId) throw new Error('intake event task not created');
1939
+ return { taskId: posted.taskId };
1940
+ },
1941
+ });
1942
+ if (result.reply) await this.sendMessage(ctx.chatId, result.reply, { threadId: ctx.threadId, markdown: true });
1943
+ }
1944
+ if (absorbUrls.length) return;
1945
+ } catch (error) {
1946
+ debug.log('intake.event', 'failed', { reason: error instanceof Error ? error.name : 'unknown' });
1947
+ }
1927
1948
  const handled = await this.runUrlRouteReply(ctx, urlDec);
1928
1949
  if (handled) return;
1929
1950
  }