cuelo 0.9.5 → 0.9.7

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 (356) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-path-routes-manifest.json +10 -9
  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/attachment-settings/route.js +1 -1
  35. package/.next/server/app/api/attachments/[id]/transcription/route.js +2 -2
  36. package/.next/server/app/api/attachments/draft/route.js +1 -1
  37. package/.next/server/app/api/attachments/route.js +3 -3
  38. package/.next/server/app/api/auth/all-providers/route.js +1 -1
  39. package/.next/server/app/api/auth/api-key/[provider]/route.js +1 -1
  40. package/.next/server/app/api/auth/login/[provider]/route.js +2 -2
  41. package/.next/server/app/api/auth/logout/[provider]/route.js +1 -1
  42. package/.next/server/app/api/auth/providers/route.js +1 -1
  43. package/.next/server/app/api/cwd/browse/route.js +1 -1
  44. package/.next/server/app/api/cwd/validate/route.js +1 -1
  45. package/.next/server/app/api/default-cwd/route.js +1 -1
  46. package/.next/server/app/api/display-settings/route.js +1 -1
  47. package/.next/server/app/api/document-text/route.js +2 -2
  48. package/.next/server/app/api/experiments/route.js +1 -1
  49. package/.next/server/app/api/file-index/route.js +1 -1
  50. package/.next/server/app/api/files/[...path]/route.js +3 -3
  51. package/.next/server/app/api/git/diff/route.js +1 -1
  52. package/.next/server/app/api/git/status/route.js +1 -1
  53. package/.next/server/app/api/github/projects/route.js +1 -0
  54. package/.next/server/app/api/github/projects/route.js.nft.json +1 -0
  55. package/.next/server/app/api/github/projects/route_client-reference-manifest.js +1 -0
  56. package/.next/server/app/api/gpt6/handles/route.js +1 -1
  57. package/.next/server/app/api/gpt6/reply/route.js +1 -1
  58. package/.next/server/app/api/gpt6/token/route.js +1 -1
  59. package/.next/server/app/api/gpt6/web6-session/route.js +1 -1
  60. package/.next/server/app/api/home/route.js +1 -1
  61. package/.next/server/app/api/live/call/[callId]/route.js +1 -1
  62. package/.next/server/app/api/live/character-voice/route.js +1 -1
  63. package/.next/server/app/api/live/events/route.js +2 -2
  64. package/.next/server/app/api/live/offer/route.js +1 -1
  65. package/.next/server/app/api/live/voices/route.js +1 -1
  66. package/.next/server/app/api/lounge/events/route.js +2 -2
  67. package/.next/server/app/api/lounge/route.js +1 -1
  68. package/.next/server/app/api/mcp/route.js +2 -2
  69. package/.next/server/app/api/model-roles/route.js +1 -1
  70. package/.next/server/app/api/models/route.js +1 -1
  71. package/.next/server/app/api/models-config/catalog/route.js +1 -1
  72. package/.next/server/app/api/models-config/discover/route.js +1 -1
  73. package/.next/server/app/api/models-config/route.js +3 -3
  74. package/.next/server/app/api/models-config/test/route.js +1 -1
  75. package/.next/server/app/api/office-avatars/[id]/route.js +1 -1
  76. package/.next/server/app/api/office-avatars/route.js +1 -1
  77. package/.next/server/app/api/plugins/route.js +1 -1
  78. package/.next/server/app/api/project-trust/route.js +1 -1
  79. package/.next/server/app/api/projects/route.js +1 -1
  80. package/.next/server/app/api/runs/[runId]/route.js +1 -1
  81. package/.next/server/app/api/runs/route.js +1 -1
  82. package/.next/server/app/api/sessions/[id]/auto-name/route.js +1 -1
  83. package/.next/server/app/api/sessions/[id]/context/route.js +1 -1
  84. package/.next/server/app/api/sessions/[id]/entries/[entryId]/thinking/route.js +1 -1
  85. package/.next/server/app/api/sessions/[id]/export/route.js +1 -1
  86. package/.next/server/app/api/sessions/[id]/route.js +1 -1
  87. package/.next/server/app/api/sessions/[id]/state/route.js +1 -1
  88. package/.next/server/app/api/sessions/route.js +1 -1
  89. package/.next/server/app/api/sessions/search/route.js +1 -1
  90. package/.next/server/app/api/settings/route.js +1 -1
  91. package/.next/server/app/api/sidecars/[service]/[...path]/route.js +1 -1
  92. package/.next/server/app/api/skills/check/route.js +1 -1
  93. package/.next/server/app/api/skills/install/route.js +2 -2
  94. package/.next/server/app/api/skills/route.js +2 -2
  95. package/.next/server/app/api/skills/search/route.js +1 -1
  96. package/.next/server/app/api/skills/update/route.js +1 -1
  97. package/.next/server/app/api/theme/route.js +1 -1
  98. package/.next/server/app/api/tool-image/route.js +1 -1
  99. package/.next/server/app/api/update-maintenance/route.js +2 -2
  100. package/.next/server/app/api/updates/route.js +1 -1
  101. package/.next/server/app/api/web-access/recovery/route.js +1 -1
  102. package/.next/server/app/api/web-access/route.js +1 -1
  103. package/.next/server/app/api/web6/consults/route.js +1 -1
  104. package/.next/server/app/api/worktrees/route.js +1 -1
  105. package/.next/server/app/favicon.ico/route.js +1 -1
  106. package/.next/server/app/index.html +1 -1
  107. package/.next/server/app/index.rsc +6 -6
  108. package/.next/server/app/index.segments/__PAGE__.segment.rsc +4 -4
  109. package/.next/server/app/index.segments/_full.segment.rsc +6 -6
  110. package/.next/server/app/index.segments/_head.segment.rsc +1 -1
  111. package/.next/server/app/index.segments/_index.segment.rsc +3 -3
  112. package/.next/server/app/index.segments/_tree.segment.rsc +3 -3
  113. package/.next/server/app/manifest.webmanifest/route.js +2 -2
  114. package/.next/server/app/office/page.js +2 -2
  115. package/.next/server/app/office/page_client-reference-manifest.js +1 -1
  116. package/.next/server/app/office-preview/page.js +2 -2
  117. package/.next/server/app/office-preview/page_client-reference-manifest.js +1 -1
  118. package/.next/server/app/office-preview.html +1 -1
  119. package/.next/server/app/office-preview.rsc +3 -3
  120. package/.next/server/app/office-preview.segments/_full.segment.rsc +3 -3
  121. package/.next/server/app/office-preview.segments/_head.segment.rsc +1 -1
  122. package/.next/server/app/office-preview.segments/_index.segment.rsc +3 -3
  123. package/.next/server/app/office-preview.segments/_tree.segment.rsc +2 -2
  124. package/.next/server/app/office-preview.segments/office-preview/__PAGE__.segment.rsc +1 -1
  125. package/.next/server/app/office-preview.segments/office-preview.segment.rsc +1 -1
  126. package/.next/server/app/office.html +1 -1
  127. package/.next/server/app/office.rsc +6 -6
  128. package/.next/server/app/office.segments/_full.segment.rsc +6 -6
  129. package/.next/server/app/office.segments/_head.segment.rsc +1 -1
  130. package/.next/server/app/office.segments/_index.segment.rsc +3 -3
  131. package/.next/server/app/office.segments/_tree.segment.rsc +3 -3
  132. package/.next/server/app/office.segments/office/__PAGE__.segment.rsc +4 -4
  133. package/.next/server/app/office.segments/office.segment.rsc +1 -1
  134. package/.next/server/app/page.js +2 -2
  135. package/.next/server/app/page_client-reference-manifest.js +1 -1
  136. package/.next/server/app/recover/page.js +2 -2
  137. package/.next/server/app/recover/page_client-reference-manifest.js +1 -1
  138. package/.next/server/app/recover.html +1 -1
  139. package/.next/server/app/recover.rsc +3 -3
  140. package/.next/server/app/recover.segments/_full.segment.rsc +3 -3
  141. package/.next/server/app/recover.segments/_head.segment.rsc +1 -1
  142. package/.next/server/app/recover.segments/_index.segment.rsc +3 -3
  143. package/.next/server/app/recover.segments/_tree.segment.rsc +2 -2
  144. package/.next/server/app/recover.segments/recover/__PAGE__.segment.rsc +1 -1
  145. package/.next/server/app/recover.segments/recover.segment.rsc +1 -1
  146. package/.next/server/app-paths-manifest.json +10 -9
  147. package/.next/server/chunks/1008.js +2 -2
  148. package/.next/server/chunks/2651.js +1 -1
  149. package/.next/server/chunks/3379.js +1 -1
  150. package/.next/server/chunks/5489.js +12 -12
  151. package/.next/server/chunks/6429.js +2 -2
  152. package/.next/server/chunks/7362.js +1 -1
  153. package/.next/server/middleware-build-manifest.js +1 -1
  154. package/.next/server/middleware.js +2 -2
  155. package/.next/server/pages/404.html +1 -1
  156. package/.next/server/pages/500.html +1 -1
  157. package/.next/server/server-reference-manifest.json +1 -1
  158. package/.next/static/chunks/6110-9f1e951aafcdd72e.js +1 -0
  159. package/.next/static/chunks/9575-fe25836fd76b4655.js +22 -0
  160. package/.next/static/chunks/app/_global-error/{page-6b1496af498bb4fd.js → page-318ea6a550b5034e.js} +1 -1
  161. package/.next/static/chunks/app/api/agent/[id]/account/{route-6b1496af498bb4fd.js → route-318ea6a550b5034e.js} +1 -1
  162. package/.next/static/chunks/app/api/agent/[id]/bash-output/{route-6b1496af498bb4fd.js → route-318ea6a550b5034e.js} +1 -1
  163. package/.next/static/chunks/app/api/agent/[id]/events/{route-6b1496af498bb4fd.js → route-318ea6a550b5034e.js} +1 -1
  164. package/.next/static/chunks/app/api/agent/[id]/route-318ea6a550b5034e.js +1 -0
  165. package/.next/static/chunks/app/api/agent/new/route-318ea6a550b5034e.js +1 -0
  166. package/.next/static/chunks/app/api/agent/running/route-318ea6a550b5034e.js +1 -0
  167. package/.next/static/chunks/app/api/attachment-settings/route-318ea6a550b5034e.js +1 -0
  168. package/.next/static/chunks/app/api/attachments/[id]/transcription/route-318ea6a550b5034e.js +1 -0
  169. package/.next/static/chunks/app/api/attachments/draft/route-318ea6a550b5034e.js +1 -0
  170. package/.next/static/chunks/app/api/attachments/route-318ea6a550b5034e.js +1 -0
  171. package/.next/static/chunks/app/api/auth/all-providers/route-318ea6a550b5034e.js +1 -0
  172. package/.next/static/chunks/app/api/auth/api-key/[provider]/route-318ea6a550b5034e.js +1 -0
  173. package/.next/static/chunks/app/api/auth/login/[provider]/route-318ea6a550b5034e.js +1 -0
  174. package/.next/static/chunks/app/api/auth/logout/[provider]/route-318ea6a550b5034e.js +1 -0
  175. package/.next/static/chunks/app/api/auth/providers/route-318ea6a550b5034e.js +1 -0
  176. package/.next/static/chunks/app/api/cwd/browse/route-318ea6a550b5034e.js +1 -0
  177. package/.next/static/chunks/app/api/cwd/validate/route-318ea6a550b5034e.js +1 -0
  178. package/.next/static/chunks/app/api/default-cwd/route-318ea6a550b5034e.js +1 -0
  179. package/.next/static/chunks/app/api/display-settings/route-318ea6a550b5034e.js +1 -0
  180. package/.next/static/chunks/app/api/document-text/route-318ea6a550b5034e.js +1 -0
  181. package/.next/static/chunks/app/api/experiments/route-318ea6a550b5034e.js +1 -0
  182. package/.next/static/chunks/app/api/file-index/route-318ea6a550b5034e.js +1 -0
  183. package/.next/static/chunks/app/api/files/[...path]/route-318ea6a550b5034e.js +1 -0
  184. package/.next/static/chunks/app/api/git/diff/route-318ea6a550b5034e.js +1 -0
  185. package/.next/static/chunks/app/api/git/status/route-318ea6a550b5034e.js +1 -0
  186. package/.next/static/chunks/app/api/github/projects/route-318ea6a550b5034e.js +1 -0
  187. package/.next/static/chunks/app/api/gpt6/handles/route-318ea6a550b5034e.js +1 -0
  188. package/.next/static/chunks/app/api/gpt6/reply/route-318ea6a550b5034e.js +1 -0
  189. package/.next/static/chunks/app/api/gpt6/token/route-318ea6a550b5034e.js +1 -0
  190. package/.next/static/chunks/app/api/gpt6/web6-session/route-318ea6a550b5034e.js +1 -0
  191. package/.next/static/chunks/app/api/home/route-318ea6a550b5034e.js +1 -0
  192. package/.next/static/chunks/app/api/live/call/[callId]/route-318ea6a550b5034e.js +1 -0
  193. package/.next/static/chunks/app/api/live/character-voice/route-318ea6a550b5034e.js +1 -0
  194. package/.next/static/chunks/app/api/live/events/route-318ea6a550b5034e.js +1 -0
  195. package/.next/static/chunks/app/api/live/offer/route-318ea6a550b5034e.js +1 -0
  196. package/.next/static/chunks/app/api/live/voices/route-318ea6a550b5034e.js +1 -0
  197. package/.next/static/chunks/app/api/lounge/events/route-318ea6a550b5034e.js +1 -0
  198. package/.next/static/chunks/app/api/lounge/route-318ea6a550b5034e.js +1 -0
  199. package/.next/static/chunks/app/api/mcp/route-318ea6a550b5034e.js +1 -0
  200. package/.next/static/chunks/app/api/model-roles/route-318ea6a550b5034e.js +1 -0
  201. package/.next/static/chunks/app/api/models/route-318ea6a550b5034e.js +1 -0
  202. package/.next/static/chunks/app/api/models-config/catalog/route-318ea6a550b5034e.js +1 -0
  203. package/.next/static/chunks/app/api/models-config/discover/route-318ea6a550b5034e.js +1 -0
  204. package/.next/static/chunks/app/api/models-config/route-318ea6a550b5034e.js +1 -0
  205. package/.next/static/chunks/app/api/models-config/test/route-318ea6a550b5034e.js +1 -0
  206. package/.next/static/chunks/app/api/office-avatars/[id]/route-318ea6a550b5034e.js +1 -0
  207. package/.next/static/chunks/app/api/office-avatars/route-318ea6a550b5034e.js +1 -0
  208. package/.next/static/chunks/app/api/plugins/route-318ea6a550b5034e.js +1 -0
  209. package/.next/static/chunks/app/api/project-trust/route-318ea6a550b5034e.js +1 -0
  210. package/.next/static/chunks/app/api/projects/route-318ea6a550b5034e.js +1 -0
  211. package/.next/static/chunks/app/api/runs/[runId]/route-318ea6a550b5034e.js +1 -0
  212. package/.next/static/chunks/app/api/runs/route-318ea6a550b5034e.js +1 -0
  213. package/.next/static/chunks/app/api/sessions/[id]/auto-name/route-318ea6a550b5034e.js +1 -0
  214. package/.next/static/chunks/app/api/sessions/[id]/context/route-318ea6a550b5034e.js +1 -0
  215. package/.next/static/chunks/app/api/sessions/[id]/entries/[entryId]/thinking/route-318ea6a550b5034e.js +1 -0
  216. package/.next/static/chunks/app/api/sessions/[id]/export/route-318ea6a550b5034e.js +1 -0
  217. package/.next/static/chunks/app/api/sessions/[id]/route-318ea6a550b5034e.js +1 -0
  218. package/.next/static/chunks/app/api/sessions/[id]/state/route-318ea6a550b5034e.js +1 -0
  219. package/.next/static/chunks/app/api/sessions/route-318ea6a550b5034e.js +1 -0
  220. package/.next/static/chunks/app/api/sessions/search/route-318ea6a550b5034e.js +1 -0
  221. package/.next/static/chunks/app/api/settings/route-318ea6a550b5034e.js +1 -0
  222. package/.next/static/chunks/app/api/sidecars/[service]/[...path]/route-318ea6a550b5034e.js +1 -0
  223. package/.next/static/chunks/app/api/skills/check/route-318ea6a550b5034e.js +1 -0
  224. package/.next/static/chunks/app/api/skills/install/route-318ea6a550b5034e.js +1 -0
  225. package/.next/static/chunks/app/api/skills/route-318ea6a550b5034e.js +1 -0
  226. package/.next/static/chunks/app/api/skills/search/route-318ea6a550b5034e.js +1 -0
  227. package/.next/static/chunks/app/api/skills/update/route-318ea6a550b5034e.js +1 -0
  228. package/.next/static/chunks/app/api/theme/route-318ea6a550b5034e.js +1 -0
  229. package/.next/static/chunks/app/api/tool-image/route-318ea6a550b5034e.js +1 -0
  230. package/.next/static/chunks/app/api/update-maintenance/route-318ea6a550b5034e.js +1 -0
  231. package/.next/static/chunks/app/api/updates/route-318ea6a550b5034e.js +1 -0
  232. package/.next/static/chunks/app/api/web-access/recovery/route-318ea6a550b5034e.js +1 -0
  233. package/.next/static/chunks/app/api/web-access/route-318ea6a550b5034e.js +1 -0
  234. package/.next/static/chunks/app/api/web6/consults/route-318ea6a550b5034e.js +1 -0
  235. package/.next/static/chunks/app/api/worktrees/route-318ea6a550b5034e.js +1 -0
  236. package/.next/static/chunks/app/{layout-890e772919e033dc.js → layout-2bf5721b24426873.js} +1 -1
  237. package/.next/static/chunks/app/manifest.webmanifest/route-318ea6a550b5034e.js +1 -0
  238. package/.next/static/chunks/app/office/page-8dc604f9b5e6f220.js +1 -0
  239. package/.next/static/chunks/app/page-952b1ef4df5ac896.js +1 -0
  240. package/.next/static/chunks/next/dist/client/components/builtin/app-error-318ea6a550b5034e.js +1 -0
  241. package/.next/static/chunks/next/dist/client/components/builtin/forbidden-318ea6a550b5034e.js +1 -0
  242. package/.next/static/chunks/next/dist/client/components/builtin/not-found-318ea6a550b5034e.js +1 -0
  243. package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-318ea6a550b5034e.js +1 -0
  244. package/.next/static/chunks/{webpack-fe8f1f81562a2b61.js → webpack-68986b211ad38cf1.js} +1 -1
  245. package/.next/static/css/4fae42750604bc1a.css +1 -0
  246. package/.next/static/css/7a264466e19eaafd.css +1 -0
  247. package/.next/static/vrwvD7kuXEjDJZtO0Q3iO/_buildManifest.js +1 -0
  248. package/.next/trace +108 -108
  249. package/.next/trace-build +1 -1
  250. package/.next/types/app/api/github/projects/route.ts +351 -0
  251. package/.next/types/routes.d.ts +2 -1
  252. package/.next/types/validator.ts +9 -0
  253. package/README.md +10 -3
  254. package/Tools/CUELO_Setup/files/native-runtime-patch.js +3 -3
  255. package/Tools/OMP_Global_Config/agent/AGENTS.md +6 -0
  256. package/Tools/OMP_Global_Config/agent/RULES.md +1 -0
  257. package/Tools/OMP_Global_Config/agent/TITLE_SYSTEM.md +17 -0
  258. package/Tools/OMP_Global_Config/agent/agents/maker.md +11 -5
  259. package/Tools/OMP_Global_Config/agent/extensions/gitnexus-autosync.ts +89 -19
  260. package/Tools/OMP_Global_Config/agent/extensions/jev-runtime.ts +205 -4
  261. package/Tools/OMP_Global_Config/agent/extensions/lib/maker-routing.ts +1 -1
  262. package/Tools/OMP_Global_Config/agent/prompt-copies/AGENTS.en.md +7 -1
  263. package/Tools/OMP_Global_Config/agent/prompt-copies/RULES.en.md +2 -1
  264. package/Tools/OMP_Global_Config/agent/rules/harness-policy.json +23 -8
  265. package/Tools/OMP_Global_Config/agent/rules/subagent.md +16 -2
  266. package/Tools/OMP_Global_Config/agent/sop/_writer.md +10 -4
  267. package/Tools/OMP_Global_Config/agent/tools/git-finalizer/finalizer.ps1 +68 -31
  268. package/Tools/OMP_Global_Config/agent/tools/git-finalizer/index.ts +8 -6
  269. package/Tools/OMP_Global_Config/patches/apply-core-patch.mjs +1277 -164
  270. package/bin/docker-entrypoint.mjs +19 -0
  271. package/install.mjs +12 -7
  272. package/package.json +8 -7
  273. package/.next/static/b4yL-hhdDIhd8tZ_DFHIS/_buildManifest.js +0 -1
  274. package/.next/static/chunks/6110-004be57c825af4d9.js +0 -1
  275. package/.next/static/chunks/9575-9eb94dc1e8bcd130.js +0 -22
  276. package/.next/static/chunks/app/api/agent/[id]/route-6b1496af498bb4fd.js +0 -1
  277. package/.next/static/chunks/app/api/agent/new/route-6b1496af498bb4fd.js +0 -1
  278. package/.next/static/chunks/app/api/agent/running/route-6b1496af498bb4fd.js +0 -1
  279. package/.next/static/chunks/app/api/attachment-settings/route-6b1496af498bb4fd.js +0 -1
  280. package/.next/static/chunks/app/api/attachments/[id]/transcription/route-6b1496af498bb4fd.js +0 -1
  281. package/.next/static/chunks/app/api/attachments/draft/route-6b1496af498bb4fd.js +0 -1
  282. package/.next/static/chunks/app/api/attachments/route-6b1496af498bb4fd.js +0 -1
  283. package/.next/static/chunks/app/api/auth/all-providers/route-6b1496af498bb4fd.js +0 -1
  284. package/.next/static/chunks/app/api/auth/api-key/[provider]/route-6b1496af498bb4fd.js +0 -1
  285. package/.next/static/chunks/app/api/auth/login/[provider]/route-6b1496af498bb4fd.js +0 -1
  286. package/.next/static/chunks/app/api/auth/logout/[provider]/route-6b1496af498bb4fd.js +0 -1
  287. package/.next/static/chunks/app/api/auth/providers/route-6b1496af498bb4fd.js +0 -1
  288. package/.next/static/chunks/app/api/cwd/browse/route-6b1496af498bb4fd.js +0 -1
  289. package/.next/static/chunks/app/api/cwd/validate/route-6b1496af498bb4fd.js +0 -1
  290. package/.next/static/chunks/app/api/default-cwd/route-6b1496af498bb4fd.js +0 -1
  291. package/.next/static/chunks/app/api/display-settings/route-6b1496af498bb4fd.js +0 -1
  292. package/.next/static/chunks/app/api/document-text/route-6b1496af498bb4fd.js +0 -1
  293. package/.next/static/chunks/app/api/experiments/route-6b1496af498bb4fd.js +0 -1
  294. package/.next/static/chunks/app/api/file-index/route-6b1496af498bb4fd.js +0 -1
  295. package/.next/static/chunks/app/api/files/[...path]/route-6b1496af498bb4fd.js +0 -1
  296. package/.next/static/chunks/app/api/git/diff/route-6b1496af498bb4fd.js +0 -1
  297. package/.next/static/chunks/app/api/git/status/route-6b1496af498bb4fd.js +0 -1
  298. package/.next/static/chunks/app/api/gpt6/handles/route-6b1496af498bb4fd.js +0 -1
  299. package/.next/static/chunks/app/api/gpt6/reply/route-6b1496af498bb4fd.js +0 -1
  300. package/.next/static/chunks/app/api/gpt6/token/route-6b1496af498bb4fd.js +0 -1
  301. package/.next/static/chunks/app/api/gpt6/web6-session/route-6b1496af498bb4fd.js +0 -1
  302. package/.next/static/chunks/app/api/home/route-6b1496af498bb4fd.js +0 -1
  303. package/.next/static/chunks/app/api/live/call/[callId]/route-6b1496af498bb4fd.js +0 -1
  304. package/.next/static/chunks/app/api/live/character-voice/route-6b1496af498bb4fd.js +0 -1
  305. package/.next/static/chunks/app/api/live/events/route-6b1496af498bb4fd.js +0 -1
  306. package/.next/static/chunks/app/api/live/offer/route-6b1496af498bb4fd.js +0 -1
  307. package/.next/static/chunks/app/api/live/voices/route-6b1496af498bb4fd.js +0 -1
  308. package/.next/static/chunks/app/api/lounge/events/route-6b1496af498bb4fd.js +0 -1
  309. package/.next/static/chunks/app/api/lounge/route-6b1496af498bb4fd.js +0 -1
  310. package/.next/static/chunks/app/api/mcp/route-6b1496af498bb4fd.js +0 -1
  311. package/.next/static/chunks/app/api/model-roles/route-6b1496af498bb4fd.js +0 -1
  312. package/.next/static/chunks/app/api/models/route-6b1496af498bb4fd.js +0 -1
  313. package/.next/static/chunks/app/api/models-config/catalog/route-6b1496af498bb4fd.js +0 -1
  314. package/.next/static/chunks/app/api/models-config/discover/route-6b1496af498bb4fd.js +0 -1
  315. package/.next/static/chunks/app/api/models-config/route-6b1496af498bb4fd.js +0 -1
  316. package/.next/static/chunks/app/api/models-config/test/route-6b1496af498bb4fd.js +0 -1
  317. package/.next/static/chunks/app/api/office-avatars/[id]/route-6b1496af498bb4fd.js +0 -1
  318. package/.next/static/chunks/app/api/office-avatars/route-6b1496af498bb4fd.js +0 -1
  319. package/.next/static/chunks/app/api/plugins/route-6b1496af498bb4fd.js +0 -1
  320. package/.next/static/chunks/app/api/project-trust/route-6b1496af498bb4fd.js +0 -1
  321. package/.next/static/chunks/app/api/projects/route-6b1496af498bb4fd.js +0 -1
  322. package/.next/static/chunks/app/api/runs/[runId]/route-6b1496af498bb4fd.js +0 -1
  323. package/.next/static/chunks/app/api/runs/route-6b1496af498bb4fd.js +0 -1
  324. package/.next/static/chunks/app/api/sessions/[id]/auto-name/route-6b1496af498bb4fd.js +0 -1
  325. package/.next/static/chunks/app/api/sessions/[id]/context/route-6b1496af498bb4fd.js +0 -1
  326. package/.next/static/chunks/app/api/sessions/[id]/entries/[entryId]/thinking/route-6b1496af498bb4fd.js +0 -1
  327. package/.next/static/chunks/app/api/sessions/[id]/export/route-6b1496af498bb4fd.js +0 -1
  328. package/.next/static/chunks/app/api/sessions/[id]/route-6b1496af498bb4fd.js +0 -1
  329. package/.next/static/chunks/app/api/sessions/[id]/state/route-6b1496af498bb4fd.js +0 -1
  330. package/.next/static/chunks/app/api/sessions/route-6b1496af498bb4fd.js +0 -1
  331. package/.next/static/chunks/app/api/sessions/search/route-6b1496af498bb4fd.js +0 -1
  332. package/.next/static/chunks/app/api/settings/route-6b1496af498bb4fd.js +0 -1
  333. package/.next/static/chunks/app/api/sidecars/[service]/[...path]/route-6b1496af498bb4fd.js +0 -1
  334. package/.next/static/chunks/app/api/skills/check/route-6b1496af498bb4fd.js +0 -1
  335. package/.next/static/chunks/app/api/skills/install/route-6b1496af498bb4fd.js +0 -1
  336. package/.next/static/chunks/app/api/skills/route-6b1496af498bb4fd.js +0 -1
  337. package/.next/static/chunks/app/api/skills/search/route-6b1496af498bb4fd.js +0 -1
  338. package/.next/static/chunks/app/api/skills/update/route-6b1496af498bb4fd.js +0 -1
  339. package/.next/static/chunks/app/api/theme/route-6b1496af498bb4fd.js +0 -1
  340. package/.next/static/chunks/app/api/tool-image/route-6b1496af498bb4fd.js +0 -1
  341. package/.next/static/chunks/app/api/update-maintenance/route-6b1496af498bb4fd.js +0 -1
  342. package/.next/static/chunks/app/api/updates/route-6b1496af498bb4fd.js +0 -1
  343. package/.next/static/chunks/app/api/web-access/recovery/route-6b1496af498bb4fd.js +0 -1
  344. package/.next/static/chunks/app/api/web-access/route-6b1496af498bb4fd.js +0 -1
  345. package/.next/static/chunks/app/api/web6/consults/route-6b1496af498bb4fd.js +0 -1
  346. package/.next/static/chunks/app/api/worktrees/route-6b1496af498bb4fd.js +0 -1
  347. package/.next/static/chunks/app/manifest.webmanifest/route-6b1496af498bb4fd.js +0 -1
  348. package/.next/static/chunks/app/office/page-424e244f1c7fcdc6.js +0 -1
  349. package/.next/static/chunks/app/page-b43b2b8679ed66e2.js +0 -1
  350. package/.next/static/chunks/next/dist/client/components/builtin/app-error-6b1496af498bb4fd.js +0 -1
  351. package/.next/static/chunks/next/dist/client/components/builtin/forbidden-6b1496af498bb4fd.js +0 -1
  352. package/.next/static/chunks/next/dist/client/components/builtin/not-found-6b1496af498bb4fd.js +0 -1
  353. package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-6b1496af498bb4fd.js +0 -1
  354. package/.next/static/css/1ffe2d5d654f7eea.css +0 -1
  355. package/.next/static/css/81a05eb6255ef12e.css +0 -1
  356. /package/.next/static/{b4yL-hhdDIhd8tZ_DFHIS → vrwvD7kuXEjDJZtO0Q3iO}/_ssgManifest.js +0 -0
