cuelo 0.10.52 → 0.10.53

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 (484) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-path-routes-manifest.json +35 -35
  3. package/.next/build-manifest.json +4 -4
  4. package/.next/next-server.js.nft.json +1 -1
  5. package/.next/prerender-manifest.json +3 -3
  6. package/.next/react-loadable-manifest.json +549 -561
  7. package/.next/required-server-files.js +25 -25
  8. package/.next/required-server-files.json +25 -25
  9. package/.next/server/app/_global-error/page.js +3 -3
  10. package/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  11. package/.next/server/app/_global-error.html +1 -1
  12. package/.next/server/app/_global-error.rsc +1 -1
  13. package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  14. package/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  15. package/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  16. package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  17. package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  18. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  19. package/.next/server/app/_not-found/page.js +2 -2
  20. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  21. package/.next/server/app/_not-found.html +1 -1
  22. package/.next/server/app/_not-found.rsc +2 -2
  23. package/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
  24. package/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  25. package/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
  26. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  27. package/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  28. package/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  29. package/.next/server/app/api/agent/[id]/account/route.js +1 -1
  30. package/.next/server/app/api/agent/[id]/account/route_client-reference-manifest.js +1 -1
  31. package/.next/server/app/api/agent/[id]/bash-output/route.js +1 -1
  32. package/.next/server/app/api/agent/[id]/bash-output/route_client-reference-manifest.js +1 -1
  33. package/.next/server/app/api/agent/[id]/events/route.js +2 -2
  34. package/.next/server/app/api/agent/[id]/events/route_client-reference-manifest.js +1 -1
  35. package/.next/server/app/api/agent/[id]/route.js +1 -1
  36. package/.next/server/app/api/agent/[id]/route_client-reference-manifest.js +1 -1
  37. package/.next/server/app/api/agent/new/route.js +1 -1
  38. package/.next/server/app/api/agent/new/route_client-reference-manifest.js +1 -1
  39. package/.next/server/app/api/agent/running/route.js +1 -1
  40. package/.next/server/app/api/agent/running/route_client-reference-manifest.js +1 -1
  41. package/.next/server/app/api/attachment-settings/route.js +1 -1
  42. package/.next/server/app/api/attachment-settings/route_client-reference-manifest.js +1 -1
  43. package/.next/server/app/api/attachments/[id]/transcription/route.js +2 -2
  44. package/.next/server/app/api/attachments/[id]/transcription/route_client-reference-manifest.js +1 -1
  45. package/.next/server/app/api/attachments/draft/route.js +1 -1
  46. package/.next/server/app/api/attachments/draft/route_client-reference-manifest.js +1 -1
  47. package/.next/server/app/api/attachments/route.js +2 -2
  48. package/.next/server/app/api/attachments/route_client-reference-manifest.js +1 -1
  49. package/.next/server/app/api/auth/all-providers/route.js +1 -1
  50. package/.next/server/app/api/auth/all-providers/route_client-reference-manifest.js +1 -1
  51. package/.next/server/app/api/auth/api-key/[provider]/route.js +1 -1
  52. package/.next/server/app/api/auth/api-key/[provider]/route_client-reference-manifest.js +1 -1
  53. package/.next/server/app/api/auth/login/[provider]/route.js +1 -1
  54. package/.next/server/app/api/auth/login/[provider]/route_client-reference-manifest.js +1 -1
  55. package/.next/server/app/api/auth/logout/[provider]/route.js +1 -1
  56. package/.next/server/app/api/auth/logout/[provider]/route_client-reference-manifest.js +1 -1
  57. package/.next/server/app/api/auth/providers/route.js +1 -1
  58. package/.next/server/app/api/auth/providers/route_client-reference-manifest.js +1 -1
  59. package/.next/server/app/api/character-mode/route.js +1 -2
  60. package/.next/server/app/api/character-mode/route.js.nft.json +1 -1
  61. package/.next/server/app/api/character-mode/route_client-reference-manifest.js +1 -1
  62. package/.next/server/app/api/cwd/browse/route.js +1 -1
  63. package/.next/server/app/api/cwd/browse/route_client-reference-manifest.js +1 -1
  64. package/.next/server/app/api/cwd/validate/route.js +1 -1
  65. package/.next/server/app/api/cwd/validate/route_client-reference-manifest.js +1 -1
  66. package/.next/server/app/api/default-cwd/route.js +1 -1
  67. package/.next/server/app/api/default-cwd/route_client-reference-manifest.js +1 -1
  68. package/.next/server/app/api/display-settings/route.js +1 -1
  69. package/.next/server/app/api/display-settings/route_client-reference-manifest.js +1 -1
  70. package/.next/server/app/api/document-text/route.js +2 -2
  71. package/.next/server/app/api/document-text/route_client-reference-manifest.js +1 -1
  72. package/.next/server/app/api/experiments/route.js +1 -1
  73. package/.next/server/app/api/experiments/route_client-reference-manifest.js +1 -1
  74. package/.next/server/app/api/file-index/route.js +1 -1
  75. package/.next/server/app/api/file-index/route_client-reference-manifest.js +1 -1
  76. package/.next/server/app/api/files/[...path]/route.js +1 -1
  77. package/.next/server/app/api/files/[...path]/route_client-reference-manifest.js +1 -1
  78. package/.next/server/app/api/git/diff/route.js +1 -1
  79. package/.next/server/app/api/git/diff/route_client-reference-manifest.js +1 -1
  80. package/.next/server/app/api/git/status/route.js +2 -2
  81. package/.next/server/app/api/git/status/route_client-reference-manifest.js +1 -1
  82. package/.next/server/app/api/github/projects/route.js +1 -1
  83. package/.next/server/app/api/github/projects/route_client-reference-manifest.js +1 -1
  84. package/.next/server/app/api/gpt6/handles/route.js +1 -1
  85. package/.next/server/app/api/gpt6/handles/route_client-reference-manifest.js +1 -1
  86. package/.next/server/app/api/gpt6/reply/route.js +1 -1
  87. package/.next/server/app/api/gpt6/reply/route_client-reference-manifest.js +1 -1
  88. package/.next/server/app/api/gpt6/token/route.js +1 -1
  89. package/.next/server/app/api/gpt6/token/route_client-reference-manifest.js +1 -1
  90. package/.next/server/app/api/gpt6/web6-session/route.js +1 -1
  91. package/.next/server/app/api/gpt6/web6-session/route_client-reference-manifest.js +1 -1
  92. package/.next/server/app/api/home/route.js +1 -1
  93. package/.next/server/app/api/home/route_client-reference-manifest.js +1 -1
  94. package/.next/server/app/api/live/call/[callId]/route.js +2 -2
  95. package/.next/server/app/api/live/call/[callId]/route.js.nft.json +1 -1
  96. package/.next/server/app/api/live/call/[callId]/route_client-reference-manifest.js +1 -1
  97. package/.next/server/app/api/live/events/route.js +2 -2
  98. package/.next/server/app/api/live/events/route.js.nft.json +1 -1
  99. package/.next/server/app/api/live/events/route_client-reference-manifest.js +1 -1
  100. package/.next/server/app/api/live/interpret/route.js +2 -2
  101. package/.next/server/app/api/live/interpret/route.js.nft.json +1 -1
  102. package/.next/server/app/api/live/interpret/route_client-reference-manifest.js +1 -1
  103. package/.next/server/app/api/live/offer/route.js +1 -1
  104. package/.next/server/app/api/live/offer/route.js.nft.json +1 -1
  105. package/.next/server/app/api/live/offer/route_client-reference-manifest.js +1 -1
  106. package/.next/server/app/api/live/voices/route.js +1 -1
  107. package/.next/server/app/api/live/voices/route.js.nft.json +1 -1
  108. package/.next/server/app/api/live/voices/route_client-reference-manifest.js +1 -1
  109. package/.next/server/app/api/mcp/route.js +2 -2
  110. package/.next/server/app/api/mcp/route_client-reference-manifest.js +1 -1
  111. package/.next/server/app/api/model-roles/route.js +1 -1
  112. package/.next/server/app/api/model-roles/route_client-reference-manifest.js +1 -1
  113. package/.next/server/app/api/models/route.js +1 -1
  114. package/.next/server/app/api/models/route_client-reference-manifest.js +1 -1
  115. package/.next/server/app/api/models-config/catalog/route.js +1 -1
  116. package/.next/server/app/api/models-config/catalog/route_client-reference-manifest.js +1 -1
  117. package/.next/server/app/api/models-config/discover/route.js +1 -1
  118. package/.next/server/app/api/models-config/discover/route_client-reference-manifest.js +1 -1
  119. package/.next/server/app/api/models-config/route.js +3 -3
  120. package/.next/server/app/api/models-config/route_client-reference-manifest.js +1 -1
  121. package/.next/server/app/api/models-config/test/route.js +1 -1
  122. package/.next/server/app/api/models-config/test/route_client-reference-manifest.js +1 -1
  123. package/.next/server/app/api/office-characters/[...path]/route.js +1 -2
  124. package/.next/server/app/api/office-characters/[...path]/route.js.nft.json +1 -1
  125. package/.next/server/app/api/office-characters/[...path]/route_client-reference-manifest.js +1 -1
  126. package/.next/server/app/api/plugins/route.js +1 -1
  127. package/.next/server/app/api/plugins/route_client-reference-manifest.js +1 -1
  128. package/.next/server/app/api/project-docs/route.js +1 -1
  129. package/.next/server/app/api/project-docs/route_client-reference-manifest.js +1 -1
  130. package/.next/server/app/api/project-trust/route.js +1 -1
  131. package/.next/server/app/api/project-trust/route_client-reference-manifest.js +1 -1
  132. package/.next/server/app/api/projects/route.js +2 -2
  133. package/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
  134. package/.next/server/app/api/runs/[runId]/prelude/route.js +1 -1
  135. package/.next/server/app/api/runs/[runId]/prelude/route_client-reference-manifest.js +1 -1
  136. package/.next/server/app/api/runs/[runId]/route.js +1 -1
  137. package/.next/server/app/api/runs/[runId]/route_client-reference-manifest.js +1 -1
  138. package/.next/server/app/api/runs/[runId]/turns/route.js +1 -1
  139. package/.next/server/app/api/runs/[runId]/turns/route_client-reference-manifest.js +1 -1
  140. package/.next/server/app/api/runs/route.js +1 -1
  141. package/.next/server/app/api/runs/route_client-reference-manifest.js +1 -1
  142. package/.next/server/app/api/sessions/[id]/auto-name/route.js +1 -1
  143. package/.next/server/app/api/sessions/[id]/auto-name/route_client-reference-manifest.js +1 -1
  144. package/.next/server/app/api/sessions/[id]/context/route.js +1 -1
  145. package/.next/server/app/api/sessions/[id]/context/route_client-reference-manifest.js +1 -1
  146. package/.next/server/app/api/sessions/[id]/entries/[entryId]/thinking/route.js +1 -1
  147. package/.next/server/app/api/sessions/[id]/entries/[entryId]/thinking/route_client-reference-manifest.js +1 -1
  148. package/.next/server/app/api/sessions/[id]/export/route.js +1 -1
  149. package/.next/server/app/api/sessions/[id]/export/route_client-reference-manifest.js +1 -1
  150. package/.next/server/app/api/sessions/[id]/route.js +1 -1
  151. package/.next/server/app/api/sessions/[id]/route_client-reference-manifest.js +1 -1
  152. package/.next/server/app/api/sessions/[id]/state/route.js +1 -1
  153. package/.next/server/app/api/sessions/[id]/state/route_client-reference-manifest.js +1 -1
  154. package/.next/server/app/api/sessions/route.js +1 -1
  155. package/.next/server/app/api/sessions/route_client-reference-manifest.js +1 -1
  156. package/.next/server/app/api/sessions/search/route.js +1 -1
  157. package/.next/server/app/api/sessions/search/route_client-reference-manifest.js +1 -1
  158. package/.next/server/app/api/settings/route.js +1 -1
  159. package/.next/server/app/api/settings/route_client-reference-manifest.js +1 -1
  160. package/.next/server/app/api/sidecars/[service]/[...path]/route.js +1 -1
  161. package/.next/server/app/api/sidecars/[service]/[...path]/route_client-reference-manifest.js +1 -1
  162. package/.next/server/app/api/skills/check/route.js +1 -1
  163. package/.next/server/app/api/skills/check/route_client-reference-manifest.js +1 -1
  164. package/.next/server/app/api/skills/install/route.js +2 -2
  165. package/.next/server/app/api/skills/install/route_client-reference-manifest.js +1 -1
  166. package/.next/server/app/api/skills/route.js +1 -1
  167. package/.next/server/app/api/skills/route_client-reference-manifest.js +1 -1
  168. package/.next/server/app/api/skills/search/route.js +1 -1
  169. package/.next/server/app/api/skills/search/route_client-reference-manifest.js +1 -1
  170. package/.next/server/app/api/skills/update/route.js +1 -1
  171. package/.next/server/app/api/skills/update/route_client-reference-manifest.js +1 -1
  172. package/.next/server/app/api/subagents/eta/route.js +1 -1
  173. package/.next/server/app/api/subagents/eta/route_client-reference-manifest.js +1 -1
  174. package/.next/server/app/api/theme/route.js +1 -1
  175. package/.next/server/app/api/theme/route_client-reference-manifest.js +1 -1
  176. package/.next/server/app/api/tool-image/route.js +1 -1
  177. package/.next/server/app/api/tool-image/route_client-reference-manifest.js +1 -1
  178. package/.next/server/app/api/update-maintenance/route.js +2 -2
  179. package/.next/server/app/api/update-maintenance/route_client-reference-manifest.js +1 -1
  180. package/.next/server/app/api/updates/route.js +1 -1
  181. package/.next/server/app/api/updates/route_client-reference-manifest.js +1 -1
  182. package/.next/server/app/api/web-access/recovery/route.js +1 -1
  183. package/.next/server/app/api/web-access/recovery/route_client-reference-manifest.js +1 -1
  184. package/.next/server/app/api/web-access/route.js +1 -1
  185. package/.next/server/app/api/web-access/route_client-reference-manifest.js +1 -1
  186. package/.next/server/app/api/web6/consults/route.js +1 -1
  187. package/.next/server/app/api/web6/consults/route_client-reference-manifest.js +1 -1
  188. package/.next/server/app/api/worktrees/route.js +1 -1
  189. package/.next/server/app/api/worktrees/route_client-reference-manifest.js +1 -1
  190. package/.next/server/app/favicon.ico/route.js +1 -1
  191. package/.next/server/app/index.html +1 -1
  192. package/.next/server/app/index.rsc +4 -4
  193. package/.next/server/app/index.segments/__PAGE__.segment.rsc +3 -3
  194. package/.next/server/app/index.segments/_full.segment.rsc +4 -4
  195. package/.next/server/app/index.segments/_head.segment.rsc +1 -1
  196. package/.next/server/app/index.segments/_index.segment.rsc +2 -2
  197. package/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  198. package/.next/server/app/manifest.webmanifest/route.js +2 -2
  199. package/.next/server/app/manifest.webmanifest/route_client-reference-manifest.js +1 -1
  200. package/.next/server/app/page.js +5 -5
  201. package/.next/server/app/page_client-reference-manifest.js +1 -1
  202. package/.next/server/app/recover/page.js +2 -2
  203. package/.next/server/app/recover/page_client-reference-manifest.js +1 -1
  204. package/.next/server/app/recover.html +1 -1
  205. package/.next/server/app/recover.rsc +3 -3
  206. package/.next/server/app/recover.segments/_full.segment.rsc +3 -3
  207. package/.next/server/app/recover.segments/_head.segment.rsc +1 -1
  208. package/.next/server/app/recover.segments/_index.segment.rsc +2 -2
  209. package/.next/server/app/recover.segments/_tree.segment.rsc +1 -1
  210. package/.next/server/app/recover.segments/recover/__PAGE__.segment.rsc +2 -2
  211. package/.next/server/app/recover.segments/recover.segment.rsc +1 -1
  212. package/.next/server/app-paths-manifest.json +35 -35
  213. package/.next/server/chunks/1008.js +2 -2
  214. package/.next/server/chunks/1966.js +2 -0
  215. package/.next/server/chunks/319.js +3 -3
  216. package/.next/server/chunks/6429.js +2 -2
  217. package/.next/server/chunks/6775.js +7 -0
  218. package/.next/server/middleware-build-manifest.js +1 -1
  219. package/.next/server/middleware-react-loadable-manifest.js +1 -1
  220. package/.next/server/middleware.js +9 -9
  221. package/.next/server/pages/404.html +1 -1
  222. package/.next/server/pages/500.html +1 -1
  223. package/.next/server/server-reference-manifest.json +1 -1
  224. package/.next/static/chunks/app/_not-found/page-041b64675adcdf82.js +1 -0
  225. package/.next/static/chunks/app/layout-3b1f8b74c7fc5e77.js +1 -0
  226. package/.next/static/chunks/app/{page-3640086c732eb87d.js → page-efae4fdcf84b7a14.js} +4 -4
  227. package/.next/static/chunks/app/recover/{page-c0c40b3c215078a4.js → page-2bcd6a351f3eda3d.js} +1 -1
  228. package/.next/static/chunks/{main-app-fbc4aa3c15d48613.js → main-app-ca46ab75db2b4e4b.js} +1 -1
  229. package/.next/static/chunks/main-dd4c9daba44bef88.js +5 -0
  230. package/.next/static/chunks/next/dist/client/components/builtin/global-error-dac2a58a3b9eb28e.js +1 -0
  231. package/.next/static/chunks/webpack-546c8221d1906945.js +1 -0
  232. package/.next/trace +107 -107
  233. package/.next/trace-build +1 -1
  234. package/.next/types/app/api/agent/[id]/account/route.ts +1 -1
  235. package/.next/types/app/api/agent/[id]/bash-output/route.ts +1 -1
  236. package/.next/types/app/api/agent/[id]/events/route.ts +1 -1
  237. package/.next/types/app/api/agent/[id]/route.ts +1 -1
  238. package/.next/types/app/api/agent/new/route.ts +1 -1
  239. package/.next/types/app/api/agent/running/route.ts +1 -1
  240. package/.next/types/app/api/attachment-settings/route.ts +1 -1
  241. package/.next/types/app/api/attachments/[id]/transcription/route.ts +1 -1
  242. package/.next/types/app/api/attachments/draft/route.ts +1 -1
  243. package/.next/types/app/api/attachments/route.ts +1 -1
  244. package/.next/types/app/api/auth/all-providers/route.ts +1 -1
  245. package/.next/types/app/api/auth/api-key/[provider]/route.ts +1 -1
  246. package/.next/types/app/api/auth/login/[provider]/route.ts +1 -1
  247. package/.next/types/app/api/auth/logout/[provider]/route.ts +1 -1
  248. package/.next/types/app/api/auth/providers/route.ts +1 -1
  249. package/.next/types/app/api/character-mode/route.ts +1 -1
  250. package/.next/types/app/api/cwd/browse/route.ts +1 -1
  251. package/.next/types/app/api/cwd/validate/route.ts +1 -1
  252. package/.next/types/app/api/default-cwd/route.ts +1 -1
  253. package/.next/types/app/api/display-settings/route.ts +1 -1
  254. package/.next/types/app/api/document-text/route.ts +1 -1
  255. package/.next/types/app/api/experiments/route.ts +1 -1
  256. package/.next/types/app/api/file-index/route.ts +1 -1
  257. package/.next/types/app/api/files/[...path]/route.ts +1 -1
  258. package/.next/types/app/api/git/diff/route.ts +1 -1
  259. package/.next/types/app/api/git/status/route.ts +1 -1
  260. package/.next/types/app/api/github/projects/route.ts +1 -1
  261. package/.next/types/app/api/gpt6/handles/route.ts +1 -1
  262. package/.next/types/app/api/gpt6/reply/route.ts +1 -1
  263. package/.next/types/app/api/gpt6/token/route.ts +1 -1
  264. package/.next/types/app/api/gpt6/web6-session/route.ts +1 -1
  265. package/.next/types/app/api/home/route.ts +1 -1
  266. package/.next/types/app/api/live/call/[callId]/route.ts +1 -1
  267. package/.next/types/app/api/live/events/route.ts +1 -1
  268. package/.next/types/app/api/live/interpret/route.ts +1 -1
  269. package/.next/types/app/api/live/offer/route.ts +1 -1
  270. package/.next/types/app/api/live/voices/route.ts +1 -1
  271. package/.next/types/app/api/mcp/route.ts +1 -1
  272. package/.next/types/app/api/model-roles/route.ts +1 -1
  273. package/.next/types/app/api/models/route.ts +1 -1
  274. package/.next/types/app/api/models-config/catalog/route.ts +1 -1
  275. package/.next/types/app/api/models-config/discover/route.ts +1 -1
  276. package/.next/types/app/api/models-config/route.ts +1 -1
  277. package/.next/types/app/api/models-config/test/route.ts +1 -1
  278. package/.next/types/app/api/office-characters/[...path]/route.ts +1 -1
  279. package/.next/types/app/api/plugins/route.ts +1 -1
  280. package/.next/types/app/api/project-docs/route.ts +1 -1
  281. package/.next/types/app/api/project-trust/route.ts +1 -1
  282. package/.next/types/app/api/projects/route.ts +1 -1
  283. package/.next/types/app/api/runs/[runId]/prelude/route.ts +1 -1
  284. package/.next/types/app/api/runs/[runId]/route.ts +1 -1
  285. package/.next/types/app/api/runs/[runId]/turns/route.ts +1 -1
  286. package/.next/types/app/api/runs/route.ts +1 -1
  287. package/.next/types/app/api/sessions/[id]/auto-name/route.ts +1 -1
  288. package/.next/types/app/api/sessions/[id]/context/route.ts +1 -1
  289. package/.next/types/app/api/sessions/[id]/entries/[entryId]/thinking/route.ts +1 -1
  290. package/.next/types/app/api/sessions/[id]/export/route.ts +1 -1
  291. package/.next/types/app/api/sessions/[id]/route.ts +1 -1
  292. package/.next/types/app/api/sessions/[id]/state/route.ts +1 -1
  293. package/.next/types/app/api/sessions/route.ts +1 -1
  294. package/.next/types/app/api/sessions/search/route.ts +1 -1
  295. package/.next/types/app/api/settings/route.ts +1 -1
  296. package/.next/types/app/api/sidecars/[service]/[...path]/route.ts +1 -1
  297. package/.next/types/app/api/skills/check/route.ts +1 -1
  298. package/.next/types/app/api/skills/install/route.ts +1 -1
  299. package/.next/types/app/api/skills/route.ts +1 -1
  300. package/.next/types/app/api/skills/search/route.ts +1 -1
  301. package/.next/types/app/api/skills/update/route.ts +1 -1
  302. package/.next/types/app/api/subagents/eta/route.ts +1 -1
  303. package/.next/types/app/api/theme/route.ts +1 -1
  304. package/.next/types/app/api/tool-image/route.ts +1 -1
  305. package/.next/types/app/api/update-maintenance/route.ts +1 -1
  306. package/.next/types/app/api/updates/route.ts +1 -1
  307. package/.next/types/app/api/web-access/recovery/route.ts +1 -1
  308. package/.next/types/app/api/web-access/route.ts +1 -1
  309. package/.next/types/app/api/web6/consults/route.ts +1 -1
  310. package/.next/types/app/api/worktrees/route.ts +1 -1
  311. package/.next/types/app/layout.ts +1 -1
  312. package/.next/types/app/page.ts +1 -1
  313. package/.next/types/app/recover/page.ts +1 -1
  314. package/LICENSE +22 -22
  315. package/README.md +273 -271
  316. package/Tools/CUELO_Setup/files/btw-server.js +346 -346
  317. package/Tools/CUELO_Setup/files/native-runtime-patch.js +238 -238
  318. package/Tools/CUELO_Setup/files/subagent-server.js +587 -587
  319. package/Tools/CUELO_Setup/files/usage-server.js +1057 -1057
  320. package/Tools/OMP_Global_Config/agent/AGENTS.md +144 -144
  321. package/Tools/OMP_Global_Config/agent/RULES.md +140 -140
  322. package/Tools/OMP_Global_Config/agent/TITLE_SYSTEM.md +17 -17
  323. package/Tools/OMP_Global_Config/agent/WATCHDOG.md +55 -55
  324. package/Tools/OMP_Global_Config/agent/agents/maker.md +413 -413
  325. package/Tools/OMP_Global_Config/agent/extensions/character-voice.ts +1035 -1035
  326. package/Tools/OMP_Global_Config/agent/extensions/command-guard/index.ts +1110 -1110
  327. package/Tools/OMP_Global_Config/agent/extensions/command-guard/interop-hint.ts +133 -133
  328. package/Tools/OMP_Global_Config/agent/extensions/command-guard/matcher.ts +2258 -2258
  329. package/Tools/OMP_Global_Config/agent/extensions/command-guard/powershell-syntax.ts +210 -210
  330. package/Tools/OMP_Global_Config/agent/extensions/command-guard/task-guard.ts +1137 -1121
  331. package/Tools/OMP_Global_Config/agent/extensions/cross-review.ts +132 -132
  332. package/Tools/OMP_Global_Config/agent/extensions/docmap-route.ts +133 -133
  333. package/Tools/OMP_Global_Config/agent/extensions/draft/markdown.ts +226 -226
  334. package/Tools/OMP_Global_Config/agent/extensions/draft/ooxml.ts +222 -222
  335. package/Tools/OMP_Global_Config/agent/extensions/draft/pdf.ts +63 -63
  336. package/Tools/OMP_Global_Config/agent/extensions/draft/zip.ts +73 -73
  337. package/Tools/OMP_Global_Config/agent/extensions/draft.ts +204 -204
  338. package/Tools/OMP_Global_Config/agent/extensions/edit-lessons.ts +159 -159
  339. package/Tools/OMP_Global_Config/agent/extensions/external-advice-check.ts +36 -36
  340. package/Tools/OMP_Global_Config/agent/extensions/gitnexus-autosync.ts +406 -406
  341. package/Tools/OMP_Global_Config/agent/extensions/image-question-router.ts +55 -55
  342. package/Tools/OMP_Global_Config/agent/extensions/jev-runtime.ts +2700 -2700
  343. package/Tools/OMP_Global_Config/agent/extensions/korean-reply-guard.ts +190 -190
  344. package/Tools/OMP_Global_Config/agent/extensions/lib/cross-review.ts +1208 -1208
  345. package/Tools/OMP_Global_Config/agent/extensions/lib/docmap-search.ts +612 -612
  346. package/Tools/OMP_Global_Config/agent/extensions/lib/job-observation.ts +182 -182
  347. package/Tools/OMP_Global_Config/agent/extensions/lib/maker-routing.ts +1332 -1332
  348. package/Tools/OMP_Global_Config/agent/extensions/lib/mcp-selection.ts +361 -361
  349. package/Tools/OMP_Global_Config/agent/extensions/lib/memory-excerpt.ts +5 -5
  350. package/Tools/OMP_Global_Config/agent/extensions/lib/native-judge.ts +113 -113
  351. package/Tools/OMP_Global_Config/agent/extensions/lib/prepared-task.ts +211 -211
  352. package/Tools/OMP_Global_Config/agent/extensions/lib/routing-ledger.ts +538 -538
  353. package/Tools/OMP_Global_Config/agent/extensions/lib/session-identity.ts +34 -34
  354. package/Tools/OMP_Global_Config/agent/extensions/lib/task-progress.ts +492 -492
  355. package/Tools/OMP_Global_Config/agent/extensions/mcp-selection.ts +301 -301
  356. package/Tools/OMP_Global_Config/agent/extensions/prompt-compact.ts +67 -67
  357. package/Tools/OMP_Global_Config/agent/extensions/report-chart-guard.ts +102 -102
  358. package/Tools/OMP_Global_Config/agent/extensions/skim.ts +332 -332
  359. package/Tools/OMP_Global_Config/agent/extensions/steering-reply-gate.ts +207 -207
  360. package/Tools/OMP_Global_Config/agent/extensions/todo-nudge.ts +80 -80
  361. package/Tools/OMP_Global_Config/agent/extensions/turn-end-guard.ts +421 -421
  362. package/Tools/OMP_Global_Config/agent/prompt-copies/AGENTS.en.md +147 -147
  363. package/Tools/OMP_Global_Config/agent/prompt-copies/RULES.en.md +49 -49
  364. package/Tools/OMP_Global_Config/agent/rules/docs-handoff.md +39 -39
  365. package/Tools/OMP_Global_Config/agent/rules/frontend.md +74 -74
  366. package/Tools/OMP_Global_Config/agent/rules/harness-policy.json +1224 -1224
  367. package/Tools/OMP_Global_Config/agent/rules/legacy-encoding.md +21 -21
  368. package/Tools/OMP_Global_Config/agent/rules/meta-harness.md +45 -45
  369. package/Tools/OMP_Global_Config/agent/rules/omp-harness.md +151 -151
  370. package/Tools/OMP_Global_Config/agent/rules/subagent.md +750 -750
  371. package/Tools/OMP_Global_Config/agent/rules/task-guard.md +62 -62
  372. package/Tools/OMP_Global_Config/agent/rules/verdict.md +210 -210
  373. package/Tools/OMP_Global_Config/agent/rules/web6-consult.md +83 -83
  374. package/Tools/OMP_Global_Config/agent/sop/_common.md +136 -136
  375. package/Tools/OMP_Global_Config/agent/sop/_writer.md +259 -259
  376. package/Tools/OMP_Global_Config/agent/sop/maker.md +9 -9
  377. package/Tools/OMP_Global_Config/agent/tools/git-finalizer/finalizer.ps1 +409 -409
  378. package/Tools/OMP_Global_Config/agent/tools/git-finalizer/index.ts +303 -303
  379. package/Tools/OMP_Global_Config/agent/tools/git-finalizer/sync-checkout.ts +184 -184
  380. package/Tools/OMP_Global_Config/agent/tools/git-finalizer/wsl-git.ts +96 -96
  381. package/Tools/OMP_Global_Config/agent/tools/windows-build/index.ts +387 -387
  382. package/Tools/OMP_Global_Config/agent/tools/windows-control/control.ps1 +200 -200
  383. package/Tools/OMP_Global_Config/agent/tools/windows-control/index.ts +255 -255
  384. package/Tools/OMP_Global_Config/notices/orchestrate.md +26 -26
  385. package/Tools/OMP_Global_Config/notices/ultrathink.md +9 -9
  386. package/Tools/OMP_Global_Config/notices/workflow.md +16 -16
  387. package/Tools/OMP_Global_Config/patches/apply-core-patch.mjs +18339 -18304
  388. package/Tools/OMP_Global_Config/patches/apply-notices.mjs +135 -135
  389. package/bin/cuelo-options.js +38 -38
  390. package/bin/cuelo.js +273 -273
  391. package/bin/docker-entrypoint.mjs +19 -19
  392. package/bin/password-prompt.js +100 -100
  393. package/bin/prepare-runtime.js +112 -112
  394. package/bin/runtime.js +134 -134
  395. package/bin/web-auth-store.d.ts +104 -104
  396. package/bin/web-auth-store.js +593 -593
  397. package/install.mjs +427 -427
  398. package/next.config.ts +104 -104
  399. package/package.json +141 -141
  400. package/public/audio/completion/voices.json +1393 -1393
  401. package/public/fonts/cuelo-display/NOTICE.txt +15 -15
  402. package/public/fonts/fonts.css +288 -288
  403. package/public/fonts/jetbrains-mono/OFL.txt +93 -93
  404. package/public/fonts/wanted-sans/OFL.txt +93 -93
  405. package/public/icons/catppuccin/LICENSE +22 -22
  406. package/public/icons/catppuccin/README.md +8 -8
  407. package/public/icons/catppuccin/latte/_file.svg +3 -3
  408. package/public/icons/catppuccin/latte/_folder.svg +3 -3
  409. package/public/icons/catppuccin/latte/_folder_open.svg +3 -3
  410. package/public/icons/catppuccin/latte/bash.svg +7 -7
  411. package/public/icons/catppuccin/latte/bun-lock.svg +8 -8
  412. package/public/icons/catppuccin/latte/config.svg +3 -3
  413. package/public/icons/catppuccin/latte/css.svg +6 -6
  414. package/public/icons/catppuccin/latte/database.svg +3 -3
  415. package/public/icons/catppuccin/latte/docker.svg +3 -3
  416. package/public/icons/catppuccin/latte/env.svg +3 -3
  417. package/public/icons/catppuccin/latte/eslint.svg +6 -6
  418. package/public/icons/catppuccin/latte/git.svg +6 -6
  419. package/public/icons/catppuccin/latte/go.svg +3 -3
  420. package/public/icons/catppuccin/latte/graphql.svg +3 -3
  421. package/public/icons/catppuccin/latte/html.svg +6 -6
  422. package/public/icons/catppuccin/latte/javascript-react.svg +8 -8
  423. package/public/icons/catppuccin/latte/javascript.svg +6 -6
  424. package/public/icons/catppuccin/latte/json.svg +3 -3
  425. package/public/icons/catppuccin/latte/lock.svg +3 -3
  426. package/public/icons/catppuccin/latte/markdown.svg +3 -3
  427. package/public/icons/catppuccin/latte/ms-word.svg +6 -6
  428. package/public/icons/catppuccin/latte/next.svg +6 -6
  429. package/public/icons/catppuccin/latte/npm-lock.svg +7 -7
  430. package/public/icons/catppuccin/latte/pdf.svg +3 -3
  431. package/public/icons/catppuccin/latte/python.svg +8 -8
  432. package/public/icons/catppuccin/latte/rust.svg +6 -6
  433. package/public/icons/catppuccin/latte/sass.svg +3 -3
  434. package/public/icons/catppuccin/latte/terraform.svg +3 -3
  435. package/public/icons/catppuccin/latte/toml.svg +3 -3
  436. package/public/icons/catppuccin/latte/typescript-react.svg +8 -8
  437. package/public/icons/catppuccin/latte/typescript.svg +6 -6
  438. package/public/icons/catppuccin/latte/yaml.svg +3 -3
  439. package/public/icons/catppuccin/mocha/_file.svg +3 -3
  440. package/public/icons/catppuccin/mocha/_folder.svg +3 -3
  441. package/public/icons/catppuccin/mocha/_folder_open.svg +3 -3
  442. package/public/icons/catppuccin/mocha/bash.svg +7 -7
  443. package/public/icons/catppuccin/mocha/bun-lock.svg +8 -8
  444. package/public/icons/catppuccin/mocha/config.svg +3 -3
  445. package/public/icons/catppuccin/mocha/css.svg +6 -6
  446. package/public/icons/catppuccin/mocha/database.svg +3 -3
  447. package/public/icons/catppuccin/mocha/docker.svg +3 -3
  448. package/public/icons/catppuccin/mocha/env.svg +3 -3
  449. package/public/icons/catppuccin/mocha/eslint.svg +6 -6
  450. package/public/icons/catppuccin/mocha/git.svg +6 -6
  451. package/public/icons/catppuccin/mocha/go.svg +3 -3
  452. package/public/icons/catppuccin/mocha/graphql.svg +3 -3
  453. package/public/icons/catppuccin/mocha/html.svg +6 -6
  454. package/public/icons/catppuccin/mocha/javascript-react.svg +8 -8
  455. package/public/icons/catppuccin/mocha/javascript.svg +6 -6
  456. package/public/icons/catppuccin/mocha/json.svg +3 -3
  457. package/public/icons/catppuccin/mocha/lock.svg +3 -3
  458. package/public/icons/catppuccin/mocha/markdown.svg +3 -3
  459. package/public/icons/catppuccin/mocha/ms-word.svg +6 -6
  460. package/public/icons/catppuccin/mocha/next.svg +6 -6
  461. package/public/icons/catppuccin/mocha/npm-lock.svg +7 -7
  462. package/public/icons/catppuccin/mocha/pdf.svg +3 -3
  463. package/public/icons/catppuccin/mocha/python.svg +8 -8
  464. package/public/icons/catppuccin/mocha/rust.svg +6 -6
  465. package/public/icons/catppuccin/mocha/sass.svg +3 -3
  466. package/public/icons/catppuccin/mocha/terraform.svg +3 -3
  467. package/public/icons/catppuccin/mocha/toml.svg +3 -3
  468. package/public/icons/catppuccin/mocha/typescript-react.svg +8 -8
  469. package/public/icons/catppuccin/mocha/typescript.svg +6 -6
  470. package/public/icons/catppuccin/mocha/yaml.svg +3 -3
  471. package/public/icons/cuelo-icon.svg +8 -8
  472. package/public/offline.html +145 -145
  473. package/public/sw.js +135 -135
  474. package/.next/server/chunks/205.js +0 -7
  475. package/.next/server/chunks/7738.js +0 -1
  476. package/.next/static/chunks/1966.b8f83f7ba376c962.js +0 -1
  477. package/.next/static/chunks/3899.3f761f7e3a944e8c.js +0 -1
  478. package/.next/static/chunks/app/_not-found/page-9e5295df095e2e41.js +0 -1
  479. package/.next/static/chunks/app/layout-8b29ba206a96135e.js +0 -1
  480. package/.next/static/chunks/main-1ddb405481ce21f6.js +0 -5
  481. package/.next/static/chunks/next/dist/client/components/builtin/global-error-50782c8c9c06c8b5.js +0 -1
  482. package/.next/static/chunks/webpack-7b514b9855bd748c.js +0 -1
  483. /package/.next/static/{dQA--5NX6NZt_A6pLZUde → -mDQEyWQ8VQsAfLpiACIP}/_buildManifest.js +0 -0
  484. /package/.next/static/{dQA--5NX6NZt_A6pLZUde → -mDQEyWQ8VQsAfLpiACIP}/_ssgManifest.js +0 -0
