cuelo 0.7.0 → 0.7.2

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 (321) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-path-routes-manifest.json +9 -8
  3. package/.next/build-manifest.json +3 -3
  4. package/.next/prerender-manifest.json +3 -3
  5. package/.next/required-server-files.js +2 -2
  6. package/.next/required-server-files.json +2 -2
  7. package/.next/routes-manifest.json +6 -0
  8. package/.next/server/app/_global-error/page.js +3 -3
  9. package/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  10. package/.next/server/app/_global-error.html +1 -1
  11. package/.next/server/app/_global-error.rsc +1 -1
  12. package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  13. package/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  14. package/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  15. package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  16. package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  17. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  18. package/.next/server/app/_not-found/page.js +2 -2
  19. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  20. package/.next/server/app/_not-found.html +1 -1
  21. package/.next/server/app/_not-found.rsc +3 -3
  22. package/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
  23. package/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  24. package/.next/server/app/_not-found.segments/_index.segment.rsc +3 -3
  25. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  26. package/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  27. package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  28. package/.next/server/app/api/agent/[id]/account/route.js +1 -1
  29. package/.next/server/app/api/agent/[id]/bash-output/route.js +1 -1
  30. package/.next/server/app/api/agent/[id]/events/route.js +2 -2
  31. package/.next/server/app/api/agent/[id]/route.js +1 -1
  32. package/.next/server/app/api/agent/new/route.js +1 -1
  33. package/.next/server/app/api/agent/running/route.js +1 -1
  34. package/.next/server/app/api/attachments/route.js +1 -1
  35. package/.next/server/app/api/auth/all-providers/route.js +1 -1
  36. package/.next/server/app/api/auth/api-key/[provider]/route.js +1 -1
  37. package/.next/server/app/api/auth/login/[provider]/route.js +2 -2
  38. package/.next/server/app/api/auth/logout/[provider]/route.js +1 -1
  39. package/.next/server/app/api/auth/providers/route.js +1 -1
  40. package/.next/server/app/api/cwd/browse/route.js +1 -1
  41. package/.next/server/app/api/cwd/validate/route.js +1 -1
  42. package/.next/server/app/api/default-cwd/route.js +1 -1
  43. package/.next/server/app/api/display-settings/route.js +1 -1
  44. package/.next/server/app/api/document-text/route.js +1 -1
  45. package/.next/server/app/api/experiments/route.js +1 -1
  46. package/.next/server/app/api/file-index/route.js +1 -1
  47. package/.next/server/app/api/files/[...path]/route.js +1 -1
  48. package/.next/server/app/api/git/diff/route.js +2 -2
  49. package/.next/server/app/api/git/status/route.js +2 -2
  50. package/.next/server/app/api/gpt6/handles/route.js +1 -1
  51. package/.next/server/app/api/gpt6/reply/route.js +1 -1
  52. package/.next/server/app/api/gpt6/token/route.js +1 -1
  53. package/.next/server/app/api/gpt6/web6-session/route.js +1 -1
  54. package/.next/server/app/api/home/route.js +1 -1
  55. package/.next/server/app/api/live/call/[callId]/route.js +1 -1
  56. package/.next/server/app/api/live/call/[callId]/route.js.nft.json +1 -1
  57. package/.next/server/app/api/live/character-voice/route.js +1 -0
  58. package/.next/server/app/api/live/character-voice/route.js.nft.json +1 -0
  59. package/.next/server/app/api/live/character-voice/route_client-reference-manifest.js +1 -0
  60. package/.next/server/app/api/live/events/route.js +2 -2
  61. package/.next/server/app/api/live/events/route.js.nft.json +1 -1
  62. package/.next/server/app/api/live/offer/route.js +1 -1
  63. package/.next/server/app/api/live/offer/route.js.nft.json +1 -1
  64. package/.next/server/app/api/live/voices/route.js +1 -1
  65. package/.next/server/app/api/live/voices/route.js.nft.json +1 -1
  66. package/.next/server/app/api/lounge/events/route.js +2 -2
  67. package/.next/server/app/api/lounge/events/route.js.nft.json +1 -1
  68. package/.next/server/app/api/lounge/route.js +1 -1
  69. package/.next/server/app/api/lounge/route.js.nft.json +1 -1
  70. package/.next/server/app/api/mcp/route.js +2 -2
  71. package/.next/server/app/api/model-roles/route.js +1 -1
  72. package/.next/server/app/api/models/route.js +1 -1
  73. package/.next/server/app/api/models-config/catalog/route.js +1 -1
  74. package/.next/server/app/api/models-config/discover/route.js +1 -1
  75. package/.next/server/app/api/models-config/route.js +3 -3
  76. package/.next/server/app/api/models-config/test/route.js +1 -1
  77. package/.next/server/app/api/plugins/route.js +1 -1
  78. package/.next/server/app/api/plugins/route.js.nft.json +1 -1
  79. package/.next/server/app/api/project-trust/route.js +1 -1
  80. package/.next/server/app/api/projects/route.js +1 -1
  81. package/.next/server/app/api/runs/[runId]/route.js +1 -1
  82. package/.next/server/app/api/runs/route.js +1 -1
  83. package/.next/server/app/api/sessions/[id]/auto-name/route.js +1 -1
  84. package/.next/server/app/api/sessions/[id]/context/route.js +1 -1
  85. package/.next/server/app/api/sessions/[id]/entries/[entryId]/thinking/route.js +1 -1
  86. package/.next/server/app/api/sessions/[id]/export/route.js +1 -1
  87. package/.next/server/app/api/sessions/[id]/route.js +1 -1
  88. package/.next/server/app/api/sessions/[id]/state/route.js +1 -1
  89. package/.next/server/app/api/sessions/route.js +1 -1
  90. package/.next/server/app/api/sessions/search/route.js +1 -1
  91. package/.next/server/app/api/settings/route.js +1 -1
  92. package/.next/server/app/api/sidecars/[service]/[...path]/route.js +1 -1
  93. package/.next/server/app/api/skills/check/route.js +1 -1
  94. package/.next/server/app/api/skills/install/route.js +2 -2
  95. package/.next/server/app/api/skills/route.js +1 -1
  96. package/.next/server/app/api/skills/search/route.js +1 -1
  97. package/.next/server/app/api/skills/update/route.js +1 -1
  98. package/.next/server/app/api/theme/route.js +1 -1
  99. package/.next/server/app/api/tool-image/route.js +1 -1
  100. package/.next/server/app/api/update-maintenance/route.js +3 -3
  101. package/.next/server/app/api/updates/route.js +1 -1
  102. package/.next/server/app/api/web-access/recovery/route.js +1 -1
  103. package/.next/server/app/api/web-access/route.js +1 -1
  104. package/.next/server/app/api/web6/consults/route.js +1 -1
  105. package/.next/server/app/api/worktrees/route.js +1 -1
  106. package/.next/server/app/favicon.ico/route.js +1 -1
  107. package/.next/server/app/index.html +1 -1
  108. package/.next/server/app/index.rsc +6 -6
  109. package/.next/server/app/index.segments/__PAGE__.segment.rsc +4 -4
  110. package/.next/server/app/index.segments/_full.segment.rsc +6 -6
  111. package/.next/server/app/index.segments/_head.segment.rsc +1 -1
  112. package/.next/server/app/index.segments/_index.segment.rsc +3 -3
  113. package/.next/server/app/index.segments/_tree.segment.rsc +3 -3
  114. package/.next/server/app/manifest.webmanifest/route.js +2 -2
  115. package/.next/server/app/page.js +19 -19
  116. package/.next/server/app/page_client-reference-manifest.js +1 -1
  117. package/.next/server/app/recover/page.js +2 -2
  118. package/.next/server/app/recover/page_client-reference-manifest.js +1 -1
  119. package/.next/server/app/recover.html +1 -1
  120. package/.next/server/app/recover.rsc +3 -3
  121. package/.next/server/app/recover.segments/_full.segment.rsc +3 -3
  122. package/.next/server/app/recover.segments/_head.segment.rsc +1 -1
  123. package/.next/server/app/recover.segments/_index.segment.rsc +3 -3
  124. package/.next/server/app/recover.segments/_tree.segment.rsc +2 -2
  125. package/.next/server/app/recover.segments/recover/__PAGE__.segment.rsc +1 -1
  126. package/.next/server/app/recover.segments/recover.segment.rsc +1 -1
  127. package/.next/server/app-paths-manifest.json +9 -8
  128. package/.next/server/chunks/205.js +6 -0
  129. package/.next/server/chunks/424.js +3 -0
  130. package/.next/server/chunks/4649.js +1 -0
  131. package/.next/server/chunks/5582.js +2 -2
  132. package/.next/server/chunks/6429.js +2 -2
  133. package/.next/server/chunks/7310.js +5 -0
  134. package/.next/server/chunks/9108.js +1 -1
  135. package/.next/server/chunks/9280.js +4 -4
  136. package/.next/server/middleware-build-manifest.js +1 -1
  137. package/.next/server/pages/404.html +1 -1
  138. package/.next/server/pages/500.html +1 -1
  139. package/.next/server/server-reference-manifest.json +1 -1
  140. package/.next/static/5vW3pyOv87cK3zk2pchwv/_buildManifest.js +1 -0
  141. package/.next/static/chunks/app/_global-error/{page-701a3390117d7557.js → page-2bfa6c2cae0dc17e.js} +1 -1
  142. package/.next/static/chunks/app/api/agent/[id]/account/{route-701a3390117d7557.js → route-2bfa6c2cae0dc17e.js} +1 -1
  143. package/.next/static/chunks/app/api/agent/[id]/bash-output/{route-701a3390117d7557.js → route-2bfa6c2cae0dc17e.js} +1 -1
  144. package/.next/static/chunks/app/api/agent/[id]/events/{route-701a3390117d7557.js → route-2bfa6c2cae0dc17e.js} +1 -1
  145. package/.next/static/chunks/app/api/agent/[id]/route-2bfa6c2cae0dc17e.js +1 -0
  146. package/.next/static/chunks/app/api/agent/new/route-2bfa6c2cae0dc17e.js +1 -0
  147. package/.next/static/chunks/app/api/agent/running/route-2bfa6c2cae0dc17e.js +1 -0
  148. package/.next/static/chunks/app/api/attachments/route-2bfa6c2cae0dc17e.js +1 -0
  149. package/.next/static/chunks/app/api/auth/all-providers/route-2bfa6c2cae0dc17e.js +1 -0
  150. package/.next/static/chunks/app/api/auth/api-key/[provider]/route-2bfa6c2cae0dc17e.js +1 -0
  151. package/.next/static/chunks/app/api/auth/login/[provider]/route-2bfa6c2cae0dc17e.js +1 -0
  152. package/.next/static/chunks/app/api/auth/logout/[provider]/route-2bfa6c2cae0dc17e.js +1 -0
  153. package/.next/static/chunks/app/api/auth/providers/route-2bfa6c2cae0dc17e.js +1 -0
  154. package/.next/static/chunks/app/api/cwd/browse/route-2bfa6c2cae0dc17e.js +1 -0
  155. package/.next/static/chunks/app/api/cwd/validate/route-2bfa6c2cae0dc17e.js +1 -0
  156. package/.next/static/chunks/app/api/default-cwd/route-2bfa6c2cae0dc17e.js +1 -0
  157. package/.next/static/chunks/app/api/display-settings/route-2bfa6c2cae0dc17e.js +1 -0
  158. package/.next/static/chunks/app/api/document-text/route-2bfa6c2cae0dc17e.js +1 -0
  159. package/.next/static/chunks/app/api/experiments/route-2bfa6c2cae0dc17e.js +1 -0
  160. package/.next/static/chunks/app/api/file-index/route-2bfa6c2cae0dc17e.js +1 -0
  161. package/.next/static/chunks/app/api/files/[...path]/route-2bfa6c2cae0dc17e.js +1 -0
  162. package/.next/static/chunks/app/api/git/diff/route-2bfa6c2cae0dc17e.js +1 -0
  163. package/.next/static/chunks/app/api/git/status/route-2bfa6c2cae0dc17e.js +1 -0
  164. package/.next/static/chunks/app/api/gpt6/handles/route-2bfa6c2cae0dc17e.js +1 -0
  165. package/.next/static/chunks/app/api/gpt6/reply/route-2bfa6c2cae0dc17e.js +1 -0
  166. package/.next/static/chunks/app/api/gpt6/token/route-2bfa6c2cae0dc17e.js +1 -0
  167. package/.next/static/chunks/app/api/gpt6/web6-session/route-2bfa6c2cae0dc17e.js +1 -0
  168. package/.next/static/chunks/app/api/home/route-2bfa6c2cae0dc17e.js +1 -0
  169. package/.next/static/chunks/app/api/live/call/[callId]/route-2bfa6c2cae0dc17e.js +1 -0
  170. package/.next/static/chunks/app/api/live/character-voice/route-2bfa6c2cae0dc17e.js +1 -0
  171. package/.next/static/chunks/app/api/live/events/route-2bfa6c2cae0dc17e.js +1 -0
  172. package/.next/static/chunks/app/api/live/offer/route-2bfa6c2cae0dc17e.js +1 -0
  173. package/.next/static/chunks/app/api/live/voices/route-2bfa6c2cae0dc17e.js +1 -0
  174. package/.next/static/chunks/app/api/lounge/events/route-2bfa6c2cae0dc17e.js +1 -0
  175. package/.next/static/chunks/app/api/lounge/route-2bfa6c2cae0dc17e.js +1 -0
  176. package/.next/static/chunks/app/api/mcp/route-2bfa6c2cae0dc17e.js +1 -0
  177. package/.next/static/chunks/app/api/model-roles/route-2bfa6c2cae0dc17e.js +1 -0
  178. package/.next/static/chunks/app/api/models/route-2bfa6c2cae0dc17e.js +1 -0
  179. package/.next/static/chunks/app/api/models-config/catalog/route-2bfa6c2cae0dc17e.js +1 -0
  180. package/.next/static/chunks/app/api/models-config/discover/route-2bfa6c2cae0dc17e.js +1 -0
  181. package/.next/static/chunks/app/api/models-config/route-2bfa6c2cae0dc17e.js +1 -0
  182. package/.next/static/chunks/app/api/models-config/test/route-2bfa6c2cae0dc17e.js +1 -0
  183. package/.next/static/chunks/app/api/plugins/route-2bfa6c2cae0dc17e.js +1 -0
  184. package/.next/static/chunks/app/api/project-trust/route-2bfa6c2cae0dc17e.js +1 -0
  185. package/.next/static/chunks/app/api/projects/route-2bfa6c2cae0dc17e.js +1 -0
  186. package/.next/static/chunks/app/api/runs/[runId]/route-2bfa6c2cae0dc17e.js +1 -0
  187. package/.next/static/chunks/app/api/runs/route-2bfa6c2cae0dc17e.js +1 -0
  188. package/.next/static/chunks/app/api/sessions/[id]/auto-name/route-2bfa6c2cae0dc17e.js +1 -0
  189. package/.next/static/chunks/app/api/sessions/[id]/context/route-2bfa6c2cae0dc17e.js +1 -0
  190. package/.next/static/chunks/app/api/sessions/[id]/entries/[entryId]/thinking/route-2bfa6c2cae0dc17e.js +1 -0
  191. package/.next/static/chunks/app/api/sessions/[id]/export/route-2bfa6c2cae0dc17e.js +1 -0
  192. package/.next/static/chunks/app/api/sessions/[id]/route-2bfa6c2cae0dc17e.js +1 -0
  193. package/.next/static/chunks/app/api/sessions/[id]/state/route-2bfa6c2cae0dc17e.js +1 -0
  194. package/.next/static/chunks/app/api/sessions/route-2bfa6c2cae0dc17e.js +1 -0
  195. package/.next/static/chunks/app/api/sessions/search/route-2bfa6c2cae0dc17e.js +1 -0
  196. package/.next/static/chunks/app/api/settings/route-2bfa6c2cae0dc17e.js +1 -0
  197. package/.next/static/chunks/app/api/sidecars/[service]/[...path]/route-2bfa6c2cae0dc17e.js +1 -0
  198. package/.next/static/chunks/app/api/skills/check/route-2bfa6c2cae0dc17e.js +1 -0
  199. package/.next/static/chunks/app/api/skills/install/route-2bfa6c2cae0dc17e.js +1 -0
  200. package/.next/static/chunks/app/api/skills/route-2bfa6c2cae0dc17e.js +1 -0
  201. package/.next/static/chunks/app/api/skills/search/route-2bfa6c2cae0dc17e.js +1 -0
  202. package/.next/static/chunks/app/api/skills/update/route-2bfa6c2cae0dc17e.js +1 -0
  203. package/.next/static/chunks/app/api/theme/route-2bfa6c2cae0dc17e.js +1 -0
  204. package/.next/static/chunks/app/api/tool-image/route-2bfa6c2cae0dc17e.js +1 -0
  205. package/.next/static/chunks/app/api/update-maintenance/route-2bfa6c2cae0dc17e.js +1 -0
  206. package/.next/static/chunks/app/api/updates/route-2bfa6c2cae0dc17e.js +1 -0
  207. package/.next/static/chunks/app/api/web-access/recovery/route-2bfa6c2cae0dc17e.js +1 -0
  208. package/.next/static/chunks/app/api/web-access/route-2bfa6c2cae0dc17e.js +1 -0
  209. package/.next/static/chunks/app/api/web6/consults/route-2bfa6c2cae0dc17e.js +1 -0
  210. package/.next/static/chunks/app/api/worktrees/route-2bfa6c2cae0dc17e.js +1 -0
  211. package/.next/static/chunks/app/{layout-89b7963cbc4da94d.js → layout-fc24ec6959a6cba5.js} +1 -1
  212. package/.next/static/chunks/app/manifest.webmanifest/route-2bfa6c2cae0dc17e.js +1 -0
  213. package/.next/static/chunks/app/page-11dfb512f246679c.js +22 -0
  214. package/.next/static/chunks/next/dist/client/components/builtin/app-error-2bfa6c2cae0dc17e.js +1 -0
  215. package/.next/static/chunks/next/dist/client/components/builtin/forbidden-2bfa6c2cae0dc17e.js +1 -0
  216. package/.next/static/chunks/next/dist/client/components/builtin/not-found-2bfa6c2cae0dc17e.js +1 -0
  217. package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-2bfa6c2cae0dc17e.js +1 -0
  218. package/.next/static/chunks/{webpack-13c58b226bf07c7e.js → webpack-b1a76402d0d0fa1a.js} +1 -1
  219. package/.next/static/css/27f16a2ddaff39eb.css +1 -0
  220. package/.next/static/css/88929dd0c6d44c84.css +1 -0
  221. package/.next/trace +106 -105
  222. package/.next/trace-build +1 -1
  223. package/.next/types/app/api/live/character-voice/route.ts +351 -0
  224. package/.next/types/routes.d.ts +2 -1
  225. package/.next/types/validator.ts +9 -0
  226. package/README.md +7 -3
  227. package/Tools/CUELO_Setup/files/native-runtime-patch.js +5 -5
  228. package/Tools/OMP_Global_Config/agent/extensions/jev-runtime.ts +289 -64
  229. package/Tools/OMP_Global_Config/agent/extensions/korean-reply-guard.ts +174 -0
  230. package/Tools/OMP_Global_Config/agent/extensions/lib/maker-routing.ts +72 -25
  231. package/Tools/OMP_Global_Config/agent/extensions/lib/routing-ledger.ts +25 -1
  232. package/Tools/OMP_Global_Config/agent/rules/subagent.md +12 -1
  233. package/Tools/OMP_Global_Config/agent/rules/verdict.md +3 -0
  234. package/Tools/OMP_Global_Config/patches/apply-core-patch.mjs +241 -20
  235. package/package.json +7 -7
  236. package/public/audio/live/ACML-1.0.txt +85 -0
  237. package/public/audio/live/hikari-reference.wav +0 -0
  238. package/public/audio/live/isana-reference.wav +0 -0
  239. package/public/audio/live/mio-reference.wav +0 -0
  240. package/public/audio/live/nova-reference.wav +0 -0
  241. package/public/audio/live/rin-reference.wav +0 -0
  242. package/public/audio/live/shion-reference.wav +0 -0
  243. package/public/audio/live/voices.json +275 -0
  244. package/public/audio/live/yuki-reference.wav +0 -0
  245. package/.next/server/chunks/3707.js +0 -5
  246. package/.next/server/chunks/6775.js +0 -6
  247. package/.next/static/JfkwRK2BKBsxwumh964am/_buildManifest.js +0 -1
  248. package/.next/static/chunks/app/api/agent/[id]/route-701a3390117d7557.js +0 -1
  249. package/.next/static/chunks/app/api/agent/new/route-701a3390117d7557.js +0 -1
  250. package/.next/static/chunks/app/api/agent/running/route-701a3390117d7557.js +0 -1
  251. package/.next/static/chunks/app/api/attachments/route-701a3390117d7557.js +0 -1
  252. package/.next/static/chunks/app/api/auth/all-providers/route-701a3390117d7557.js +0 -1
  253. package/.next/static/chunks/app/api/auth/api-key/[provider]/route-701a3390117d7557.js +0 -1
  254. package/.next/static/chunks/app/api/auth/login/[provider]/route-701a3390117d7557.js +0 -1
  255. package/.next/static/chunks/app/api/auth/logout/[provider]/route-701a3390117d7557.js +0 -1
  256. package/.next/static/chunks/app/api/auth/providers/route-701a3390117d7557.js +0 -1
  257. package/.next/static/chunks/app/api/cwd/browse/route-701a3390117d7557.js +0 -1
  258. package/.next/static/chunks/app/api/cwd/validate/route-701a3390117d7557.js +0 -1
  259. package/.next/static/chunks/app/api/default-cwd/route-701a3390117d7557.js +0 -1
  260. package/.next/static/chunks/app/api/display-settings/route-701a3390117d7557.js +0 -1
  261. package/.next/static/chunks/app/api/document-text/route-701a3390117d7557.js +0 -1
  262. package/.next/static/chunks/app/api/experiments/route-701a3390117d7557.js +0 -1
  263. package/.next/static/chunks/app/api/file-index/route-701a3390117d7557.js +0 -1
  264. package/.next/static/chunks/app/api/files/[...path]/route-701a3390117d7557.js +0 -1
  265. package/.next/static/chunks/app/api/git/diff/route-701a3390117d7557.js +0 -1
  266. package/.next/static/chunks/app/api/git/status/route-701a3390117d7557.js +0 -1
  267. package/.next/static/chunks/app/api/gpt6/handles/route-701a3390117d7557.js +0 -1
  268. package/.next/static/chunks/app/api/gpt6/reply/route-701a3390117d7557.js +0 -1
  269. package/.next/static/chunks/app/api/gpt6/token/route-701a3390117d7557.js +0 -1
  270. package/.next/static/chunks/app/api/gpt6/web6-session/route-701a3390117d7557.js +0 -1
  271. package/.next/static/chunks/app/api/home/route-701a3390117d7557.js +0 -1
  272. package/.next/static/chunks/app/api/live/call/[callId]/route-701a3390117d7557.js +0 -1
  273. package/.next/static/chunks/app/api/live/events/route-701a3390117d7557.js +0 -1
  274. package/.next/static/chunks/app/api/live/offer/route-701a3390117d7557.js +0 -1
  275. package/.next/static/chunks/app/api/live/voices/route-701a3390117d7557.js +0 -1
  276. package/.next/static/chunks/app/api/lounge/events/route-701a3390117d7557.js +0 -1
  277. package/.next/static/chunks/app/api/lounge/route-701a3390117d7557.js +0 -1
  278. package/.next/static/chunks/app/api/mcp/route-701a3390117d7557.js +0 -1
  279. package/.next/static/chunks/app/api/model-roles/route-701a3390117d7557.js +0 -1
  280. package/.next/static/chunks/app/api/models/route-701a3390117d7557.js +0 -1
  281. package/.next/static/chunks/app/api/models-config/catalog/route-701a3390117d7557.js +0 -1
  282. package/.next/static/chunks/app/api/models-config/discover/route-701a3390117d7557.js +0 -1
  283. package/.next/static/chunks/app/api/models-config/route-701a3390117d7557.js +0 -1
  284. package/.next/static/chunks/app/api/models-config/test/route-701a3390117d7557.js +0 -1
  285. package/.next/static/chunks/app/api/plugins/route-701a3390117d7557.js +0 -1
  286. package/.next/static/chunks/app/api/project-trust/route-701a3390117d7557.js +0 -1
  287. package/.next/static/chunks/app/api/projects/route-701a3390117d7557.js +0 -1
  288. package/.next/static/chunks/app/api/runs/[runId]/route-701a3390117d7557.js +0 -1
  289. package/.next/static/chunks/app/api/runs/route-701a3390117d7557.js +0 -1
  290. package/.next/static/chunks/app/api/sessions/[id]/auto-name/route-701a3390117d7557.js +0 -1
  291. package/.next/static/chunks/app/api/sessions/[id]/context/route-701a3390117d7557.js +0 -1
  292. package/.next/static/chunks/app/api/sessions/[id]/entries/[entryId]/thinking/route-701a3390117d7557.js +0 -1
  293. package/.next/static/chunks/app/api/sessions/[id]/export/route-701a3390117d7557.js +0 -1
  294. package/.next/static/chunks/app/api/sessions/[id]/route-701a3390117d7557.js +0 -1
  295. package/.next/static/chunks/app/api/sessions/[id]/state/route-701a3390117d7557.js +0 -1
  296. package/.next/static/chunks/app/api/sessions/route-701a3390117d7557.js +0 -1
  297. package/.next/static/chunks/app/api/sessions/search/route-701a3390117d7557.js +0 -1
  298. package/.next/static/chunks/app/api/settings/route-701a3390117d7557.js +0 -1
  299. package/.next/static/chunks/app/api/sidecars/[service]/[...path]/route-701a3390117d7557.js +0 -1
  300. package/.next/static/chunks/app/api/skills/check/route-701a3390117d7557.js +0 -1
  301. package/.next/static/chunks/app/api/skills/install/route-701a3390117d7557.js +0 -1
  302. package/.next/static/chunks/app/api/skills/route-701a3390117d7557.js +0 -1
  303. package/.next/static/chunks/app/api/skills/search/route-701a3390117d7557.js +0 -1
  304. package/.next/static/chunks/app/api/skills/update/route-701a3390117d7557.js +0 -1
  305. package/.next/static/chunks/app/api/theme/route-701a3390117d7557.js +0 -1
  306. package/.next/static/chunks/app/api/tool-image/route-701a3390117d7557.js +0 -1
  307. package/.next/static/chunks/app/api/update-maintenance/route-701a3390117d7557.js +0 -1
  308. package/.next/static/chunks/app/api/updates/route-701a3390117d7557.js +0 -1
  309. package/.next/static/chunks/app/api/web-access/recovery/route-701a3390117d7557.js +0 -1
  310. package/.next/static/chunks/app/api/web-access/route-701a3390117d7557.js +0 -1
  311. package/.next/static/chunks/app/api/web6/consults/route-701a3390117d7557.js +0 -1
  312. package/.next/static/chunks/app/api/worktrees/route-701a3390117d7557.js +0 -1
  313. package/.next/static/chunks/app/manifest.webmanifest/route-701a3390117d7557.js +0 -1
  314. package/.next/static/chunks/app/page-042cc56cc090bf73.js +0 -22
  315. package/.next/static/chunks/next/dist/client/components/builtin/app-error-701a3390117d7557.js +0 -1
  316. package/.next/static/chunks/next/dist/client/components/builtin/forbidden-701a3390117d7557.js +0 -1
  317. package/.next/static/chunks/next/dist/client/components/builtin/not-found-701a3390117d7557.js +0 -1
  318. package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-701a3390117d7557.js +0 -1
  319. package/.next/static/css/bb6b220f400250d8.css +0 -1
  320. package/.next/static/css/d0ec5531aef136d6.css +0 -1
  321. /package/.next/static/{JfkwRK2BKBsxwumh964am → 5vW3pyOv87cK3zk2pchwv}/_ssgManifest.js +0 -0
