elanous 0.2.25 → 0.2.27

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 (244) 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/chunks/{1454-37fd16f479f5554a.js → 1454-a9c338f5bb556f42.js} +1 -1
  4. package/apps/pwa/out/_next/static/chunks/app/approvals/{page-9360b1285b2a346e.js → page-a1ce306364f08da9.js} +1 -1
  5. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-07110da72c45e68f.js +1 -0
  6. package/apps/pwa/out/_next/static/chunks/app/board/page-d2d76d4a7eae91bd.js +1 -0
  7. package/apps/pwa/out/_next/static/chunks/app/botlab/page-30007380f2e2a3c1.js +1 -0
  8. package/apps/pwa/out/_next/static/chunks/app/bots/page-e5a9302c105725a3.js +1 -0
  9. package/apps/pwa/out/_next/static/chunks/app/ceo/page-b93c04b5311c97ec.js +1 -0
  10. package/apps/pwa/out/_next/static/chunks/app/chat/page-718a49e86bf25f9c.js +1 -0
  11. package/apps/pwa/out/_next/static/chunks/app/consult/{page-44eaad486bc98a45.js → page-20662900d6b4b455.js} +1 -1
  12. package/apps/pwa/out/_next/static/chunks/app/control/{page-97fa6cf3d34c7eb9.js → page-f707a509515ac50c.js} +1 -1
  13. package/apps/pwa/out/_next/static/chunks/app/decisions/{page-305cd294e754906e.js → page-6a16a48d1aa6e91f.js} +1 -1
  14. package/apps/pwa/out/_next/static/chunks/app/design-check/page-fcb06e305f515527.js +1 -0
  15. package/apps/pwa/out/_next/static/chunks/app/editor/page-6acf5ec4e6c6d1af.js +1 -0
  16. package/apps/pwa/out/_next/static/chunks/app/exec/{page-45889e7070d11126.js → page-39f82acf619cca23.js} +1 -1
  17. package/apps/pwa/out/_next/static/chunks/app/field/{page-16a2e893f83ae4d0.js → page-7c2c8f81b185f9c0.js} +1 -1
  18. package/apps/pwa/out/_next/static/chunks/app/inside/page-ae7aff07e32676bb.js +1 -0
  19. package/apps/pwa/out/_next/static/chunks/app/intake/{page-56888bc744e08dac.js → page-482c60c1bc540d0a.js} +1 -1
  20. package/apps/pwa/out/_next/static/chunks/app/layout-7cb996c1aa08b6f5.js +1 -0
  21. package/apps/pwa/out/_next/static/chunks/app/live/{page-952c6ceec6d9de38.js → page-623e08fe4477d606.js} +1 -1
  22. package/apps/pwa/out/_next/static/chunks/app/live-run/{page-6d0202af568f4c63.js → page-db460cd8aecb8490.js} +1 -1
  23. package/apps/pwa/out/_next/static/chunks/app/loops/page-ff27902d0b11ef4f.js +1 -0
  24. package/apps/pwa/out/_next/static/chunks/app/market/{page-74366ae2693a7d95.js → page-8397a5c6ca569bf4.js} +1 -1
  25. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/{page-4fb6fc34e20c07b7.js → page-1917ea87ea71ab6b.js} +1 -1
  26. package/apps/pwa/out/_next/static/chunks/app/morning/{page-e9502921f4f01b1f.js → page-25a302273b054fd7.js} +1 -1
  27. package/apps/pwa/out/_next/static/chunks/app/{not-found-b6169ea2f09cf287.js → not-found-670510eda8b9443f.js} +1 -1
  28. package/apps/pwa/out/_next/static/chunks/app/observatory/page-d73c3f1e00d93986.js +1 -0
  29. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-65caacf54389081e.js +1 -0
  30. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-4bb7556e2e97a750.js +1 -0
  31. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-77e04b4f8762c666.js +1 -0
  32. package/apps/pwa/out/_next/static/chunks/app/outputs/{page-b9cc8a10bc6078b4.js → page-15c0a4095baddfba.js} +1 -1
  33. package/apps/pwa/out/_next/static/chunks/app/{page-3d3fc73dd187ed52.js → page-4eb96fc7241c2456.js} +1 -1
  34. package/apps/pwa/out/_next/static/chunks/app/reflection/page-63e272a4cd3b6634.js +1 -0
  35. package/apps/pwa/out/_next/static/chunks/app/scheduler/{page-9c04dac0831c2bbb.js → page-0d64ff212dc55d03.js} +1 -1
  36. package/apps/pwa/out/_next/static/chunks/app/sessions/page-7f096248993fc2d4.js +1 -0
  37. package/apps/pwa/out/_next/static/chunks/app/settings/devices/{page-04de9ca6d52a3893.js → page-e47fc72049149a38.js} +1 -1
  38. package/apps/pwa/out/_next/static/chunks/app/settings/{page-62527677bbc2fdc4.js → page-f7d808e31e005895.js} +1 -1
  39. package/apps/pwa/out/_next/static/chunks/app/setup/done/{page-9a2e09c4a311764c.js → page-5c2b24b5496dad73.js} +1 -1
  40. package/apps/pwa/out/_next/static/chunks/app/setup/{page-bfeb35d69e8b24bb.js → page-76cdb531668b5191.js} +1 -1
  41. package/apps/pwa/out/_next/static/chunks/app/share/page-e2b63345bba1a11f.js +1 -0
  42. package/apps/pwa/out/_next/static/chunks/app/showroom/{page-b008aff96be56654.js → page-d376e0c83edc041a.js} +1 -1
  43. package/apps/pwa/out/_next/static/chunks/app/tasks/page-d6db55fe2c04d128.js +1 -0
  44. package/apps/pwa/out/_next/static/chunks/app/term/page-87b88f551f421d8b.js +1 -0
  45. package/apps/pwa/out/_next/static/chunks/app/today/{page-6445ac4cbb8dd869.js → page-dab163b338e3b966.js} +1 -1
  46. package/apps/pwa/out/_next/static/chunks/app/trace/{page-852170303c6cd755.js → page-08019efef111ed2e.js} +1 -1
  47. package/apps/pwa/out/_next/static/chunks/app/vault/page-0260ffc7eda50db1.js +1 -0
  48. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/{page-97c7361cc2a19319.js → page-980ce6fe36ad95eb.js} +1 -1
  49. package/apps/pwa/out/_next/static/chunks/app/workflows/{page-20162174b77f96cf.js → page-99c4e6c7345a9156.js} +1 -1
  50. package/apps/pwa/out/_next/static/chunks/app/workspace/{page-8c6901833f897496.js → page-0ab42e0f05823013.js} +1 -1
  51. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-ad9220422d20ba33.js +1 -0
  52. package/apps/pwa/out/approvals/index.html +1 -1
  53. package/apps/pwa/out/approvals/index.txt +13 -13
  54. package/apps/pwa/out/autopilot/index.html +1 -1
  55. package/apps/pwa/out/autopilot/index.txt +13 -13
  56. package/apps/pwa/out/board/index.html +1 -1
  57. package/apps/pwa/out/board/index.txt +13 -13
  58. package/apps/pwa/out/botlab/index.html +1 -1
  59. package/apps/pwa/out/botlab/index.txt +13 -13
  60. package/apps/pwa/out/bots/index.html +1 -1
  61. package/apps/pwa/out/bots/index.txt +13 -13
  62. package/apps/pwa/out/ceo/index.html +1 -1
  63. package/apps/pwa/out/ceo/index.txt +13 -13
  64. package/apps/pwa/out/chat/index.html +1 -1
  65. package/apps/pwa/out/chat/index.txt +13 -13
  66. package/apps/pwa/out/consult/index.html +1 -1
  67. package/apps/pwa/out/consult/index.txt +13 -13
  68. package/apps/pwa/out/control/index.html +1 -1
  69. package/apps/pwa/out/control/index.txt +13 -13
  70. package/apps/pwa/out/decisions/index.html +1 -1
  71. package/apps/pwa/out/decisions/index.txt +13 -13
  72. package/apps/pwa/out/design-check/index.html +1 -1
  73. package/apps/pwa/out/design-check/index.txt +13 -13
  74. package/apps/pwa/out/editor/index.html +1 -1
  75. package/apps/pwa/out/editor/index.txt +13 -13
  76. package/apps/pwa/out/exec/index.html +1 -1
  77. package/apps/pwa/out/exec/index.txt +13 -13
  78. package/apps/pwa/out/field/index.html +1 -1
  79. package/apps/pwa/out/field/index.txt +13 -13
  80. package/apps/pwa/out/healthz +1 -1
  81. package/apps/pwa/out/index.html +1 -1
  82. package/apps/pwa/out/index.txt +13 -13
  83. package/apps/pwa/out/inside/index.html +1 -1
  84. package/apps/pwa/out/inside/index.txt +13 -13
  85. package/apps/pwa/out/intake/index.html +1 -1
  86. package/apps/pwa/out/intake/index.txt +13 -13
  87. package/apps/pwa/out/live/index.html +1 -1
  88. package/apps/pwa/out/live/index.txt +13 -13
  89. package/apps/pwa/out/live-run/index.html +1 -1
  90. package/apps/pwa/out/live-run/index.txt +13 -13
  91. package/apps/pwa/out/loops/index.html +1 -1
  92. package/apps/pwa/out/loops/index.txt +13 -13
  93. package/apps/pwa/out/market/index.html +1 -1
  94. package/apps/pwa/out/market/index.txt +13 -13
  95. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  96. package/apps/pwa/out/missions/placeholder/index.txt +13 -13
  97. package/apps/pwa/out/morning/index.html +1 -1
  98. package/apps/pwa/out/morning/index.txt +13 -13
  99. package/apps/pwa/out/observatory/index.html +1 -1
  100. package/apps/pwa/out/observatory/index.txt +13 -13
  101. package/apps/pwa/out/ops/checklist/index.html +1 -1
  102. package/apps/pwa/out/ops/checklist/index.txt +13 -13
  103. package/apps/pwa/out/ops/release/index.html +1 -1
  104. package/apps/pwa/out/ops/release/index.txt +13 -13
  105. package/apps/pwa/out/ops/seats/index.html +1 -1
  106. package/apps/pwa/out/ops/seats/index.txt +13 -13
  107. package/apps/pwa/out/outputs/index.html +1 -1
  108. package/apps/pwa/out/outputs/index.txt +13 -13
  109. package/apps/pwa/out/reflection/index.html +1 -1
  110. package/apps/pwa/out/reflection/index.txt +14 -14
  111. package/apps/pwa/out/scheduler/index.html +1 -1
  112. package/apps/pwa/out/scheduler/index.txt +13 -13
  113. package/apps/pwa/out/sessions/index.html +1 -1
  114. package/apps/pwa/out/sessions/index.txt +13 -13
  115. package/apps/pwa/out/settings/devices/index.html +1 -1
  116. package/apps/pwa/out/settings/devices/index.txt +13 -13
  117. package/apps/pwa/out/settings/index.html +1 -1
  118. package/apps/pwa/out/settings/index.txt +13 -13
  119. package/apps/pwa/out/setup/done/index.html +1 -1
  120. package/apps/pwa/out/setup/done/index.txt +13 -13
  121. package/apps/pwa/out/setup/index.html +1 -1
  122. package/apps/pwa/out/setup/index.txt +13 -13
  123. package/apps/pwa/out/share/index.html +1 -1
  124. package/apps/pwa/out/share/index.txt +13 -13
  125. package/apps/pwa/out/showroom/index.html +1 -1
  126. package/apps/pwa/out/showroom/index.txt +13 -13
  127. package/apps/pwa/out/tasks/index.html +1 -1
  128. package/apps/pwa/out/tasks/index.txt +13 -13
  129. package/apps/pwa/out/term/index.html +1 -1
  130. package/apps/pwa/out/term/index.txt +13 -13
  131. package/apps/pwa/out/today/index.html +1 -1
  132. package/apps/pwa/out/today/index.txt +13 -13
  133. package/apps/pwa/out/trace/index.html +1 -1
  134. package/apps/pwa/out/trace/index.txt +13 -13
  135. package/apps/pwa/out/vault/index.html +1 -1
  136. package/apps/pwa/out/vault/index.txt +13 -13
  137. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  138. package/apps/pwa/out/workflows/chat-ui/index.txt +13 -13
  139. package/apps/pwa/out/workflows/index.html +1 -1
  140. package/apps/pwa/out/workflows/index.txt +13 -13
  141. package/apps/pwa/out/workspace/index.html +1 -1
  142. package/apps/pwa/out/workspace/index.txt +13 -13
  143. package/apps/pwa/out/worktrees/index.html +1 -1
  144. package/apps/pwa/out/worktrees/index.txt +13 -13
  145. package/package.json +1 -1
  146. package/scripts/ci-typecheck-changed.ts +81 -2
  147. package/scripts/coord-msg-bridge.ts +209 -0
  148. package/scripts/coord-post.sh +40 -7
  149. package/scripts/coord-requests-bridge.ts +10 -5
  150. package/scripts/expose-rubric-suggest.ts +1 -1
  151. package/scripts/measure-tsc-affected-roots.ts +10 -1
  152. package/scripts/release-loop/export-check-node.ts +8 -5
  153. package/scripts/release-loop/node-verdict.ts +7 -2
  154. package/src/agent/task-notification.ts +30 -1
  155. package/src/agent-substrate/review-observation.ts +9 -2
  156. package/src/agent-substrate/self-review-cli.ts +28 -6
  157. package/src/boot/daemon-tools/self-implement.ts +72 -4
  158. package/src/budget/llm-cost.ts +5 -1
  159. package/src/cli/control-cli.ts +29 -0
  160. package/src/cli/doctor-cli.ts +90 -8
  161. package/src/cli/msg-cli.ts +15 -5
  162. package/src/cli/pr-cli.ts +53 -0
  163. package/src/cli/pr-land-overlap-evidence.ts +18 -10
  164. package/src/cli/release-cli.ts +32 -5
  165. package/src/cli/seat-cli.ts +121 -0
  166. package/src/cli/self-utterances-cli.ts +140 -0
  167. package/src/cli/tasks-cli.ts +139 -9
  168. package/src/context-card/cell.ts +40 -0
  169. package/src/dashboard/boot-timing.ts +35 -0
  170. package/src/dashboard/index.ts +86 -24
  171. package/src/dashboard/input/chat-main-plain-turn-runtime.ts +26 -0
  172. package/src/dashboard/slash-runtime/dashboard-handlers.ts +6 -0
  173. package/src/debug/log.ts +1 -1
  174. package/src/domains/release-tool.ts +29 -12
  175. package/src/domains/surface-events.ts +16 -0
  176. package/src/elanous-config-dir.ts +2 -2
  177. package/src/fleet/host-doctor.ts +73 -2
  178. package/src/graph-runner/runner.ts +3 -1
  179. package/src/harness/harness-cli-command.ts +46 -4
  180. package/src/harness/harness-stop.ts +11 -7
  181. package/src/index.ts +35 -4
  182. package/src/instance/current.ts +2 -6
  183. package/src/instance/resolve.ts +5 -6
  184. package/src/llm/openai-sampling-fields.ts +89 -0
  185. package/src/llm/runtime-model-family.ts +61 -0
  186. package/src/llm/zai-coding-plan.ts +27 -1
  187. package/src/llm.ts +13 -89
  188. package/src/machines/machine-ledger.ts +31 -3
  189. package/src/maturity/cli-maturity.ts +13 -3
  190. package/src/msg/msg-store.ts +69 -7
  191. package/src/ops/overview-collect.ts +3 -2
  192. package/src/ops/overview-owner.ts +29 -0
  193. package/src/plugin-hooks/shell-hook.ts +17 -2
  194. package/src/plugin-missions/evaluator-runner.ts +18 -2
  195. package/src/plugins/install/plugin-install.ts +11 -1
  196. package/src/prompt-enhance/enhance.ts +11 -0
  197. package/src/release-loop/checklist.ts +41 -4
  198. package/src/release-loop/feature-store.ts +10 -9
  199. package/src/seat-loop/seat-loop.ts +5 -1
  200. package/src/self-dev/draft-sweep.ts +23 -14
  201. package/src/self-dev/orchestrate.ts +6 -2
  202. package/src/self-dev/run-supervisor.ts +35 -1
  203. package/src/self-implement/goal-author.ts +3 -27
  204. package/src/self-implement/goal-command-execution.ts +26 -0
  205. package/src/self-implement/orchestrator.ts +2 -1
  206. package/src/self-implement/pwa-import-graph.ts +194 -7
  207. package/src/self-implement/self-implement-runtime.ts +126 -21
  208. package/src/session/index.ts +14 -4
  209. package/src/session-runtime/index.ts +55 -11
  210. package/src/task-agent/cover.ts +28 -9
  211. package/src/task-agent/live-moves.ts +14 -6
  212. package/src/task-agent/rejudge-on-head.ts +220 -23
  213. package/src/task-agent/review-result-capture.ts +64 -1
  214. package/src/task-agent/shadow.ts +137 -22
  215. package/src/task-agent/task-hand.ts +18 -2
  216. package/src/task-orchestrator/surfaces/self-implement-pod.ts +1 -0
  217. package/src/tool-runtime/tool-search-runtime.ts +25 -0
  218. package/src/tui.ts +20 -1
  219. package/src/user-config.ts +3 -59
  220. package/src/version/packed-revision.json +1 -1
  221. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-f08f6529d09ffae1.js +0 -1
  222. package/apps/pwa/out/_next/static/chunks/app/board/page-f000d54d258f9ef6.js +0 -1
  223. package/apps/pwa/out/_next/static/chunks/app/botlab/page-b12149c51f7d04b1.js +0 -1
  224. package/apps/pwa/out/_next/static/chunks/app/bots/page-22f674e7e4c2c9d5.js +0 -1
  225. package/apps/pwa/out/_next/static/chunks/app/ceo/page-8888733e40cea6d1.js +0 -1
  226. package/apps/pwa/out/_next/static/chunks/app/chat/page-9717ec6964b92276.js +0 -1
  227. package/apps/pwa/out/_next/static/chunks/app/design-check/page-dea4b795741126fd.js +0 -1
  228. package/apps/pwa/out/_next/static/chunks/app/editor/page-35e05d7450461a67.js +0 -1
  229. package/apps/pwa/out/_next/static/chunks/app/inside/page-b2638013c0bf7dfd.js +0 -1
  230. package/apps/pwa/out/_next/static/chunks/app/layout-c44f93a41167536f.js +0 -1
  231. package/apps/pwa/out/_next/static/chunks/app/loops/page-1b810474507f3702.js +0 -1
  232. package/apps/pwa/out/_next/static/chunks/app/observatory/page-6a14333d1b1b3967.js +0 -1
  233. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-8c91a9a66c9ec90a.js +0 -1
  234. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-4e634c5b98fd4e93.js +0 -1
  235. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-ed3070d637850548.js +0 -1
  236. package/apps/pwa/out/_next/static/chunks/app/reflection/page-128458c8d86fbb5f.js +0 -1
  237. package/apps/pwa/out/_next/static/chunks/app/sessions/page-cf0c2b0183991d09.js +0 -1
  238. package/apps/pwa/out/_next/static/chunks/app/share/page-89cb33bced824e4a.js +0 -1
  239. package/apps/pwa/out/_next/static/chunks/app/tasks/page-814394dcbae789b1.js +0 -1
  240. package/apps/pwa/out/_next/static/chunks/app/term/page-6a87325c87f09dd2.js +0 -1
  241. package/apps/pwa/out/_next/static/chunks/app/vault/page-087ceeb975ef272b.js +0 -1
  242. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-05a4b2c9b91034aa.js +0 -1
  243. /package/apps/pwa/out/_next/static/{dRS6lEhSRRBr1abbzp7fJ → Ev2jivDMTSoEc-5rIXdcy}/_buildManifest.js +0 -0
  244. /package/apps/pwa/out/_next/static/{dRS6lEhSRRBr1abbzp7fJ → Ev2jivDMTSoEc-5rIXdcy}/_ssgManifest.js +0 -0