@@ -1,346 +1,346 @@
1
- // Sidecar: /btw-style ephemeral side chat for CUELO at http://127.0.0.1:30143/ask.
2
- // MUST run under Bun (the omp SDK is TS sources importing bun: builtins); launch.ps1
3
- // starts it with ~\.bun\bin\bun.exe. The UI lives in components/workspace/SideChatPanel.tsx.
4
- //
5
- // How it works: the panel POSTs { sessionPath, question, history? }. This server
6
- // copies the session .jsonl to a temp file (SessionManager.open takes a single-writer
7
- // lock, so the live file CUELO owns is never opened), builds an AgentSession over
8
- // the copy, and calls session.runEphemeralTurn() — the same primitive the TUI's /btw
9
- // uses: full session context, no tools, nothing persisted. Deltas stream back as
10
- // NDJSON lines: {t:'d',v:delta} | {t:'done',v:full} | {t:'err',v:message}.
11
- 'use strict';
12
- import { createHash } from 'node:crypto';
13
- import fs from 'node:fs';
14
- import os from 'node:os';
15
- import path from 'node:path';
16
- import { pathToFileURL } from 'node:url';
17
-
18
- const PORT = Number(process.env.OMP_BTW_PORT) || 30143;
19
- const ALLOW_ORIGIN = 'http://127.0.0.1:' + (Number(process.env.PORT) || 30141);
20
- const IDLE_DISPOSE_MS = 15 * 60_000;
21
- const SESSIONS_ROOT = path.join(os.homedir(), '.omp', 'agent', 'sessions');
22
- const TMP_DIR = path.join(os.tmpdir(), 'omp-btw');
23
-
24
- // omp SDK out of CUELO's own node_modules, so both always share one SDK version.
25
- // npm global prefix 를 바꿔 쓰는 PC 도 있으므로 기본 위치가 없으면 `npm root -g` 로 찾는다.
26
- function resolveCueloDir() {
27
- const candidates = [];
28
- if (process.env.CUELO_DIR) candidates.push(process.env.CUELO_DIR);
29
- // 공식 fallback 설치본은 upstream 이름(omp-web)으로 들어간다.
30
- for (const name of ['cuelo', 'omp-web']) candidates.push(path.join(process.env.APPDATA || '', 'npm', 'node_modules', name));
31
- try {
32
- // 명령이 고정 문자열이라 주입 여지가 없다. execFileSync + shell:true 는
33
- // Node 가 deprecation 경고를 내므로 execSync 를 쓴다.
34
- const root = require('node:child_process')
35
- .execSync('npm root -g', { encoding: 'utf8', windowsHide: true })
36
- .trim();
37
- if (root) for (const name of ['cuelo', 'omp-web']) candidates.push(path.join(root, name));
38
- } catch { /* npm 이 없으면 기본 후보만 쓴다 */ }
39
- const hit = candidates.find(dir => dir && fs.existsSync(path.join(dir, 'node_modules', '@oh-my-pi', 'pi-coding-agent', 'src', 'index.ts')));
40
- return hit || candidates[candidates.length - 1];
41
- }
42
- const cueloDir = resolveCueloDir();
43
- const sdkEntry = path.join(cueloDir, 'node_modules', '@oh-my-pi', 'pi-coding-agent', 'src', 'index.ts');
44
- if (!fs.existsSync(sdkEntry)) {
45
- console.error('omp SDK not found: ' + sdkEntry + '\nSet CUELO_DIR to the CUELO folder (the one with node_modules/@oh-my-pi), for example by starting it with `node install.mjs start`.');
46
- process.exit(1);
47
- }
48
- const { createAgentSession, SessionManager, AgentRegistry } = await import(pathToFileURL(sdkEntry).href);
49
-
50
- fs.mkdirSync(TMP_DIR, { recursive: true });
51
-
52
- // sessionPath -> { mtimeMs, size, lastUsed, session, copyPath }
53
- const cache = new Map();
54
-
55
- function headerCwd(jsonlPath) {
56
- try {
57
- const firstLine = fs.readFileSync(jsonlPath, 'utf8').split('\n', 1)[0];
58
- const cwd = JSON.parse(firstLine).cwd;
59
- if (typeof cwd === 'string' && fs.existsSync(cwd)) return cwd;
60
- } catch {}
61
- return undefined;
62
- }
63
-
64
- /** 세션 파일에서 sessionId 를 얻는다. 헤더 항목이 우선, 없으면 파일명에서 뽑는다. */
65
- function sessionIdOf(jsonlPath) {
66
- try {
67
- for (const line of fs.readFileSync(jsonlPath, 'utf8').slice(0, 16384).split('\n')) {
68
- if (!line.trim()) continue;
69
- const entry = JSON.parse(line);
70
- if (entry.type === 'session' && typeof entry.id === 'string') return entry.id;
71
- }
72
- } catch {}
73
- const m = /_([0-9a-f]{8}-[0-9a-f-]+)\.jsonl$/i.exec(path.basename(jsonlPath));
74
- return m ? m[1] : undefined;
75
- }
76
-
77
- /**
78
- * 사본 위에 새로 만든 AgentSession 은 설정된 default 모델로 복원된다. 메인 세션이 자동
79
- * 폴백으로 다른 모델에서 돌고 있으면(주 모델 한도 소진 등) 사본은 소진된 모델을 그대로
80
- * 다시 호출하고, runEphemeralTurn 은 TurnRecovery 를 거치지 않으므로 429 가 그대로
81
- * 사용자에게 노출된다. 그래서 매 질문 전에 메인의 live state.model 로 맞춘다.
82
- * 조회나 적용이 실패하면 조용히 기존 모델을 유지한다.
83
- */
84
- async function syncModelFromMain(session, jsonlPath) {
85
- const sessionId = sessionIdOf(jsonlPath);
86
- if (!sessionId) return;
87
- let live;
88
- try {
89
- const res = await fetch(ALLOW_ORIGIN + '/api/agent/' + sessionId, {
90
- headers: { Accept: 'application/json' },
91
- signal: AbortSignal.timeout(3000),
92
- });
93
- if (!res.ok) return;
94
- live = (await res.json())?.state?.model;
95
- } catch { return; }
96
- if (!live || typeof live.provider !== 'string' || typeof live.id !== 'string') return;
97
- const current = session.model;
98
- if (current && current.provider === live.provider && current.id === live.id) return;
99
- const target = session.modelRegistry.find(live.provider, live.id);
100
- if (!target) return;
101
- try {
102
- await session.setModel(target);
103
- } catch {}
104
- }
105
-
106
- // --- 모델 폴백 --------------------------------------------------------------
107
- // runEphemeralTurn 은 TurnRecovery 를 거치지 않으므로 retry.fallbackChains 가 적용되지
108
- // 않는다(TUI /btw 와 같은 한계). 그래서 주 모델 한도가 소진되면 메인 세션은 폴백으로
109
- // 멀쩡히 돌아가는데 사이드채팅만 429 를 그대로 뱉었다. 같은 설정을 직접 읽어 체인을 밟는다.
110
-
111
- const CONFIG_PATH = path.join(os.homedir(), '.omp', 'agent', 'config.yml');
112
- const THINKING_LEVELS = new Set(['off', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max']);
113
-
114
- // config.yml 은 omp 재시작 없이 바뀔 수 있으므로 mtime 이 달라질 때만 다시 읽는다.
115
- let chainCache = { mtimeMs: -1, chains: {} };
116
- function fallbackChains() {
117
- let st;
118
- try { st = fs.statSync(CONFIG_PATH); } catch { return {}; }
119
- if (st.mtimeMs !== chainCache.mtimeMs) {
120
- const chains = {};
121
- try {
122
- const raw = Bun.YAML.parse(fs.readFileSync(CONFIG_PATH, 'utf8'))?.retry?.fallbackChains;
123
- for (const key of Object.keys(raw || {})) {
124
- if (Array.isArray(raw[key])) chains[key] = raw[key].filter(s => typeof s === 'string');
125
- }
126
- } catch {}
127
- chainCache = { mtimeMs: st.mtimeMs, chains };
128
- }
129
- return chainCache.chains;
130
- }
131
-
132
- /** omp 와 같은 우선순위: 정확한 모델 키 > provider/* > default. 역할 키는 여기 해당 없음. */
133
- function chainFor(model) {
134
- if (!model) return [];
135
- const chains = fallbackChains();
136
- return chains[model.provider + '/' + model.id] ?? chains[model.provider + '/*'] ?? chains.default ?? [];
137
- }
138
-
139
- /**
140
- * "provider/id" 또는 "provider/id:thinking" 을 나눈다. provider 는 첫 구간이고 나머지가
141
- * id 다(openrouter/google/x 처럼 id 에 슬래시가 있을 수 있다). thinking 접미사는 레지스트리
142
- * 조회에 방해되므로 떼어내기만 하고 적용하지는 않는다 — 여기서 필요한 건 모델 교체다.
143
- */
144
- function parseSelector(selector) {
145
- let base = selector;
146
- const colon = selector.lastIndexOf(':');
147
- // 모델 id 자체에 콜론이 있을 수 있어 알려진 thinking 값일 때만 접미사로 본다.
148
- if (colon > 0 && THINKING_LEVELS.has(selector.slice(colon + 1))) base = selector.slice(0, colon);
149
- const slash = base.indexOf('/');
150
- if (slash < 1 || slash === base.length - 1) return null;
151
- return { provider: base.slice(0, slash), id: base.slice(slash + 1) };
152
- }
153
-
154
- // 모델 교체로 풀릴 만한 실패를 넓게 잡는다. 사이드채팅에서 다른 모델로 한 번 더 시도하는
155
- // 비용은 낮고, 놓치면 사용자에게 429 가 그대로 보인다.
156
- const SWITCH_WORTHY =
157
- /\b(?:429|402)\b|rate.?limit|usage.?limit|quota|insufficient_quota|resource_exhausted|overloaded|over capacity|too many requests|service unavailable|bad gateway|gateway timeout|internal server error/i;
158
- function isSwitchWorthy(err) {
159
- const status = err?.status ?? err?.statusCode;
160
- if (typeof status === 'number' && (status === 429 || status === 402 || status >= 500)) return true;
161
- return SWITCH_WORTHY.test(String(err?.message || err));
162
- }
163
-
164
- /**
165
- * 현재 모델로 먼저 시도하고, 모델 교체로 풀릴 실패면 체인을 순서대로 밟는다.
166
- * 이미 화면에 글자가 나간 뒤에는 교체하지 않는다 — 다른 모델로 다시 스트리밍하면 앞서 보낸
167
- * 내용과 뒤섞인다. TurnRecovery 의 replay-safety 와 같은 판단이다.
168
- */
169
- async function runTurnWithFallback(session, args, onSwitch) {
170
- const chain = chainFor(session.model);
171
- const tried = new Set(session.model ? [session.model.provider + '/' + session.model.id] : []);
172
- let streamed = false;
173
- const turnArgs = { ...args, onTextDelta: d => { streamed = true; args.onTextDelta?.(d); } };
174
- let next = 0;
175
- for (;;) {
176
- try {
177
- return await session.runEphemeralTurn(turnArgs);
178
- } catch (err) {
179
- if (args.signal?.aborted || streamed || !isSwitchWorthy(err)) throw err;
180
- let switched;
181
- while (next < chain.length && !switched) {
182
- const parsed = parseSelector(chain[next++]);
183
- if (!parsed) continue;
184
- const key = parsed.provider + '/' + parsed.id;
185
- if (tried.has(key)) continue;
186
- tried.add(key);
187
- const model = session.modelRegistry.find(parsed.provider, parsed.id);
188
- if (!model) continue;
189
- try {
190
- await session.setModel(model);
191
- switched = model;
192
- } catch {}
193
- }
194
- if (!switched) throw err;
195
- onSwitch?.(switched);
196
- }
197
- }
198
- }
199
-
200
- async function disposeEntry(key, entry) {
201
- cache.delete(key);
202
- try { await entry.session.dispose(); } catch {}
203
- fs.rmSync(entry.copyPath, { force: true });
204
- }
205
-
206
- /** Session over a temp copy of the .jsonl; rebuilt when the original changes. */
207
- async function getSession(sessionPath) {
208
- const st = fs.statSync(sessionPath);
209
- const hit = cache.get(sessionPath);
210
- if (hit && hit.mtimeMs === st.mtimeMs && hit.size === st.size) {
211
- hit.lastUsed = Date.now();
212
- return hit.session;
213
- }
214
- if (hit) await disposeEntry(sessionPath, hit);
215
-
216
- const copyPath = path.join(
217
- TMP_DIR,
218
- createHash('sha1').update(sessionPath).digest('hex').slice(0, 16) + '.jsonl',
219
- );
220
- fs.copyFileSync(sessionPath, copyPath);
221
- const sessionManager = await SessionManager.open(copyPath, undefined, undefined, {
222
- suppressBreadcrumb: true,
223
- });
224
- const { session } = await createAgentSession({
225
- cwd: headerCwd(copyPath),
226
- sessionManager,
227
- enableMCP: false,
228
- enableLsp: false,
229
- enableIrc: false,
230
- disableExtensionDiscovery: true,
231
- // Private registry: several cached sessions may coexist in this process.
232
- agentRegistry: new AgentRegistry(),
233
- });
234
- cache.set(sessionPath, { mtimeMs: st.mtimeMs, size: st.size, lastUsed: Date.now(), session, copyPath });
235
- return session;
236
- }
237
-
238
- setInterval(() => {
239
- const now = Date.now();
240
- for (const [key, entry] of cache) {
241
- if (now - entry.lastUsed > IDLE_DISPOSE_MS) void disposeEntry(key, entry);
242
- }
243
- }, 60_000).unref?.();
244
-
245
- /**
246
- * 사이드챗은 도구가 없고 메인 세션 파일의 사본만 본다. 안내가 없으면 메인인 것처럼 진행 상태를
247
- * 단정하거나 메인 대신 작업을 약속한다. 그래서 매 질문 앞에 사본 시점과 자기 위치를 알린다.
248
- */
249
- function buildPromptText(question, history, snapshotAt) {
250
- const lines = [
251
- '[사이드챗 안내] 너는 메인 세션의 사본(마지막 기록 ' + snapshotAt.toLocaleString('ko-KR', { hour12: false }) + ')을 읽는 사이드챗이다. '
252
- + '도구가 없고, 이 시각 이후 메인이 한 일이나 지금 진행 중인 턴은 모른다. '
253
- + '진행·완료 상태를 말할 때는 "사본 기준"이라고 밝히고 이후 바뀌었을 수 있다고 알린다. '
254
- + '메인 대신 작업을 약속하거나 한 것처럼 말하지 않는다. 메인이 해야 할 일이면 사용자에게 이 답의 "메인에 보내기"를 권한다.',
255
- '',
256
- ];
257
- if (Array.isArray(history) && history.length > 0) {
258
- lines.push('(참고: 이 사이드채팅의 이전 문답. 메인 대화 기록에는 없는 내용이다.)');
259
- for (const turn of history.slice(-6)) {
260
- if (turn && typeof turn.q === 'string' && typeof turn.a === 'string') {
261
- lines.push('Q: ' + turn.q, 'A: ' + turn.a, '');
262
- }
263
- }
264
- }
265
- lines.push(question);
266
- return lines.join('\n');
267
- }
268
-
269
- const CORS = {
270
- 'Access-Control-Allow-Origin': ALLOW_ORIGIN,
271
- 'Access-Control-Allow-Methods': 'POST, GET, OPTIONS',
272
- 'Access-Control-Allow-Headers': 'Content-Type',
273
- 'Cache-Control': 'no-store',
274
- };
275
-
276
- function json(status, body) {
277
- return new Response(JSON.stringify(body), {
278
- status,
279
- headers: { ...CORS, 'Content-Type': 'application/json; charset=utf-8' },
280
- });
281
- }
282
-
283
- async function handleAsk(req) {
284
- let body;
285
- try { body = await req.json(); } catch { return json(400, { error: 'invalid JSON body' }); }
286
- const question = typeof body.question === 'string' ? body.question.trim() : '';
287
- if (!question) return json(400, { error: 'question required' });
288
- const resolved = path.resolve(String(body.sessionPath || ''));
289
- // Only session files under the default agent dir; this is a context loader, not a file reader.
290
- if (!resolved.toLowerCase().startsWith(SESSIONS_ROOT.toLowerCase() + path.sep) ||
291
- !resolved.endsWith('.jsonl') || !fs.existsSync(resolved)) {
292
- return json(400, { error: 'sessionPath must be an existing .jsonl under ' + SESSIONS_ROOT });
293
- }
294
-
295
- let session;
296
- try { session = await getSession(resolved); } catch (e) {
297
- return json(500, { error: 'session load failed: ' + String(e?.message || e) });
298
- }
299
-
300
- // 메인이 폴백으로 다른 모델에 있으면 사본도 그 모델로 맞춘다(소진된 모델 재호출 방지).
301
- await syncModelFromMain(session, resolved);
302
-
303
- const promptText = buildPromptText(question, body.history, fs.statSync(resolved).mtime);
304
- const enc = new TextEncoder();
305
- const stream = new ReadableStream({
306
- async start(controller) {
307
- const send = obj => { try { controller.enqueue(enc.encode(JSON.stringify(obj) + '\n')); } catch {} };
308
- try {
309
- const { replyText } = await runTurnWithFallback(
310
- session,
311
- {
312
- promptText,
313
- // 웹 사이드챗은 긴 답을 읽는 자리다. 기본 4 KiB 제한은 이미 스트리밍한 답을
314
- // done에서 잘라 바꿔 버리므로 푼다. 반복 줄 정리는 TUI /btw와 같이 둔다(이슈 #16).
315
- replyMaxBytes: Infinity,
316
- onTextDelta: d => send({ t: 'd', v: d }),
317
- signal: req.signal,
318
- },
319
- model => send({ t: 'm', v: model.provider + '/' + model.id }),
320
- );
321
- send({ t: 'done', v: replyText });
322
- } catch (e) {
323
- if (!req.signal.aborted) send({ t: 'err', v: String(e?.message || e) });
324
- } finally {
325
- try { controller.close(); } catch {}
326
- }
327
- },
328
- });
329
- return new Response(stream, {
330
- headers: { ...CORS, 'Content-Type': 'application/x-ndjson; charset=utf-8' },
331
- });
332
- }
333
-
334
- Bun.serve({
335
- port: PORT,
336
- hostname: '127.0.0.1',
337
- idleTimeout: 0, // ephemeral turns on big contexts can exceed the 10 s default
338
- async fetch(req) {
339
- const url = new URL(req.url);
340
- if (req.method === 'OPTIONS') return new Response(null, { status: 204, headers: CORS });
341
- if (url.pathname === '/health') return json(200, { ok: true, cached: cache.size });
342
- if (url.pathname === '/ask' && req.method === 'POST') return handleAsk(req);
343
- return json(404, { error: 'not found' });
344
- },
345
- });
346
- console.log('btw sidecar on http://127.0.0.1:' + PORT + '/ask');
1
+ // Sidecar: /btw-style ephemeral side chat for CUELO at http://127.0.0.1:30143/ask.
2
+ // MUST run under Bun (the omp SDK is TS sources importing bun: builtins); launch.ps1
3
+ // starts it with ~\.bun\bin\bun.exe. The UI lives in components/workspace/SideChatPanel.tsx.
4
+ //
5
+ // How it works: the panel POSTs { sessionPath, question, history? }. This server
6
+ // copies the session .jsonl to a temp file (SessionManager.open takes a single-writer
7
+ // lock, so the live file CUELO owns is never opened), builds an AgentSession over
8
+ // the copy, and calls session.runEphemeralTurn() — the same primitive the TUI's /btw
9
+ // uses: full session context, no tools, nothing persisted. Deltas stream back as
10
+ // NDJSON lines: {t:'d',v:delta} | {t:'done',v:full} | {t:'err',v:message}.
11
+ 'use strict';
12
+ import { createHash } from 'node:crypto';
13
+ import fs from 'node:fs';
14
+ import os from 'node:os';
15
+ import path from 'node:path';
16
+ import { pathToFileURL } from 'node:url';
17
+
18
+ const PORT = Number(process.env.OMP_BTW_PORT) || 30143;
19
+ const ALLOW_ORIGIN = 'http://127.0.0.1:' + (Number(process.env.PORT) || 30141);
20
+ const IDLE_DISPOSE_MS = 15 * 60_000;
21
+ const SESSIONS_ROOT = path.join(os.homedir(), '.omp', 'agent', 'sessions');
22
+ const TMP_DIR = path.join(os.tmpdir(), 'omp-btw');
23
+
24
+ // omp SDK out of CUELO's own node_modules, so both always share one SDK version.
25
+ // npm global prefix 를 바꿔 쓰는 PC 도 있으므로 기본 위치가 없으면 `npm root -g` 로 찾는다.
26
+ function resolveCueloDir() {
27
+ const candidates = [];
28
+ if (process.env.CUELO_DIR) candidates.push(process.env.CUELO_DIR);
29
+ // 공식 fallback 설치본은 upstream 이름(omp-web)으로 들어간다.
30
+ for (const name of ['cuelo', 'omp-web']) candidates.push(path.join(process.env.APPDATA || '', 'npm', 'node_modules', name));
31
+ try {
32
+ // 명령이 고정 문자열이라 주입 여지가 없다. execFileSync + shell:true 는
33
+ // Node 가 deprecation 경고를 내므로 execSync 를 쓴다.
34
+ const root = require('node:child_process')
35
+ .execSync('npm root -g', { encoding: 'utf8', windowsHide: true })
36
+ .trim();
37
+ if (root) for (const name of ['cuelo', 'omp-web']) candidates.push(path.join(root, name));
38
+ } catch { /* npm 이 없으면 기본 후보만 쓴다 */ }
39
+ const hit = candidates.find(dir => dir && fs.existsSync(path.join(dir, 'node_modules', '@oh-my-pi', 'pi-coding-agent', 'src', 'index.ts')));
40
+ return hit || candidates[candidates.length - 1];
41
+ }
42
+ const cueloDir = resolveCueloDir();
43
+ const sdkEntry = path.join(cueloDir, 'node_modules', '@oh-my-pi', 'pi-coding-agent', 'src', 'index.ts');
44
+ if (!fs.existsSync(sdkEntry)) {
45
+ console.error('omp SDK not found: ' + sdkEntry + '\nSet CUELO_DIR to the CUELO folder (the one with node_modules/@oh-my-pi), for example by starting it with `node install.mjs start`.');
46
+ process.exit(1);
47
+ }
48
+ const { createAgentSession, SessionManager, AgentRegistry } = await import(pathToFileURL(sdkEntry).href);
49
+
50
+ fs.mkdirSync(TMP_DIR, { recursive: true });
51
+
52
+ // sessionPath -> { mtimeMs, size, lastUsed, session, copyPath }
53
+ const cache = new Map();
54
+
55
+ function headerCwd(jsonlPath) {
56
+ try {
57
+ const firstLine = fs.readFileSync(jsonlPath, 'utf8').split('\n', 1)[0];
58
+ const cwd = JSON.parse(firstLine).cwd;
59
+ if (typeof cwd === 'string' && fs.existsSync(cwd)) return cwd;
60
+ } catch {}
61
+ return undefined;
62
+ }
63
+
64
+ /** 세션 파일에서 sessionId 를 얻는다. 헤더 항목이 우선, 없으면 파일명에서 뽑는다. */
65
+ function sessionIdOf(jsonlPath) {
66
+ try {
67
+ for (const line of fs.readFileSync(jsonlPath, 'utf8').slice(0, 16384).split('\n')) {
68
+ if (!line.trim()) continue;
69
+ const entry = JSON.parse(line);
70
+ if (entry.type === 'session' && typeof entry.id === 'string') return entry.id;
71
+ }
72
+ } catch {}
73
+ const m = /_([0-9a-f]{8}-[0-9a-f-]+)\.jsonl$/i.exec(path.basename(jsonlPath));
74
+ return m ? m[1] : undefined;
75
+ }
76
+
77
+ /**
78
+ * 사본 위에 새로 만든 AgentSession 은 설정된 default 모델로 복원된다. 메인 세션이 자동
79
+ * 폴백으로 다른 모델에서 돌고 있으면(주 모델 한도 소진 등) 사본은 소진된 모델을 그대로
80
+ * 다시 호출하고, runEphemeralTurn 은 TurnRecovery 를 거치지 않으므로 429 가 그대로
81
+ * 사용자에게 노출된다. 그래서 매 질문 전에 메인의 live state.model 로 맞춘다.
82
+ * 조회나 적용이 실패하면 조용히 기존 모델을 유지한다.
83
+ */
84
+ async function syncModelFromMain(session, jsonlPath) {
85
+ const sessionId = sessionIdOf(jsonlPath);
86
+ if (!sessionId) return;
87
+ let live;
88
+ try {
89
+ const res = await fetch(ALLOW_ORIGIN + '/api/agent/' + sessionId, {
90
+ headers: { Accept: 'application/json' },
91
+ signal: AbortSignal.timeout(3000),
92
+ });
93
+ if (!res.ok) return;
94
+ live = (await res.json())?.state?.model;
95
+ } catch { return; }
96
+ if (!live || typeof live.provider !== 'string' || typeof live.id !== 'string') return;
97
+ const current = session.model;
98
+ if (current && current.provider === live.provider && current.id === live.id) return;
99
+ const target = session.modelRegistry.find(live.provider, live.id);
100
+ if (!target) return;
101
+ try {
102
+ await session.setModel(target);
103
+ } catch {}
104
+ }
105
+
106
+ // --- 모델 폴백 --------------------------------------------------------------
107
+ // runEphemeralTurn 은 TurnRecovery 를 거치지 않으므로 retry.fallbackChains 가 적용되지
108
+ // 않는다(TUI /btw 와 같은 한계). 그래서 주 모델 한도가 소진되면 메인 세션은 폴백으로
109
+ // 멀쩡히 돌아가는데 사이드채팅만 429 를 그대로 뱉었다. 같은 설정을 직접 읽어 체인을 밟는다.
110
+
111
+ const CONFIG_PATH = path.join(os.homedir(), '.omp', 'agent', 'config.yml');
112
+ const THINKING_LEVELS = new Set(['off', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max']);
113
+
114
+ // config.yml 은 omp 재시작 없이 바뀔 수 있으므로 mtime 이 달라질 때만 다시 읽는다.
115
+ let chainCache = { mtimeMs: -1, chains: {} };
116
+ function fallbackChains() {
117
+ let st;
118
+ try { st = fs.statSync(CONFIG_PATH); } catch { return {}; }
119
+ if (st.mtimeMs !== chainCache.mtimeMs) {
120
+ const chains = {};
121
+ try {
122
+ const raw = Bun.YAML.parse(fs.readFileSync(CONFIG_PATH, 'utf8'))?.retry?.fallbackChains;
123
+ for (const key of Object.keys(raw || {})) {
124
+ if (Array.isArray(raw[key])) chains[key] = raw[key].filter(s => typeof s === 'string');
125
+ }
126
+ } catch {}
127
+ chainCache = { mtimeMs: st.mtimeMs, chains };
128
+ }
129
+ return chainCache.chains;
130
+ }
131
+
132
+ /** omp 와 같은 우선순위: 정확한 모델 키 > provider/* > default. 역할 키는 여기 해당 없음. */
133
+ function chainFor(model) {
134
+ if (!model) return [];
135
+ const chains = fallbackChains();
136
+ return chains[model.provider + '/' + model.id] ?? chains[model.provider + '/*'] ?? chains.default ?? [];
137
+ }
138
+
139
+ /**
140
+ * "provider/id" 또는 "provider/id:thinking" 을 나눈다. provider 는 첫 구간이고 나머지가
141
+ * id 다(openrouter/google/x 처럼 id 에 슬래시가 있을 수 있다). thinking 접미사는 레지스트리
142
+ * 조회에 방해되므로 떼어내기만 하고 적용하지는 않는다 — 여기서 필요한 건 모델 교체다.
143
+ */
144
+ function parseSelector(selector) {
145
+ let base = selector;
146
+ const colon = selector.lastIndexOf(':');
147
+ // 모델 id 자체에 콜론이 있을 수 있어 알려진 thinking 값일 때만 접미사로 본다.
148
+ if (colon > 0 && THINKING_LEVELS.has(selector.slice(colon + 1))) base = selector.slice(0, colon);
149
+ const slash = base.indexOf('/');
150
+ if (slash < 1 || slash === base.length - 1) return null;
151
+ return { provider: base.slice(0, slash), id: base.slice(slash + 1) };
152
+ }
153
+
154
+ // 모델 교체로 풀릴 만한 실패를 넓게 잡는다. 사이드채팅에서 다른 모델로 한 번 더 시도하는
155
+ // 비용은 낮고, 놓치면 사용자에게 429 가 그대로 보인다.
156
+ const SWITCH_WORTHY =
157
+ /\b(?:429|402)\b|rate.?limit|usage.?limit|quota|insufficient_quota|resource_exhausted|overloaded|over capacity|too many requests|service unavailable|bad gateway|gateway timeout|internal server error/i;
158
+ function isSwitchWorthy(err) {
159
+ const status = err?.status ?? err?.statusCode;
160
+ if (typeof status === 'number' && (status === 429 || status === 402 || status >= 500)) return true;
161
+ return SWITCH_WORTHY.test(String(err?.message || err));
162
+ }
163
+
164
+ /**
165
+ * 현재 모델로 먼저 시도하고, 모델 교체로 풀릴 실패면 체인을 순서대로 밟는다.
166
+ * 이미 화면에 글자가 나간 뒤에는 교체하지 않는다 — 다른 모델로 다시 스트리밍하면 앞서 보낸
167
+ * 내용과 뒤섞인다. TurnRecovery 의 replay-safety 와 같은 판단이다.
168
+ */
169
+ async function runTurnWithFallback(session, args, onSwitch) {
170
+ const chain = chainFor(session.model);
171
+ const tried = new Set(session.model ? [session.model.provider + '/' + session.model.id] : []);
172
+ let streamed = false;
173
+ const turnArgs = { ...args, onTextDelta: d => { streamed = true; args.onTextDelta?.(d); } };
174
+ let next = 0;
175
+ for (;;) {
176
+ try {
177
+ return await session.runEphemeralTurn(turnArgs);
178
+ } catch (err) {
179
+ if (args.signal?.aborted || streamed || !isSwitchWorthy(err)) throw err;
180
+ let switched;
181
+ while (next < chain.length && !switched) {
182
+ const parsed = parseSelector(chain[next++]);
183
+ if (!parsed) continue;
184
+ const key = parsed.provider + '/' + parsed.id;
185
+ if (tried.has(key)) continue;
186
+ tried.add(key);
187
+ const model = session.modelRegistry.find(parsed.provider, parsed.id);
188
+ if (!model) continue;
189
+ try {
190
+ await session.setModel(model);
191
+ switched = model;
192
+ } catch {}
193
+ }
194
+ if (!switched) throw err;
195
+ onSwitch?.(switched);
196
+ }
197
+ }
198
+ }
199
+
200
+ async function disposeEntry(key, entry) {
201
+ cache.delete(key);
202
+ try { await entry.session.dispose(); } catch {}
203
+ fs.rmSync(entry.copyPath, { force: true });
204
+ }
205
+
206
+ /** Session over a temp copy of the .jsonl; rebuilt when the original changes. */
207
+ async function getSession(sessionPath) {
208
+ const st = fs.statSync(sessionPath);
209
+ const hit = cache.get(sessionPath);
210
+ if (hit && hit.mtimeMs === st.mtimeMs && hit.size === st.size) {
211
+ hit.lastUsed = Date.now();
212
+ return hit.session;
213
+ }
214
+ if (hit) await disposeEntry(sessionPath, hit);
215
+
216
+ const copyPath = path.join(
217
+ TMP_DIR,
218
+ createHash('sha1').update(sessionPath).digest('hex').slice(0, 16) + '.jsonl',
219
+ );
220
+ fs.copyFileSync(sessionPath, copyPath);
221
+ const sessionManager = await SessionManager.open(copyPath, undefined, undefined, {
222
+ suppressBreadcrumb: true,
223
+ });
224
+ const { session } = await createAgentSession({
225
+ cwd: headerCwd(copyPath),
226
+ sessionManager,
227
+ enableMCP: false,
228
+ enableLsp: false,
229
+ enableIrc: false,
230
+ disableExtensionDiscovery: true,
231
+ // Private registry: several cached sessions may coexist in this process.
232
+ agentRegistry: new AgentRegistry(),
233
+ });
234
+ cache.set(sessionPath, { mtimeMs: st.mtimeMs, size: st.size, lastUsed: Date.now(), session, copyPath });
235
+ return session;
236
+ }
237
+
238
+ setInterval(() => {
239
+ const now = Date.now();
240
+ for (const [key, entry] of cache) {
241
+ if (now - entry.lastUsed > IDLE_DISPOSE_MS) void disposeEntry(key, entry);
242
+ }
243
+ }, 60_000).unref?.();
244
+
245
+ /**
246
+ * 사이드챗은 도구가 없고 메인 세션 파일의 사본만 본다. 안내가 없으면 메인인 것처럼 진행 상태를
247
+ * 단정하거나 메인 대신 작업을 약속한다. 그래서 매 질문 앞에 사본 시점과 자기 위치를 알린다.
248
+ */
249
+ function buildPromptText(question, history, snapshotAt) {
250
+ const lines = [
251
+ '[사이드챗 안내] 너는 메인 세션의 사본(마지막 기록 ' + snapshotAt.toLocaleString('ko-KR', { hour12: false }) + ')을 읽는 사이드챗이다. '
252
+ + '도구가 없고, 이 시각 이후 메인이 한 일이나 지금 진행 중인 턴은 모른다. '
253
+ + '진행·완료 상태를 말할 때는 "사본 기준"이라고 밝히고 이후 바뀌었을 수 있다고 알린다. '
254
+ + '메인 대신 작업을 약속하거나 한 것처럼 말하지 않는다. 메인이 해야 할 일이면 사용자에게 이 답의 "메인에 보내기"를 권한다.',
255
+ '',
256
+ ];
257
+ if (Array.isArray(history) && history.length > 0) {
258
+ lines.push('(참고: 이 사이드채팅의 이전 문답. 메인 대화 기록에는 없는 내용이다.)');
259
+ for (const turn of history.slice(-6)) {
260
+ if (turn && typeof turn.q === 'string' && typeof turn.a === 'string') {
261
+ lines.push('Q: ' + turn.q, 'A: ' + turn.a, '');
262
+ }
263
+ }
264
+ }
265
+ lines.push(question);
266
+ return lines.join('\n');
267
+ }
268
+
269
+ const CORS = {
270
+ 'Access-Control-Allow-Origin': ALLOW_ORIGIN,
271
+ 'Access-Control-Allow-Methods': 'POST, GET, OPTIONS',
272
+ 'Access-Control-Allow-Headers': 'Content-Type',
273
+ 'Cache-Control': 'no-store',
274
+ };
275
+
276
+ function json(status, body) {
277
+ return new Response(JSON.stringify(body), {
278
+ status,
279
+ headers: { ...CORS, 'Content-Type': 'application/json; charset=utf-8' },
280
+ });
281
+ }
282
+
283
+ async function handleAsk(req) {
284
+ let body;
285
+ try { body = await req.json(); } catch { return json(400, { error: 'invalid JSON body' }); }
286
+ const question = typeof body.question === 'string' ? body.question.trim() : '';
287
+ if (!question) return json(400, { error: 'question required' });
288
+ const resolved = path.resolve(String(body.sessionPath || ''));
289
+ // Only session files under the default agent dir; this is a context loader, not a file reader.
290
+ if (!resolved.toLowerCase().startsWith(SESSIONS_ROOT.toLowerCase() + path.sep) ||
291
+ !resolved.endsWith('.jsonl') || !fs.existsSync(resolved)) {
292
+ return json(400, { error: 'sessionPath must be an existing .jsonl under ' + SESSIONS_ROOT });
293
+ }
294
+
295
+ let session;
296
+ try { session = await getSession(resolved); } catch (e) {
297
+ return json(500, { error: 'session load failed: ' + String(e?.message || e) });
298
+ }
299
+
300
+ // 메인이 폴백으로 다른 모델에 있으면 사본도 그 모델로 맞춘다(소진된 모델 재호출 방지).
301
+ await syncModelFromMain(session, resolved);
302
+
303
+ const promptText = buildPromptText(question, body.history, fs.statSync(resolved).mtime);
304
+ const enc = new TextEncoder();
305
+ const stream = new ReadableStream({
306
+ async start(controller) {
307
+ const send = obj => { try { controller.enqueue(enc.encode(JSON.stringify(obj) + '\n')); } catch {} };
308
+ try {
309
+ const { replyText } = await runTurnWithFallback(
310
+ session,
311
+ {
312
+ promptText,
313
+ // 웹 사이드챗은 긴 답을 읽는 자리다. 기본 4 KiB 제한은 이미 스트리밍한 답을
314
+ // done에서 잘라 바꿔 버리므로 푼다. 반복 줄 정리는 TUI /btw와 같이 둔다(이슈 #16).
315
+ replyMaxBytes: Infinity,
316
+ onTextDelta: d => send({ t: 'd', v: d }),
317
+ signal: req.signal,
318
+ },
319
+ model => send({ t: 'm', v: model.provider + '/' + model.id }),
320
+ );
321
+ send({ t: 'done', v: replyText });
322
+ } catch (e) {
323
+ if (!req.signal.aborted) send({ t: 'err', v: String(e?.message || e) });
324
+ } finally {
325
+ try { controller.close(); } catch {}
326
+ }
327
+ },
328
+ });
329
+ return new Response(stream, {
330
+ headers: { ...CORS, 'Content-Type': 'application/x-ndjson; charset=utf-8' },
331
+ });
332
+ }
333
+
334
+ Bun.serve({
335
+ port: PORT,
336
+ hostname: '127.0.0.1',
337
+ idleTimeout: 0, // ephemeral turns on big contexts can exceed the 10 s default
338
+ async fetch(req) {
339
+ const url = new URL(req.url);
340
+ if (req.method === 'OPTIONS') return new Response(null, { status: 204, headers: CORS });
341
+ if (url.pathname === '/health') return json(200, { ok: true, cached: cache.size });
342
+ if (url.pathname === '/ask' && req.method === 'POST') return handleAsk(req);
343
+ return json(404, { error: 'not found' });
344
+ },
345
+ });
346
+ console.log('btw sidecar on http://127.0.0.1:' + PORT + '/ask');