@@ -1,8 +1,8 @@
1
1
 
2
- import type { ExtensionAPI, ExtensionContext } from "@oh-my-pi/pi-coding-agent";
2
+ import type { AsyncJobSnapshot, ExtensionAPI, ExtensionContext } from "@oh-my-pi/pi-coding-agent";
3
3
  import { registerMakerRouting, workspaceOf, type Owner } from "./lib/maker-routing";
4
4
  import { clearPreparedTaskSession } from "./lib/prepared-task";
5
- import { createRoutingLedger, DEFAULT_LEDGER_PATH, scopedAttempts, type AttemptIdentity, type DispatchOwnership, type DispatchRecord, type OutcomeRecord, type VerdictRecord } from "./lib/routing-ledger";
5
+ import { createRoutingLedger, DEFAULT_LEDGER_PATH, scopedAttempts, type AttemptIdentity, type DispatchOwnership, type DispatchRecord, type OutcomeRecord, type RevisionRelation, type VerdictRecord } from "./lib/routing-ledger";
6
6
  import {
7
7
  advanceTodoProgressState,
8
8
  assessTodoProgress,
@@ -335,8 +335,8 @@ interface SettledJobMeta {
335
335
  agentId?: string;
336
336
  label?: string;
337
337
  durationMs?: number;
338
- /** async job 상태(completed|failed|cancelled). */
339
- status?: string;
338
+ /** 실제 async job terminal 상태. 관측한 값이 completed|failed|cancelled일 때만 있고, 없으면 미상이다(성공으로 추정하지 않는다). */
339
+ status?: OutcomeRecord["status"];
340
340
  schemaStatus?: string;
341
341
  hasData: boolean;
342
342
  /** data.validation 맵에서 state가 unverified/pending 계열인 항목 수. */
@@ -463,7 +463,7 @@ function readSettledTaskJobs(details: unknown, content?: unknown): SettledJobMet
463
463
  ...(typeof record.agentId === "string" && record.agentId ? { agentId: record.agentId } : {}),
464
464
  ...(typeof record.label === "string" ? { label: record.label } : {}),
465
465
  ...(typeof record.durationMs === "number" ? { durationMs: record.durationMs } : {}),
466
- ...(typeof record.status === "string" ? { status: record.status } : {}),
466
+ ...(record.status === "completed" || record.status === "failed" || record.status === "cancelled" ? { status: record.status } : {}),
467
467
  ...(typeof schema?.status === "string" ? { schemaStatus: schema.status } : {}),
468
468
  hasData: data !== undefined,
469
469
  hasError: typeof schema?.error === "string" || record.status === "failed",
@@ -767,7 +767,10 @@ export function createJevRuntime(deps: JevRuntimeDeps = {}) {
767
767
  const liveMakers = new Map<string, SpawnedTaskMeta[]>();
768
768
  /** jobId → {callId, taskIndex}. settle 시 해당 child만 liveMakers에서 닫는다. */
769
769
  const jobIndex = new Map<string, { callId: string; taskIndex: number }>();
770
- /** 완료/parked 뒤에도 같은 session에서 `write agent://` 대상으로 식별할 수 있었던 maker. */
770
+ /**
771
+ * 완료/parked 뒤에도 같은 session에서 `write agent://` 대상으로 식별할 수 있었던 maker. 표시 이름은 별도 task 호출에서 재사용되고
772
+ * core가 실제 child를 Fix·Fix-2처럼 구분하므로, 키는 이름이 아니라 그 spawn(assignment)이다. 각 child의 소유 경로·identity를 따로 보존한다.
773
+ */
771
774
  const knownMakers = new Map<string, SpawnedTaskMeta>();
772
775
  /** 이미 판정한 경계. */
773
776
  // 발주 판정은 maker_route 한 곳에서만 수행한다.
@@ -781,11 +784,14 @@ export function createJevRuntime(deps: JevRuntimeDeps = {}) {
781
784
  /** 미회신 advisory를 이미 낸 체크포인트 id. */
782
785
  const advisedCheckpoints = new Set<string>();
783
786
 
787
+ /** canonical agentId가 정확히 같은 child가 우선이다. 일치하는 agentId가 없을 때만 표시 이름으로 가장 최근 child를 찾는다. */
784
788
  function knownMakerForTarget(target: string): SpawnedTaskMeta | undefined {
789
+ let byName: SpawnedTaskMeta | undefined;
785
790
  for (const owner of knownMakers.values()) {
786
791
  if (owner.agentId === target) return owner;
792
+ if (owner.name === target) byName = owner;
787
793
  }
788
- return knownMakers.get(target);
794
+ return byName;
789
795
  }
790
796
 
791
797
  function noteCheckpoint(record: unknown): void {
@@ -862,17 +868,35 @@ export function createJevRuntime(deps: JevRuntimeDeps = {}) {
862
868
  // spawn으로 실제 관측된 attempt. routing_verdict는 여기 있는 identity만 받는다.
863
869
  // 상태를 함께 두어 실행 중 attempt를 완료로 오인하지 않게 하고, session_start에서 원장 scoped 기록으로 복원한다.
864
870
  // ownership은 그 attempt dispatch의 소유 계약(과거 row는 null=미상), restored는 session_start 원장 복원 계열이다.
871
+ // sourceRevision은 그 attempt의 terminal report가 실제로 낸 revision이며, 관측하지 못했으면 null(미상)이다. 추정으로 채우지 않는다.
865
872
  type ObservedAttempt = AttemptIdentity & {
866
873
  name: string;
867
874
  status: "running" | OutcomeRecord["status"];
868
875
  ownership: DispatchOwnership | null;
869
876
  restored: boolean;
877
+ sourceRevision: string | null;
870
878
  };
871
879
  const observedAttempts = new Map<string, ObservedAttempt>();
872
880
  /** outcome을 이미 기록한 attempt. 같은 attempt를 두 번 소비하지 않는다. */
873
881
  const settledAttempts = new Set<string>();
874
882
  /** settle로 이미 소비한 async jobId. 채널(via)과 무관하게 중복 소비하지 않는다. */
875
883
  const consumedJobs = new Set<string>();
884
+ /**
885
+ * terminal status를 싣지 못한 관측으로 정산을 보류한 job: jobId → 그 attempt와 그 실행의 시작 시각. 첫 관측의 dedupe가 뒤이은
886
+ * wait·`read proc://`의 권위 status 관측을 막지 않게 한다. 같은 실행 근거(snapshot row 시작 시각 일치)가 있을 때만 그 attempt에
887
+ * outcome을 한 번 남긴다. core는 evict 뒤 같은 jobId를 재사용하므로 jobId만으로 옛 attempt에 status를 붙이지 않는다.
888
+ */
889
+ const statusPending = new Map<string, { attemptId: string; runStart?: number; terminalRevision?: string; durationMs?: number }>();
890
+ /** attempt 실행의 시작 시각: spawn·재개 binding 때 core snapshot의 exact jobId row에서 본 값. 같은 실행 판정의 근거다. */
891
+ const runStarts = new Map<string, number>();
892
+ /** 끝났지만 terminal status를 관측하지 못한 attempt. 실행 중이 아니므로 REWORK 재개를 연다. outcome은 미상으로 남는다. */
893
+ const unobservedEnds = new Set<string>();
894
+ /**
895
+ * 성공한 `write proc://<jobId>/kill` receipt가 가리킨 실행 중 attempt: jobId → attempt와 그 실행의 시작 시각.
896
+ * core cancel은 abort 요청 직후 반환하고 cancelled job은 async-result를 보내지 않는다. receipt는 종료가 아니므로, 같은 실행의
897
+ * snapshot row가 cancelled이고 endTime이 생긴 뒤에만 owner 경계에서 cancelled로 정산한다. 그 전에는 owner 보호를 유지한다.
898
+ */
899
+ const pendingCancels = new Map<string, { attemptId: string; runStart: number }>();
876
900
  /** 명시 REWORK 전송이 성공해 열린 재개 attempt. agentId → 그 전송 직후 관측한 정확한 새 jobId와 전송 시각. */
877
901
  const resumeByAgent = new Map<string, { attemptId: string; jobId: string; sentAt: number }>();
878
902
  /** 전송별 snapshot을 보존해 같은 actor로 겹친 DM도 서로의 기준선을 덮지 않는다. */
@@ -884,7 +908,7 @@ export function createJevRuntime(deps: JevRuntimeDeps = {}) {
884
908
  const VERDICTS = ["accepted", "rework", "held"] as const;
885
909
  type VerdictInput = {
886
910
  sessionId: string; assignmentId: string; attemptId: string; verdict: string;
887
- reason: string; revision?: string; evidenceLocators?: string[]; appliedLessons?: string[];
911
+ reason: string; revision?: string; integratedFrom?: string; evidenceLocators?: string[]; appliedLessons?: string[];
888
912
  };
889
913
  /**
890
914
  * Main 명시 수용 판정 기록. advisory 원장에 한 줄 append할 뿐 gate도 LLM 호출도 아니다.
@@ -900,6 +924,7 @@ export function createJevRuntime(deps: JevRuntimeDeps = {}) {
900
924
  const revision = (request.revision ?? "").trim();
901
925
  const evidenceLocators = (request.evidenceLocators ?? []).map((value) => value.trim()).filter(Boolean);
902
926
  const appliedLessons = [...new Set((request.appliedLessons ?? []).map((value) => value.trim()).filter(Boolean))];
927
+ const integratedFrom = (request.integratedFrom ?? "").trim();
903
928
  if (!sessionId || !assignmentId || !attemptId) {
904
929
  return { ok: false, error: "sessionId·assignmentId·attemptId가 모두 필요합니다. maker_route 결과가 아니라 spawn advisory의 실제 triple을 쓰세요.", knownAttemptIds: [] };
905
930
  }
@@ -926,6 +951,19 @@ export function createJevRuntime(deps: JevRuntimeDeps = {}) {
926
951
  if (request.verdict === "accepted" && observed.status !== "completed") {
927
952
  return { ok: false, error: `accepted는 완료가 관측된 attempt에만 쓸 수 있습니다. 현재 실행 상태: ${observed.status}`, knownAttemptIds: [] };
928
953
  }
954
+ // 원 revision과 검수 revision의 관계는 advisory 기록이다. 다르거나 미상이어도 막지 않고, 조용히 같은 결과로 취급하지 않도록
955
+ // 관계와 진단을 남긴다. integratedFrom은 Main의 명시 연결 주장일 뿐이며 실제 검수 근거는 reason·evidenceLocators가 담당한다.
956
+ const sourceRevision = observed.sourceRevision;
957
+ const revisionRelation: RevisionRelation = !sourceRevision || !revision ? "unknown"
958
+ : revision === sourceRevision ? "same"
959
+ : integratedFrom === sourceRevision ? "integrated" : "mismatch";
960
+ const diagnostic = revisionRelation === "mismatch"
961
+ ? integratedFrom
962
+ ? `integratedFrom '${integratedFrom}'이 이 attempt에서 관측한 Maker 원 revision '${sourceRevision}'과 다릅니다. 검수 revision '${revision}'은 mismatch로 기록했습니다. 이 attempt의 원 revision을 통합한 검수라면 integratedFrom='${sourceRevision}'로 다시 기록하고, 다른 attempt의 결과라면 그 attempt identity로 기록하세요.`
963
+ : `Maker 원 revision '${sourceRevision}'과 검수 revision '${revision}'이 다르고 연결 주장이 없어 mismatch로 기록했습니다. 통합 뒤 새 revision을 직접 검수했다면 integratedFrom='${sourceRevision}'로 연결을 명시하고 그 검수 근거를 reason·evidenceLocators에 남기세요.`
964
+ : revisionRelation === "unknown" && revision
965
+ ? `이 attempt의 terminal report revision이 미관측이라 검수 revision '${revision}'과의 관계를 unknown으로 기록했습니다. 원 revision을 추정해 채우지 않습니다.`
966
+ : undefined;
929
967
  const record: VerdictRecord = {
930
968
  type: "verdict",
931
969
  ts: new Date().toISOString(),
@@ -937,6 +975,9 @@ export function createJevRuntime(deps: JevRuntimeDeps = {}) {
937
975
  jobId: observed.jobId,
938
976
  verdict: request.verdict as VerdictRecord["verdict"],
939
977
  revision: revision || null,
978
+ sourceRevision,
979
+ revisionRelation,
980
+ ...(integratedFrom ? { integratedFrom } : {}),
940
981
  evidenceLocators,
941
982
  reason,
942
983
  ...(appliedLessons.length > 0 ? { appliedLessons } : {}),
@@ -944,20 +985,39 @@ export function createJevRuntime(deps: JevRuntimeDeps = {}) {
944
985
  if (!baseLedger.append(record)) {
945
986
  return { ok: false, error: "원장 기록에 실패했습니다(저장 오류). 판정은 남지 않았습니다.", knownAttemptIds: [] };
946
987
  }
947
- return { ok: true, record };
988
+ return { ok: true, record, ...(diagnostic ? { diagnostic } : {}) };
948
989
  }
949
990
  const routing = registerMakerRouting(pi, {
950
991
  ledger: baseLedger,
951
992
  owners: (ctx) => {
993
+ // 취소 요청 뒤 실제로 끝난 실행은 추가 조회 없이 여기서 cancelled로 정산해 그 child만 owner에서 해제한다.
994
+ // 같은 경계의 running 판정도 이 snapshot을 그대로 쓴다.
995
+ const snapshot = snapshotWithPendingCancels(ctx);
996
+ reconcileEndedCancels(snapshot);
997
+ // active는 그 child 자체로 판정한다. 같은 표시 이름의 다른 child가 실행 중이라는 이유로 끝난 child를 잠그지 않는다.
952
998
  const activeTasks = [...liveMakers.values()].flat();
953
- const activeNames = new Set(
954
- activeTasks.flatMap((task) => task.name ? [task.name] : []),
955
- );
999
+ const liveTasks = new Set(activeTasks);
1000
+ // 원장 running은 프로세스 재시작으로 끝나지 못한 row일 수 있다. core async snapshot에 지금 실행 중인
1001
+ // job이 있을 때만 active로 보아, 끝난·죽은 attempt가 영구 잠금이 되지 않게 한다.
1002
+ const running = snapshot?.running ?? [];
1003
+ // 완료로 live 목록에서 닫힌 known maker도 REWORK·후속 지시로 다시 실행 중이면 같은 소유 경로의 writer다.
1004
+ // 그 canonical agentId(또는 이 runtime이 관측한 그 agent attempt의 jobId)가 지금 실행 중일 때만 active다.
1005
+ const runningJobs = new Set(running.map((job) => job.id));
1006
+ const runningAgents = new Set(running.flatMap((job) => typeof job.agentId === "string" && job.agentId ? [job.agentId] : []));
1007
+ for (const entry of observedAttempts.values()) {
1008
+ if (entry.agentId && entry.jobId !== "" && runningJobs.has(entry.jobId)) runningAgents.add(entry.agentId);
1009
+ }
1010
+ // core는 cancel 즉시 그 job을 running에서 뺀다. 취소 요청 뒤 아직 끝나지 않은 실행(재개·REWORK 포함)은 계속 writer다.
1011
+ for (const [jobId, pending] of pendingCancels) {
1012
+ runningJobs.add(jobId);
1013
+ const agentId = observedAttempts.get(pending.attemptId)?.agentId;
1014
+ if (agentId) runningAgents.add(agentId);
1015
+ }
956
1016
  const owners: Owner[] = [...knownMakers.values()].map((task) => ({
957
1017
  name: task.name ?? null,
958
1018
  primaryDeliverable: task.guard.primaryDeliverable ?? null,
959
1019
  ownedPaths: task.guard.ownedPaths,
960
- active: Boolean(task.name && activeNames.has(task.name)),
1020
+ active: liveTasks.has(task) || Boolean(task.agentId && runningAgents.has(task.agentId)),
961
1021
  workspace: task.workspace,
962
1022
  }));
963
1023
  for (const task of activeTasks) {
@@ -970,19 +1030,16 @@ export function createJevRuntime(deps: JevRuntimeDeps = {}) {
970
1030
  workspace: task.workspace,
971
1031
  });
972
1032
  }
973
- // reload 전 spawn은 원장 복원 attempt로만 남는다. 이 runtime이 다시 관측한 이름은 위 목록이 우선한다.
974
- // 이름별(이름 없으면 assignment별) 최신 attempt 하나만 owner로 두어 재작업이 소유를 중복 주장하지 않게 한다.
1033
+ // reload 전 spawn은 원장 복원 attempt로만 남는다. 복원 attempt는 이 runtime이 spawn한 child와 같은 child가 아니다.
1034
+ // assignment별 최신 attempt 하나만 owner로 두어 재작업이 소유를 중복 주장하지 않게 한다.
975
1035
  const restored = new Map<string, ObservedAttempt>();
976
1036
  for (const entry of observedAttempts.values()) {
977
- if (!entry.restored || (entry.name && knownMakers.has(entry.name))) continue;
978
- restored.set(entry.name || entry.assignmentId, entry);
1037
+ if (entry.restored) restored.set(entry.assignmentId, entry);
979
1038
  }
980
- // 원장 running은 프로세스 재시작으로 끝나지 못한 row일 수 있다. core async snapshot에 지금 실행 중인
981
- // job이 있을 때만 active로 보아, 끝난·죽은 attempt가 영구 잠금이 되지 않게 한다.
982
- const running = ctx.getAsyncJobSnapshot?.()?.running ?? [];
983
1039
  for (const entry of restored.values()) {
984
- const active = entry.status === "running" && running.some((job) =>
985
- job.agentId === entry.agentId || (entry.jobId !== "" && job.id === entry.jobId));
1040
+ // 원장 status가 아니라 실제 snapshot이 권위다. 완료로 복원된 owner도 REWORK·후속 지시로 그 canonical agent의 job이
1041
+ // 지금 실행 중이면 active이고, running row라도 snapshot에 없으면 inactive다.
1042
+ const active = Boolean(entry.agentId && runningAgents.has(entry.agentId)) || (entry.jobId !== "" && runningJobs.has(entry.jobId));
986
1043
  if (entry.ownership) {
987
1044
  if (!entry.name && !active) continue;
988
1045
  owners.push({
@@ -997,7 +1054,15 @@ export function createJevRuntime(deps: JevRuntimeDeps = {}) {
997
1054
  owners.push({ name: entry.name || null, primaryDeliverable: null, ownedPaths: [], active, ownershipUnknown: true });
998
1055
  }
999
1056
  }
1000
- return owners;
1057
+ // placement 표시용 목록만 접는다: 실행 중 owner는 실제 child마다 모두 남기고, 실행 중인 것이 없는 이름은 가장 최근
1058
+ // child 하나만 후보로 보인다(복원 owner보다 이 runtime의 spawn이 최근이다). knownMakers의 child별 정보는 그대로다.
1059
+ const activeNames = new Set(owners.flatMap((owner) => owner.active && owner.name ? [owner.name] : []));
1060
+ const latestInactive = new Map<string, Owner>();
1061
+ for (const owner of [...owners.slice(knownMakers.size), ...owners.slice(0, knownMakers.size)]) {
1062
+ if (!owner.active && owner.name) latestInactive.set(owner.name, owner);
1063
+ }
1064
+ return owners.filter((owner) => owner.active || !owner.name
1065
+ || (!activeNames.has(owner.name) && latestInactive.get(owner.name) === owner));
1001
1066
  },
1002
1067
  // legacy-pi loader가 host SDK specifier를 재작성하므로 미러의 static runtime import는 불가하다.
1003
1068
  settings: async (ctx) => deps.findScopedSettings?.(ctx.cwd) ??
@@ -1015,7 +1080,7 @@ export function createJevRuntime(deps: JevRuntimeDeps = {}) {
1015
1080
  const z = pi.zod;
1016
1081
  pi.registerTool({
1017
1082
  name: "routing_verdict", label: "Routing Verdict", loadMode: "essential", approval: "read",
1018
- description: "Main 전용 발주 수용 판정 기록(advisory). 실행 상태와 분리해 accepted·rework·held만 남기며 gate도 LLM 호출도 아니다. identity는 spawn/pre-review advisory와 maker_route 결과의 plan이 아니라 실제 spawn triple(sessionId·assignmentId·attemptId)을 쓴다. accepted·rework는 비어 있지 않은 revision과 evidenceLocators 최소 1개, reason이 필요하고 held는 revision·evidence를 생략할 수 있다. accepted는 완료가 관측된 attempt에만 쓸 수 있다. 선택 appliedLessons에는 그 attempt가 실제로 적용한 교훈의 기억 id(`<memories>`·recall·learn 결과의 `id:`)를 넣으며, 적용 근거는 evidenceLocators로 남긴다. spawn으로 관측되지 않은 attempt, stale identity, 현재 session이 아닌 identity는 기록하지 않고 오류와 알려진 attemptId 목록으로 알린다.",
1083
+ description: "Main 전용 발주 수용 판정 기록(advisory). 실행 상태와 분리해 accepted·rework·held만 남기며 gate도 LLM 호출도 아니다. identity는 spawn/pre-review advisory와 maker_route 결과의 plan이 아니라 실제 spawn triple(sessionId·assignmentId·attemptId)을 쓴다. accepted·rework는 비어 있지 않은 revision과 evidenceLocators 최소 1개, reason이 필요하고 held는 revision·evidence를 생략할 수 있다. accepted는 완료가 관측된 attempt에만 쓸 수 있다. revision은 Main이 실제로 검수한 revision이다. 원장은 그 attempt의 terminal report revision(sourceRevision, 미관측이면 null)과의 관계를 same·integrated·mismatch·unknown으로 함께 남기고, mismatch·unknown이면 기록한 뒤 diagnostic으로 알린다. 통합 뒤 새 revision을 검수했다면 선택 integratedFrom에 이 attempt의 원 revision을 적어 연결을 명시한다. integratedFrom은 연결 주장일 뿐이며 검수 근거는 reason·evidenceLocators가 담당한다. 선택 appliedLessons에는 그 attempt가 실제로 적용한 교훈의 기억 id(`<memories>`·recall·learn 결과의 `id:`)를 넣으며, 적용 근거는 evidenceLocators로 남긴다. spawn으로 관측되지 않은 attempt, stale identity, 현재 session이 아닌 identity는 기록하지 않고 오류와 알려진 attemptId 목록으로 알린다.",
1019
1084
  parameters: z.object({
1020
1085
  sessionId: z.string(),
1021
1086
  assignmentId: z.string(),
@@ -1023,6 +1088,7 @@ export function createJevRuntime(deps: JevRuntimeDeps = {}) {
1023
1088
  verdict: z.string(),
1024
1089
  reason: z.string(),
1025
1090
  revision: z.string().optional(),
1091
+ integratedFrom: z.string().optional(),
1026
1092
  evidenceLocators: z.array(z.string()).optional(),
1027
1093
  appliedLessons: z.array(z.string()).optional(),
1028
1094
  }) as never,
@@ -1060,6 +1126,91 @@ export function createJevRuntime(deps: JevRuntimeDeps = {}) {
1060
1126
  else liveMakers.delete(ref.callId);
1061
1127
  }
1062
1128
  }
1129
+ /**
1130
+ * attempt 하나의 실행 상태를 원장에 한 번 남기고 관측 상태를 맞춘다. 정산 jobId를 durable outcome에 남겨 reload 뒤에도 중복 소비를 막는다.
1131
+ * 실행 상태일 뿐 품질 판정이 아니다. sourceRevision은 Maker terminal report가 낸 원 revision을 관측한 경우에만 남긴다.
1132
+ */
1133
+ function appendOutcome(identity: AttemptIdentity, jobId: string, status: OutcomeRecord["status"], durationMs: number | undefined, terminalRevision: string | undefined): ObservedAttempt | undefined {
1134
+ statusPending.delete(jobId);
1135
+ pendingCancels.delete(jobId);
1136
+ unobservedEnds.delete(identity.attemptId);
1137
+ consumedJobs.add(jobId);
1138
+ settledAttempts.add(identity.attemptId);
1139
+ baseLedger.append({
1140
+ type: "outcome",
1141
+ ts: new Date().toISOString(),
1142
+ sessionId: identity.sessionId,
1143
+ assignmentId: identity.assignmentId,
1144
+ attempt: identity.attempt,
1145
+ attemptId: identity.attemptId,
1146
+ agentId: identity.agentId,
1147
+ jobId,
1148
+ status,
1149
+ durationSec: durationMs === undefined ? null : Math.round(durationMs / 1000),
1150
+ ...(terminalRevision ? { sourceRevision: terminalRevision } : {}),
1151
+ });
1152
+ const observed = observedAttempts.get(identity.attemptId);
1153
+ if (observed) {
1154
+ observed.status = status;
1155
+ observed.sourceRevision = terminalRevision ?? null;
1156
+ }
1157
+ return observed;
1158
+ }
1159
+ /**
1160
+ * 취소 대기 job이 있을 때만 그 jobId들의 exact row를 함께 청한다. 표시용 recent(기본 5개) 창 밖으로 밀린 같은 owner의 실행도
1161
+ * 종료 근거를 볼 수 있게 한다. 대기가 없으면 기본 snapshot 그대로다.
1162
+ */
1163
+ function snapshotWithPendingCancels(ctx: ExtensionContext, jobIds: readonly string[] = [...pendingCancels.keys()]): AsyncJobSnapshot | null | undefined {
1164
+ return jobIds.length > 0 ? ctx.getAsyncJobSnapshot?.({ jobIds }) : ctx.getAsyncJobSnapshot?.();
1165
+ }
1166
+ /**
1167
+ * 취소 요청한 실행의 실제 종료 여부. 같은 jobId·같은 실행(startTime 일치)의 core row가 cancelled이고 endTime이 있을 때만
1168
+ * 끝난 것이다. exact row(`jobs`)를 주는 host는 그것만 근거로 쓰고, 주지 않는 host는 running·recent에서만 찾는다.
1169
+ * row가 없거나(evict·다른 owner) 다른 실행이면 판단하지 않는다(종료로 추정하지 않고 보호를 유지한다).
1170
+ */
1171
+ function cancelEndedRow(jobId: string, snapshot: AsyncJobSnapshot | null | undefined): { startTime: number; endTime: number } | undefined {
1172
+ const pending = pendingCancels.get(jobId);
1173
+ if (!pending) return undefined;
1174
+ const rows = snapshot?.jobs ?? [...(snapshot?.running ?? []), ...(snapshot?.recent ?? [])];
1175
+ const row = rows.find((entry) => entry.id === jobId);
1176
+ if (!row || row.startTime !== pending.runStart || row.status !== "cancelled" || typeof row.endTime !== "number") return undefined;
1177
+ return { startTime: row.startTime, endTime: row.endTime };
1178
+ }
1179
+ /** owner·admission 경계: 취소 요청 뒤 실제로 끝난 실행만 cancelled로 한 번 정산하고 그 child만 live owner에서 닫는다. */
1180
+ function reconcileEndedCancels(snapshot: AsyncJobSnapshot | null | undefined): void {
1181
+ for (const [jobId, pending] of [...pendingCancels]) {
1182
+ const ended = cancelEndedRow(jobId, snapshot);
1183
+ if (!ended) continue;
1184
+ const observed = observedAttempts.get(pending.attemptId);
1185
+ if (!observed || settledAttempts.has(pending.attemptId)) {
1186
+ pendingCancels.delete(jobId);
1187
+ continue;
1188
+ }
1189
+ appendOutcome(observed, jobId, "cancelled", ended.endTime - ended.startTime, undefined);
1190
+ // 같은 실행의 뒤 wait·read proc:// 관측은 이미 정산한 결과다.
1191
+ judgedReviews.add(jobId);
1192
+ if (resumeByAgent.get(observed.agentId)?.attemptId === pending.attemptId) resumeByAgent.delete(observed.agentId);
1193
+ closeSettledMakers([jobId]);
1194
+ }
1195
+ }
1196
+ /**
1197
+ * 성공한 proc kill receipt가 가리킨 실행 중 Maker attempt를 종료 대기로 둔다. attempt는 그 jobId의 live spawn, 재개 binding,
1198
+ * 실행 중 관측 attempt 순으로 찾는다. 실행 시작 시각은 spawn·재개 때 기록한 값이 정본이고, 없으면 지금 exact row의 값을 쓴다.
1199
+ * 기록한 시작 시각과 지금 row가 다르면 다른 실행이므로 두지 않는다. 시작 시각을 모르면 같은 실행을 확인할 수 없어 두지 않는다.
1200
+ */
1201
+ function noteCancelRequested(jobId: string, ctx: ExtensionContext): void {
1202
+ const ref = jobIndex.get(jobId);
1203
+ const spawned = ref ? liveMakers.get(ref.callId)?.find((task) => task.index === ref.taskIndex) : undefined;
1204
+ const attemptId = spawned?.identity?.attemptId
1205
+ ?? [...resumeByAgent.values()].find((binding) => binding.jobId === jobId)?.attemptId
1206
+ ?? [...observedAttempts.values()].filter((entry) => entry.status === "running" && entry.jobId === jobId).at(-1)?.attemptId;
1207
+ if (!attemptId || settledAttempts.has(attemptId) || observedAttempts.get(attemptId)?.status !== "running") return;
1208
+ const rowStart = snapshotRunStart(jobId, ctx);
1209
+ const recorded = runStarts.get(attemptId);
1210
+ const runStart = recorded ?? rowStart;
1211
+ if (runStart === undefined || (rowStart !== undefined && rowStart !== runStart)) return;
1212
+ pendingCancels.set(jobId, { attemptId, runStart });
1213
+ }
1063
1214
  /**
1064
1215
  * core는 task job을 `id: agentId`로 등록하고 소비된 row를 곧 evict하므로 재개(IRC wake) 실행이 같은 jobId를 다시 받는다.
1065
1216
  * async-result에는 agentId도 없다. 그래서 agentId는 현재 snapshot row(id 정확 일치)에서만 읽고, 열린 REWORK receipt의
@@ -1075,9 +1226,36 @@ export function createJevRuntime(deps: JevRuntimeDeps = {}) {
1075
1226
  ? { agentId, resumeKey: `${job.jobId}@${row.startTime}` }
1076
1227
  : { agentId };
1077
1228
  }
1078
- /** 처음 보는 jobId이거나, 재사용 jobId라도 아직 검수하지 않은 재개 실행이면 검수 대상이다. */
1229
+ /** 이 jobId의 현재 core snapshot row 시작 시각. core가 같은 jobId를 재사용해도 실행마다 다르다. */
1230
+ function snapshotRunStart(jobId: string, ctx: ExtensionContext): number | undefined {
1231
+ const snapshot = ctx.getAsyncJobSnapshot?.();
1232
+ const row = [...(snapshot?.running ?? []), ...(snapshot?.recent ?? [])].find((entry) => entry.id === jobId);
1233
+ return typeof row?.startTime === "number" ? row.startTime : undefined;
1234
+ }
1235
+ /**
1236
+ * 그 실행의 실제 terminal 상태. 관측 결과의 status가 정본이다. 없으면 core snapshot의 exact jobId settled row를 쓰되,
1237
+ * 그 attempt 실행의 시작 시각(runStart)과 row 시작 시각이 같을 때만 같은 실행으로 본다. 아니면 undefined이며 완료로 추정하지 않는다.
1238
+ */
1239
+ function settledStatus(job: SettledJobMeta, ctx: ExtensionContext, runStart: number | undefined): OutcomeRecord["status"] | undefined {
1240
+ if (job.status) return job.status;
1241
+ const row = ctx.getAsyncJobSnapshot?.()?.recent?.find((entry) => entry.id === job.jobId);
1242
+ if (!row || runStart === undefined || row.startTime !== runStart || (row.type !== undefined && row.type !== "task")) return undefined;
1243
+ return row.status === "completed" || row.status === "failed" || row.status === "cancelled" ? row.status : undefined;
1244
+ }
1245
+ /**
1246
+ * 처음 보는 jobId이거나, 재사용 jobId라도 아직 검수하지 않은 재개 실행이면 검수 대상이다.
1247
+ * status 미상으로 정산을 보류한 job은 권위 status 관측이 오면 한 번 더 통과시킨다. 어느 실행의 관측인지는 runPreReview가 가린다.
1248
+ */
1079
1249
  function takeUnjudged(jobs: SettledJobMeta[], ctx: ExtensionContext): SettledJobMeta[] {
1080
1250
  return jobs.filter((job) => {
1251
+ // 취소 요청한 실행은 실제로 끝나기 전 wait·read proc://가 cancelled를 보여 줘도 정산하지 않는다(owner 보호 유지).
1252
+ // 끝난 뒤의 관측은 아직 owner 경계에서 정산되지 않았으면 통과시켜 같은 실행을 한 번 정산한다.
1253
+ if (pendingCancels.has(job.jobId)) {
1254
+ // admission과 같은 exact 근거로만 판정한다. 명시 read가 recent 창 밖 실행을 볼 때도 마찬가지다.
1255
+ if (!cancelEndedRow(job.jobId, snapshotWithPendingCancels(ctx, [job.jobId]))) return false;
1256
+ judgedReviews.add(job.jobId);
1257
+ return true;
1258
+ }
1081
1259
  if (!judgedReviews.has(job.jobId)) {
1082
1260
  judgedReviews.add(job.jobId);
1083
1261
  const { resumeKey } = settledRun(job, ctx);
@@ -1085,9 +1263,12 @@ export function createJevRuntime(deps: JevRuntimeDeps = {}) {
1085
1263
  return true;
1086
1264
  }
1087
1265
  const { resumeKey } = settledRun(job, ctx);
1088
- if (!resumeKey || judgedResumeRuns.has(resumeKey)) return false;
1089
- judgedResumeRuns.add(resumeKey);
1090
- return true;
1266
+ if (resumeKey && !judgedResumeRuns.has(resumeKey)) {
1267
+ judgedResumeRuns.add(resumeKey);
1268
+ return true;
1269
+ }
1270
+ const pending = statusPending.get(job.jobId);
1271
+ return pending !== undefined && settledStatus(job, ctx, pending.runStart) !== undefined;
1091
1272
  });
1092
1273
  }
1093
1274
  async function runPreReview(
@@ -1111,8 +1292,14 @@ export function createJevRuntime(deps: JevRuntimeDeps = {}) {
1111
1292
  const newRun = !consumedJobs.has(job.jobId) || run.resumeKey !== undefined;
1112
1293
  const binding = agentId ? resumeByAgent.get(agentId) : undefined;
1113
1294
  let resumeAttemptId = binding && binding.jobId === job.jobId && newRun ? binding.attemptId : undefined;
1295
+ // status 미상으로 보류한 attempt는 같은 실행 근거가 있을 때만 이 관측으로 정산한다: 보류 때 기록한 그 attempt 실행의
1296
+ // 시작 시각과 지금 이 jobId snapshot row의 시작 시각이 같아야 한다. core는 evict 뒤 같은 jobId를 새 실행에 다시 쓴다.
1297
+ const pendingEntry = statusPending.get(job.jobId);
1298
+ const rowStart = snapshotRunStart(job.jobId, ctx);
1299
+ const deferred = !resumeAttemptId && pendingEntry?.runStart !== undefined && rowStart === pendingEntry.runStart
1300
+ ? pendingEntry : undefined;
1114
1301
  // 늦은 이벤트 자체를 새 실행으로 간주하지 않는다. 현재 snapshot에서 확인한 유일한 새 job만 잇는다.
1115
- if (!resumeAttemptId && agentId && !spawned?.identity && newRun) {
1302
+ if (!resumeAttemptId && agentId && !spawned?.identity && newRun && !deferred) {
1116
1303
  const pending = pendingResume.get(agentId);
1117
1304
  const source = pending ? observedAttempts.get(pending.sourceAttemptId) : undefined;
1118
1305
  const snapshot = pending ? ctx.getAsyncJobSnapshot?.() : undefined;
@@ -1151,38 +1338,41 @@ export function createJevRuntime(deps: JevRuntimeDeps = {}) {
1151
1338
  // 재개 attempt는 원 attempt의 소유 계약을 그대로 잇는다. 미상이면 필드를 만들지 않는다.
1152
1339
  ...(source.ownership ? { ownership: source.ownership } : {}),
1153
1340
  });
1154
- observedAttempts.set(identity.attemptId, { ...identity, name: source.name, status: "running", ownership: source.ownership, restored: source.restored });
1341
+ observedAttempts.set(identity.attemptId, { ...identity, name: source.name, status: "running", ownership: source.ownership, restored: source.restored, sourceRevision: null });
1342
+ if (rowStart !== undefined) runStarts.set(identity.attemptId, rowStart);
1155
1343
  pendingResume.delete(agentId);
1156
1344
  resumeAttemptId = identity.attemptId;
1157
1345
  }
1158
1346
  }
1159
- // session_start에서 복원한 in-flight dispatch도 실제 jobId·agentId로 정산한다.
1347
+ // session_start에서 복원한 in-flight dispatch도 실제 jobId·agentId로 정산한다. 재개 binding이 자기 실행에 우선하고,
1348
+ // 같은 실행으로 확인된 보류 attempt가 그다음이다. 같은 실행 근거가 없는 보류 attempt에는 이 관측의 status를 붙이지 않는다.
1160
1349
  const identity = resumeAttemptId ? observedAttempts.get(resumeAttemptId)
1350
+ : deferred ? observedAttempts.get(deferred.attemptId)
1161
1351
  : spawned?.identity ?? [...observedAttempts.values()].find((entry) =>
1162
- entry.status === "running" && entry.jobId === job.jobId && entry.agentId === agentId);
1352
+ entry.status === "running" && entry.jobId === job.jobId && entry.agentId === agentId
1353
+ && entry.attemptId !== pendingEntry?.attemptId);
1354
+ // 이 jobId의 다른 실행 row가 보이면 옛 보류는 더는 정산할 수 없다. 그 attempt의 outcome은 미상으로 남는다.
1355
+ if (pendingEntry && !deferred && rowStart !== undefined && rowStart !== pendingEntry.runStart) statusPending.delete(job.jobId);
1163
1356
  // 채널과 무관하게 이미 소비한 jobId는 다시 처리하지 않고, 같은 attempt도 한 번만 소비한다.
1164
- if (identity && (!consumedJobs.has(job.jobId) || resumeAttemptId !== undefined) && !settledAttempts.has(identity.attemptId)
1357
+ if (identity && (deferred !== undefined || !consumedJobs.has(job.jobId) || resumeAttemptId !== undefined) && !settledAttempts.has(identity.attemptId)
1165
1358
  && (spawned?.agent === undefined || spawned.agent === "maker")) {
1166
- consumedJobs.add(job.jobId);
1167
- settledAttempts.add(identity.attemptId);
1168
1359
  if (resumeAttemptId && agentId) resumeByAgent.delete(agentId);
1169
- const status: OutcomeRecord["status"] = job.status === "cancelled" ? "cancelled" : job.hasError ? "failed" : "completed";
1170
- baseLedger.append({
1171
- type: "outcome",
1172
- ts: new Date().toISOString(),
1173
- sessionId: identity.sessionId,
1174
- assignmentId: identity.assignmentId,
1175
- attempt: identity.attempt,
1176
- attemptId: identity.attemptId,
1177
- agentId: identity.agentId,
1178
- // durable outcome에 실제 정산 jobId를 남겨 reload 뒤에도 중복 소비를 막는다.
1179
- jobId: job.jobId,
1180
- status,
1181
- durationSec: job.durationMs === undefined ? null : Math.round(job.durationMs / 1000),
1182
- });
1183
- const observed = observedAttempts.get(identity.attemptId);
1184
- if (observed) observed.status = status;
1185
- identityNotes.push(`${spawned?.name ?? observed?.name ?? job.label ?? job.jobId} ${identity.sessionId}|${identity.assignmentId}|${identity.attemptId}`);
1360
+ const runStart = runStarts.get(identity.attemptId);
1361
+ const terminal = settledStatus(job, ctx, runStart);
1362
+ // schema.error는 status가 없어도 실패다. 그 밖에는 관측한 terminal 상태만 쓰고 status 부재를 완료로 읽지 않는다.
1363
+ const status: OutcomeRecord["status"] | undefined = terminal === "cancelled" ? "cancelled"
1364
+ : job.hasError || terminal === "failed" ? "failed"
1365
+ : terminal === "completed" ? "completed" : undefined;
1366
+ const terminalRevision = job.terminalRevision ?? deferred?.terminalRevision;
1367
+ const durationMs = job.durationMs ?? deferred?.durationMs;
1368
+ if (!status) {
1369
+ statusPending.set(job.jobId, { attemptId: identity.attemptId, ...(runStart !== undefined ? { runStart } : {}), terminalRevision, durationMs });
1370
+ unobservedEnds.add(identity.attemptId);
1371
+ identityNotes.push(`${spawned?.name ?? job.label ?? job.jobId} ${identity.sessionId}|${identity.assignmentId}|${identity.attemptId} terminal status 미관측: outcome 보류(완료로 추정하지 않음), 같은 실행으로 확인된 wait·read proc:// 관측으로만 정산`);
1372
+ } else {
1373
+ const observed = appendOutcome(identity, job.jobId, status, durationMs, terminalRevision);
1374
+ identityNotes.push(`${spawned?.name ?? observed?.name ?? job.label ?? job.jobId} ${identity.sessionId}|${identity.assignmentId}|${identity.attemptId}`);
1375
+ }
1186
1376
  }
1187
1377
  const todoRevisionKey = spawned && job.terminalRevision
1188
1378
  ? `${spawned.name ?? job.jobId}:${job.terminalRevision}`
@@ -1289,11 +1479,15 @@ export function createJevRuntime(deps: JevRuntimeDeps = {}) {
1289
1479
  observedAttempts.clear();
1290
1480
  settledAttempts.clear();
1291
1481
  consumedJobs.clear();
1482
+ pendingCancels.clear();
1483
+ statusPending.clear();
1484
+ runStarts.clear();
1485
+ unobservedEnds.clear();
1292
1486
  resumeByAgent.clear();
1293
1487
  preWriteJobs.clear();
1294
1488
  pendingResume.clear();
1295
1489
  for (const entry of scopedAttempts(baseLedger.read(), typeof sessionId === "string" ? sessionId : "")) {
1296
- observedAttempts.set(entry.identity.attemptId, { ...entry.identity, name: entry.name, status: entry.status, ownership: entry.ownership, restored: true });
1490
+ observedAttempts.set(entry.identity.attemptId, { ...entry.identity, name: entry.name, status: entry.status, ownership: entry.ownership, restored: true, sourceRevision: entry.sourceRevision });
1297
1491
  if (entry.status !== "running") settledAttempts.add(entry.identity.attemptId);
1298
1492
  }
1299
1493
  // durable outcome이 남긴 jobId는 reload 뒤에도 중복 소비를 막는다.
@@ -1371,7 +1565,7 @@ export function createJevRuntime(deps: JevRuntimeDeps = {}) {
1371
1565
  });
1372
1566
 
1373
1567
  pi.on("tool_call", async (event, ctx) => {
1374
- if (event.toolName === "task") return routing.beforeTask(event.input, ctx);
1568
+ if (event.toolName === "task") return routing.beforeTask(event.input, ctx, event.toolCallId);
1375
1569
 
1376
1570
  if (event.toolName === "wait") {
1377
1571
  const unadvised = [...pendingCheckpoints].filter(([, id]) => !advisedCheckpoints.has(id));
@@ -1531,6 +1725,12 @@ export function createJevRuntime(deps: JevRuntimeDeps = {}) {
1531
1725
  );
1532
1726
  });
1533
1727
 
1728
+ // core는 실행한 호출·block·승인 거부·abort·skip 모두에 tool_execution_end를 낸다. 하류 guard 거절처럼 tool_result가 없는
1729
+ // task 호출의 예약은 여기서 그 호출 것만 푼다. spawn 성공은 tool_result(wrapper 안)가 먼저 끝나 이미 owner로 넘어갔다.
1730
+ pi.on("tool_execution_end", (event) => {
1731
+ if (event.toolName === "task") routing.releaseCall(event.toolCallId);
1732
+ });
1733
+
1534
1734
 
1535
1735
  pi.on("tool_result", async (event, ctx) => {
1536
1736
  if (event.toolName === "bash") {
@@ -1578,10 +1778,13 @@ export function createJevRuntime(deps: JevRuntimeDeps = {}) {
1578
1778
  }
1579
1779
  }
1580
1780
  const cancelled = fieldOf(proc, "cancelled");
1781
+ const killedJobId = event.toolName === "write" ? PROC_KILL_URL.exec(path.trim())?.[1] : undefined;
1581
1782
  if (Array.isArray(cancelled)) {
1582
1783
  for (const item of cancelled) {
1583
1784
  const id = fieldOf(item, "id");
1584
1785
  if (typeof id === "string") bashJobs.delete(id);
1786
+ // 이 호출이 직접 취소한 실행 중 Maker attempt만 종료 대기로 둔다. 거부(already_completed 등)·다른 job·read 관측은 대상이 아니다.
1787
+ if (id === killedJobId && fieldOf(item, "status") === "cancelled") noteCancelRequested(id, ctx);
1585
1788
  }
1586
1789
  }
1587
1790
  }
@@ -1597,8 +1800,10 @@ export function createJevRuntime(deps: JevRuntimeDeps = {}) {
1597
1800
  const previous = targetAgentId
1598
1801
  ? [...observedAttempts.values()].filter((entry) => entry.agentId === targetAgentId).at(-1)
1599
1802
  : undefined;
1803
+ // status를 관측하지 못하고 끝난 attempt도 실행 중이 아니다. 재개는 그 attempt의 outcome을 추정하지 않고 새 attempt로 연다.
1600
1804
  if (targetAgentId && previous && /(?:^|\n)\s*REWORK\s+task_id=\S+/i.test(message)
1601
- && previous.status !== "running" && !resumeByAgent.has(targetAgentId) && !pendingResume.has(targetAgentId) && beforeWrite) {
1805
+ && (previous.status !== "running" || unobservedEnds.has(previous.attemptId))
1806
+ && !resumeByAgent.has(targetAgentId) && !pendingResume.has(targetAgentId) && beforeWrite) {
1602
1807
  // 성공 receipt와 전송 전 snapshot이 있는 경우에만 실제 새 job을 바인딩한다.
1603
1808
  const { known: prior, sentAt } = beforeWrite;
1604
1809
  const snapshot = ctx.getAsyncJobSnapshot?.();
@@ -1642,7 +1847,9 @@ export function createJevRuntime(deps: JevRuntimeDeps = {}) {
1642
1847
  purpose: source?.purpose ?? null,
1643
1848
  ...(previous.ownership ? { ownership: previous.ownership } : {}),
1644
1849
  });
1645
- observedAttempts.set(identity.attemptId, { ...identity, name: previous.name, status: "running", ownership: previous.ownership, restored: previous.restored });
1850
+ observedAttempts.set(identity.attemptId, { ...identity, name: previous.name, status: "running", ownership: previous.ownership, restored: previous.restored, sourceRevision: null });
1851
+ const freshStart = snapshotRunStart(fresh[0]!, ctx);
1852
+ if (freshStart !== undefined) runStarts.set(identity.attemptId, freshStart);
1646
1853
  // 바인딩에 전송 직후 관측한 정확한 새 jobId를 둔다. 다른 job이 먼저 정산돼도 이 attempt를 소비하지 않는다.
1647
1854
  resumeByAgent.set(targetAgentId, { attemptId: identity.attemptId, jobId: fresh[0]!, sentAt });
1648
1855
  sendAdvisory(
@@ -1663,20 +1870,27 @@ export function createJevRuntime(deps: JevRuntimeDeps = {}) {
1663
1870
 
1664
1871
  // task 호출은 pre-dispatch 경계가 따로 있으므로 재시도 문맥을 세우지 않는다.
1665
1872
  if (event.toolName === "task") {
1666
- if (event.isError) return;
1873
+ if (event.isError) {
1874
+ // spawn하지 못한 task 실행 오류. 이 호출의 예약만 푼다.
1875
+ routing.releaseCall(event.toolCallId);
1876
+ return;
1877
+ }
1667
1878
  // task spawn 성공: 여기서 live maker로 등록한다. tool_call 시점에 등록하면
1668
1879
  // 다른 guard의 block으로 spawn이 없는데도 phantom maker가 남는다.
1669
1880
  // 실제 attempt identity는 이 spawn의 task 호출 id·index·session으로, canonical child id는 progress row에서 캡처한다.
1670
1881
  const sessionId = (ctx.sessionManager?.getSessionId?.() ?? "").trim();
1671
- const details = event.details as { progress?: unknown; async?: { jobId?: unknown } } | undefined;
1882
+ const details = event.details as { progress?: unknown; async?: { jobId?: unknown }; results?: unknown } | undefined;
1672
1883
  // progress row의 index가 tasks[] 위치와 대응한다(AgentProgress.index). row.id가 canonical child id(= agent:// target)다.
1673
1884
  const progressAgentIds = new Map<number, string>();
1885
+ const failedRows = new Set<number>();
1674
1886
  if (Array.isArray(details?.progress)) {
1675
1887
  for (const [position, row] of details.progress.entries()) {
1676
1888
  if (!row || typeof row !== "object") continue;
1677
- const record = row as { id?: unknown; index?: unknown };
1889
+ const record = row as { id?: unknown; index?: unknown; status?: unknown };
1678
1890
  if (typeof record.id !== "string" || !record.id) continue;
1679
- progressAgentIds.set(typeof record.index === "number" ? record.index : position, record.id);
1891
+ const index = typeof record.index === "number" ? record.index : position;
1892
+ progressAgentIds.set(index, record.id);
1893
+ if (record.status === "failed") failedRows.add(index);
1680
1894
  }
1681
1895
  }
1682
1896
  const spawned = extractSpawnedTasks(event.input, todoProgressState.currentTodos);
@@ -1708,7 +1922,9 @@ export function createJevRuntime(deps: JevRuntimeDeps = {}) {
1708
1922
  task.identity = identity;
1709
1923
  if (identity.agentId) task.agentId = identity.agentId;
1710
1924
  if (identity.jobId) task.jobId = identity.jobId;
1711
- observedAttempts.set(identity.attemptId, { ...identity, name: task.name ?? "", status: "running", ownership, restored: false });
1925
+ observedAttempts.set(identity.attemptId, { ...identity, name: task.name ?? "", status: "running", ownership, restored: false, sourceRevision: null });
1926
+ const spawnStart = identity.jobId ? snapshotRunStart(identity.jobId, ctx) : undefined;
1927
+ if (spawnStart !== undefined) runStarts.set(identity.attemptId, spawnStart);
1712
1928
  identities.push(`${task.name ?? "<unnamed>"} agentId=${identity.agentId || "<unobserved>"} jobId=${identity.jobId || "<unobserved>"} ${identity.sessionId}|${identity.assignmentId}|${identity.attemptId}`);
1713
1929
  }
1714
1930
  if (identities.length > 0) {
@@ -1722,9 +1938,18 @@ export function createJevRuntime(deps: JevRuntimeDeps = {}) {
1722
1938
  ),
1723
1939
  );
1724
1940
  }
1725
- liveMakers.set(event.toolCallId, spawned);
1726
- for (const task of spawned) {
1727
- if (task.name && (task.agent === undefined || task.agent === "maker")) knownMakers.set(task.name, task);
1941
+ // core는 child job을 등록한 뒤 반환하므로 시작한 child의 job은 snapshot에 있다. schedule에 실패한 child는 progress가
1942
+ // failed이고 job이 없다. 둘 다일 때만 시작하지 않은 child로 보고 owner에서 뺀다. snapshot 부재만으로는 빼지 않는다.
1943
+ // 하나도 시작하지 못하면 core는 오류가 아닌 결과에 progress·async 없이 빈 results만 싣는다(task/index.ts).
1944
+ const noneStarted = !Array.isArray(details?.progress) && details?.async === undefined
1945
+ && Array.isArray(details?.results) && details.results.length === 0;
1946
+ const started = noneStarted ? [] : spawned.filter((task) => !(failedRows.has(task.index) && !ids.get(task.index)?.jobId));
1947
+ liveMakers.set(event.toolCallId, started);
1948
+ for (const task of started) {
1949
+ // 별도 호출이 같은 이름을 다시 써도 앞 child를 덮지 않는다. 키는 그 spawn identity다.
1950
+ if (task.name && (task.agent === undefined || task.agent === "maker")) {
1951
+ knownMakers.set(task.identity?.assignmentId ?? `${event.toolCallId}#${task.index}`, task);
1952
+ }
1728
1953
  }
1729
1954
  return;
1730
1955
  }