@@ -85,6 +85,131 @@ const OPUS55_ROW_18410 = {
85
85
  */
86
86
  const OPUS55_ROW_1851 = OPUS55_ROW_1846.patched.replace('"tps":95.2,', '"tps":93,');
87
87
 
88
+ /**
89
+ * learn topic upsert 적용본. 갱신 때 metadata 의 session_id/cwd 는 최초 저장자로 남고,
90
+ * updated_session_id/updated_cwd/updated_at 이 최신 정정자·시점이며 context 는 최신 이유다.
91
+ * first_context/first_timestamp 는 기존 revision 이 명시적으로 1 일 때만 갱신 직전 값으로 보존한다
92
+ * (revision 없음/2+ 는 최초 이유·시점을 알 수 없으므로 지어내지 않는다). `$` 중괄호와 백틱은 쓰지 않는다.
93
+ */
94
+ const learnTopicUpsertBlock = `// HANSE: topic-key learn upsert
95
+ function upsertLearnTopic(
96
+ state: MnemopiSessionState,
97
+ params: LearnParams,
98
+ target: ReturnType<MnemopiSessionState["getScopedRetainTarget"]>,
99
+ ): { id: string; revision: number } {
100
+ const topic = params.topic!.normalize("NFKC").trim().toLowerCase();
101
+ if (!topic) throw new Error("Learn topic must not be blank.");
102
+ if (redactMemorySecrets(topic) !== topic) throw new Error("Learn topic contains a credential.");
103
+ const db = target.memory.conn;
104
+ const existing = db.query(
105
+ "SELECT id, timestamp, metadata_json FROM working_memory WHERE json_extract(metadata_json, '$.topic_key') = ? ORDER BY timestamp DESC, id DESC LIMIT 1",
106
+ ).get(topic) as { id: string; timestamp: string; metadata_json: string } | null;
107
+ const revision = existing ? (Number(JSON.parse(existing.metadata_json).revision) || 1) + 1 : 1;
108
+ if (existing) return db.transaction(() => {
109
+ const content = redactMemorySecrets(params.memory);
110
+ const stored = JSON.parse(existing.metadata_json);
111
+ const now = new Date().toISOString();
112
+ // HANSE: learn topic provenance keeps first and latest source
113
+ const firstSeen = stored.revision === 1 ? { first_context: stored.context ?? null, first_timestamp: existing.timestamp } : {};
114
+ const latest = { updated_session_id: state.sessionId, updated_cwd: state.session.sessionManager.getCwd(), updated_at: now };
115
+ db.query("UPDATE working_memory SET content = ?, embed_text = NULL, timestamp = ?, metadata_json = ? WHERE id = ?").run(
116
+ content, now,
117
+ JSON.stringify({ ...firstSeen, ...stored, context: params.context == null ? null : redactMemorySecrets(params.context), topic_key: topic, revision, ...latest }),
118
+ existing.id,
119
+ );
120
+ db.query("DELETE FROM memory_embeddings WHERE memory_id = ?").run(existing.id);
121
+ return { id: existing.id, revision };
122
+ })();
123
+ const id = state.rememberScoped(params.memory, {
124
+ source: "coding-agent-learn", importance: 0.8,
125
+ metadata: { session_id: state.sessionId, cwd: state.session.sessionManager.getCwd(), context: params.context ?? null, tool: "learn", topic_key: topic, revision },
126
+ scope: "bank", extract: true, extractEntities: true, veracity: "tool", memoryType: "fact",
127
+ }, target);
128
+ return { id, revision };
129
+ }
130
+
131
+ export class LearnTool implements AgentTool<LearnSchema> {`;
132
+
133
+ /**
134
+ * 회상 줄의 범위(global/project)·정정 표시 보조. 순수 표시이며 DB 와 RecallResult 원본은 바꾸지 않는다.
135
+ * 범위는 collectScopedRecallResults 가 고른 결과의 실제 bank 로 붙인 일시 필드(recallScope)에서만 읽는다.
136
+ * 정정 표시는 revision>1 일 때만, 최초 시점은 first_timestamp 가 있을 때만, 정정 시점은 updated_at 이 있을 때만 쓴다.
137
+ */
138
+ const recallProvenanceBlock = String.raw`// HANSE: recall provenance note
139
+ type RecallProvenance = { scope?: string; revision: number; first?: string; corrected?: string };
140
+ function recallProvenance(result: RecallResult): RecallProvenance {
141
+ const scope = (result as { recallScope?: string }).recallScope;
142
+ let meta: Record<string, unknown> | null = null;
143
+ try {
144
+ const raw: unknown = result.metadata ?? (result.metadata_json ? JSON.parse(result.metadata_json) : null);
145
+ if (raw && typeof raw === "object") meta = raw as Record<string, unknown>;
146
+ } catch {
147
+ meta = null;
148
+ }
149
+ const day = (value: unknown): string | undefined =>
150
+ typeof value === "string" && /^\d{4}-\d{2}-\d{2}/.test(value) ? value.slice(0, 10) : undefined;
151
+ const revision = Number(meta?.revision);
152
+ return {
153
+ scope,
154
+ revision: Number.isInteger(revision) ? revision : 0,
155
+ first: day(meta?.first_timestamp),
156
+ corrected: day(meta?.updated_at),
157
+ };
158
+ }
159
+ function recallProvenanceNote(result: RecallResult): string {
160
+ const info = recallProvenance(result);
161
+ const parts: string[] = [];
162
+ if (info.scope) parts.push(info.scope);
163
+ if (info.revision > 1) {
164
+ parts.push("corrected r" + info.revision);
165
+ parts.push("first " + (info.first ?? "unknown"));
166
+ if (info.corrected) parts.push("corrected " + info.corrected);
167
+ }
168
+ return parts.length > 0 ? " {" + parts.join("; ") + "}" : "";
169
+ }
170
+ function recallRevisionFooter(results: readonly RecallResult[]): string {
171
+ return results.some(result => recallProvenance(result).revision > 1)
172
+ ? "\n\nCorrected rows: read memory://<id> for the full metadata and correction reason."
173
+ : "";
174
+ }
175
+
176
+ `;
177
+
178
+ /** 출처 구분 이전 learn topic upsert 적용본. 새 판(learnTopicUpsertBlock)이 이 본문을 대체한다(legacyPatched). */
179
+ const learnTopicUpsertLegacyBlock = `// HANSE: topic-key learn upsert
180
+ function upsertLearnTopic(
181
+ state: MnemopiSessionState,
182
+ params: LearnParams,
183
+ target: ReturnType<MnemopiSessionState["getScopedRetainTarget"]>,
184
+ ): { id: string; revision: number } {
185
+ const topic = params.topic!.normalize("NFKC").trim().toLowerCase();
186
+ if (!topic) throw new Error("Learn topic must not be blank.");
187
+ if (redactMemorySecrets(topic) !== topic) throw new Error("Learn topic contains a credential.");
188
+ const db = target.memory.conn;
189
+ const existing = db.query(
190
+ "SELECT id, metadata_json FROM working_memory WHERE json_extract(metadata_json, '$.topic_key') = ? ORDER BY timestamp DESC, id DESC LIMIT 1",
191
+ ).get(topic) as { id: string; metadata_json: string } | null;
192
+ const revision = existing ? (Number(JSON.parse(existing.metadata_json).revision) || 1) + 1 : 1;
193
+ if (existing) return db.transaction(() => {
194
+ const content = redactMemorySecrets(params.memory);
195
+ db.query("UPDATE working_memory SET content = ?, embed_text = NULL, timestamp = ?, metadata_json = ? WHERE id = ?").run(
196
+ content, new Date().toISOString(),
197
+ JSON.stringify({ ...JSON.parse(existing.metadata_json), context: params.context == null ? null : redactMemorySecrets(params.context), topic_key: topic, revision }),
198
+ existing.id,
199
+ );
200
+ db.query("DELETE FROM memory_embeddings WHERE memory_id = ?").run(existing.id);
201
+ return { id: existing.id, revision };
202
+ })();
203
+ const id = state.rememberScoped(params.memory, {
204
+ source: "coding-agent-learn", importance: 0.8,
205
+ metadata: { session_id: state.sessionId, cwd: state.session.sessionManager.getCwd(), context: params.context ?? null, tool: "learn", topic_key: topic, revision },
206
+ scope: "bank", extract: true, extractEntities: true, veracity: "tool", memoryType: "fact",
207
+ }, target);
208
+ return { id, revision };
209
+ }
210
+
211
+ export class LearnTool implements AgentTool<LearnSchema> {`;
212
+
88
213
  /**
89
214
  * 기억 임베딩 (6) 항목의 본문. 0.9.3 은 recall 이 e5 여부로 dense 하한을 고르게 `usesE5Prefix` 를 export 한다.
90
215
  * 0.9.2 적용본(export 없음)은 legacyPatched 로 남겨 같은 항목이 새 판으로 바꾼다.
@@ -219,6 +344,131 @@ const TASK_AGENT_OPTIONAL_EDITS = [
219
344
  },
220
345
  ];
221
346
 
347
+ /**
348
+ * 부모 steer bracket 장부(#ircTurnBracketOpen·adopted steer)와 입양·continuation 관찰 메서드. setIrcWakeTurnObserver
349
+ * 항목의 18.6.1 후보와 18.6.3 후보가 같은 본문을 붙이므로 한 곳에 둔다(값은 18.6.1 후보 patched 의 해당 구간 그대로).
350
+ */
351
+ const IRC_STEER_BRACKET_MEMBERS = `
352
+
353
+ /** True while some bracket already owes the steering parent this turn's
354
+ * result: the wake monitor, a continuation that started owing a steer, or an
355
+ * adoption below. One owner per turn - never two relays for one turn. */
356
+ #ircTurnBracketOpen = false;
357
+
358
+ /** Parent steers that landed after this turn's bracket was already open - a
359
+ * sibling peer's wake turn, or an earlier adoption. The open bracket owns
360
+ * them: a turn finalizes exactly once, so they ride that finalization
361
+ * instead of opening a second monitor for the same turn. */
362
+ #adoptedParentSteerEntries: ParentSteerRelay[] = [];
363
+
364
+ /** Hands the open bracket the adopted steers this turn actually consumed, so
365
+ * its single finalization answers their sender too. Stranded ones stay parked
366
+ * for the continuation that consumes them. */
367
+ #takeAdoptedParentSteerRecords(): AgentMessage[] {
368
+ const entries = this.#adoptedParentSteerEntries;
369
+ if (entries.length === 0) return [];
370
+ this.#adoptedParentSteerEntries = [];
371
+ const queued = this.agent.peekSteeringQueue();
372
+ return entries.filter(entry => !queued.some(message => message === entry.steered)).map(entry => entry.record);
373
+ }
374
+
375
+ /**
376
+ * A parent steer landed inside a turn that is ALREADY running, so no bracket
377
+ * could have been opened for it at turn start. Ownership follows consumption:
378
+ * if this running turn polls the steer out of the agent-core queue, this
379
+ * turn's result is the answer, and nothing else will ever send one - the
380
+ * spawn job settled long ago and #scheduleAgentContinue only brackets turns
381
+ * that START owing a steer. So bracket the running turn now, while the
382
+ * monitor can still watch it, and decide at settle from the queue itself:
383
+ * - steer gone -> this turn consumed it -> relay its result, exactly once;
384
+ * - steer queued -> genuinely stranded -> stay silent and leave the
385
+ * parked record to the continuation that will consume it.
386
+ * No-ops while the spawn job still owns the run (no observer installed).
387
+ */
388
+ #adoptParentSteerForRunningTurn(): void {
389
+ if (this.#promptInFlightCount === 0 || !this.isStreaming) return;
390
+ const observer = this.#ircWakeTurnObserver;
391
+ if (observer === undefined) return;
392
+ const entries = this.#irc.peekParentSteerRelays();
393
+ if (entries.length === 0) return;
394
+ if (this.#ircTurnBracketOpen) {
395
+ // Another bracket already owes this turn's one finalization (a sibling's
396
+ // wake turn, or an earlier adoption). Opening a second monitor would
397
+ // finalize the same turn twice - two lifecycle frames, two artifacts -
398
+ // while dropping the steer would leave the parent unanswered whenever
399
+ // this turn consumes it. Ride the open bracket instead.
400
+ for (const entry of entries) {
401
+ if (!this.#adoptedParentSteerEntries.includes(entry)) this.#adoptedParentSteerEntries.push(entry);
402
+ }
403
+ return;
404
+ }
405
+ let finish:
406
+ | ((error?: unknown, suppressRelay?: boolean, adoptedRecords?: AgentMessage[]) => void | Promise<void>)
407
+ | undefined;
408
+ try {
409
+ // The parent's steer did not wake this turn, and it may still strand:
410
+ // the monitor must not claim the parent's answer up front.
411
+ finish = observer(entries.map(entry => entry.record), { runningTurn: true });
412
+ } catch (error) {
413
+ logger.warn("IRC steer continuation observer failed to start", { error: String(error) });
414
+ return;
415
+ }
416
+ if (finish === undefined) return;
417
+ this.#ircTurnBracketOpen = true;
418
+ // Same settle hook every other bracket uses; it runs before the queue
419
+ // reconciliation in #drainStrandedQueuedMessages, so the entries this turn
420
+ // consumed are still parked here and retire immediately afterwards.
421
+ this.#inFlightSettledCallbacks.push(async () => {
422
+ this.#ircTurnBracketOpen = false;
423
+ const queued = this.agent.peekSteeringQueue();
424
+ const stranded = entries.some(entry => queued.some(message => message === entry.steered));
425
+ try {
426
+ await finish?.(undefined, stranded, this.#takeAdoptedParentSteerRecords());
427
+ } catch (error) {
428
+ logger.warn("IRC steer continuation observer failed to finish", { error: String(error) });
429
+ }
430
+ });
431
+ }
432
+
433
+ /**
434
+ * Brackets an autonomous continuation with the wake-turn monitor when a parent
435
+ * IRC steer is what it resumes. \`#ircWakeTurnObserver\` exists only after the
436
+ * kept-alive subagent's spawn run settled (task executor
437
+ * \`installIrcWakeTurnMonitor\`), so its presence is exactly the condition "this
438
+ * agent's job can no longer deliver its results". That flag is handed to the
439
+ * bridge rather than short-circuiting here: while the job still owns the run
440
+ * the record must stay parked, and the queue-backed reconciliation in
441
+ * #drainStrandedQueuedMessages - not this call - decides when it retires.
442
+ * Returns the consumed entries so a continuation that never ran can re-arm.
443
+ */
444
+ #beginIrcSteerContinuationObservation():
445
+ | {
446
+ entries: ParentSteerRelay[];
447
+ finish:
448
+ | ((error?: unknown, suppressRelay?: boolean, adoptedRecords?: AgentMessage[]) => void | Promise<void>)
449
+ | undefined;
450
+ }
451
+ | undefined {
452
+ const observer = this.#ircWakeTurnObserver;
453
+ // A bracket is already open for this turn: #adoptParentSteerForRunningTurn
454
+ // took the running turn and settles the same obligation from the queue.
455
+ // Taking the entries here would open a second monitor for one obligation -
456
+ // two "started" lifecycle frames, two finalized run artifacts, and a parent
457
+ // notice that stays single only because the bus drops the duplicate send.
458
+ if (this.#ircTurnBracketOpen) return undefined;
459
+ const entries = this.#irc.takeParentSteerRelays(observer !== undefined);
460
+ if (observer === undefined || entries.length === 0) return undefined;
461
+ const records = entries.map(entry => entry.record);
462
+ try {
463
+ const finish = observer(records);
464
+ if (finish !== undefined) this.#ircTurnBracketOpen = true;
465
+ return { entries, finish };
466
+ } catch (error) {
467
+ logger.warn("IRC steer continuation observer failed to start", { error: String(error) });
468
+ return { entries, finish: undefined };
469
+ }
470
+ }`;
471
+
222
472
  /** 각 항목: 원본 앵커를 찾아 patched 로 바꾼다. marker 가 있으면 이미 적용된 것으로 본다. */
223
473
  const EDITS = [
224
474
  {
@@ -1254,6 +1504,9 @@ export interface IrcBridgeHost {
1254
1504
  // 더 이상 결과를 전달할 수 없다"는 조건이다.
1255
1505
  file: "src/session/agent-session.ts",
1256
1506
  marker: "#beginIrcSteerContinuationObservation",
1507
+ // 18.6.3(#14428)은 이 setter 의 주석을 바꾸고 관찰 수명을 #openTurnObservation 으로 관리한다. 두 판의 결과가 같은
1508
+ // marker 를 가지므로 그 필드 유무로 후보를 가른다. 붙이는 장부·메서드 본문은 같다(IRC_STEER_BRACKET_MEMBERS).
1509
+ excludes: "#openTurnObservation",
1257
1510
  anchor: ` /** Installs task-executor monitoring around autonomous IRC wake turns. */
1258
1511
  setIrcWakeTurnObserver(
1259
1512
  observer: ((records: AgentMessage[]) => ((error?: unknown) => void | Promise<void>) | undefined) | undefined,
@@ -1280,126 +1533,45 @@ export interface IrcBridgeHost {
1280
1533
  | undefined,
1281
1534
  ): void {
1282
1535
  this.#ircWakeTurnObserver = observer;
1283
- }
1284
-
1285
- /** True while some bracket already owes the steering parent this turn's
1286
- * result: the wake monitor, a continuation that started owing a steer, or an
1287
- * adoption below. One owner per turn - never two relays for one turn. */
1288
- #ircTurnBracketOpen = false;
1289
-
1290
- /** Parent steers that landed after this turn's bracket was already open - a
1291
- * sibling peer's wake turn, or an earlier adoption. The open bracket owns
1292
- * them: a turn finalizes exactly once, so they ride that finalization
1293
- * instead of opening a second monitor for the same turn. */
1294
- #adoptedParentSteerEntries: ParentSteerRelay[] = [];
1295
-
1296
- /** Hands the open bracket the adopted steers this turn actually consumed, so
1297
- * its single finalization answers their sender too. Stranded ones stay parked
1298
- * for the continuation that consumes them. */
1299
- #takeAdoptedParentSteerRecords(): AgentMessage[] {
1300
- const entries = this.#adoptedParentSteerEntries;
1301
- if (entries.length === 0) return [];
1302
- this.#adoptedParentSteerEntries = [];
1303
- const queued = this.agent.peekSteeringQueue();
1304
- return entries.filter(entry => !queued.some(message => message === entry.steered)).map(entry => entry.record);
1305
- }
1306
-
1307
- /**
1308
- * A parent steer landed inside a turn that is ALREADY running, so no bracket
1309
- * could have been opened for it at turn start. Ownership follows consumption:
1310
- * if this running turn polls the steer out of the agent-core queue, this
1311
- * turn's result is the answer, and nothing else will ever send one - the
1312
- * spawn job settled long ago and #scheduleAgentContinue only brackets turns
1313
- * that START owing a steer. So bracket the running turn now, while the
1314
- * monitor can still watch it, and decide at settle from the queue itself:
1315
- * - steer gone -> this turn consumed it -> relay its result, exactly once;
1316
- * - steer queued -> genuinely stranded -> stay silent and leave the
1317
- * parked record to the continuation that will consume it.
1318
- * No-ops while the spawn job still owns the run (no observer installed).
1536
+ }` + IRC_STEER_BRACKET_MEMBERS,
1537
+ alternates: [{
1538
+ file: "src/session/agent-session.ts",
1539
+ requires: "#openTurnObservation",
1540
+ marker: "#beginIrcSteerContinuationObservation",
1541
+ anchor: ` /**
1542
+ * Installs task-executor monitoring around turns no executor drives: autonomous IRC
1543
+ * wake turns and user prompts (focused-session steering of a kept-alive subagent).
1319
1544
  */
1320
- #adoptParentSteerForRunningTurn(): void {
1321
- if (this.#promptInFlightCount === 0 || !this.isStreaming) return;
1322
- const observer = this.#ircWakeTurnObserver;
1323
- if (observer === undefined) return;
1324
- const entries = this.#irc.peekParentSteerRelays();
1325
- if (entries.length === 0) return;
1326
- if (this.#ircTurnBracketOpen) {
1327
- // Another bracket already owes this turn's one finalization (a sibling's
1328
- // wake turn, or an earlier adoption). Opening a second monitor would
1329
- // finalize the same turn twice - two lifecycle frames, two artifacts -
1330
- // while dropping the steer would leave the parent unanswered whenever
1331
- // this turn consumes it. Ride the open bracket instead.
1332
- for (const entry of entries) {
1333
- if (!this.#adoptedParentSteerEntries.includes(entry)) this.#adoptedParentSteerEntries.push(entry);
1334
- }
1335
- return;
1336
- }
1337
- let finish:
1338
- | ((error?: unknown, suppressRelay?: boolean, adoptedRecords?: AgentMessage[]) => void | Promise<void>)
1339
- | undefined;
1340
- try {
1341
- // The parent's steer did not wake this turn, and it may still strand:
1342
- // the monitor must not claim the parent's answer up front.
1343
- finish = observer(entries.map(entry => entry.record), { runningTurn: true });
1344
- } catch (error) {
1345
- logger.warn("IRC steer continuation observer failed to start", { error: String(error) });
1346
- return;
1347
- }
1348
- if (finish === undefined) return;
1349
- this.#ircTurnBracketOpen = true;
1350
- // Same settle hook every other bracket uses; it runs before the queue
1351
- // reconciliation in #drainStrandedQueuedMessages, so the entries this turn
1352
- // consumed are still parked here and retire immediately afterwards.
1353
- this.#inFlightSettledCallbacks.push(async () => {
1354
- this.#ircTurnBracketOpen = false;
1355
- const queued = this.agent.peekSteeringQueue();
1356
- const stranded = entries.some(entry => queued.some(message => message === entry.steered));
1357
- try {
1358
- await finish?.(undefined, stranded, this.#takeAdoptedParentSteerRecords());
1359
- } catch (error) {
1360
- logger.warn("IRC steer continuation observer failed to finish", { error: String(error) });
1361
- }
1362
- });
1363
- }
1364
-
1365
- /**
1366
- * Brackets an autonomous continuation with the wake-turn monitor when a parent
1367
- * IRC steer is what it resumes. \`#ircWakeTurnObserver\` exists only after the
1368
- * kept-alive subagent's spawn run settled (task executor
1369
- * \`installIrcWakeTurnMonitor\`), so its presence is exactly the condition "this
1370
- * agent's job can no longer deliver its results". That flag is handed to the
1371
- * bridge rather than short-circuiting here: while the job still owns the run
1372
- * the record must stay parked, and the queue-backed reconciliation in
1373
- * #drainStrandedQueuedMessages - not this call - decides when it retires.
1374
- * Returns the consumed entries so a continuation that never ran can re-arm.
1545
+ setIrcWakeTurnObserver(
1546
+ observer: ((records: AgentMessage[]) => ((error?: unknown) => void | Promise<void>) | undefined) | undefined,
1547
+ ): void {
1548
+ this.#ircWakeTurnObserver = observer;
1549
+ }`,
1550
+ patched: ` /**
1551
+ * Installs task-executor monitoring around turns no executor drives: autonomous IRC
1552
+ * wake turns and user prompts (focused-session steering of a kept-alive subagent).
1553
+ * \`suppressRelay\` lets the session finalize a bracketed turn without
1554
+ * claiming it answered the parent (see #adoptParentSteerForRunningTurn).
1555
+ * \`adoptedRecords\` carries steers that landed after the bracket opened and
1556
+ * that this same turn consumed: one finalization answers every source.
1557
+ * \`bracket.runningTurn\` marks a bracket opened over a turn that was ALREADY
1558
+ * running (the parent's steer did not wake it), so the monitor must not
1559
+ * treat it as parent-woken work: whether it owns the answer is only known
1560
+ * at settle.
1375
1561
  */
1376
- #beginIrcSteerContinuationObservation():
1377
- | {
1378
- entries: ParentSteerRelay[];
1379
- finish:
1562
+ setIrcWakeTurnObserver(
1563
+ observer:
1564
+ | ((
1565
+ records: AgentMessage[],
1566
+ bracket?: { runningTurn?: boolean },
1567
+ ) =>
1380
1568
  | ((error?: unknown, suppressRelay?: boolean, adoptedRecords?: AgentMessage[]) => void | Promise<void>)
1381
- | undefined;
1382
- }
1383
- | undefined {
1384
- const observer = this.#ircWakeTurnObserver;
1385
- // A bracket is already open for this turn: #adoptParentSteerForRunningTurn
1386
- // took the running turn and settles the same obligation from the queue.
1387
- // Taking the entries here would open a second monitor for one obligation -
1388
- // two "started" lifecycle frames, two finalized run artifacts, and a parent
1389
- // notice that stays single only because the bus drops the duplicate send.
1390
- if (this.#ircTurnBracketOpen) return undefined;
1391
- const entries = this.#irc.takeParentSteerRelays(observer !== undefined);
1392
- if (observer === undefined || entries.length === 0) return undefined;
1393
- const records = entries.map(entry => entry.record);
1394
- try {
1395
- const finish = observer(records);
1396
- if (finish !== undefined) this.#ircTurnBracketOpen = true;
1397
- return { entries, finish };
1398
- } catch (error) {
1399
- logger.warn("IRC steer continuation observer failed to start", { error: String(error) });
1400
- return { entries, finish: undefined };
1401
- }
1402
- }`,
1569
+ | undefined)
1570
+ | undefined,
1571
+ ): void {
1572
+ this.#ircWakeTurnObserver = observer;
1573
+ }` + IRC_STEER_BRACKET_MEMBERS,
1574
+ }],
1403
1575
  },
1404
1576
  {
1405
1577
  // 실제 bracket. #wakeForIrc 가 wake turn 을 감싸는 방식과 같은 계약이다:
@@ -1640,6 +1812,50 @@ export interface IrcBridgeHost {
1640
1812
  } catch (error) {
1641
1813
  logger.warn("IRC wake turn observer failed to start", { error: String(error) });
1642
1814
  }`,
1815
+ // 18.6.3(#14428)은 wake 와 사용자 prompt 의 관찰을 #startTurnObservation 으로 모으고 "세션당 열린 관찰 하나"를
1816
+ // #openTurnObservation 으로 지킨다. 우리 장부도 그 관찰을 이 turn 의 bracket 으로 본다. 반대로 우리 bracket(입양·
1817
+ // stranded steer continuation)이 돌고 있는 turn 을 잡고 있으면, 상류가 자기 관찰의 turn 이 아직 돌 때 하듯 시작하지
1818
+ // 않는다. 두 장부가 서로를 모르면 같은 turn 을 두 monitor 가 정산한다(ADAPT).
1819
+ alternates: [{
1820
+ file: "src/session/agent-session.ts",
1821
+ marker: "this.#openTurnObservation = observation;\n\t\t// The observation is this turn's bracket",
1822
+ anchor: ` const open = this.#openTurnObservation;
1823
+ if (open) {
1824
+ if (!open.turnEnded) return undefined;
1825
+ void this.#closeTurnObservation(open);
1826
+ }
1827
+ let finish: TurnObservation["finish"] | undefined;
1828
+ try {
1829
+ finish = this.#ircWakeTurnObserver?.(records);
1830
+ } catch (error) {
1831
+ logger.warn("Turn observer failed to start", { error: String(error) });
1832
+ }
1833
+ if (!finish) return undefined;
1834
+ const observation: TurnObservation = { finish, turnEnded: false, closed: false, turnError: undefined };
1835
+ this.#openTurnObservation = observation;`,
1836
+ patched: ` const open = this.#openTurnObservation;
1837
+ if (open) {
1838
+ if (!open.turnEnded) return undefined;
1839
+ void this.#closeTurnObservation(open);
1840
+ } else if (this.#ircTurnBracketOpen) {
1841
+ // A parent-steer bracket (#adoptParentSteerForRunningTurn, or the
1842
+ // continuation that resumes a stranded steer) already owns the running
1843
+ // turn. One monitor per turn, as for an open observation whose turn runs.
1844
+ return undefined;
1845
+ }
1846
+ let finish: TurnObservation["finish"] | undefined;
1847
+ try {
1848
+ finish = this.#ircWakeTurnObserver?.(records);
1849
+ } catch (error) {
1850
+ logger.warn("Turn observer failed to start", { error: String(error) });
1851
+ }
1852
+ if (!finish) return undefined;
1853
+ const observation: TurnObservation = { finish, turnEnded: false, closed: false, turnError: undefined };
1854
+ this.#openTurnObservation = observation;
1855
+ // The observation is this turn's bracket: a parent steer landing in it rides
1856
+ // its single finalization instead of opening a second monitor.
1857
+ this.#ircTurnBracketOpen = true;`,
1858
+ }],
1643
1859
  },
1644
1860
  {
1645
1861
  // wake turn 의 지역 선언도 같은 계약을 실어야 한다. 이 turn 도중에 도착해
@@ -1650,6 +1866,15 @@ export interface IrcBridgeHost {
1650
1866
  patched: ` let finishObservation:
1651
1867
  | ((error?: unknown, suppressRelay?: boolean, adoptedRecords?: AgentMessage[]) => void | Promise<void>)
1652
1868
  | undefined;`,
1869
+ // 18.6.3: wake 지역 변수 대신 TurnObservation.finish 가 같은 계약을 싣는다(ADAPT).
1870
+ alternates: [{
1871
+ file: "src/session/agent-session.ts",
1872
+ marker: "type TurnObservation = {\n\tfinish: (error?: unknown, suppressRelay?: boolean",
1873
+ anchor: `type TurnObservation = {
1874
+ finish: (error?: unknown) => void | Promise<void>;`,
1875
+ patched: `type TurnObservation = {
1876
+ finish: (error?: unknown, suppressRelay?: boolean, adoptedRecords?: AgentMessage[]) => void | Promise<void>;`,
1877
+ }],
1653
1878
  },
1654
1879
  {
1655
1880
  // wake bracket 의 닫는 쪽. 같은 장부를 되돌린다.
@@ -1703,6 +1928,54 @@ export interface IrcBridgeHost {
1703
1928
  };
1704
1929
  if (this.#promptInFlightCount > 0) this.#inFlightSettledCallbacks.push(finishWakeObservation);
1705
1930
  else await finishWakeObservation();`,
1931
+ }, {
1932
+ // 18.6.3: 같은 정산이 #finishTurnObservation(settle 대기)과 #closeTurnObservation(한 번만 finish)으로 옮겨졌다.
1933
+ // settle 뒤에도 turn 이 in-flight 면 그 settle 콜백에서 닫고, 닫을 때 장부를 내리고 adopted steer 를 넘긴다(ADAPT).
1934
+ // 새 wake·사용자 prompt 가 먼저 닫으면(상류 supersede) 미뤄 둔 닫기는 closed 로 끝난다.
1935
+ file: "src/session/agent-session.ts",
1936
+ marker: "await observation.finish(observation.turnError, undefined, adoptedRecords);",
1937
+ anchor: ` try {
1938
+ await this.settleAsyncWork();
1939
+ } catch (error) {
1940
+ logger.warn("Turn observation async-work settle failed", { error: String(error) });
1941
+ }
1942
+ await this.#closeTurnObservation(observation);
1943
+ }
1944
+
1945
+ /** Runs an observation's finish exactly once; the observer detaches synchronously before the first await. */
1946
+ async #closeTurnObservation(observation: TurnObservation): Promise<void> {
1947
+ if (observation.closed) return;
1948
+ observation.closed = true;
1949
+ if (this.#openTurnObservation === observation) this.#openTurnObservation = undefined;
1950
+ try {
1951
+ await observation.finish(observation.turnError);`,
1952
+ patched: ` try {
1953
+ await this.settleAsyncWork();
1954
+ } catch (error) {
1955
+ logger.warn("Turn observation async-work settle failed", { error: String(error) });
1956
+ }
1957
+ // The settle waits for agent streaming only. A turn still in flight past it
1958
+ // (a continuation between #beginInFlight and agent.continue that resumes a
1959
+ // stranded parent steer) runs inside this bracket, so close at its own
1960
+ // settle: the adopted steers it consumes then ride this one finalization.
1961
+ if (this.#promptInFlightCount > 0) {
1962
+ this.#inFlightSettledCallbacks.push(() => this.#closeTurnObservation(observation));
1963
+ return;
1964
+ }
1965
+ await this.#closeTurnObservation(observation);
1966
+ }
1967
+
1968
+ /** Runs an observation's finish exactly once; the observer detaches synchronously before the first await. */
1969
+ async #closeTurnObservation(observation: TurnObservation): Promise<void> {
1970
+ if (observation.closed) return;
1971
+ observation.closed = true;
1972
+ if (this.#openTurnObservation === observation) this.#openTurnObservation = undefined;
1973
+ // The bracket ledger closes with the observation; parent steers that landed
1974
+ // after it opened and that its turn consumed ride this finalization.
1975
+ this.#ircTurnBracketOpen = false;
1976
+ const adoptedRecords = this.#takeAdoptedParentSteerRecords();
1977
+ try {
1978
+ await observation.finish(observation.turnError, undefined, adoptedRecords);`,
1706
1979
  }],
1707
1980
  },
1708
1981
  {
@@ -5551,6 +5824,39 @@ export function hostNextServerEnvUnsets(
5551
5824
  : hostNextServerEnvUnsets(process.env, shellEnv).filter(name => !(options?.env && name in options.env));
5552
5825
  if (hostEnvUnsets.length > 0) preflight.command = \`unset -v \${hostEnvUnsets.join(" ")}; \${preflight.command}\`;
5553
5826
  const commandEnv = buildNonInteractiveEnv(preflight.env);
5827
+ `,
5828
+ },
5829
+ {
5830
+ // 2026-10-07 Linux: 위 항목이 건너뛰는 PTY 경로(zsh/fish 사용자 셸의 `!` 핫키)는 map 에서 지운 서버 변수가 되살아났다.
5831
+ // native PTY 가 넘긴 env map 을 OS environ 위에 덧씌우기 때문이다(실측: zsh 이름 셸 + usePty 에서 NODE_ENV=production).
5832
+ // 셸과 무관하게 argv 앞에 `env -u NAME…` 를 붙여 뺀다. 이름 목록은 위 `unset -v` 와 같은 규칙이고, 비면 argv 는 그대로다.
5833
+ // Windows 는 사용자 셸 PTY 를 쓰지 않는다(resolveUserShellConfig 가 기본 셸로 돌아간다).
5834
+ file: "src/exec/bash-executor.ts",
5835
+ marker: "\tenvUnsets: string[]; // HANSE: CUELO next server env",
5836
+ anchor: "\tcwd: string | undefined;\n\tenv: Record<string, string>;\n\tpty: BashPtyOptions;\n",
5837
+ patched: "\tcwd: string | undefined;\n\tenv: Record<string, string>;\n\tenvUnsets: string[]; // HANSE: CUELO next server env\n\tpty: BashPtyOptions;\n",
5838
+ },
5839
+ {
5840
+ file: "src/exec/bash-executor.ts",
5841
+ marker: '...(run.envUnsets.length > 0 ? [...run.envUnsets.flatMap(name => ["-u", name]), run.shell] : []),',
5842
+ anchor: "\t\t\tapplication: run.shell,\n\t\t\targs: [...ensureInteractiveShellArgs(run.shell, run.args), run.command],\n",
5843
+ patched: ` // HANSE: CUELO next server env stays out of the user-shell PTY. The native PTY layers the env map
5844
+ // over the OS environ, so names dropped from the map come back unless env -u removes them at exec.
5845
+ application: run.envUnsets.length > 0 ? "env" : run.shell,
5846
+ args: [
5847
+ ...(run.envUnsets.length > 0 ? [...run.envUnsets.flatMap(name => ["-u", name]), run.shell] : []),
5848
+ ...ensureInteractiveShellArgs(run.shell, run.args),
5849
+ run.command,
5850
+ ],
5851
+ `,
5852
+ },
5853
+ {
5854
+ file: "src/exec/bash-executor.ts",
5855
+ marker: "envUnsets: hostNextServerEnvUnsets(process.env, shellEnv).filter(",
5856
+ anchor: "\t\t\t\tenv: buildUserShellPtyEnv(shellEnv, commandEnv),\n\t\t\t\tpty: ptyRequest,\n",
5857
+ patched: ` env: buildUserShellPtyEnv(shellEnv, commandEnv),
5858
+ envUnsets: hostNextServerEnvUnsets(process.env, shellEnv).filter(name => !(options?.env && name in options.env)),
5859
+ pty: ptyRequest,
5554
5860
  `,
5555
5861
  },
5556
5862
  {
@@ -5568,15 +5874,54 @@ export function hostNextServerEnvUnsets(
5568
5874
  anchor: 'import { workerEnvFromParent } from "../subprocess/worker-client";\n',
5569
5875
  patched: 'import { workerEnvFromParent } from "../subprocess/worker-client";\nimport { hostNextServerEnvUnsets } from "@oh-my-pi/pi-utils/env";\n',
5570
5876
  },
5877
+ {
5878
+ // 2026-10-07 Linux: 아래 startup 의 `delete process.env` 는 Windows 에서는 OS env 에도 반영되지만, Linux Bun 에서는 OS
5879
+ // environ 까지 내려가지 않는다. pi-natives PtySession 은 OS environ 위에 넘긴 env map 을 덧씌우므로 pty service 에
5880
+ // 서버 변수가 되살아났다(실측: map·pipe 는 unset, pty 는 OS 값). 지운 이름을 기억해 POSIX PTY 실행에서 뺀다(아래 항목).
5881
+ file: "src/launch/broker.ts",
5882
+ marker: "const brokerDroppedHostEnv = new Set<string>();",
5883
+ anchor: 'const LOG_FILE = "output.log";\n',
5884
+ patched: `/** HANSE: CUELO Next server env names this broker dropped from its own env at startup. */
5885
+ const brokerDroppedHostEnv = new Set<string>();
5886
+ const LOG_FILE = "output.log";
5887
+ `,
5888
+ },
5571
5889
  {
5572
5890
  file: "src/launch/broker.ts",
5573
- marker: "// HANSE: broker env drops inherited CUELO next server env",
5891
+ marker: "brokerDroppedHostEnv.add(name);",
5574
5892
  anchor: "\tdelete process.env[DAEMON_IDLE_GRACE_ENV];\n",
5575
5893
  patched: ` delete process.env[DAEMON_IDLE_GRACE_ENV];
5576
5894
  // HANSE: broker env drops inherited CUELO next server env
5895
+ for (const name of hostNextServerEnvUnsets(process.env, {})) {
5896
+ delete process.env[name];
5897
+ brokerDroppedHostEnv.add(name);
5898
+ }
5899
+ `,
5900
+ // 2026-10-07 이전 적용본(이름을 기억하지 않는다). 그 위에 다시 적용하면 이 본문으로 바뀐다.
5901
+ legacyPatched: ` delete process.env[DAEMON_IDLE_GRACE_ENV];
5902
+ // HANSE: broker env drops inherited CUELO next server env
5577
5903
  for (const name of hostNextServerEnvUnsets(process.env, {})) delete process.env[name];
5578
5904
  `,
5579
5905
  },
5906
+ {
5907
+ // POSIX PTY service 는 사용자 셸(bash·zsh·fish)로 `exec <argv>` 를 띄운다. 셸과 무관하게 argv 앞에 `env -u NAME…` 를
5908
+ // 붙여 OS environ 에서 온 서버 변수를 뺀다(`exec env` 가 같은 pid 로 app 을 exec 한다). 호출자가 준 이름은 map 에 있으니
5909
+ // 남긴다. Windows(startArgv)·pipe 경로와 서버 변수가 없던 broker 는 그대로다.
5910
+ file: "src/launch/broker.ts",
5911
+ marker: "const dropped = [...brokerDroppedHostEnv]",
5912
+ anchor: ` const argv = [record.spec.application, ...record.spec.args];
5913
+ const command = \`exec \${argv.map(quoteShellArg).join(" ")}\`;`,
5914
+ patched: ` // HANSE: CUELO next server env stays out of POSIX PTY services. The native PTY layers its env
5915
+ // map over the OS environ, and the startup delete of process.env does not reach the OS environ
5916
+ // on Linux, so drop those names at exec. Names the caller set are in the map and stay.
5917
+ const dropped = [...brokerDroppedHostEnv].filter(name => !(name in options.env));
5918
+ const argv = [
5919
+ ...(dropped.length > 0 ? ["env", ...dropped.flatMap(name => ["-u", name])] : []),
5920
+ record.spec.application,
5921
+ ...record.spec.args,
5922
+ ];
5923
+ const command = \`exec \${argv.map(quoteShellArg).join(" ")}\`;`,
5924
+ },
5580
5925
  {
5581
5926
  // 2026-09-29 jevgrep 비교(.omp/jevgrep-comparison/SUMMARY.md §4): jfind 의 비밀 파일 제외는 이름을
5582
5927
  // 대소문자 구분으로 비교해, Windows(NTFS 대소문자 무시)에서 같은 파일인 `Credentials.json`·`ID_RSA`
@@ -5723,7 +6068,35 @@ function operationFromNative(op: string): Operation | undefined {
5723
6068
  file: "src/mnemopi/state.ts",
5724
6069
  marker: "// HANSE: recall line carries memory id",
5725
6070
  anchor: "\t\treturn `- ${content}${source}${date}`;\n",
5726
- patched: "\t\t// HANSE: recall line carries memory id\n\t\tconst memoryId = result.id ? ` (id: ${result.id})` : \"\";\n\t\treturn `- ${content}${source}${date}${memoryId}`;\n",
6071
+ patched: "\t\t// HANSE: recall line carries memory id\n\t\tconst memoryId = result.id ? ` (id: ${result.id})` : \"\";\n\t\treturn `- ${content}${source}${date}${recallProvenanceNote(result)}${memoryId}`;\n",
6072
+ legacyPatched: "\t\t// HANSE: recall line carries memory id\n\t\tconst memoryId = result.id ? ` (id: ${result.id})` : \"\";\n\t\treturn `- ${content}${source}${date}${memoryId}`;\n",
6073
+ },
6074
+ {
6075
+ // 위 항목의 짝. 범위·정정 표시 보조 함수와 정정 행이 있을 때만 붙는 한 줄 안내(상세는 memory://<id>).
6076
+ file: "src/mnemopi/state.ts",
6077
+ marker: "// HANSE: recall provenance note",
6078
+ anchor: "function formatRecallBlock(results: RecallResult[]): string {\n",
6079
+ patched: recallProvenanceBlock + "function formatRecallBlock(results: RecallResult[]): string {\n",
6080
+ },
6081
+ {
6082
+ file: "src/mnemopi/state.ts",
6083
+ marker: "${lines.join(\"\\n\\n\")}${recallRevisionFooter(results)}\\n</memories>",
6084
+ anchor: "${lines.join(\"\\n\\n\")}\\n</memories>`;\n",
6085
+ patched: "${lines.join(\"\\n\\n\")}${recallRevisionFooter(results)}\\n</memories>`;\n",
6086
+ },
6087
+ {
6088
+ // recall 도구 출력도 같은 표시를 쓴다.
6089
+ file: "src/mnemopi/state.ts",
6090
+ marker: "${source}${date}${recallProvenanceNote(result)}${confidence}",
6091
+ anchor: "\t\t\treturn `- ${result.content}${id}${source}${date}${confidence}`;\n\t\t});\n\t\treturn lines.join(\"\\n\\n\");\n",
6092
+ patched: "\t\t\treturn `- ${result.content}${id}${source}${date}${recallProvenanceNote(result)}${confidence}`;\n\t\t});\n\t\treturn lines.join(\"\\n\\n\") + recallRevisionFooter(results);\n",
6093
+ },
6094
+ {
6095
+ // 결과가 실제로 나온 bank 로 범위를 일시 표시한다(DB 저장 없음). 전역 bank 가 없는 구성은 추정하지 않는다.
6096
+ file: "src/mnemopi/state.ts",
6097
+ marker: "// HANSE: recall result carries its bank scope",
6098
+ anchor: "\t\t\t\t\tfor (const result of results) {\n\t\t\t\t\t\tmergeRecallResult(merged, byId, byContent, result);\n",
6099
+ patched: "\t\t\t\t\tfor (const result of results) {\n\t\t\t\t\t\t// HANSE: recall result carries its bank scope\n\t\t\t\t\t\tconst recallScope = this.scoped.global ? (target.bank === this.scoped.global.bank ? \"global\" : \"project\") : undefined;\n\t\t\t\t\t\tmergeRecallResult(merged, byId, byContent, { ...result, recallScope } as RecallResult);\n",
5727
6100
  },
5728
6101
  {
5729
6102
  file: "src/tools/learn.ts",
@@ -5744,42 +6117,12 @@ function operationFromNative(op: string): Operation | undefined {
5744
6117
  patched: '\t"topic?": type("string").describe("stable topic key; later lessons with this key replace the prior project lesson"),\n\t"scope?": type("\'project\' | \'global\'").describe(',
5745
6118
  },
5746
6119
  {
6120
+ // 출처 구분: 갱신해도 최초 session/cwd 는 남고 updated_* 가 최신 정정자다. legacyPatched 는 출처 구분 이전 적용본이다.
5747
6121
  file: "src/tools/learn.ts",
5748
- marker: "// HANSE: topic-key learn upsert",
6122
+ marker: "// HANSE: learn topic provenance keeps first and latest source",
5749
6123
  anchor: "export class LearnTool implements AgentTool<LearnSchema> {",
5750
- patched: `// HANSE: topic-key learn upsert
5751
- function upsertLearnTopic(
5752
- state: MnemopiSessionState,
5753
- params: LearnParams,
5754
- target: ReturnType<MnemopiSessionState["getScopedRetainTarget"]>,
5755
- ): { id: string; revision: number } {
5756
- const topic = params.topic!.normalize("NFKC").trim().toLowerCase();
5757
- if (!topic) throw new Error("Learn topic must not be blank.");
5758
- if (redactMemorySecrets(topic) !== topic) throw new Error("Learn topic contains a credential.");
5759
- const db = target.memory.conn;
5760
- const existing = db.query(
5761
- "SELECT id, metadata_json FROM working_memory WHERE json_extract(metadata_json, '$.topic_key') = ? ORDER BY timestamp DESC, id DESC LIMIT 1",
5762
- ).get(topic) as { id: string; metadata_json: string } | null;
5763
- const revision = existing ? (Number(JSON.parse(existing.metadata_json).revision) || 1) + 1 : 1;
5764
- if (existing) return db.transaction(() => {
5765
- const content = redactMemorySecrets(params.memory);
5766
- db.query("UPDATE working_memory SET content = ?, embed_text = NULL, timestamp = ?, metadata_json = ? WHERE id = ?").run(
5767
- content, new Date().toISOString(),
5768
- JSON.stringify({ ...JSON.parse(existing.metadata_json), context: params.context == null ? null : redactMemorySecrets(params.context), topic_key: topic, revision }),
5769
- existing.id,
5770
- );
5771
- db.query("DELETE FROM memory_embeddings WHERE memory_id = ?").run(existing.id);
5772
- return { id: existing.id, revision };
5773
- })();
5774
- const id = state.rememberScoped(params.memory, {
5775
- source: "coding-agent-learn", importance: 0.8,
5776
- metadata: { session_id: state.sessionId, cwd: state.session.sessionManager.getCwd(), context: params.context ?? null, tool: "learn", topic_key: topic, revision },
5777
- scope: "bank", extract: true, extractEntities: true, veracity: "tool", memoryType: "fact",
5778
- }, target);
5779
- return { id, revision };
5780
- }
5781
-
5782
- export class LearnTool implements AgentTool<LearnSchema> {`,
6124
+ legacyPatched: learnTopicUpsertLegacyBlock,
6125
+ patched: learnTopicUpsertBlock,
5783
6126
  },
5784
6127
  {
5785
6128
  // learn 은 rememberScoped 가 돌려준 기억 id 를 버리고 "Lesson stored." 만 알렸다. 교훈을 뒤에서
@@ -7240,6 +7583,10 @@ class El {
7240
7583
  // 모든 ref에 지문을 남기고, find()/ref()는 발급하는 요소에 지문을 남긴다.
7241
7584
  file: "src/tools/computer/worker.ts",
7242
7585
  marker: "return windowInputResult(action, this.id, options);",
7586
+ // 18.6.3 은 같은 클래스에 holdKeys·holdMouse·observe·menu·bringToCurrentSpace 를 넣고 move 에 pointerOptions() 를
7587
+ // 넘긴다. 결과가 같은 marker 를 가지므로 holdKeys 유무로 후보를 가른다. 새 메서드는 상류 그대로 두고, observe 만
7588
+ // ax() 와 같은 규칙(텍스트의 모든 ref 에 지문, goal probe 안에서는 출력 없음)을 따른다(ADAPT).
7589
+ excludes: "async holdKeys(",
7243
7590
  anchor: ` async click(x: number, y: number, options?: ClickOptions): Promise<void> {
7244
7591
  const context = this.#getContext();
7245
7592
  guardRun(context, "click");
@@ -7280,18 +7627,230 @@ class El {
7280
7627
  );
7281
7628
  }
7282
7629
 
7283
- async type(text: string, options?: InputOptions): Promise<void> {
7284
- const context = this.#getContext();
7285
- guardRun(context, "type");
7286
- await nativeCall(context.signal, () => this.#session.typeText(this.id, text, pointerOptions(options)));
7630
+ async type(text: string, options?: InputOptions): Promise<void> {
7631
+ const context = this.#getContext();
7632
+ guardRun(context, "type");
7633
+ await nativeCall(context.signal, () => this.#session.typeText(this.id, text, pointerOptions(options)));
7634
+ }
7635
+
7636
+ async press(chord: string | string[], options?: InputOptions): Promise<void> {
7637
+ const context = this.#getContext();
7638
+ guardRun(context, "press");
7639
+ await nativeCall(context.signal, () =>
7640
+ this.#session.keyChord(this.id, chordKeys(chord), pointerOptions(options)),
7641
+ );
7642
+ }
7643
+
7644
+ async raise(): Promise<void> {
7645
+ const context = this.#getContext();
7646
+ guardRun(context, "raise");
7647
+ await nativeCall(context.signal, () => this.#session.raiseWindow(this.id));
7648
+ }
7649
+
7650
+ async ax(options?: AxOptions): Promise<string> {
7651
+ const { signal } = this.#getContext();
7652
+ return (await nativeCall(signal, () => this.#session.axSnapshot(this.id, options))).text;
7653
+ }
7654
+
7655
+ async find(query: AxQuery): Promise<El[]> {
7656
+ const { signal } = this.#getContext();
7657
+ return (await nativeCall(signal, () => this.#session.axQuery(this.id, query))).map(
7658
+ node => new El(this.#session, this.#getContext, node),
7659
+ );
7660
+ }
7661
+
7662
+ async ref(ref: string): Promise<El> {
7663
+ const { signal } = this.#getContext();
7664
+ return new El(this.#session, this.#getContext, await nativeCall(signal, () => this.#session.axNode(ref)));
7665
+ }`,
7666
+ patched: ` async #input(action: string, options: InputOptions | undefined, call: () => Promise<void>): Promise<ComputerActionResult> {
7667
+ const context = this.#getContext();
7668
+ guardRun(context, action);
7669
+ try {
7670
+ await nativeCall(context.signal, call);
7671
+ } catch (error) {
7672
+ throw refuseAction(error, action, undefined, options?.takeover);
7673
+ }
7674
+ return windowInputResult(action, this.id, options);
7675
+ }
7676
+
7677
+ click(x: number, y: number, options?: ClickOptions): Promise<ComputerActionResult> {
7678
+ return this.#input("click", options, () => this.#session.click(this.id, x, y, pointerOptions(options)));
7679
+ }
7680
+
7681
+ doubleClick(x: number, y: number, options?: Omit<ClickOptions, "count">): Promise<ComputerActionResult> {
7682
+ return this.#input("doubleClick", options, () =>
7683
+ this.#session.click(this.id, x, y, pointerOptions({ ...options, count: 2 })),
7684
+ );
7685
+ }
7686
+
7687
+ move(x: number, y: number): Promise<ComputerActionResult> {
7688
+ return this.#input("move", undefined, () => this.#session.moveMouse(this.id, x, y));
7689
+ }
7690
+
7691
+ drag(points: Array<[number, number]>, options?: DragOptions): Promise<ComputerActionResult> {
7692
+ return this.#input("drag", options, () =>
7693
+ this.#session.drag(
7694
+ this.id,
7695
+ points.map(([x, y]) => ({ x, y })),
7696
+ pointerOptions(options),
7697
+ ),
7698
+ );
7699
+ }
7700
+
7701
+ scroll(x: number, y: number, options: ScrollOptions = {}): Promise<ComputerActionResult> {
7702
+ return this.#input("scroll", options, () =>
7703
+ this.#session.scroll(this.id, x, y, options.dx ?? 0, options.dy ?? 0, pointerOptions(options)),
7704
+ );
7705
+ }
7706
+
7707
+ type(text: string, options?: InputOptions): Promise<ComputerActionResult> {
7708
+ return this.#input("type", options, () => this.#session.typeText(this.id, text, pointerOptions(options)));
7709
+ }
7710
+
7711
+ press(chord: string | string[], options?: InputOptions): Promise<ComputerActionResult> {
7712
+ return this.#input("press", options, () =>
7713
+ this.#session.keyChord(this.id, chordKeys(chord), pointerOptions(options)),
7714
+ );
7715
+ }
7716
+
7717
+ raise(): Promise<ComputerActionResult> {
7718
+ return this.#input("raise", undefined, () => this.#session.raiseWindow(this.id));
7719
+ }
7720
+
7721
+ async ax(options?: AxOptions): Promise<string> {
7722
+ const { signal } = this.#getContext();
7723
+ const text = (await nativeCall(signal, () => this.#session.axSnapshot(this.id, options))).text;
7724
+ refBook(this.#session).noteSnapshot(this.id, text);
7725
+ return text;
7726
+ }
7727
+
7728
+ async find(query: AxQuery): Promise<El[]> {
7729
+ const { signal } = this.#getContext();
7730
+ const nodes = await nativeCall(signal, () => this.#session.axQuery(this.id, query));
7731
+ return issueElements(this.#session, this.#getContext, nodes, this.id);
7732
+ }
7733
+
7734
+ ref(ref: string): Promise<El> {
7735
+ return resolveElement(this.#session, this.#getContext, ref, this.id);
7736
+ }`,
7737
+ alternates: [{
7738
+ file: "src/tools/computer/worker.ts",
7739
+ requires: "async holdKeys(",
7740
+ marker: "return windowInputResult(action, this.id, options);",
7741
+ anchor: ` async click(x: number, y: number, options?: ClickOptions): Promise<void> {
7742
+ const context = this.#getContext();
7743
+ guardRun(context, "click");
7744
+ await nativeCall(context.signal, () => this.#session.click(this.id, x, y, pointerOptions(options)));
7745
+ }
7746
+
7747
+ async doubleClick(x: number, y: number, options?: Omit<ClickOptions, "count">): Promise<void> {
7748
+ const context = this.#getContext();
7749
+ guardRun(context, "doubleClick");
7750
+ await nativeCall(context.signal, () =>
7751
+ this.#session.click(this.id, x, y, pointerOptions({ ...options, count: 2 })),
7752
+ );
7753
+ }
7754
+
7755
+ async move(x: number, y: number): Promise<void> {
7756
+ const context = this.#getContext();
7757
+ guardRun(context, "move");
7758
+ await nativeCall(context.signal, () => this.#session.moveMouse(this.id, x, y, pointerOptions()));
7759
+ }
7760
+
7761
+ async drag(points: Array<[number, number]>, options?: DragOptions): Promise<void> {
7762
+ const context = this.#getContext();
7763
+ guardRun(context, "drag");
7764
+ await nativeCall(context.signal, () =>
7765
+ this.#session.drag(
7766
+ this.id,
7767
+ points.map(([x, y]) => ({ x, y })),
7768
+ pointerOptions(options),
7769
+ ),
7770
+ );
7771
+ }
7772
+
7773
+ async scroll(x: number, y: number, options: ScrollOptions = {}): Promise<void> {
7774
+ const context = this.#getContext();
7775
+ guardRun(context, "scroll");
7776
+ await nativeCall(context.signal, () =>
7777
+ this.#session.scroll(this.id, x, y, options.dx ?? 0, options.dy ?? 0, pointerOptions(options)),
7778
+ );
7779
+ }
7780
+
7781
+ async type(text: string, options?: InputOptions): Promise<void> {
7782
+ const context = this.#getContext();
7783
+ guardRun(context, "type");
7784
+ await nativeCall(context.signal, () => this.#session.typeText(this.id, text, pointerOptions(options)));
7785
+ }
7786
+
7787
+ async press(chord: string | string[], options?: InputOptions): Promise<void> {
7788
+ const context = this.#getContext();
7789
+ guardRun(context, "press");
7790
+ await nativeCall(context.signal, () =>
7791
+ this.#session.keyChord(this.id, chordKeys(chord), pointerOptions(options)),
7792
+ );
7793
+ }
7794
+
7795
+ async holdKeys(keys: string[], options: HoldOptions): Promise<void> {
7796
+ const context = this.#getContext();
7797
+ guardRun(context, "holdKeys");
7798
+ validateHold(options);
7799
+ validateKeys(keys, "keys");
7800
+ await nativeCall(context.signal, () => this.#session.holdKeys(this.id, keys, options));
7801
+ }
7802
+
7803
+ async holdMouse(x: number, y: number, options: HoldMouseOptions): Promise<void> {
7804
+ const context = this.#getContext();
7805
+ guardRun(context, "holdMouse");
7806
+ validateHold(options);
7807
+ if (options.keys !== undefined) validateKeys(options.keys, "keys");
7808
+ await nativeCall(context.signal, () => this.#session.holdMouse(this.id, x, y, options));
7809
+ }
7810
+
7811
+ async observe(options?: ScreenshotOptions & AxOptions): Promise<ObservationResult> {
7812
+ const context = this.#getContext();
7813
+ const result = await nativeCall(context.signal, () =>
7814
+ this.#session.observe(
7815
+ this.id,
7816
+ {
7817
+ maxWidth: context.snapshot.captureMaxWidth,
7818
+ maxHeight: context.snapshot.captureMaxHeight,
7819
+ },
7820
+ options && { all: options.all, maxDepth: options.maxDepth },
7821
+ ),
7822
+ );
7823
+ const screenshot = await emitScreenshot(context, result.capture, options);
7824
+ if (!options?.silent) context.output.push({ type: "text", text: result.accessibility.text });
7825
+ return {
7826
+ ...screenshot,
7827
+ ax: result.accessibility.text,
7828
+ nodeCount: result.accessibility.nodeCount,
7829
+ truncated: result.accessibility.truncated,
7830
+ };
7831
+ }
7832
+
7833
+ get menu() {
7834
+ return {
7835
+ items: async (path?: string | string[]): Promise<MenuItem[]> => {
7836
+ const context = this.#getContext();
7837
+ const segments = path === undefined ? undefined : typeof path === "string" ? [path] : path;
7838
+ if (segments !== undefined) validateKeys(segments, "menu path", { allowEmpty: true });
7839
+ return await nativeCall(context.signal, () => this.#session.menuItems(this.id, segments));
7840
+ },
7841
+ select: async (path: string[]): Promise<void> => {
7842
+ const context = this.#getContext();
7843
+ guardRun(context, "menu.select");
7844
+ validateKeys(path, "menu path");
7845
+ await nativeCall(context.signal, () => this.#session.menuSelect(this.id, path));
7846
+ },
7847
+ };
7287
7848
  }
7288
7849
 
7289
- async press(chord: string | string[], options?: InputOptions): Promise<void> {
7850
+ async bringToCurrentSpace(): Promise<void> {
7290
7851
  const context = this.#getContext();
7291
- guardRun(context, "press");
7292
- await nativeCall(context.signal, () =>
7293
- this.#session.keyChord(this.id, chordKeys(chord), pointerOptions(options)),
7294
- );
7852
+ guardRun(context, "bringToCurrentSpace");
7853
+ await nativeCall(context.signal, () => this.#session.bringToCurrentSpace(this.id));
7295
7854
  }
7296
7855
 
7297
7856
  async raise(): Promise<void> {
@@ -7316,7 +7875,7 @@ class El {
7316
7875
  const { signal } = this.#getContext();
7317
7876
  return new El(this.#session, this.#getContext, await nativeCall(signal, () => this.#session.axNode(ref)));
7318
7877
  }`,
7319
- patched: ` async #input(action: string, options: InputOptions | undefined, call: () => Promise<void>): Promise<ComputerActionResult> {
7878
+ patched: ` async #input(action: string, options: InputOptions | undefined, call: () => Promise<void>): Promise<ComputerActionResult> {
7320
7879
  const context = this.#getContext();
7321
7880
  guardRun(context, action);
7322
7881
  try {
@@ -7338,7 +7897,7 @@ class El {
7338
7897
  }
7339
7898
 
7340
7899
  move(x: number, y: number): Promise<ComputerActionResult> {
7341
- return this.#input("move", undefined, () => this.#session.moveMouse(this.id, x, y));
7900
+ return this.#input("move", undefined, () => this.#session.moveMouse(this.id, x, y, pointerOptions()));
7342
7901
  }
7343
7902
 
7344
7903
  drag(points: Array<[number, number]>, options?: DragOptions): Promise<ComputerActionResult> {
@@ -7367,6 +7926,69 @@ class El {
7367
7926
  );
7368
7927
  }
7369
7928
 
7929
+ async holdKeys(keys: string[], options: HoldOptions): Promise<void> {
7930
+ const context = this.#getContext();
7931
+ guardRun(context, "holdKeys");
7932
+ validateHold(options);
7933
+ validateKeys(keys, "keys");
7934
+ await nativeCall(context.signal, () => this.#session.holdKeys(this.id, keys, options));
7935
+ }
7936
+
7937
+ async holdMouse(x: number, y: number, options: HoldMouseOptions): Promise<void> {
7938
+ const context = this.#getContext();
7939
+ guardRun(context, "holdMouse");
7940
+ validateHold(options);
7941
+ if (options.keys !== undefined) validateKeys(options.keys, "keys");
7942
+ await nativeCall(context.signal, () => this.#session.holdMouse(this.id, x, y, options));
7943
+ }
7944
+
7945
+ async observe(options?: ScreenshotOptions & AxOptions): Promise<ObservationResult> {
7946
+ const context = this.#getContext();
7947
+ const result = await nativeCall(context.signal, () =>
7948
+ this.#session.observe(
7949
+ this.id,
7950
+ {
7951
+ maxWidth: context.snapshot.captureMaxWidth,
7952
+ maxHeight: context.snapshot.captureMaxHeight,
7953
+ },
7954
+ options && { all: options.all, maxDepth: options.maxDepth },
7955
+ ),
7956
+ );
7957
+ const screenshot = await emitScreenshot(context, result.capture, options);
7958
+ // Same tree text as ax(): every ref it shows gets a fingerprint, and a goal probe stays silent.
7959
+ refBook(this.#session).noteSnapshot(this.id, result.accessibility.text);
7960
+ if (!options?.silent && !context.goalProbe) context.output.push({ type: "text", text: result.accessibility.text });
7961
+ return {
7962
+ ...screenshot,
7963
+ ax: result.accessibility.text,
7964
+ nodeCount: result.accessibility.nodeCount,
7965
+ truncated: result.accessibility.truncated,
7966
+ };
7967
+ }
7968
+
7969
+ get menu() {
7970
+ return {
7971
+ items: async (path?: string | string[]): Promise<MenuItem[]> => {
7972
+ const context = this.#getContext();
7973
+ const segments = path === undefined ? undefined : typeof path === "string" ? [path] : path;
7974
+ if (segments !== undefined) validateKeys(segments, "menu path", { allowEmpty: true });
7975
+ return await nativeCall(context.signal, () => this.#session.menuItems(this.id, segments));
7976
+ },
7977
+ select: async (path: string[]): Promise<void> => {
7978
+ const context = this.#getContext();
7979
+ guardRun(context, "menu.select");
7980
+ validateKeys(path, "menu path");
7981
+ await nativeCall(context.signal, () => this.#session.menuSelect(this.id, path));
7982
+ },
7983
+ };
7984
+ }
7985
+
7986
+ async bringToCurrentSpace(): Promise<void> {
7987
+ const context = this.#getContext();
7988
+ guardRun(context, "bringToCurrentSpace");
7989
+ await nativeCall(context.signal, () => this.#session.bringToCurrentSpace(this.id));
7990
+ }
7991
+
7370
7992
  raise(): Promise<ComputerActionResult> {
7371
7993
  return this.#input("raise", undefined, () => this.#session.raiseWindow(this.id));
7372
7994
  }
@@ -7387,6 +8009,7 @@ class El {
7387
8009
  ref(ref: string): Promise<El> {
7388
8010
  return resolveElement(this.#session, this.#getContext, ref, this.id);
7389
8011
  }`,
8012
+ }],
7390
8013
  },
7391
8014
  {
7392
8015
  // Python·JS 직접 헬퍼는 win.ref(ref)도 매번 desktop.ref(ref)로 푼다. 재획득은 이 경로에서도 일어나야 한다.
@@ -7402,6 +8025,9 @@ class El {
7402
8025
  // 모델이 보는 타입: 행동 결과·거절 필드와 재획득 규칙을 적는다.
7403
8026
  file: "src/tools/computer/declarations.d.ts",
7404
8027
  marker: "interface ComputerActionResult {",
8028
+ // 18.6.3 은 ComputerInputTarget 에 zoom·holdKeys·holdMouse 를 넣고 좌표 설명을 "full screenshot" 기준으로 바꿨다.
8029
+ // 새 멤버와 설명은 상류 그대로 두고, 우리 결과 타입이 걸린 멤버만 바꾼다. holdKeys 유무로 후보를 가른다(ADAPT).
8030
+ excludes: "holdKeys(keys: string[]",
7405
8031
  anchor: `/** Live accessibility element resolved from a snapshot ref; expired refs throw \`StaleRef\`. */
7406
8032
  interface ComputerElement {
7407
8033
  /** Snapshot ref tag, e.g. \`e5\`. */
@@ -7511,6 +8137,122 @@ interface ComputerInputTarget {
7511
8137
  /** Key chord such as \`"cmd+shift+p"\` or \`["cmd", "shift", "p"]\`. */
7512
8138
  press(chord: string | string[], options?: ComputerInputOptions): Promise<ComputerActionResult>;
7513
8139
  }`,
8140
+ alternates: [{
8141
+ file: "src/tools/computer/declarations.d.ts",
8142
+ requires: "holdKeys(keys: string[]",
8143
+ marker: "interface ComputerActionResult {",
8144
+ anchor: `/** Live accessibility element resolved from a snapshot ref; expired refs throw \`StaleRef\`. */
8145
+ interface ComputerElement {
8146
+ /** Snapshot ref tag, e.g. \`e5\`. */
8147
+ readonly ref: string;
8148
+ readonly role: string;
8149
+ readonly nativeRole: string;
8150
+ readonly title?: string;
8151
+ readonly description?: string;
8152
+ readonly enabled: boolean;
8153
+ readonly focused: boolean;
8154
+ readonly childCount: number;
8155
+ value(): Promise<string | undefined>;
8156
+ setValue(value: string): Promise<void>;
8157
+ /** Bounds in global desktop coordinates, or null when the element has none. */
8158
+ bounds(): Promise<ComputerBounds | null>;
8159
+ attributes(): Promise<Record<string, string>>;
8160
+ actions(): Promise<string[]>;
8161
+ perform(action: string): Promise<void>;
8162
+ /** Perform the element's native press action; needs no screenshot. */
8163
+ press(): Promise<void>;
8164
+ /** Click the element's center with native input. */
8165
+ click(options?: ComputerInputOptions): Promise<void>;
8166
+ focus(): Promise<void>;
8167
+ parent(): Promise<ComputerElement | null>;
8168
+ children(): Promise<ComputerElement[]>;
8169
+ }
8170
+
8171
+ /** Native input helpers shared by the desktop root and window handles; \`x\`/\`y\` are pixels in the most recent full screenshot of the same target, never zoom pixels. */
8172
+ interface ComputerInputTarget {
8173
+ screenshot(options?: ComputerScreenshotOptions): Promise<ComputerScreenshotResult>;
8174
+ /** Display a detailed region without replacing the input frame. Use base full screenshot coordinates for subsequent input, not zoom pixels. */
8175
+ zoom(region: CaptureRegion, options?: ComputerScreenshotOptions): Promise<ComputerScreenshotResult>;
8176
+ click(x: number, y: number, options?: ComputerClickOptions): Promise<void>;
8177
+ doubleClick(x: number, y: number, options?: Omit<ComputerClickOptions, "count">): Promise<void>;
8178
+ move(x: number, y: number): Promise<void>;
8179
+ drag(points: Array<[number, number]>, options?: ComputerDragOptions): Promise<void>;
8180
+ scroll(x: number, y: number, options?: ComputerScrollOptions): Promise<void>;
8181
+ type(text: string, options?: ComputerInputOptions): Promise<void>;
8182
+ /** Key chord such as \`"cmd+shift+p"\` or \`["cmd", "shift", "p"]\`. */
8183
+ press(chord: string | string[], options?: ComputerInputOptions): Promise<void>;`,
8184
+ patched: `/** Result of an input or element action. Delivery is not proof of effect. */
8185
+ interface ComputerActionResult {
8186
+ action: string;
8187
+ /** \`verified\`: an AX readback shows the change. \`unverified\`: delivered, effect not observed. \`suspected_noop\`: the readback contradicts the intended change. */
8188
+ status: "verified" | "unverified" | "suspected_noop";
8189
+ /** \`reobserve\`: screenshot or AX before relying on the effect. \`escalate\`: follow \`escalation\`. */
8190
+ suggestedNext: "continue" | "reobserve" | "escalate";
8191
+ escalation?: { target: "pixel" | "takeover"; reason: string };
8192
+ /** Readback the status rests on. */
8193
+ evidence?: string;
8194
+ ref?: string;
8195
+ /** Present when the action ran on an element reacquired from a stale ref. */
8196
+ reacquired?: { from: string; to: string; matchedBy: string };
8197
+ }
8198
+
8199
+ /** Attached to a failed action's error as \`error.computerAction\` (visible inside \`computer.run\`); the error message is unchanged. */
8200
+ interface ComputerActionRefusal {
8201
+ action: string;
8202
+ status: "refused";
8203
+ suggestedNext: "reobserve" | "escalate";
8204
+ escalation?: { target: "takeover"; reason: string };
8205
+ ref?: string;
8206
+ /** Why a stale ref was not reacquired, with up to five candidates. */
8207
+ reacquire?: {
8208
+ outcome: "no-fingerprint" | "no-window" | "no-candidate" | "parent-mismatch" | "ambiguous";
8209
+ candidates: Array<{ ref: string; role: string; title?: string; bounds?: ComputerBounds }>;
8210
+ };
8211
+ }
8212
+
8213
+ /** Live accessibility element resolved from a snapshot ref. A stale ref is reacquired only when its fingerprint matches exactly one element; otherwise it throws \`StaleRef\`. */
8214
+ interface ComputerElement {
8215
+ /** Snapshot ref tag, e.g. \`e5\`; the new ref after a reacquisition. */
8216
+ readonly ref: string;
8217
+ /** Stale ref this element was reacquired from. */
8218
+ readonly reacquiredFrom?: string;
8219
+ readonly role: string;
8220
+ readonly nativeRole: string;
8221
+ readonly title?: string;
8222
+ readonly description?: string;
8223
+ readonly enabled: boolean;
8224
+ readonly focused: boolean;
8225
+ readonly childCount: number;
8226
+ value(): Promise<string | undefined>;
8227
+ setValue(value: string): Promise<ComputerActionResult>;
8228
+ /** Bounds in global desktop coordinates, or null when the element has none. */
8229
+ bounds(): Promise<ComputerBounds | null>;
8230
+ attributes(): Promise<Record<string, string>>;
8231
+ actions(): Promise<string[]>;
8232
+ perform(action: string): Promise<ComputerActionResult>;
8233
+ /** Perform the element's native press action; needs no screenshot. */
8234
+ press(): Promise<ComputerActionResult>;
8235
+ /** Click the element's center with native input. */
8236
+ click(options?: ComputerInputOptions): Promise<ComputerActionResult>;
8237
+ focus(): Promise<ComputerActionResult>;
8238
+ parent(): Promise<ComputerElement | null>;
8239
+ children(): Promise<ComputerElement[]>;
8240
+ }
8241
+
8242
+ /** Native input helpers shared by the desktop root and window handles; \`x\`/\`y\` are pixels in the most recent full screenshot of the same target, never zoom pixels. Pointer and keyboard action results are always \`unverified\`. */
8243
+ interface ComputerInputTarget {
8244
+ screenshot(options?: ComputerScreenshotOptions): Promise<ComputerScreenshotResult>;
8245
+ /** Display a detailed region without replacing the input frame. Use base full screenshot coordinates for subsequent input, not zoom pixels. */
8246
+ zoom(region: CaptureRegion, options?: ComputerScreenshotOptions): Promise<ComputerScreenshotResult>;
8247
+ click(x: number, y: number, options?: ComputerClickOptions): Promise<ComputerActionResult>;
8248
+ doubleClick(x: number, y: number, options?: Omit<ComputerClickOptions, "count">): Promise<ComputerActionResult>;
8249
+ move(x: number, y: number): Promise<ComputerActionResult>;
8250
+ drag(points: Array<[number, number]>, options?: ComputerDragOptions): Promise<ComputerActionResult>;
8251
+ scroll(x: number, y: number, options?: ComputerScrollOptions): Promise<ComputerActionResult>;
8252
+ type(text: string, options?: ComputerInputOptions): Promise<ComputerActionResult>;
8253
+ /** Key chord such as \`"cmd+shift+p"\` or \`["cmd", "shift", "p"]\`. */
8254
+ press(chord: string | string[], options?: ComputerInputOptions): Promise<ComputerActionResult>;`,
8255
+ }],
7514
8256
  },
7515
8257
  {
7516
8258
  file: "src/tools/computer/declarations.d.ts",
@@ -7524,6 +8266,15 @@ interface ComputerInputTarget {
7524
8266
  marker: "Actions return `{ action, status, suggestedNext",
7525
8267
  anchor: "- Each window `.ax()` starts a ref generation. Current/previous snapshot refs remain valid; older refs throw `StaleRef`. Re-snapshot; NEVER guess.\n",
7526
8268
  patched: "- Each window `.ax()` starts a ref generation. Current/previous snapshot refs remain valid. An older ref is reacquired only when its saved fingerprint (role, name, AutomationId, named parents, position) matches exactly one element; the element's `ref` changes and the action result carries `reacquired`. Otherwise it throws `StaleRef` naming why and the candidates: re-snapshot; NEVER guess.\n- Actions return `{ action, status, suggestedNext, evidence?, escalation?, reacquired? }`. `verified` rests on an AX readback; `unverified` (all pointer/keyboard input) means delivered but unobserved: `reobserve` before relying on it; `suspected_noop` → follow `escalation`. Inside `computer.run`, a thrown action error carries `error.computerAction`.\n",
8269
+ // 18.6.3(#14485)은 ref 가 role·label 이 바뀔 때까지 유지되고 마지막 두 `.ax()` 에 없는 요소의 ref 만 StaleRef 라고
8270
+ // 고쳐 적었다. 그 규칙은 상류 그대로 두고, StaleRef 가 날 때의 지문 재획득과 결과 필드 읽는 법을 잇는다. 결과를
8271
+ // 돌려주는 helper 는 상류가 새로 넣은 hold·menu 와 구별해 이름으로 적는다(ADAPT).
8272
+ alternates: [{
8273
+ file: "src/prompts/tools/computer.md",
8274
+ marker: "snapshots is reacquired only when its saved fingerprint",
8275
+ anchor: "- An element keeps its `[ref=eN]` across `.ax()`/`find()` reads until its role or label changes; a ref whose element is missing from the window's last two `.ax()` snapshots throws `StaleRef`. Re-snapshot; NEVER guess.\n",
8276
+ patched: "- An element keeps its `[ref=eN]` across `.ax()`/`find()` reads until its role or label changes. A ref whose element is missing from the window's last two `.ax()` snapshots is reacquired only when its saved fingerprint (role, name, AutomationId, named parents, position) matches exactly one element; the element's `ref` changes and the action result carries `reacquired`. Otherwise it throws `StaleRef` naming why and the candidates: re-snapshot; NEVER guess.\n- Element actions and window/display `click`, `doubleClick`, `move`, `drag`, `scroll`, `type`, `press`, `raise` return `{ action, status, suggestedNext, evidence?, escalation?, reacquired? }`. `verified` rests on an AX readback; `unverified` (all pointer/keyboard input) means delivered but unobserved: `reobserve` before relying on it; `suspected_noop` → follow `escalation`. Inside `computer.run`, a thrown action error carries `error.computerAction`.\n",
8277
+ }],
7527
8278
  },
7528
8279
  {
7529
8280
  // 2026-09-30 computer 목표 대기(사용자 요청 0.6.6): 행동은 사용자 코드에서 한 번, 목표는 wait(predicate)가 로컬 읽기로만 확인한다.
@@ -7538,6 +8289,9 @@ import * as postmortem from "@oh-my-pi/pi-utils/postmortem";`,
7538
8289
  {
7539
8290
  file: "src/tools/computer/worker.ts",
7540
8291
  marker: " goalProbe?: boolean;",
8292
+ // 18.6.3 은 run context 에 confirmControl(전경 제어 승인 요청)을 넣었다. probe 는 context 를 펼쳐 복제하므로 그 필드도
8293
+ // 따라가고, control.acquire 는 guardRun 에서 probe 거절된다. 두 판 결과가 같은 marker 를 가지므로 그 필드로 가른다(ADAPT).
8294
+ excludes: "confirmControl(reason: string): Promise<boolean>;",
7541
8295
  anchor: `interface ComputerRunContext {
7542
8296
  signal: AbortSignal;
7543
8297
  readOnly: boolean;
@@ -7564,6 +8318,39 @@ function goalProbeRefusal(method: string): ToolError {
7564
8318
  \`wait(predicate) probe cannot run '\${method}': probes only read desktop state (windows, ax, find, value); act once before wait()\`,
7565
8319
  );
7566
8320
  }`,
8321
+ alternates: [{
8322
+ file: "src/tools/computer/worker.ts",
8323
+ requires: "confirmControl(reason: string): Promise<boolean>;",
8324
+ marker: " goalProbe?: boolean;",
8325
+ anchor: `interface ComputerRunContext {
8326
+ signal: AbortSignal;
8327
+ readOnly: boolean;
8328
+ snapshot: ComputerSessionSnapshot;
8329
+ output: RunOutput;
8330
+ confirmControl(reason: string): Promise<boolean>;
8331
+ screenshots: ComputerScreenshot[];
8332
+ }`,
8333
+ patched: `interface ComputerRunContext {
8334
+ signal: AbortSignal;
8335
+ readOnly: boolean;
8336
+ /** Set while a \`wait(predicate)\` probe runs; \`signal\` then also ends at the wait's deadline and when it settles. */
8337
+ goalProbe?: boolean;
8338
+ snapshot: ComputerSessionSnapshot;
8339
+ output: RunOutput;
8340
+ confirmControl(reason: string): Promise<boolean>;
8341
+ screenshots: ComputerScreenshot[];
8342
+ }
8343
+
8344
+ /** Pauses between goal probes when \`interval\` is not given: Playwright's \`pollAgainstDeadline\` default; the last one repeats. */
8345
+ const GOAL_POLL_INTERVALS = [100, 250, 500, 1000] as const;
8346
+
8347
+ /** A goal probe only reads: desktop input, clipboard writes, and tool-bridge calls would repeat on every probe. */
8348
+ function goalProbeRefusal(method: string): ToolError {
8349
+ return new ToolError(
8350
+ \`wait(predicate) probe cannot run '\${method}': probes only read desktop state (windows, ax, find, value); act once before wait()\`,
8351
+ );
8352
+ }`,
8353
+ }],
7567
8354
  },
7568
8355
  {
7569
8356
  file: "src/tools/computer/worker.ts",
@@ -7703,8 +8490,18 @@ function goalProbeRefusal(method: string): ToolError {
7703
8490
  // 모델 지시: 행동은 한 번, 목표는 wait(predicate)로 읽고, 미달이면 다시 누르지 않는다.
7704
8491
  file: "src/prompts/tools/computer.md",
7705
8492
  marker: "- Goal after an action: act ONCE",
8493
+ // 18.6.3 은 같은 줄 끝에 "Group predictable actions …" 문장을 붙였다. 그 문장 뒤에 같은 지시를 잇는다. 두 판 결과가
8494
+ // 같은 marker 를 가지므로 그 문장 유무로 후보를 가른다(ADAPT).
8495
+ excludes: "Group predictable actions and their verification in one run",
7706
8496
  anchor: "Plain data, functions, and `RegExp` values are supported in `args`.\n",
7707
8497
  patched: "Plain data, functions, and `RegExp` values are supported in `args`.\n- Goal after an action: act ONCE, then `await wait(async () => …read…, { timeout })` for the row, text, or value the user asked for; it resolves with that value. Inside the predicate, desktop input, `clipboard.write`, and tool calls throw and screenshots stay silent; probes back off 100→1000 ms, and a late read cannot continue past the deadline. The predicate is still unsandboxed Bun/Node code. On `wait(predicate) timed out` NEVER re-click, take over, or switch windows: reobserve and report. A button state change alone does not prove a save.\n",
8498
+ alternates: [{
8499
+ file: "src/prompts/tools/computer.md",
8500
+ requires: "Group predictable actions and their verification in one run",
8501
+ marker: "- Goal after an action: act ONCE",
8502
+ anchor: "Plain data, functions, and `RegExp` values are supported in `args`. Group predictable actions and their verification in one run; stop and inspect when the outcome is uncertain.\n",
8503
+ patched: "Plain data, functions, and `RegExp` values are supported in `args`. Group predictable actions and their verification in one run; stop and inspect when the outcome is uncertain.\n- Goal after an action: act ONCE, then `await wait(async () => …read…, { timeout })` for the row, text, or value the user asked for; it resolves with that value. Inside the predicate, desktop input, `clipboard.write`, and tool calls throw and screenshots stay silent; probes back off 100→1000 ms, and a late read cannot continue past the deadline. The predicate is still unsandboxed Bun/Node code. On `wait(predicate) timed out` NEVER re-click, take over, or switch windows: reobserve and report. A button state change alone does not prove a save.\n",
8504
+ }],
7708
8505
  },
7709
8506
  {
7710
8507
  file: "src/prompts/tools/computer.md",
@@ -7712,6 +8509,287 @@ function goalProbeRefusal(method: string): ToolError {
7712
8509
  anchor: "\treturn await target.ax();\n}, { timeout: 30 });\n",
7713
8510
  patched: "\treturn await target.ax();\n}, { timeout: 30 });\nawait computer.run(async ({ desktop, wait }) => {\n\tconst orders = await desktop.window({ title: \"Orders\" });\n\tconst [save] = await orders.find({ role: \"button\", title: \"Save\" });\n\tawait save.press();\n\treturn await wait(async () => (await orders.find({ title: \"Saved row 1\" }))[0]?.title, { timeout: 5000 });\n}, { timeout: 30 });\n",
7714
8511
  },
8512
+ {
8513
+ // 2026-10-06 WSL 단일 CUELO의 Windows 화면 조작. upstream computer worker는 같은 프로세스의 Bun Worker라
8514
+ // Linux CUELO는 linux-x64 addon(WSLg)만 본다. Windows helper exe(Tools/CUELO_Setup/wsl/windows-computer)를
8515
+ // interop 자식으로 띄우고 stdin/stdout으로 아래 frame을 주고받는다. 포트·리스너 없음.
8516
+ file: "src/tools/computer/protocol.ts",
8517
+ marker: `import { deserialize, serialize } from "bun:jsc";`,
8518
+ excludes: "CaptureRegion, DesktopCapabilities",
8519
+ anchor: `import type { ImageContent, TextContent } from "@oh-my-pi/pi-ai";
8520
+ import type { DesktopCapabilities } from "@oh-my-pi/pi-natives";`,
8521
+ patched: `import { deserialize, serialize } from "bun:jsc";
8522
+ import type { ImageContent, TextContent } from "@oh-my-pi/pi-ai";
8523
+ import type { DesktopCapabilities } from "@oh-my-pi/pi-natives";`,
8524
+ // 18.6.3 은 같은 import 에 CaptureRegion 을 더했다(zoom). 프레임 직렬화 import 는 그대로 앞에 둔다. 두 판 결과가 같은
8525
+ // marker 를 가지므로 그 import 이름으로 후보를 가른다(ADAPT).
8526
+ alternates: [{
8527
+ file: "src/tools/computer/protocol.ts",
8528
+ requires: "CaptureRegion, DesktopCapabilities",
8529
+ marker: `import { deserialize, serialize } from "bun:jsc";`,
8530
+ anchor: `import type { ImageContent, TextContent } from "@oh-my-pi/pi-ai";
8531
+ import type { CaptureRegion, DesktopCapabilities } from "@oh-my-pi/pi-natives";`,
8532
+ patched: `import { deserialize, serialize } from "bun:jsc";
8533
+ import type { ImageContent, TextContent } from "@oh-my-pi/pi-ai";
8534
+ import type { CaptureRegion, DesktopCapabilities } from "@oh-my-pi/pi-natives";`,
8535
+ }],
8536
+ },
8537
+ {
8538
+ // frame: "CMP1" magic + big-endian u32 길이 + Bun structured clone 본문. Worker postMessage와 같은 값 의미.
8539
+ // 잘못된 magic·읽을 수 없는 본문은 throw해 읽는 쪽이 fail closed 한다. 조각난 frame은 완성될 때 한 번만 복사한다.
8540
+ file: "src/tools/computer/protocol.ts",
8541
+ marker: "export class ComputerFrameReader {",
8542
+ anchor: `/** Transport used by the worker core in Bun workers and tests. */
8543
+ export interface ComputerWorkerTransport {
8544
+ send(message: ComputerWorkerOutbound, transfer?: Bun.Transferable[]): void;
8545
+ onMessage(handler: (message: ComputerWorkerInbound) => void): () => void;
8546
+ close(): void;
8547
+ }`,
8548
+ patched: `/** Transport used by the worker core in Bun workers and tests. */
8549
+ export interface ComputerWorkerTransport {
8550
+ send(message: ComputerWorkerOutbound, transfer?: Bun.Transferable[]): void;
8551
+ onMessage(handler: (message: ComputerWorkerInbound) => void): () => void;
8552
+ close(): void;
8553
+ }
8554
+
8555
+ /**
8556
+ * CUELO: stdio framing for a computer worker hosted in another process (a WSL CUELO driving the
8557
+ * Windows host executable through interop). Each frame is the "CMP1" magic, a big-endian u32 body
8558
+ * length, and a Bun structured-clone body, so values cross exactly as through Worker postMessage.
8559
+ * A wrong magic or an unreadable body throws: the stream is corrupt and the reader fails closed
8560
+ * instead of guessing where the next frame starts.
8561
+ */
8562
+ const COMPUTER_FRAME_MAGIC = 0x434d5031;
8563
+ const COMPUTER_FRAME_HEADER_BYTES = 8;
8564
+
8565
+ /** Encodes one worker message as a stdio frame; a non-cloneable message throws like postMessage. */
8566
+ export function encodeComputerFrame(message: ComputerWorkerInbound | ComputerWorkerOutbound): Uint8Array {
8567
+ const body = new Uint8Array(serialize(message));
8568
+ const frame = new Uint8Array(COMPUTER_FRAME_HEADER_BYTES + body.byteLength);
8569
+ const view = new DataView(frame.buffer);
8570
+ view.setUint32(0, COMPUTER_FRAME_MAGIC);
8571
+ view.setUint32(4, body.byteLength);
8572
+ frame.set(body, COMPUTER_FRAME_HEADER_BYTES);
8573
+ return frame;
8574
+ }
8575
+
8576
+ /** Reassembles frames from stdio chunks. After a throw the stream is corrupt; drop the reader. */
8577
+ export class ComputerFrameReader {
8578
+ #chunks: Uint8Array[] = [];
8579
+ #buffered = 0;
8580
+
8581
+ /** Adds one chunk and returns every message it completes, in order. */
8582
+ push(chunk: Uint8Array): unknown[] {
8583
+ this.#chunks.push(chunk);
8584
+ this.#buffered += chunk.byteLength;
8585
+ const messages: unknown[] = [];
8586
+ while (this.#buffered >= COMPUTER_FRAME_HEADER_BYTES) {
8587
+ const header = this.#read(COMPUTER_FRAME_HEADER_BYTES);
8588
+ const view = new DataView(header.buffer, header.byteOffset, COMPUTER_FRAME_HEADER_BYTES);
8589
+ if (view.getUint32(0) !== COMPUTER_FRAME_MAGIC) throw new Error("computer frame stream is corrupt: bad magic");
8590
+ const length = view.getUint32(4);
8591
+ if (this.#buffered < COMPUTER_FRAME_HEADER_BYTES + length) break;
8592
+ const frame = this.#read(COMPUTER_FRAME_HEADER_BYTES + length);
8593
+ this.#drop(COMPUTER_FRAME_HEADER_BYTES + length);
8594
+ messages.push(deserialize(frame.subarray(COMPUTER_FRAME_HEADER_BYTES)));
8595
+ }
8596
+ return messages;
8597
+ }
8598
+
8599
+ /** The first \`size\` buffered bytes: a view when one chunk holds them, else one copy. */
8600
+ #read(size: number): Uint8Array {
8601
+ const first = this.#chunks[0]!;
8602
+ if (first.byteLength >= size) return first.subarray(0, size);
8603
+ const bytes = new Uint8Array(size);
8604
+ let filled = 0;
8605
+ for (const chunk of this.#chunks) {
8606
+ const take = Math.min(chunk.byteLength, size - filled);
8607
+ bytes.set(chunk.subarray(0, take), filled);
8608
+ filled += take;
8609
+ if (filled === size) break;
8610
+ }
8611
+ return bytes;
8612
+ }
8613
+
8614
+ #drop(size: number): void {
8615
+ this.#buffered -= size;
8616
+ let left = size;
8617
+ while (left > 0) {
8618
+ const chunk = this.#chunks[0]!;
8619
+ if (chunk.byteLength <= left) {
8620
+ this.#chunks.shift();
8621
+ left -= chunk.byteLength;
8622
+ } else {
8623
+ this.#chunks[0] = chunk.subarray(left);
8624
+ left = 0;
8625
+ }
8626
+ }
8627
+ }
8628
+ }`,
8629
+ },
8630
+ {
8631
+ file: "src/tools/computer/supervisor.ts",
8632
+ marker: "\tComputerFrameReader,\n\ttype ComputerRunOk,",
8633
+ anchor: `import {
8634
+ COMPUTER_WORKER_ARG,
8635
+ type ComputerRunOk,
8636
+ type ComputerSessionSnapshot,
8637
+ type ComputerWorkerInbound,
8638
+ type ComputerWorkerOutbound,
8639
+ type RunErrorPayload,
8640
+ } from "./protocol";`,
8641
+ patched: `import {
8642
+ COMPUTER_WORKER_ARG,
8643
+ ComputerFrameReader,
8644
+ type ComputerRunOk,
8645
+ type ComputerSessionSnapshot,
8646
+ type ComputerWorkerInbound,
8647
+ type ComputerWorkerOutbound,
8648
+ encodeComputerFrame,
8649
+ type RunErrorPayload,
8650
+ } from "./protocol";`,
8651
+ },
8652
+ {
8653
+ // linux + CUELO_WINDOWS_COMPUTER_HOST 일 때만 helper를 쓴다. 다른 OS·변수 없음은 upstream Worker 경로 그대로이고,
8654
+ // helper 실패는 오류로 보고할 뿐 WSLg desktop으로 바꾸지 않는다. spawn 실패·host 종료·끊긴 pipe·손상 frame은
8655
+ // onError로 한 번 알려 supervisor가 대기 중인 호출을 바로 실패시킨다. terminate는 stdin EOF → 2초 → kill.
8656
+ file: "src/tools/computer/supervisor.ts",
8657
+ marker: "export function spawnInteropComputerWorker(command: string[]): ComputerWorkerHandle {",
8658
+ anchor: `/** Spawns the computer worker through the active CLI host when available. */
8659
+ export function spawnComputerWorker(): ComputerWorkerHandle {
8660
+ const hostEntry = workerHostEntry();`,
8661
+ patched: `/** CUELO: names the Windows computer host executable a WSL CUELO runs through interop. */
8662
+ const WINDOWS_COMPUTER_HOST_ENV = "CUELO_WINDOWS_COMPUTER_HOST";
8663
+ /** Wait after closing the host's stdin before killing it; the host exits on EOF. */
8664
+ const INTEROP_TERMINATE_MS = 2_000;
8665
+ /** Wait for the exit status after the host closes its output, so the error names it. */
8666
+ const INTEROP_EXIT_AFTER_EOF_MS = 1_000;
8667
+ const INTEROP_STDERR_TAIL_CHARS = 2_000;
8668
+
8669
+ function errorText(error: unknown): string {
8670
+ return error instanceof Error ? error.message : String(error);
8671
+ }
8672
+
8673
+ /**
8674
+ * CUELO: runs the computer worker in a separate process speaking {@link encodeComputerFrame} frames on
8675
+ * stdin/stdout. A WSL CUELO starts the Windows host executable this way through interop: only the
8676
+ * parent's pipes connect the two (no port, no listener), and the host exits when its stdin closes. A
8677
+ * spawn failure, an exited host, a closed pipe, or a corrupt frame surfaces once through \`onError\`, so
8678
+ * the supervisor fails pending calls instead of waiting for them.
8679
+ */
8680
+ export function spawnInteropComputerWorker(command: string[]): ComputerWorkerHandle {
8681
+ let child: Bun.Subprocess<"pipe", "pipe", "pipe">;
8682
+ try {
8683
+ child = Bun.spawn(command, { stdin: "pipe", stdout: "pipe", stderr: "pipe" });
8684
+ } catch (error) {
8685
+ throw new ToolError(\`Cannot start the computer host \${command[0]}: \${errorText(error)}\`);
8686
+ }
8687
+ const messageHandlers = new Set<(message: ComputerWorkerOutbound) => void>();
8688
+ const errorHandlers = new Set<(error: Error) => void>();
8689
+ let failure: Error | undefined;
8690
+ let stderrTail = "";
8691
+ const describe = (text: string): Error => {
8692
+ const tail = stderrTail.trim();
8693
+ return new Error(tail ? \`\${text}: \${tail}\` : text);
8694
+ };
8695
+ const fail = (error: Error): void => {
8696
+ if (failure) return;
8697
+ failure = error;
8698
+ for (const handler of [...errorHandlers]) handler(error);
8699
+ };
8700
+
8701
+ void (async () => {
8702
+ const decoder = new TextDecoder();
8703
+ try {
8704
+ for await (const chunk of child.stderr) {
8705
+ const text = decoder.decode(chunk, { stream: true });
8706
+ stderrTail = (stderrTail + text).slice(-INTEROP_STDERR_TAIL_CHARS);
8707
+ logger.debug("Computer host stderr", { text });
8708
+ }
8709
+ } catch {
8710
+ // stderr only feeds diagnostics; the exit and stdout paths report the failure.
8711
+ }
8712
+ })();
8713
+ void (async () => {
8714
+ const reader = new ComputerFrameReader();
8715
+ try {
8716
+ for await (const chunk of child.stdout) {
8717
+ let messages: unknown[];
8718
+ try {
8719
+ messages = reader.push(chunk);
8720
+ } catch (error) {
8721
+ child.kill(9);
8722
+ fail(describe(\`Computer host sent an unreadable frame (\${errorText(error)}); rebuild the host for this CUELO build\`));
8723
+ return;
8724
+ }
8725
+ for (const message of messages) {
8726
+ // The host is built from this core's protocol, exactly like a Worker entry.
8727
+ const outbound = message as ComputerWorkerOutbound;
8728
+ for (const handler of [...messageHandlers]) handler(outbound);
8729
+ }
8730
+ }
8731
+ } catch (error) {
8732
+ fail(describe(\`Computer host output failed: \${errorText(error)}\`));
8733
+ return;
8734
+ }
8735
+ const exited = await Promise.race([child.exited.then(() => true), Bun.sleep(INTEROP_EXIT_AFTER_EOF_MS).then(() => false)]);
8736
+ if (!exited) fail(describe("Computer host closed its output"));
8737
+ })();
8738
+ void child.exited.then(code => fail(describe(\`Computer host exited (\${code ?? child.signalCode})\`)));
8739
+
8740
+ return {
8741
+ send(message) {
8742
+ if (failure) throw failure;
8743
+ // A non-cloneable message throws here, before any byte is written, exactly like postMessage.
8744
+ const frame = encodeComputerFrame(message);
8745
+ try {
8746
+ child.stdin.write(frame);
8747
+ const flushed = child.stdin.flush();
8748
+ if (flushed instanceof Promise) flushed.catch(error => fail(describe(\`Computer host input closed: \${errorText(error)}\`)));
8749
+ } catch (error) {
8750
+ const closed = describe(\`Computer host input closed: \${errorText(error)}\`);
8751
+ fail(closed);
8752
+ throw closed;
8753
+ }
8754
+ },
8755
+ onMessage(handler) {
8756
+ messageHandlers.add(handler);
8757
+ return () => {
8758
+ messageHandlers.delete(handler);
8759
+ };
8760
+ },
8761
+ onError(handler) {
8762
+ errorHandlers.add(handler);
8763
+ const early = failure;
8764
+ if (early) queueMicrotask(() => handler(early));
8765
+ return () => {
8766
+ errorHandlers.delete(handler);
8767
+ };
8768
+ },
8769
+ async terminate() {
8770
+ try {
8771
+ await child.stdin.end();
8772
+ } catch {
8773
+ // Already closed: the host has its EOF.
8774
+ }
8775
+ const deadline = Promise.withResolvers<boolean>();
8776
+ const timer = setTimeout(() => deadline.resolve(false), INTEROP_TERMINATE_MS);
8777
+ const exited = await Promise.race([child.exited.then(() => true), deadline.promise]);
8778
+ clearTimeout(timer);
8779
+ if (!exited) child.kill(9);
8780
+ },
8781
+ };
8782
+ }
8783
+
8784
+ /** Spawns the computer worker through the active CLI host when available. */
8785
+ export function spawnComputerWorker(): ComputerWorkerHandle {
8786
+ // CUELO: a WSL CUELO drives the Windows desktop through the host executable named by
8787
+ // CUELO_WINDOWS_COMPUTER_HOST. Linux only; without the variable nothing changes, and a host
8788
+ // failure is reported, never replaced by the Linux (WSLg) desktop.
8789
+ const windowsHost = process.platform === "linux" ? process.env[WINDOWS_COMPUTER_HOST_ENV]?.trim() : undefined;
8790
+ if (windowsHost) return spawnInteropComputerWorker([windowsHost]);
8791
+ const hostEntry = workerHostEntry();`,
8792
+ },
7715
8793
  {
7716
8794
  // MCP 선택 연결: 요청별 MCP 선택(opt-in). all이 기본이며 per-request면 시작 시 연결하지 않고 요청마다 확장이 고른다.
7717
8795
  file: "src/mcp/settings.ts",
@@ -8721,6 +9799,13 @@ export function summarizeAutoLearnSaved(messages: readonly AgentMessage[], from:
8721
9799
  marker: "\t\t\t\tif (parsed.type === \"error\" && parsed.code === CODEX_NATIVE_LANE_STEER_REJECTED_CODE) {\n\t\t\t\t\tthis.#refuseSteerWaiters(",
8722
9800
  anchor: "\t\t\t\tif (parsed.type === \"error\" && parsed.code === CODEX_NATIVE_LANE_STEER_REJECTED_CODE) {\n\t\t\t\t\tthis.#refuseSteerWaiters(",
8723
9801
  patched: "\t\t\t\tif (parsed.type === \"error\" && parsed.code === CODEX_NATIVE_LANE_STEER_REJECTED_CODE) {\n\t\t\t\t\tthis.#refuseSteerWaiters(",
9802
+ }, {
9803
+ // 18.6.3: upstream 이 상수를 AIError.CODEX_NATIVE_LANE_STEER_REJECTED_CODE 로 옮겼다(#14242). 같은 upstream 줄의 no-op.
9804
+ file: "../pi-ai/src/providers/openai-codex-responses.ts",
9805
+ requires: "AIError.CODEX_NATIVE_LANE_STEER_REJECTED_CODE",
9806
+ marker: "\t\t\t\tif (parsed.type === \"error\" && parsed.code === AIError.CODEX_NATIVE_LANE_STEER_REJECTED_CODE) {\n\t\t\t\t\tthis.#refuseSteerWaiters(",
9807
+ anchor: "\t\t\t\tif (parsed.type === \"error\" && parsed.code === AIError.CODEX_NATIVE_LANE_STEER_REJECTED_CODE) {\n\t\t\t\t\tthis.#refuseSteerWaiters(",
9808
+ patched: "\t\t\t\tif (parsed.type === \"error\" && parsed.code === AIError.CODEX_NATIVE_LANE_STEER_REJECTED_CODE) {\n\t\t\t\t\tthis.#refuseSteerWaiters(",
8724
9809
  }],
8725
9810
  },
8726
9811
  {
@@ -8774,9 +9859,19 @@ export function summarizeAutoLearnSaved(messages: readonly AgentMessage[], from:
8774
9859
  },
8775
9860
  {
8776
9861
  file: "src/tools/ask.ts",
9862
+ // 18.6.3(#14369)은 formatQuestionResult 를 다시 짜 timeout 접미사를 `timeoutSuffix` 한 줄로 옮겼다. 그 줄에 같은 문구를 넣는다(ADAPT).
9863
+ // 두 판 결과가 같은 marker 를 가지므로 `timeoutSuffix` 유무로 후보를 가른다.
9864
+ excludes: "const timeoutSuffix = ",
8777
9865
  marker: "no user response, not user approval",
8778
9866
  anchor: ` const suffix = \`\${result.timedOut ? " (auto-selected after timeout)" : ""}\${noteSuffix}\`;`,
8779
9867
  patched: ` const suffix = \`\${result.timedOut ? " (auto-selected recommended option after timeout; no user response, not user approval)" : ""}\${noteSuffix}\`;`,
9868
+ alternates: [{
9869
+ file: "src/tools/ask.ts",
9870
+ requires: "const timeoutSuffix = ",
9871
+ marker: "no user response, not user approval",
9872
+ anchor: ` const timeoutSuffix = result.timedOut ? " (auto-selected after timeout)" : "";`,
9873
+ patched: ` const timeoutSuffix = result.timedOut ? " (auto-selected recommended option after timeout; no user response, not user approval)" : "";`,
9874
+ }],
8780
9875
  },
8781
9876
  {
8782
9877
  file: "src/tools/ask.ts",
@@ -8895,6 +9990,12 @@ function parentSubagentServiceTiers(
8895
9990
  marker: '{"source":"classes/anthropic.kdl:85","class":"anthropic","family":"opus","revision":[{"op":">=","revision":"5.5.0"},{"op":"<","revision":"6.0.0"}],"wire":{"supportsForcedToolChoice":false},"thinking":{"prefixBinding":true}},{"source":"classes/anthropic.kdl:101",',
8896
9991
  anchor: '{"source":"classes/anthropic.kdl:85","class":"anthropic","family":"opus","revision":[{"op":">=","revision":"5.5.0"},{"op":"<","revision":"6.0.0"}],"wire":{"supportsForcedToolChoice":false},"thinking":{"prefixBinding":true}},{"source":"classes/anthropic.kdl:101",',
8897
9992
  patched: '{"source":"classes/anthropic.kdl:85","class":"anthropic","family":"opus","revision":[{"op":">=","revision":"5.5.0"},{"op":"<","revision":"6.0.0"}],"wire":{"supportsForcedToolChoice":false},"thinking":{"prefixBinding":true}},{"source":"classes/anthropic.kdl:101",',
9993
+ }, {
9994
+ // 18.6.3: 같은 upstream 규칙이 kdl:150(뒤 Sonnet 규칙 kdl:166)로 옮겨졌다. 본문은 그대로다. 그 줄의 no-op.
9995
+ file: "../pi-catalog/src/compat/rules.json",
9996
+ marker: '{"source":"classes/anthropic.kdl:150","class":"anthropic","family":"opus","revision":[{"op":">=","revision":"5.5.0"},{"op":"<","revision":"6.0.0"}],"wire":{"supportsForcedToolChoice":false},"thinking":{"prefixBinding":true}},{"source":"classes/anthropic.kdl:166",',
9997
+ anchor: '{"source":"classes/anthropic.kdl:150","class":"anthropic","family":"opus","revision":[{"op":">=","revision":"5.5.0"},{"op":"<","revision":"6.0.0"}],"wire":{"supportsForcedToolChoice":false},"thinking":{"prefixBinding":true}},{"source":"classes/anthropic.kdl:166",',
9998
+ patched: '{"source":"classes/anthropic.kdl:150","class":"anthropic","family":"opus","revision":[{"op":">=","revision":"5.5.0"},{"op":"<","revision":"6.0.0"}],"wire":{"supportsForcedToolChoice":false},"thinking":{"prefixBinding":true}},{"source":"classes/anthropic.kdl:166",',
8898
9999
  }],
8899
10000
  },
8900
10001
  {
@@ -8932,6 +10033,12 @@ function parentSubagentServiceTiers(
8932
10033
  marker: '{"source":"classes/anthropic.kdl:175","class":"anthropic","providers":["anthropic","cloudflare-ai-gateway"],"family":"opus","revision":[{"op":">=","revision":"5.5.0"},{"op":"<","revision":"6.0.0"}],"wire":{"supportsThinkingBindingControls":true}}',
8933
10034
  anchor: '{"source":"classes/anthropic.kdl:175","class":"anthropic","providers":["anthropic","cloudflare-ai-gateway"],"family":"opus","revision":[{"op":">=","revision":"5.5.0"},{"op":"<","revision":"6.0.0"}],"wire":{"supportsThinkingBindingControls":true}}',
8934
10035
  patched: '{"source":"classes/anthropic.kdl:175","class":"anthropic","providers":["anthropic","cloudflare-ai-gateway"],"family":"opus","revision":[{"op":">=","revision":"5.5.0"},{"op":"<","revision":"6.0.0"}],"wire":{"supportsThinkingBindingControls":true}}',
10036
+ }, {
10037
+ // 18.6.3: 같은 upstream 규칙이 kdl:240 으로 옮겨졌다. 본문·provider 범위는 그대로다. 그 줄의 no-op.
10038
+ file: "../pi-catalog/src/compat/rules.json",
10039
+ marker: '{"source":"classes/anthropic.kdl:240","class":"anthropic","providers":["anthropic","cloudflare-ai-gateway"],"family":"opus","revision":[{"op":">=","revision":"5.5.0"},{"op":"<","revision":"6.0.0"}],"wire":{"supportsThinkingBindingControls":true}}',
10040
+ anchor: '{"source":"classes/anthropic.kdl:240","class":"anthropic","providers":["anthropic","cloudflare-ai-gateway"],"family":"opus","revision":[{"op":">=","revision":"5.5.0"},{"op":"<","revision":"6.0.0"}],"wire":{"supportsThinkingBindingControls":true}}',
10041
+ patched: '{"source":"classes/anthropic.kdl:240","class":"anthropic","providers":["anthropic","cloudflare-ai-gateway"],"family":"opus","revision":[{"op":">=","revision":"5.5.0"},{"op":"<","revision":"6.0.0"}],"wire":{"supportsThinkingBindingControls":true}}',
8935
10042
  }],
8936
10043
  },
8937
10044
  // 위 세 항목과 짝: modelOverrides의 thinking은 buildModel이 규칙으로 채운 thinking을 통째로 덮어써서, override가
@@ -9133,6 +10240,12 @@ function parentSubagentServiceTiers(
9133
10240
  marker: 'if(!i.endsWith(".jsonl")||i.includes(".bak")||NRe(i))continue;let a=i.slice(0,-6),',
9134
10241
  anchor: 'if(!i.endsWith(".jsonl")||i.includes(".bak")||NRe(i))continue;let a=i.slice(0,-6),',
9135
10242
  patched: 'if(!i.endsWith(".jsonl")||i.includes(".bak")||NRe(i))continue;let a=i.slice(0,-6),',
10243
+ }, {
10244
+ // 18.6.3 번들의 같은 upstream 조건(ZRe = isAdvisorTranscriptName) no-op. 이름만 바뀌었다.
10245
+ file: "dist/cli.js",
10246
+ marker: 'if(!i.endsWith(".jsonl")||i.includes(".bak")||ZRe(i))continue;let a=i.slice(0,-6),',
10247
+ anchor: 'if(!i.endsWith(".jsonl")||i.includes(".bak")||ZRe(i))continue;let a=i.slice(0,-6),',
10248
+ patched: 'if(!i.endsWith(".jsonl")||i.includes(".bak")||ZRe(i))continue;let a=i.slice(0,-6),',
9136
10249
  }],
9137
10250
  },
9138
10251
  // 18.4.5 는 /ratchet 을 새로 넣으면서 `src/ratchet/prelude.ts`(코드)와 `prelude.js`(eval 텍스트 자산)를 같은 stem 으로