@@ -25,6 +25,8 @@ import { classifyPodExitStop, recordRunStop, runStopRecorded, RUN_STOP_EVENT } f
25
25
  import { ensureOpenRouterChildModelLive, resolveChildLlmEffort, resolveImplementationChildModel } from '../self-dev/dev-cli.js';
26
26
  import { resolveHarnessTarget } from '../self-implement/harness-target-options.js';
27
27
  import { buildRunProgressRows, isPartialRunningRuns, queryRunningRuns, renderRunProgressTable } from '../self-implement/running-runs.js';
28
+ import { launchBindingRunIds, readLaunchBinding } from './launch-registry.js';
29
+ import { psProcessStartMs } from './harness-stop.js';
28
30
  import { DevPipelineError } from '../self-dev/dev-pipeline.js';
29
31
  import { collectDraftMetrics, collectOverlapMetrics, DRAFT_SWEEP_CONCURRENCY, mapBounded, resolveDraftSweepCloseCap, runDraftSweep, sweepFailureReason, type DraftSweepAdapters, type DraftSweepResult, type OverlapMetricAdapters, type SweepDraft, type SweepMergedPr, type SweepReviewGate } from '../self-dev/draft-sweep.js';
30
32
  import { readAskPreflightRowsSince, readSalvagedRowsSince } from '../self-dev/ask-launch-io.js';
@@ -2391,6 +2393,7 @@ const defaultDraftSweepStopRun: DraftSweepStopRun = async (runId) => {
2391
2393
  export function githubDraftSweepAdapters(execute: GhExecute = executeGh, git: DraftSweepGitExecute = runGitCommand, cwd = process.cwd(),
2392
2394
  executeAsync: (args: string[]) => Promise<string> = execute === executeGh ? executeGhAsync : async (args) => execute(args),
2393
2395
  stopRun: DraftSweepStopRun = defaultDraftSweepStopRun): GithubDraftSweepAdapters {
2396
+ const ensuredSweepLabels = new Set<string>();
2394
2397
  const inventory = async (repository: string, state: 'open' | 'closed'): Promise<GithubPull[]> => {
2395
2398
  // ⛔ A large repository has tens of thousands of closed PRs (2026-09-28: 21k → «exceeded pagination limit», no sweep at all).
2396
2399
  // Merged twins only matter after the oldest open draft was opened, so the closed listing stops at that time.
@@ -2642,11 +2645,19 @@ export function githubDraftSweepAdapters(execute: GhExecute = executeGh, git: Dr
2642
2645
  .map((line) => line.slice('branch refs/heads/'.length).trim()));
2643
2646
  },
2644
2647
  setLabels: async (repository, number, change) => {
2648
+ if (change.add.startsWith('elanous:swept:')) {
2649
+ const key = `${repository}:${change.add}`;
2650
+ if (!ensuredSweepLabels.has(key)) {
2651
+ execute(['label', 'create', change.add, '--repo', repository, '--color', '6A737D',
2652
+ '--description', 'Draft sweep close reason; reopen this PR to restore it', '--force']);
2653
+ ensuredSweepLabels.add(key);
2654
+ }
2655
+ }
2645
2656
  execute(['pr', 'edit', String(number), '--repo', repository, '--add-label', change.add,
2646
2657
  ...change.remove.flatMap((name) => ['--remove-label', name])]);
2647
2658
  },
2648
- closeDraft: async (repository, number, comment) => {
2649
- execute(['pr', 'close', String(number), '--repo', repository, '--comment', comment]);
2659
+ closeDraft: async (repository, number) => {
2660
+ execute(['pr', 'close', String(number), '--repo', repository]);
2650
2661
  },
2651
2662
  getClaimOwner: async (repository, number) => claimCommentOwner(repository, number, execute),
2652
2663
  getActiveClaimOwner: async (repository, number) => activeClaimOwnerFromBodies(await claimCommentsFor(repository, number)),
@@ -3137,7 +3148,13 @@ export interface HarnessCliCommandDeps {
3137
3148
  mission?: HarnessMissionHandler;
3138
3149
  missionLoop?: HarnessMissionLoopHandler;
3139
3150
  /** `harness progress` 가 도는 런 관측을 읽는 자리(시험 주입). 기본은 queryRunningRuns. */
3140
- progress?: { query?: typeof queryRunningRuns; nowMs?: () => number };
3151
+ progress?: {
3152
+ query?: typeof queryRunningRuns;
3153
+ nowMs?: () => number;
3154
+ bindingRunIds?: typeof launchBindingRunIds;
3155
+ readBinding?: typeof readLaunchBinding;
3156
+ processStartMs?: typeof psProcessStartMs;
3157
+ };
3141
3158
  }
3142
3159
 
3143
3160
  export function installHarnessCliCommand(program: Command, deps: HarnessCliCommandDeps): Command {
@@ -3198,8 +3215,33 @@ export function installHarnessCliCommand(program: Command, deps: HarnessCliComma
3198
3215
  });
3199
3216
  harnessCmd.command('progress').description('도는 골마다 진도를 한 표로 읽는다 — 단계 · 진입 시각 · 마지막 진전 · pty (읽기 전용)')
3200
3217
  .option('--json', '같은 행 배열을 JSON 으로 출력')
3201
- .action((opts: { json?: boolean }) => {
3218
+ .option('--pid <number>', '기록된 로컬 PID 에 연결된 런 한 건만 조회')
3219
+ .action((opts: { json?: boolean; pid?: string }) => {
3202
3220
  try {
3221
+ if (opts.pid !== undefined) {
3222
+ const pid = Number(opts.pid);
3223
+ if (!Number.isSafeInteger(pid) || pid <= 1) {
3224
+ console.error('❌ --pid 는 2 이상의 정수여야 합니다');
3225
+ process.exitCode = 2;
3226
+ return;
3227
+ }
3228
+ const bindings = (deps.progress?.bindingRunIds ?? launchBindingRunIds)()
3229
+ .map((runId) => ({ runId, binding: (deps.progress?.readBinding ?? readLaunchBinding)(runId) }))
3230
+ .filter(({ binding }) => binding?.pid === pid);
3231
+ const binding = bindings.length === 1 && !bindings[0]!.binding?.podName ? bindings[0] : undefined;
3232
+ const startedAt = binding ? (deps.progress?.processStartMs ?? psProcessStartMs)(pid) : null;
3233
+ // ps lstart has second precision. A wider stop-oriented tolerance would attribute a reused PID to this run.
3234
+ const observed = binding && startedAt !== null
3235
+ && startedAt === Math.floor(binding.binding!.startedAt / 1_000) * 1_000;
3236
+ const result = observed
3237
+ ? (deps.progress?.query ?? queryRunningRuns)({ runIds: [binding.runId], caller: 'harness.progress.pid' })
3238
+ : null;
3239
+ const row = result ? buildRunProgressRows(result, (deps.progress?.nowMs ?? Date.now)())
3240
+ .find((item) => item.runId === binding!.runId && item.status !== 'ended-unclosed' && item.status !== 'unknown') : undefined;
3241
+ if (opts.json) console.log(JSON.stringify({ pid, observation: row ? 'observed' : 'unobserved', runId: row?.runId ?? null, progress: row ?? null }));
3242
+ else console.log(row ? `pid ${pid} · ${renderRunProgressTable({ ...result!, entries: result!.entries.filter((item) => item.runId === row.runId) }, (deps.progress?.nowMs ?? Date.now)()).split('\n').slice(1).join('\n')}` : `pid ${pid} · 미관측`);
3243
+ return;
3244
+ }
3203
3245
  const result = (deps.progress?.query ?? queryRunningRuns)({ caller: 'harness.progress' });
3204
3246
  const nowMs = (deps.progress?.nowMs ?? Date.now)();
3205
3247
  if (opts.json) {
@@ -81,7 +81,7 @@ export function discoverStopProcesses(runId: string, goalPath: string | null, ta
81
81
  const match = /^\s*(\d+)\s+((?:Mon|Tue|Wed|Thu|Fri|Sat|Sun)\s+\w+\s+\d+\s+\d\d:\d\d:\d\d\s+\d{4})\s+(.+)$/.exec(line);
82
82
  if (!match) return { status: 'failed', reason: 'unparseable process table row' };
83
83
  const pid = Number(match[1]);
84
- const startedAt = Date.parse(match[2]!);
84
+ const startedAt = Date.parse(`${match[2]!} GMT`);
85
85
  if (!Number.isSafeInteger(pid) || !Number.isFinite(startedAt)) return { status: 'failed', reason: 'invalid process table row' };
86
86
  if (pid <= 1) continue;
87
87
  const args = match[3]!.match(/"[^"]*"|'[^']*'|\S+/g)?.map((part) => part.replace(/^(['"])(.*)\1$/, '$2')) ?? [];
@@ -132,7 +132,9 @@ function stopProcessCwd(pid: number): string | null {
132
132
 
133
133
  function scanStopProcessTable(runId: string, goalPath: string | null): StopProcessScan {
134
134
  let r: ReturnType<typeof spawnSync>;
135
- try { r = spawnSync('ps', ['-eo', 'pid=,lstart=,args='], { encoding: 'utf8', timeout: 5_000, maxBuffer: 16 * 1024 * 1024 }); }
135
+ try { r = spawnSync('ps', ['-eo', 'pid=,lstart=,args='], {
136
+ encoding: 'utf8', timeout: 5_000, maxBuffer: 16 * 1024 * 1024, env: { ...process.env, TZ: 'UTC' },
137
+ }); }
136
138
  catch (error) { return { status: 'failed', reason: String(error) }; }
137
139
  if (r.error || r.status !== 0 || typeof r.stdout !== 'string') return { status: 'failed', reason: String(r.error ?? r.stderr ?? `ps status ${r.status}`) };
138
140
  return discoverStopProcesses(runId, goalPath, r.stdout, stopProcessCwd);
@@ -201,13 +203,15 @@ export function pidRecordFromLaunchBinding(runId: string, registryDir = launchRe
201
203
  return { pid: binding.pid, startedAt: binding.startedAt, ...(binding.argv0 ? { argv0: binding.argv0 } : {}) };
202
204
  }
203
205
 
204
- /** macOS·Linux 공통 — `ps -o lstart=` 는 로컬 시각 문자열(초 단위)이다. */
206
+ /** macOS·Linux 공통 — UTC 시작 시각과 상태를 같은 ps 관측에서 읽는다. */
205
207
  export function psProcessStartMs(pid: number): number | null {
206
- const r = spawnSync('ps', ['-o', 'lstart=', '-p', String(pid)], { encoding: 'utf8', timeout: 5_000 });
208
+ const r = spawnSync('ps', ['-o', 'lstart=,stat=', '-p', String(pid)], {
209
+ encoding: 'utf8', timeout: 5_000, env: { ...process.env, TZ: 'UTC' },
210
+ });
207
211
  if (r.status !== 0) return null;
208
- const text = r.stdout.trim();
209
- if (!text) return null;
210
- const ms = Date.parse(text);
212
+ const match = /^\s*((?:Mon|Tue|Wed|Thu|Fri|Sat|Sun)\s+\w+\s+\d+\s+\d\d:\d\d:\d\d\s+\d{4})\s+(\S+)\s*$/.exec(r.stdout);
213
+ if (!match || match[2]!.startsWith('Z')) return null;
214
+ const ms = Date.parse(`${match[1]!} GMT`);
211
215
  return Number.isFinite(ms) ? ms : null;
212
216
  }
213
217
 
package/src/index.ts CHANGED
@@ -38,6 +38,7 @@ import { DEV_PIPELINE_SINK_SURFACE } from './self-implement/self-cli-sink-surfac
38
38
  import { registerPrivateLedgerCommands } from './cli/private-ledger-commands.js';
39
39
  import { registerKnowCommand } from './cli/know-cli.js';
40
40
  import { registerSeatRequestsCommands } from './cli/seat-requests-cli.js';
41
+ import { registerSeatBootCommands } from './cli/seat-cli.js';
41
42
  import { applyTestStateDirFlagFromArgv } from './cli/test-state-dir-flag.js';
42
43
  import { applyTestFlagFromArgv, observeTestFlagOwnership, uncoveredTestFlagPaths, staleTestFlagPaths } from './cli/test-flag.js';
43
44
  import { registerPtyTakeoverCommands } from './cli/pty-takeover-cli.js';
@@ -1118,6 +1119,7 @@ hqCmd.command('fence').description('이 호스트가 임대를 쥐었을 때만
1118
1119
  process.exitCode = hqFenceRun(opts.role as 'cron', command);
1119
1120
  });
1120
1121
  const seatCmd = program.command('seat').description('분배된 자리의 하루 루프와 보고');
1122
+ registerSeatBootCommands(seatCmd);
1121
1123
  registerSeatRequestsCommands(seatCmd);
1122
1124
  seatCmd.command('asks').description('최근 CTO 되묻기 왕복과 10분 판정')
1123
1125
  .option('--since <duration>', '조회 창 (예: 24h)', '24h')
@@ -3352,6 +3354,7 @@ coordCmd.command('events')
3352
3354
  });
3353
3355
 
3354
3356
  const selfCmd = program.command('self').description('Self-awareness memory — 외부 도구(Claude Code/Codex)가 구현/변경 이력을 elanous 기억에 주입·회상');
3357
+ { const { registerSelfUtterancesCommand } = await import('./cli/self-utterances-cli.js'); registerSelfUtterancesCommand(selfCmd); }
3355
3358
 
3356
3359
  // M12 Claude Code 의존도 계기판 — `elanous self dependence [--since] [--until] [--json]`(읽기만).
3357
3360
  { const { registerDependenceCommand } = await import('./cli/dependence-gauge.js'); registerDependenceCommand(selfCmd); }
@@ -5744,6 +5747,19 @@ selfCmd
5744
5747
  envModel: () => process.env.ELANOUS_PR_REVIEW_MODEL,
5745
5748
  });
5746
5749
  if (opts.json) await writeStdoutJson(selfReviewJsonLine(results));
5750
+ // Detached TA review: stdout is now complete in its result file. Resume only the bound card.
5751
+ try {
5752
+ const { rejudgeCompletedTaskReview, TA_REVIEW_COMPLETION_CARD_ENV, TA_REVIEW_COMPLETION_STATE_ENV, TA_REVIEW_COMPLETION_RESULT_ENV } = await import('./task-agent/review-result-capture.js');
5753
+ const completionEnv: Record<string, string | undefined> = {};
5754
+ for (const key of [TA_REVIEW_COMPLETION_CARD_ENV, TA_REVIEW_COMPLETION_STATE_ENV, TA_REVIEW_COMPLETION_RESULT_ENV]) {
5755
+ const value = process.env[key];
5756
+ if (value) completionEnv[key] = value;
5757
+ delete process.env[key];
5758
+ }
5759
+ await rejudgeCompletedTaskReview(completionEnv);
5760
+ } catch (error) {
5761
+ try { debug.log('task-agent', 'review-completion-rejudge-failed', { reason: String(error).slice(-300) }, { level: 'warn' }); } catch { /* fail-soft */ }
5762
+ }
5747
5763
  if (failed >= 1) process.exitCode = 1;
5748
5764
  });
5749
5765
 
@@ -5822,22 +5838,25 @@ selfCmd
5822
5838
  .description('외부 도구 발화를 elanous 기억에 주입 — origin/git/branch/cwd/시간 태그(회상이 "누가 언제 어디서" 구분). text=--text 또는 stdin')
5823
5839
  .option('-s, --text <text>', '발화 원문(생략 시 stdin)')
5824
5840
  .option('--source <origin>', 'claude-code|codex|gemini|…', 'claude-code')
5841
+ .option('--origin <origin>', '발화 주체: human|agent (기본 agent)')
5825
5842
  .option('--session <id>', '세션 id')
5826
5843
  .option('--git-hash <hash>', '발화 시점 git HEAD(short)')
5827
5844
  .option('--branch <name>', 'git 브랜치')
5828
5845
  .option('--cwd <dir>', '작업 디렉토리')
5829
5846
  .option('--ts <iso>', 'ISO 시간(기본 now)')
5830
5847
  .option('-i, --importance <n>', '0-10 현저성', '5')
5831
- .action(async (opts: { text?: string; source: string; session?: string; gitHash?: string; branch?: string; cwd?: string; ts?: string; importance: string }) => {
5848
+ .action(async (opts: { text?: string; source: string; origin?: string; session?: string; gitHash?: string; branch?: string; cwd?: string; ts?: string; importance: string }) => {
5849
+ if (opts.origin !== undefined && opts.origin !== 'human' && opts.origin !== 'agent') { ui.error('utterance: --origin must be human or agent'); process.exitCode = 1; return; }
5832
5850
  let text = opts.text;
5833
5851
  if (!text) text = await readPipedStdin(); // ★ OBS-T3
5834
5852
  if (!text || !text.trim()) { ui.error('utterance: --text 또는 stdin 필요'); process.exit(1); }
5835
5853
  // 독립 CLI 프로세스 — 데몬 StoreSink 를 상속 안 하므로 logs.db 싱크를 붙여야
5836
5854
  // injectUtterance 의 `debug.log('memory.utterance','ingress')` 가 logs.db 에 닿음(제1원칙 관측).
5837
- const { injectUtterance } = await import('./domains/self-awareness.js');
5838
- const r = injectUtterance({
5855
+ const { recordSelfUtterance } = await import('./cli/self-utterances-cli.js');
5856
+ const r = recordSelfUtterance({
5839
5857
  text: text.trim(),
5840
5858
  origin: opts.source,
5859
+ utteranceOrigin: opts.origin === 'human' ? 'human' : 'agent',
5841
5860
  ...(opts.session ? { sessionId: opts.session } : {}),
5842
5861
  ...(opts.gitHash ? { gitHash: opts.gitHash } : {}),
5843
5862
  ...(opts.branch ? { branch: opts.branch } : {}),
@@ -11722,7 +11741,10 @@ async function main(): Promise<void> {
11722
11741
  // 한다. 인터랙티브 TUI 는 데몬 sink 를 상속 안 하므로 안 붙이면 debug.log 가 no-op
11723
11742
  // (제1원칙 관측 갭 · tui-sim 드라이브에서 agent.source 0건으로 실측 2026-07-19).
11724
11743
  // PR#4622(self utterance CLI)·session-cli 미러. fail-open·비블로킹.
11744
+ const bootTimer = (await import('./dashboard/boot-timing.js')).createBootTimer();
11745
+ bootTimer.begin();
11725
11746
  try { await (await import('./domains/standalone-log-sink.js')).registerStandaloneLogSink('tui'); } catch { /* fail-open */ }
11747
+ bootTimer.phase('log-sink');
11726
11748
  // P3(대표 2026-07-26) — footgun 관측을 logs.db 에 도달시킨다. 초반(arg 파싱 前) warnProdSpawnFootgun 은
11727
11749
  // StoreSink 등록 前이라 debug.log 가 logs.db 미도달이었다(순서 갭). sink 등록 직후 관측만 재발행(stderr 중복 X).
11728
11750
  try { (await import('./instance-root-coherence.js')).warnProdSpawnFootgun({ emit: 'log' }); } catch { /* fail-open */ }
@@ -11732,7 +11754,9 @@ async function main(): Promise<void> {
11732
11754
  // so `elanous self screen` / `/v1/terminals` / (P2) PWA can see the
11733
11755
  // dashboard the user is actually looking at. Off unless this boots
11734
11756
  // (no producer in headless/daemon paths). fail-soft·비블로킹.
11757
+ bootTimer.begin();
11735
11758
  try { (await import('./capture/tui-self-report.js')).startTuiSelfReport(); } catch { /* fail-soft */ }
11759
+ bootTimer.phase('self-report');
11736
11760
 
11737
11761
  // Dashboard handles sync inline — only returns on quit
11738
11762
  // ⛔ stdin 이 TTY 가 아니면 «뜨지 않는다» — 뜨면 readKey 가 엔터를 지어내 초당 수백 프레임을
@@ -11745,7 +11769,14 @@ async function main(): Promise<void> {
11745
11769
  return;
11746
11770
  }
11747
11771
  }
11748
- await showDashboard(dashboardOpts);
11772
+ bootTimer.begin();
11773
+ await showDashboard({
11774
+ ...dashboardOpts,
11775
+ onFirstFrameRendered: () => {
11776
+ bootTimer.phase('show-dashboard');
11777
+ bootTimer.done();
11778
+ },
11779
+ });
11749
11780
  closeTui();
11750
11781
  // Force exit: the dashboard starts background timers (clipboard
11751
11782
  // poller, shimmer/blink animations) that keep the Bun event loop
@@ -1,9 +1,8 @@
1
- import { join } from 'node:path';
2
1
  import { debug } from '../debug/log.js';
3
2
  import { getElanousConfigDirOverride } from '../elanous-config-dir.js';
4
3
  import { getTestStateRoot } from '../nexus/paths.js';
5
- import { findTreeRoot, getAppliedGlobalTestRoot } from '../cli/test-flag.js';
6
- import { isInstalledCopyScript, treeFromScriptPath } from './leader.js';
4
+ import { getAppliedGlobalTestRoot } from '../cli/test-flag.js';
5
+ import { isInstalledCopyScript } from './leader.js';
7
6
  import { prodInstanceRoot, resolveInstance, type InstanceResolution } from './resolve.js';
8
7
 
9
8
  interface ResolveCurrentInstanceDeps {
@@ -16,15 +15,12 @@ interface ResolveCurrentInstanceDeps {
16
15
 
17
16
  /** 현재 프로세스의 4층 판정 입력을 모아 순수 리졸버에 전달한다. */
18
17
  export function resolveCurrentInstance(deps: ResolveCurrentInstanceDeps = {}): InstanceResolution {
19
- const cwd = (deps.cwd ?? (() => process.cwd()))();
20
18
  const explicitFlagRoot = (deps.explicitFlagRoot ?? (() =>
21
19
  getAppliedGlobalTestRoot() ?? getTestStateRoot() ?? getElanousConfigDirOverride()))();
22
- const treeRoot = (process.argv[1] ? treeFromScriptPath(process.argv[1]) : null) ?? findTreeRoot(cwd);
23
20
  const resolution = resolveInstance({
24
21
  explicitFlagRoot,
25
22
  stampedStateDir: (deps.stampedStateDir ?? (() => process.env.ELANOUS_STATE_DIR))(),
26
23
  installedCopy: isInstalledCopyScript(),
27
- treeTestRoot: treeRoot ? join(treeRoot, '.elanous-test') : null,
28
24
  prodRoot: prodInstanceRoot(),
29
25
  });
30
26
  debug.log('instance.current', 'resolved', { layer: resolution.layer, kind: resolution.kind });
@@ -1,9 +1,9 @@
1
- // 인스턴스 우주: 명시 루트 → 부모 스탬프 → 설치본 → 소스 트리 격리.
1
+ // 인스턴스 우주: 명시 루트 → 부모 스탬프 → 운영 집. 트리 위치는 우주를 고르지 않는다.
2
2
  import { homedir } from 'node:os';
3
3
  import { join, resolve } from 'node:path';
4
4
 
5
5
  export type InstanceKind = 'prod' | 'test';
6
- export type ResolutionLayer = 'explicit-flag' | 'parent-stamp' | 'installed' | 'tree-derived' | 'default';
6
+ export type ResolutionLayer = 'explicit-flag' | 'parent-stamp' | 'installed' | 'default';
7
7
 
8
8
  export interface InstanceResolution {
9
9
  kind: InstanceKind;
@@ -50,10 +50,9 @@ export function resolveInstance(deps: ResolveDeps = {}): InstanceResolution {
50
50
  }
51
51
  const installed = deps.installedCopy ?? isInstalledExecution();
52
52
  if (installed) return { kind: 'prod', root: prodRoot, layer: 'installed', why: '설치본으로 실행' };
53
- const treeTestRoot = deps.treeTestRoot === undefined ? treeDerivedRootFor(process.cwd()) : deps.treeTestRoot;
54
- if (treeTestRoot) return { kind: 'test', root: normRoot(treeTestRoot), layer: 'tree-derived', why: '소스 트리(격리)' };
55
- // 레포 밖의 비설치 실행은 운영 권위를 얻지 않는다. 격리 루트를 cwd 에 둔다.
56
- return { kind: 'test', root: normRoot(join(process.cwd(), '.elanous-test')), layer: 'default', why: '소스 트리 밖 실행(격리)' };
53
+ // Explicit --test selects a test root before this resolver runs. A checkout,
54
+ // worktree, or arbitrary cwd alone never changes the operating house.
55
+ return { kind: 'prod', root: prodRoot, layer: 'default', why: '명시 시험 우주가 없는 운영 집' };
57
56
  }
58
57
 
59
58
  /** 부모가 도장으로 물려준 우주와 자식이 실제로 쓰는 우주가 다른가. 비어 있는 쪽은 비교하지 않는다. */
@@ -0,0 +1,89 @@
1
+ /**
2
+ * ⛔⭐ OpenAI Chat Completions 의 «출력 상한» 칸 이름은 모델 세대로 갈린다.
3
+ *
4
+ * `gpt-5` 이후 계열은 `max_tokens` 를 ***거부한다*** — 400 `unsupported_parameter` 를 내고
5
+ * *"Use 'max_completion_tokens' instead"* 라고 말한다. `gpt-4o` 계열은 반대로 옛 이름만 받는다.
6
+ *
7
+ * 📏 2026-08-22 실측: `provider auto` 의 티어 사다리가 `gpt-5.6-*` 로 바뀐 뒤(`#11161`)
8
+ * 3시간 만에 이 400 이 **93건** 났고 «가속»했다(20h:2 → 21h:16 → 22h:30 → 23h:45).
9
+ * 7일 창에서 그 이전 발생은 ***0***이다. ⇒ 칸 이름을 모델로 갈라야 한다.
10
+ *
11
+ * ⚠️ 값을 «안 보내는» 선택은 하지 않는다 — 상한이 사라지면 비용이 조용히 는다.
12
+ */
13
+ /**
14
+ * ⛔⭐⭐ **브랜드마다 «칸 이름」이 다르다**(2026-09-12). `makeOpenAICompatProvider` 는 `grok` 과
15
+ * `openai` 를 «한 경로»로 태우는데, 종전엔 둘 다 옛 칸(`max_tokens`·`temperature`)을 «생으로» 박았다.
16
+ * ⇒ `openai` 브랜드의 새 세대(`gpt-5`+)는 그 둘을 «거부»한다 — 400 `unsupported_parameter`.
17
+ *
18
+ * 📏 실측 2026-09-12: `gpt-6-astra · maxTokens=2048 · api.openai.com/v1/chat/completions` 가 400 을 받았고,
19
+ * 그 400 이 `review.done` 미실행 57건 중 ***30건***의 사유였다(무인 리뷰가 그만큼 «안 돌았다»).
20
+ *
21
+ * ⭐ 자는 «이미» 있었다 — `openAiOutputTokenField`·`openAiTemperatureField`. ***이 호출부가 안 물었을 뿐이다.***
22
+ * 🪞 같은 파일의 `stripLocalLlmSpec` 이 「두 builder 중 하나만 정규화했다」로 난 사고의 처방이었다.
23
+ * ***같은 모양이 다시 났다*** — 그래서 이번에도 «순수 함수로 뽑아» 양쪽이 같은 자를 쓰게 한다.
24
+ *
25
+ * ⛔ grok 은 옛 칸을 그대로 받는다 — 바꾸지 않는다.
26
+ */
27
+ export function openAiCompatSamplingFields(
28
+ brand: string,
29
+ model: string,
30
+ temperature: number,
31
+ maxTokens: number,
32
+ ): Record<string, number> {
33
+ return brand === 'openai'
34
+ ? { ...openAiTemperatureField(model, temperature), ...openAiOutputTokenField(model, maxTokens) }
35
+ : { temperature, max_tokens: maxTokens };
36
+ }
37
+
38
+ export function openAiOutputTokenField(model: string, maxTokens: number): Record<string, number> {
39
+ return isNewGenerationOpenAiModel(model)
40
+ ? { max_completion_tokens: maxTokens }
41
+ : { max_tokens: maxTokens };
42
+ }
43
+
44
+ /** `gpt-5` 이후 계열 판정 — 옛 Chat Completions 파라미터를 거부하는 세대.
45
+ *
46
+ * 🩸⭐⭐ **2026-09-12 — 이 자가 «늙어서» 400 이 돌아왔다.** 종전 문면은 `/^(?:gpt-5|o[1-9])/` 라
47
+ * ***`gpt-6` 계열을 «못 봤다»***. 그래서 `gpt-6-astra` 가 `max_tokens` 로 나갔고 400 을 받았다:
48
+ * `Unsupported parameter: 'max_tokens' is not supported with this model. Use 'max_completion_tokens'`
49
+ * 📏 실측(2026-09-12 · 12시간 창): `review.done` 200행 중 `reviewed=false` 57, 그중 ***30***이 이 400 이다.
50
+ * ⊕ 요청 로그가 그것을 한 줄로 말한다 — `gpt-6-astra · maxTokens=2048 · api.openai.com/v1/chat/completions`.
51
+ *
52
+ * ⇒ ⭐ **세대를 「이름 목록」으로 세지 않는다.** 번호를 «범위»로 읽어 다음 세대가 와도 안 늙게 한다.
53
+ * `gpt-5` 이상(두 자리 포함) ⊕ `o1`~`o9`. ⛔ `gpt-4` 이하와 로컬 모델은 종전대로 옛 칸을 쓴다.
54
+ *
55
+ * 🌐 **외부 그라운딩**(2026-09-12 · omni-crawl `fc-dev`) — 같은 사고가 «업스트림에도» 있었다:
56
+ * · `quantumnous/new-api#7211` — *"GPT-6 Astra 未命中原来的 GPT-5 前缀判断"* (똑같은 접두 판정 누락)
57
+ * · `genkit-ai/genkit#6320` — *"max_tokens is sent verbatim and rejected by reasoning models"*
58
+ * · Azure AI docs — `gpt-6-astra`(2026-09-03) Chat Completions ⊕ Reasoning
59
+ * · overchat.ai — *"Astra does not support `temperature`, `top_p`, `top_logprobs`"*
60
+ * ⇒ ✅ 우리가 `temperature` 를 «생략»하는 것이 외부 사실과 «맞는다».
61
+ * ⚠️ `top_p`·`logprobs` 는 이 경로가 «안 보낸다»(local 프리셋 경로만 보낸다) — 그래서 조치 없음.
62
+ *
63
+ * ⚖️ ⛔ **알려진 트레이드오프 — new-api 는 «반대» 결론을 냈다.** 그들은
64
+ * *"모든 미래 GPT 메이저를 GPT-5 로 묶으면 서로 다른 모델의 파라미터 규칙이 엮인다"* 며
65
+ * ***미지의 미래 메이저는 추론하지 않는다***로 갔다(명시 목록 + 날짜 스냅샷).
66
+ * ⇒ 우리는 «반대»를 택했다. 이유: ***목록이 늙어서 이 사고가 났기 때문***이다
67
+ * (이 저장소에서 실제로 리뷰 미실행 30건을 냈다). 새 규칙은 «새 관례 쪽으로» 실패한다.
68
+ * 📌 그러므로 ***`gpt-N` 중 옛 칸을 «되찾는» 모델이 나오면 이 자가 틀린다.*** 그때는
69
+ * 여기서 갈지 말고 «모델별 능력 선언»으로 옮긴다(new-api 가 간 길). 지금은 그 표본이 0이다. */
70
+ export function isNewGenerationOpenAiModel(model: string): boolean {
71
+ const match = /^gpt-(\d+)/i.exec(model);
72
+ if (match) return Number.parseInt(match[1]!, 10) >= 5;
73
+ return /^o[1-9]/i.test(model);
74
+ }
75
+
76
+ /**
77
+ * ⛔⭐ 같은 세대가 `temperature` 도 거부한다 — 400 `unsupported_value`,
78
+ * *"Only the default (1) value is supported"*.
79
+ *
80
+ * 📏 2026-08-22 실측: `max_tokens` 를 고친 «2분 뒤»(00:13:03Z)부터 이 400 이 났고
81
+ * 90분에 **41건**이었다. ⇒ ***한 파라미터만 고치면 다음 파라미터에서 막힌다.***
82
+ * ⚠️ 그때 나는 「수리 뒤 400 이 0건」이라 보고했는데, 그 자가 `grep max_completion_tokens`
83
+ * 라서 «새 400»을 못 봤다 — ***좁은 자로 「나았다」를 읽었다.***
84
+ *
85
+ * ⇒ 값을 «1로 보내지» 않고 ***칸을 생략***한다. 서버 기본이 1이고, 보내면 또 다른 거부를 부른다.
86
+ */
87
+ export function openAiTemperatureField(model: string, temperature: number): Record<string, number> {
88
+ return isNewGenerationOpenAiModel(model) ? {} : { temperature };
89
+ }
@@ -0,0 +1,61 @@
1
+ import type { LLMProviderName } from '../user-config.js';
2
+
3
+ /** Lightweight model-id family — prefix/substring only. No catalog, no
4
+ * `src/llm/model-defaults.ts` (that module imports this file). */
5
+ export type RuntimeLlmModelFamily =
6
+ | 'grok' | 'anthropic' | 'openai' | 'openai-codex' | 'gemini' | 'local'
7
+ | 'kimi' | 'qwen' | 'glm' | 'openrouter' | 'bedrock' | 'unknown';
8
+
9
+ export function inferRuntimeLlmModelFamily(model: string | undefined): RuntimeLlmModelFamily {
10
+ if (!model) return 'unknown';
11
+ const m = model.trim().toLowerCase();
12
+ if (!m) return 'unknown';
13
+ // ⛔ 2026-09-23 — 게이트웨이 접두가 «먼저»다. 안 그러면 `openrouter/anthropic/claude-…` 가 `anthropic` 으로
14
+ // 읽혀 openrouter 와 «비호환»이 되고, `openrouter/z-ai/…` 는 `unknown` 이라 아무 provider 와도 «호환»이 됐다.
15
+ if (m.startsWith('openrouter/')) return 'openrouter';
16
+ // ⭐ BEDROCK-PROVIDER — Bedrock 전용 꼴(`anthropic.claude-…`·지역 접두·`bedrock/…`)이 `includes('claude')` 보다 «먼저».
17
+ // 1st-party id(`claude-…`)는 그대로 anthropic 이다(불변).
18
+ if (m.startsWith('bedrock/') || /^(?:[a-z]{2,6}\.)?anthropic\.claude-/.test(m)) return 'bedrock';
19
+ if (m.includes('grok')) return 'grok';
20
+ if (m.includes('claude')) return 'anthropic';
21
+ if (m.startsWith('gemini-') || m.includes('gemini')) return 'gemini';
22
+ if (m.startsWith('local:')) return 'local';
23
+ if (m.startsWith('kimi')) return 'kimi';
24
+ if (m.startsWith('qwen')) return 'qwen';
25
+ if (m.startsWith('glm')) return 'glm';
26
+ // openai-codex 구독 모델만 여기 — gpt-5.5 등 Chat Completions 모델은 openai.
27
+ // ⛔ openai 와 openai-codex 를 한 계열로 접지 마라. 서로 다른 엔드포인트다.
28
+ // ✅ 2026-09-23 (대표 ⒜ 결정) — ***`gpt-6` 를 여기 «더했다».***
29
+ // 종전엔 `gpt-6-*` 가 아래 `startsWith('gpt-')` 로 떨어져 `openai` 가 됐고, 그래서
30
+ // ***사다리가 «자기 가드가 거부하는 모델»을 가리켰다*** (`CODEX.loaded = gpt-6-astra`).
31
+ // 📏 구독 경로 실호출로 확인(codex-cli 0.155.1): gpt-6-sol · luna · astra 전부 응답.
32
+ // ⚠️ 이 셋은 API 로도 불린다(`/v1/models` 에 있다). 「계열」이 재는 것은 «가용성»이 아니라
33
+ // ***「우리가 어느 문으로 보내나」***다 — 5.6 계열도 API 에 있지만 여기로 온다.
34
+ if (m.includes('codex') || m.startsWith('gpt-5.6') || m.startsWith('gpt-6')) return 'openai-codex';
35
+ // Exact o1/o3/o4 ids are standard OpenAI models; prefix-only (`o1-`) misses them.
36
+ if (
37
+ m.startsWith('gpt-')
38
+ || m === 'o1' || m.startsWith('o1-')
39
+ || m === 'o3' || m.startsWith('o3-')
40
+ || m === 'o4' || m.startsWith('o4-')
41
+ ) return 'openai';
42
+ return 'unknown';
43
+ }
44
+
45
+ /** Cycle-safe compatibility: same-provider model overrides pass;
46
+ * cross-family ids fail. Unknown prefixes and `auto` stay silent.
47
+ * ⛔ `openai` 와 `openai-codex` 는 별개 provider — 양방향 호환으로 접지 않는다. */
48
+ export function isRuntimeLlmModelCompatibleWithProvider(
49
+ provider: LLMProviderName,
50
+ model: string | undefined,
51
+ ): boolean {
52
+ if (!model) return true;
53
+ if (provider === 'auto') return true;
54
+ const family = inferRuntimeLlmModelFamily(model);
55
+ if (family === 'unknown') return true;
56
+ if (provider === 'local') return family === 'local';
57
+ // bedrock 은 1st-party claude id 도 받는다(공급자가 `anthropic.` 접두로 바꿔 보낸다).
58
+ if (provider === 'bedrock') return family === 'bedrock' || family === 'anthropic';
59
+ return provider === family;
60
+ }
61
+
@@ -122,6 +122,26 @@ export interface ZaiErrorClass {
122
122
  resetRaw: string | null;
123
123
  }
124
124
 
125
+ /** Provider-neutral signal shape for supervisors coordinating plan-limit recovery. */
126
+ export interface ProviderPlanLimitSignal {
127
+ provider: string;
128
+ kind: 'plan-limit';
129
+ resetAt: number | null;
130
+ overage: string;
131
+ }
132
+
133
+ /** Extract the structured recovery signal from a Z.ai 1308 response; unrelated errors return null. */
134
+ export function extractZaiPlanLimitSignal(
135
+ status: number,
136
+ bodyText: string,
137
+ overage: string,
138
+ ): ProviderPlanLimitSignal | null {
139
+ const classified = classifyZaiError(status, bodyText);
140
+ if (!classified || classified.code !== 1308 || classified.kind !== 'plan-limit') return null;
141
+ return { provider: 'glm', kind: 'plan-limit', resetAt: classified.resetAt, overage };
142
+ }
143
+
144
+
125
145
  const PLAN_LIMIT = new Set([1308, 1310]);
126
146
  const BALANCE_LIMIT = new Set([1113, 1316, 1317, 1318, 1319, 1320, 1321]);
127
147
  const PLAN_UNAVAILABLE = new Set([1309, 1311, 1313, 1314, 1315]);
@@ -433,13 +453,15 @@ export class ZaiCodingPlanLimitError extends Error {
433
453
  readonly code: number;
434
454
  readonly limitKind: ZaiLimitKind;
435
455
  readonly resetAt: number | null;
436
- constructor(opts: { code: number; limitKind: ZaiLimitKind; resetAt: number | null; policy: ZaiOveragePolicy; reason: string }) {
456
+ readonly planLimitSignal?: ProviderPlanLimitSignal;
457
+ constructor(opts: { code: number; limitKind: ZaiLimitKind; resetAt: number | null; policy: ZaiOveragePolicy; reason: string; planLimitSignal?: ProviderPlanLimitSignal }) {
437
458
  const when = opts.resetAt ? ` · 리셋 ${new Date(opts.resetAt).toISOString()}` : '';
438
459
  super(`GLM Coding Plan 한도(code ${opts.code} · ${opts.limitKind}${when}) — 정책 llm.zaiCodingPlan.overage=${opts.policy}: ${opts.reason}`);
439
460
  this.name = 'ZaiCodingPlanLimitError';
440
461
  this.code = opts.code;
441
462
  this.limitKind = opts.limitKind;
442
463
  this.resetAt = opts.resetAt;
464
+ if (opts.planLimitSignal) this.planLimitSignal = opts.planLimitSignal;
443
465
  }
444
466
  }
445
467
 
@@ -575,6 +597,9 @@ export function makeZaiCodingPlanProvider(cfg: UCLLMConfig, deps: ZaiProviderDep
575
597
  const status = (error as { status?: unknown })?.status;
576
598
  const bodyText = (error as { bodyText?: unknown })?.bodyText;
577
599
  const cls = typeof status === 'number' && typeof bodyText === 'string' ? classifyZaiError(status, bodyText) : null;
600
+ const planLimitSignal = typeof status === 'number' && typeof bodyText === 'string'
601
+ ? extractZaiPlanLimitSignal(status, bodyText, settings.overage)
602
+ : null;
578
603
  if (!cls || cls.kind === 'rate' || cls.kind === 'other') {
579
604
  if (cls) debug.log('zai.coding-plan', 'error', { code: cls.code, kind: cls.kind, model: wireModel }, { level: 'warn' });
580
605
  throw error;
@@ -588,6 +613,7 @@ export function makeZaiCodingPlanProvider(cfg: UCLLMConfig, deps: ZaiProviderDep
588
613
  if (action === 'stop' || emitted) {
589
614
  throw new ZaiCodingPlanLimitError({
590
615
  code: cls.code, limitKind: cls.kind, resetAt: cls.resetAt, policy: settings.overage,
616
+ ...(planLimitSignal ? { planLimitSignal } : {}),
591
617
  reason: emitted ? '출력이 이미 나간 뒤라 폴백하지 않는다' : '리셋까지 멈춘다',
592
618
  });
593
619
  }
package/src/llm.ts CHANGED
@@ -11699,92 +11699,16 @@ export function getProviderForConfig(
11699
11699
  import { getModelFamily } from './models/prompts.js';
11700
11700
  import type { ModelFamily } from './models/prompts.js';
11701
11701
 
11702
- /**
11703
- * ⛔⭐ OpenAI Chat Completions 의 «출력 상한» 칸 이름은 모델 세대로 갈린다.
11704
- *
11705
- * `gpt-5` 이후 계열은 `max_tokens` 를 ***거부한다*** — 400 `unsupported_parameter` 를 내고
11706
- * *"Use 'max_completion_tokens' instead"* 라고 말한다. `gpt-4o` 계열은 반대로 옛 이름만 받는다.
11707
- *
11708
- * 📏 2026-08-22 실측: `provider auto` 의 티어 사다리가 `gpt-5.6-*` 로 바뀐 뒤(`#11161`)
11709
- * 3시간 만에 이 400 이 **93건** 났고 «가속»했다(20h:2 → 21h:16 → 22h:30 → 23h:45).
11710
- * 7일 창에서 그 이전 발생은 ***0***이다. ⇒ 칸 이름을 모델로 갈라야 한다.
11711
- *
11712
- * ⚠️ 값을 «안 보내는» 선택은 하지 않는다 — 상한이 사라지면 비용이 조용히 는다.
11713
- */
11714
- /**
11715
- * ⛔⭐⭐ **브랜드마다 «칸 이름」이 다르다**(2026-09-12). `makeOpenAICompatProvider` 는 `grok` 과
11716
- * `openai` 를 «한 경로»로 태우는데, 종전엔 둘 다 옛 칸(`max_tokens`·`temperature`)을 «생으로» 박았다.
11717
- * ⇒ `openai` 브랜드의 새 세대(`gpt-5`+)는 그 둘을 «거부»한다 — 400 `unsupported_parameter`.
11718
- *
11719
- * 📏 실측 2026-09-12: `gpt-6-astra · maxTokens=2048 · api.openai.com/v1/chat/completions` 가 400 을 받았고,
11720
- * 그 400 이 `review.done` 미실행 57건 중 ***30건***의 사유였다(무인 리뷰가 그만큼 «안 돌았다»).
11721
- *
11722
- * ⭐ 자는 «이미» 있었다 — `openAiOutputTokenField`·`openAiTemperatureField`. ***이 호출부가 안 물었을 뿐이다.***
11723
- * 🪞 같은 파일의 `stripLocalLlmSpec` 이 「두 builder 중 하나만 정규화했다」로 난 사고의 처방이었다.
11724
- * ***같은 모양이 다시 났다*** — 그래서 이번에도 «순수 함수로 뽑아» 양쪽이 같은 자를 쓰게 한다.
11725
- *
11726
- * ⛔ grok 은 옛 칸을 그대로 받는다 — 바꾸지 않는다.
11727
- */
11728
- export function openAiCompatSamplingFields(
11729
- brand: string,
11730
- model: string,
11731
- temperature: number,
11732
- maxTokens: number,
11733
- ): Record<string, number> {
11734
- return brand === 'openai'
11735
- ? { ...openAiTemperatureField(model, temperature), ...openAiOutputTokenField(model, maxTokens) }
11736
- : { temperature, max_tokens: maxTokens };
11737
- }
11738
-
11739
- export function openAiOutputTokenField(model: string, maxTokens: number): Record<string, number> {
11740
- return isNewGenerationOpenAiModel(model)
11741
- ? { max_completion_tokens: maxTokens }
11742
- : { max_tokens: maxTokens };
11743
- }
11744
-
11745
- /** `gpt-5` 이후 계열 판정 — 옛 Chat Completions 파라미터를 거부하는 세대.
11746
- *
11747
- * 🩸⭐⭐ **2026-09-12 — 이 자가 «늙어서» 400 이 돌아왔다.** 종전 문면은 `/^(?:gpt-5|o[1-9])/` 라
11748
- * ***`gpt-6` 계열을 «못 봤다»***. 그래서 `gpt-6-astra` 가 `max_tokens` 로 나갔고 400 을 받았다:
11749
- * `Unsupported parameter: 'max_tokens' is not supported with this model. Use 'max_completion_tokens'`
11750
- * 📏 실측(2026-09-12 · 12시간 창): `review.done` 200행 중 `reviewed=false` 57, 그중 ***30***이 이 400 이다.
11751
- * ⊕ 요청 로그가 그것을 한 줄로 말한다 — `gpt-6-astra · maxTokens=2048 · api.openai.com/v1/chat/completions`.
11752
- *
11753
- * ⇒ ⭐ **세대를 「이름 목록」으로 세지 않는다.** 번호를 «범위»로 읽어 다음 세대가 와도 안 늙게 한다.
11754
- * `gpt-5` 이상(두 자리 포함) ⊕ `o1`~`o9`. ⛔ `gpt-4` 이하와 로컬 모델은 종전대로 옛 칸을 쓴다.
11755
- *
11756
- * 🌐 **외부 그라운딩**(2026-09-12 · omni-crawl `fc-dev`) — 같은 사고가 «업스트림에도» 있었다:
11757
- * · `quantumnous/new-api#7211` — *"GPT-6 Astra 未命中原来的 GPT-5 前缀判断"* (똑같은 접두 판정 누락)
11758
- * · `genkit-ai/genkit#6320` — *"max_tokens is sent verbatim and rejected by reasoning models"*
11759
- * · Azure AI docs — `gpt-6-astra`(2026-09-03) Chat Completions ⊕ Reasoning
11760
- * · overchat.ai — *"Astra does not support `temperature`, `top_p`, `top_logprobs`"*
11761
- * ⇒ ✅ 우리가 `temperature` 를 «생략»하는 것이 외부 사실과 «맞는다».
11762
- * ⚠️ `top_p`·`logprobs` 는 이 경로가 «안 보낸다»(local 프리셋 경로만 보낸다) — 그래서 조치 없음.
11763
- *
11764
- * ⚖️ ⛔ **알려진 트레이드오프 — new-api 는 «반대» 결론을 냈다.** 그들은
11765
- * *"모든 미래 GPT 메이저를 GPT-5 로 묶으면 서로 다른 모델의 파라미터 규칙이 엮인다"* 며
11766
- * ***미지의 미래 메이저는 추론하지 않는다***로 갔다(명시 목록 + 날짜 스냅샷).
11767
- * ⇒ 우리는 «반대»를 택했다. 이유: ***목록이 늙어서 이 사고가 났기 때문***이다
11768
- * (이 저장소에서 실제로 리뷰 미실행 30건을 냈다). 새 규칙은 «새 관례 쪽으로» 실패한다.
11769
- * 📌 그러므로 ***`gpt-N` 중 옛 칸을 «되찾는» 모델이 나오면 이 자가 틀린다.*** 그때는
11770
- * 여기서 갈지 말고 «모델별 능력 선언»으로 옮긴다(new-api 가 간 길). 지금은 그 표본이 0이다. */
11771
- export function isNewGenerationOpenAiModel(model: string): boolean {
11772
- const match = /^gpt-(\d+)/i.exec(model);
11773
- if (match) return Number.parseInt(match[1]!, 10) >= 5;
11774
- return /^o[1-9]/i.test(model);
11775
- }
11776
-
11777
- /**
11778
- * ⛔⭐ 같은 세대가 `temperature` 도 거부한다 — 400 `unsupported_value`,
11779
- * *"Only the default (1) value is supported"*.
11780
- *
11781
- * 📏 2026-08-22 실측: `max_tokens` 를 고친 «2분 뒤»(00:13:03Z)부터 이 400 이 났고
11782
- * 90분에 **41건**이었다. ⇒ ***한 파라미터만 고치면 다음 파라미터에서 막힌다.***
11783
- * ⚠️ 그때 나는 「수리 뒤 400 이 0건」이라 보고했는데, 그 자가 `grep max_completion_tokens`
11784
- * 라서 «새 400»을 못 봤다 — ***좁은 자로 「나았다」를 읽었다.***
11785
- *
11786
- * ⇒ 값을 «1로 보내지» 않고 ***칸을 생략***한다. 서버 기본이 1이고, 보내면 또 다른 거부를 부른다.
11787
- */
11788
- export function openAiTemperatureField(model: string, temperature: number): Record<string, number> {
11789
- return isNewGenerationOpenAiModel(model) ? {} : { temperature };
11790
- }
11702
+ // COMPAT(llm-facade-openai-sampling-fields) · 기존 llm.ts 공개 API 유지.
11703
+ import {
11704
+ openAiCompatSamplingFields,
11705
+ openAiOutputTokenField,
11706
+ isNewGenerationOpenAiModel,
11707
+ openAiTemperatureField,
11708
+ } from './llm/openai-sampling-fields.js';
11709
+ export {
11710
+ openAiCompatSamplingFields,
11711
+ openAiOutputTokenField,
11712
+ isNewGenerationOpenAiModel,
11713
+ openAiTemperatureField,
11714
+ };