synabun 1.3.0 → 2.0.0

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 (1163) hide show
  1. package/CHANGELOG.md +205 -662
  2. package/LICENSE-COMMERCIAL.md +33 -0
  3. package/NOTICE +17 -0
  4. package/README.md +175 -853
  5. package/THIRD-PARTY-LICENSES.md +136 -0
  6. package/benchmarks/browser-live-results.json +79 -0
  7. package/benchmarks/browser-v2-results.json +258 -0
  8. package/benchmarks/fixtures/browser/README.md +57 -0
  9. package/benchmarks/fixtures/browser/page-state.json +132 -0
  10. package/benchmarks/fixtures/browser/social.json +92 -0
  11. package/benchmarks/fixtures/browser/target.json +319 -0
  12. package/benchmarks/fixtures/desktop/README.md +58 -0
  13. package/benchmarks/fixtures/desktop/abstention.json +59 -0
  14. package/benchmarks/fixtures/desktop/adversarial.json +22 -0
  15. package/benchmarks/fixtures/desktop/forbidden.json +93 -0
  16. package/benchmarks/fixtures/desktop/generate.mjs +1023 -0
  17. package/benchmarks/fixtures/desktop/target.json +136 -0
  18. package/bin/synabun.js +30 -0
  19. package/docs/whatsapp.md +325 -0
  20. package/hooks/claude-code/post-plan.mjs +613 -391
  21. package/hooks/claude-code/post-remember.mjs +246 -122
  22. package/hooks/claude-code/pre-compact.mjs +171 -110
  23. package/hooks/claude-code/pre-task.mjs +133 -0
  24. package/hooks/claude-code/pre-websearch.mjs +15 -4
  25. package/hooks/claude-code/prompt-origin.mjs +94 -0
  26. package/hooks/claude-code/prompt-submit.mjs +1244 -861
  27. package/hooks/claude-code/session-start.mjs +114 -156
  28. package/hooks/claude-code/shared.mjs +1228 -368
  29. package/hooks/claude-code/state.mjs +167 -0
  30. package/hooks/claude-code/stop.mjs +515 -140
  31. package/hooks/claude-code/subagent-start.mjs +154 -0
  32. package/hooks/claude-code/subagent-stop.mjs +102 -0
  33. package/hooks/claude-code/task-gate.mjs +150 -0
  34. package/launcher.mjs +40 -0
  35. package/lib/claude-hooks.js +137 -0
  36. package/lib/claude-usage-audit.js +141 -0
  37. package/lib/client-config-repair.js +145 -0
  38. package/lib/data-home-migration.js +502 -0
  39. package/lib/desktop-pwa.js +206 -0
  40. package/lib/external-tools.js +221 -0
  41. package/lib/first-launch-protection.js +245 -0
  42. package/lib/packaged-runtime.js +110 -0
  43. package/lib/paths.d.ts +21 -0
  44. package/lib/paths.js +103 -0
  45. package/lib/start-launcher.js +1309 -0
  46. package/mcp-server/dist/config.d.ts +22 -0
  47. package/mcp-server/dist/config.d.ts.map +1 -0
  48. package/mcp-server/dist/config.js +109 -0
  49. package/mcp-server/dist/config.js.map +1 -0
  50. package/mcp-server/dist/http.d.ts +30 -0
  51. package/mcp-server/dist/http.d.ts.map +1 -0
  52. package/mcp-server/dist/http.js +276 -0
  53. package/mcp-server/dist/http.js.map +1 -0
  54. package/mcp-server/dist/index.d.ts +48 -0
  55. package/mcp-server/dist/index.d.ts.map +1 -0
  56. package/mcp-server/dist/index.js +538 -0
  57. package/mcp-server/dist/index.js.map +1 -0
  58. package/mcp-server/dist/preload-http.d.ts +3 -0
  59. package/mcp-server/dist/preload-http.d.ts.map +1 -0
  60. package/mcp-server/dist/preload-http.js +28 -0
  61. package/mcp-server/dist/preload-http.js.map +1 -0
  62. package/mcp-server/dist/preload.d.ts +3 -0
  63. package/mcp-server/dist/preload.d.ts.map +1 -0
  64. package/mcp-server/dist/preload.js +32 -0
  65. package/mcp-server/dist/preload.js.map +1 -0
  66. package/mcp-server/dist/scripts/migrate-qdrant-to-sqlite.d.ts +19 -0
  67. package/mcp-server/dist/scripts/migrate-qdrant-to-sqlite.d.ts.map +1 -0
  68. package/mcp-server/dist/scripts/migrate-qdrant-to-sqlite.js +403 -0
  69. package/mcp-server/dist/scripts/migrate-qdrant-to-sqlite.js.map +1 -0
  70. package/mcp-server/dist/scripts/setup-embeddings.d.ts +3 -0
  71. package/mcp-server/dist/scripts/setup-embeddings.d.ts.map +1 -0
  72. package/mcp-server/dist/scripts/setup-embeddings.js +15 -0
  73. package/mcp-server/dist/scripts/setup-embeddings.js.map +1 -0
  74. package/mcp-server/dist/services/assistant-judgments.d.ts +79 -0
  75. package/mcp-server/dist/services/assistant-judgments.d.ts.map +1 -0
  76. package/mcp-server/dist/services/assistant-judgments.js +128 -0
  77. package/mcp-server/dist/services/assistant-judgments.js.map +1 -0
  78. package/mcp-server/dist/services/browser-assist-gate.d.ts +314 -0
  79. package/mcp-server/dist/services/browser-assist-gate.d.ts.map +1 -0
  80. package/mcp-server/dist/services/browser-assist-gate.js +344 -0
  81. package/mcp-server/dist/services/browser-assist-gate.js.map +1 -0
  82. package/mcp-server/dist/services/browser-assist.d.ts +104 -0
  83. package/mcp-server/dist/services/browser-assist.d.ts.map +1 -0
  84. package/mcp-server/dist/services/browser-assist.js +282 -0
  85. package/mcp-server/dist/services/browser-assist.js.map +1 -0
  86. package/mcp-server/dist/services/browser-bench.d.ts +772 -0
  87. package/mcp-server/dist/services/browser-bench.d.ts.map +1 -0
  88. package/mcp-server/dist/services/browser-bench.js +424 -0
  89. package/mcp-server/dist/services/browser-bench.js.map +1 -0
  90. package/mcp-server/dist/services/browser-judgments.d.ts +196 -0
  91. package/mcp-server/dist/services/browser-judgments.d.ts.map +1 -0
  92. package/mcp-server/dist/services/browser-judgments.js +363 -0
  93. package/mcp-server/dist/services/browser-judgments.js.map +1 -0
  94. package/mcp-server/dist/services/browser-risk.d.ts +166 -0
  95. package/mcp-server/dist/services/browser-risk.d.ts.map +1 -0
  96. package/mcp-server/dist/services/browser-risk.js +458 -0
  97. package/mcp-server/dist/services/browser-risk.js.map +1 -0
  98. package/mcp-server/dist/services/categories.d.ts +61 -0
  99. package/mcp-server/dist/services/categories.d.ts.map +1 -0
  100. package/mcp-server/dist/services/categories.js +377 -0
  101. package/mcp-server/dist/services/categories.js.map +1 -0
  102. package/mcp-server/dist/services/codex-config-heal.d.ts +49 -0
  103. package/mcp-server/dist/services/codex-config-heal.d.ts.map +1 -0
  104. package/mcp-server/dist/services/codex-config-heal.js +346 -0
  105. package/mcp-server/dist/services/codex-config-heal.js.map +1 -0
  106. package/mcp-server/dist/services/desktop-assist-gate.d.ts +198 -0
  107. package/mcp-server/dist/services/desktop-assist-gate.d.ts.map +1 -0
  108. package/mcp-server/dist/services/desktop-assist-gate.js +202 -0
  109. package/mcp-server/dist/services/desktop-assist-gate.js.map +1 -0
  110. package/mcp-server/dist/services/desktop-assist.d.ts +104 -0
  111. package/mcp-server/dist/services/desktop-assist.d.ts.map +1 -0
  112. package/mcp-server/dist/services/desktop-assist.js +266 -0
  113. package/mcp-server/dist/services/desktop-assist.js.map +1 -0
  114. package/mcp-server/dist/services/desktop-bench.d.ts +553 -0
  115. package/mcp-server/dist/services/desktop-bench.d.ts.map +1 -0
  116. package/mcp-server/dist/services/desktop-bench.js +311 -0
  117. package/mcp-server/dist/services/desktop-bench.js.map +1 -0
  118. package/mcp-server/dist/services/desktop-client.d.ts +23 -0
  119. package/mcp-server/dist/services/desktop-client.d.ts.map +1 -0
  120. package/mcp-server/dist/services/desktop-client.js +79 -0
  121. package/mcp-server/dist/services/desktop-client.js.map +1 -0
  122. package/mcp-server/dist/services/desktop-judgments.d.ts +125 -0
  123. package/mcp-server/dist/services/desktop-judgments.d.ts.map +1 -0
  124. package/mcp-server/dist/services/desktop-judgments.js +177 -0
  125. package/mcp-server/dist/services/desktop-judgments.js.map +1 -0
  126. package/mcp-server/dist/services/desktop-risk.d.ts +283 -0
  127. package/mcp-server/dist/services/desktop-risk.d.ts.map +1 -0
  128. package/mcp-server/dist/services/desktop-risk.js +605 -0
  129. package/mcp-server/dist/services/desktop-risk.js.map +1 -0
  130. package/mcp-server/dist/services/discord.d.ts +112 -0
  131. package/mcp-server/dist/services/discord.d.ts.map +1 -0
  132. package/mcp-server/dist/services/discord.js +477 -0
  133. package/mcp-server/dist/services/discord.js.map +1 -0
  134. package/mcp-server/dist/services/edit-stale.d.ts +81 -0
  135. package/mcp-server/dist/services/edit-stale.d.ts.map +1 -0
  136. package/mcp-server/dist/services/edit-stale.js +358 -0
  137. package/mcp-server/dist/services/edit-stale.js.map +1 -0
  138. package/mcp-server/dist/services/embedding-service.d.ts +16 -0
  139. package/mcp-server/dist/services/embedding-service.d.ts.map +1 -0
  140. package/mcp-server/dist/services/embedding-service.js +126 -0
  141. package/mcp-server/dist/services/embedding-service.js.map +1 -0
  142. package/mcp-server/dist/services/embedding-worker.d.ts +2 -0
  143. package/mcp-server/dist/services/embedding-worker.d.ts.map +1 -0
  144. package/mcp-server/dist/services/embedding-worker.js +52 -0
  145. package/mcp-server/dist/services/embedding-worker.js.map +1 -0
  146. package/mcp-server/dist/services/fb-regions.d.ts +71 -0
  147. package/mcp-server/dist/services/fb-regions.d.ts.map +1 -0
  148. package/mcp-server/dist/services/fb-regions.js +211 -0
  149. package/mcp-server/dist/services/fb-regions.js.map +1 -0
  150. package/mcp-server/dist/services/file-checksums.d.ts +22 -0
  151. package/mcp-server/dist/services/file-checksums.d.ts.map +1 -0
  152. package/mcp-server/dist/services/file-checksums.js +55 -0
  153. package/mcp-server/dist/services/file-checksums.js.map +1 -0
  154. package/mcp-server/dist/services/file-excerpt.d.ts +24 -0
  155. package/mcp-server/dist/services/file-excerpt.d.ts.map +1 -0
  156. package/mcp-server/dist/services/file-excerpt.js +65 -0
  157. package/mcp-server/dist/services/file-excerpt.js.map +1 -0
  158. package/mcp-server/dist/services/identity.d.ts +124 -0
  159. package/mcp-server/dist/services/identity.d.ts.map +1 -0
  160. package/mcp-server/dist/services/identity.js +241 -0
  161. package/mcp-server/dist/services/identity.js.map +1 -0
  162. package/mcp-server/dist/services/local-embeddings.d.ts +2 -0
  163. package/mcp-server/dist/services/local-embeddings.d.ts.map +1 -0
  164. package/mcp-server/dist/services/local-embeddings.js +3 -0
  165. package/mcp-server/dist/services/local-embeddings.js.map +1 -0
  166. package/mcp-server/dist/services/memory-client.d.ts +4 -0
  167. package/mcp-server/dist/services/memory-client.d.ts.map +1 -0
  168. package/mcp-server/dist/services/memory-client.js +27 -0
  169. package/mcp-server/dist/services/memory-client.js.map +1 -0
  170. package/mcp-server/dist/services/memory-judge-backfill.d.ts +135 -0
  171. package/mcp-server/dist/services/memory-judge-backfill.d.ts.map +1 -0
  172. package/mcp-server/dist/services/memory-judge-backfill.js +510 -0
  173. package/mcp-server/dist/services/memory-judge-backfill.js.map +1 -0
  174. package/mcp-server/dist/services/memory-judgments.d.ts +362 -0
  175. package/mcp-server/dist/services/memory-judgments.d.ts.map +1 -0
  176. package/mcp-server/dist/services/memory-judgments.js +674 -0
  177. package/mcp-server/dist/services/memory-judgments.js.map +1 -0
  178. package/mcp-server/dist/services/memory-maintenance.d.ts +108 -0
  179. package/mcp-server/dist/services/memory-maintenance.d.ts.map +1 -0
  180. package/mcp-server/dist/services/memory-maintenance.js +403 -0
  181. package/mcp-server/dist/services/memory-maintenance.js.map +1 -0
  182. package/mcp-server/dist/services/memory-retrieval.d.ts +66 -0
  183. package/mcp-server/dist/services/memory-retrieval.d.ts.map +1 -0
  184. package/mcp-server/dist/services/memory-retrieval.js +412 -0
  185. package/mcp-server/dist/services/memory-retrieval.js.map +1 -0
  186. package/mcp-server/dist/services/memory-schema.d.ts +16 -0
  187. package/mcp-server/dist/services/memory-schema.d.ts.map +1 -0
  188. package/mcp-server/dist/services/memory-schema.js +282 -0
  189. package/mcp-server/dist/services/memory-schema.js.map +1 -0
  190. package/mcp-server/dist/services/memory-sessions.d.ts +24 -0
  191. package/mcp-server/dist/services/memory-sessions.d.ts.map +1 -0
  192. package/mcp-server/dist/services/memory-sessions.js +110 -0
  193. package/mcp-server/dist/services/memory-sessions.js.map +1 -0
  194. package/mcp-server/dist/services/memory-triage.d.ts +60 -0
  195. package/mcp-server/dist/services/memory-triage.d.ts.map +1 -0
  196. package/mcp-server/dist/services/memory-triage.js +189 -0
  197. package/mcp-server/dist/services/memory-triage.js.map +1 -0
  198. package/mcp-server/dist/services/memory-verification.d.ts +24 -0
  199. package/mcp-server/dist/services/memory-verification.d.ts.map +1 -0
  200. package/mcp-server/dist/services/memory-verification.js +92 -0
  201. package/mcp-server/dist/services/memory-verification.js.map +1 -0
  202. package/mcp-server/dist/services/memory-writer.d.ts +26 -0
  203. package/mcp-server/dist/services/memory-writer.d.ts.map +1 -0
  204. package/mcp-server/dist/services/memory-writer.js +126 -0
  205. package/mcp-server/dist/services/memory-writer.js.map +1 -0
  206. package/mcp-server/dist/services/neural-interface.d.ts +439 -0
  207. package/mcp-server/dist/services/neural-interface.d.ts.map +1 -0
  208. package/mcp-server/dist/services/neural-interface.js +1135 -0
  209. package/mcp-server/dist/services/neural-interface.js.map +1 -0
  210. package/mcp-server/dist/services/profiles.d.ts +120 -0
  211. package/mcp-server/dist/services/profiles.d.ts.map +1 -0
  212. package/mcp-server/dist/services/profiles.js +387 -0
  213. package/mcp-server/dist/services/profiles.js.map +1 -0
  214. package/mcp-server/dist/services/secret-gate.d.ts +29 -0
  215. package/mcp-server/dist/services/secret-gate.d.ts.map +1 -0
  216. package/mcp-server/dist/services/secret-gate.js +43 -0
  217. package/mcp-server/dist/services/secret-gate.js.map +1 -0
  218. package/mcp-server/dist/services/server-instructions.d.ts +10 -0
  219. package/mcp-server/dist/services/server-instructions.d.ts.map +1 -0
  220. package/mcp-server/dist/services/server-instructions.js +101 -0
  221. package/mcp-server/dist/services/server-instructions.js.map +1 -0
  222. package/mcp-server/dist/services/sqlite.d.ts +235 -0
  223. package/mcp-server/dist/services/sqlite.d.ts.map +1 -0
  224. package/mcp-server/dist/services/sqlite.js +1185 -0
  225. package/mcp-server/dist/services/sqlite.js.map +1 -0
  226. package/mcp-server/dist/services/typesafe-bench.d.ts +99 -0
  227. package/mcp-server/dist/services/typesafe-bench.d.ts.map +1 -0
  228. package/mcp-server/dist/services/typesafe-bench.js +218 -0
  229. package/mcp-server/dist/services/typesafe-bench.js.map +1 -0
  230. package/mcp-server/dist/services/typesafe-config.d.ts +342 -0
  231. package/mcp-server/dist/services/typesafe-config.d.ts.map +1 -0
  232. package/mcp-server/dist/services/typesafe-config.js +746 -0
  233. package/mcp-server/dist/services/typesafe-config.js.map +1 -0
  234. package/mcp-server/dist/services/typesafe.d.ts +213 -0
  235. package/mcp-server/dist/services/typesafe.d.ts.map +1 -0
  236. package/mcp-server/dist/services/typesafe.js +330 -0
  237. package/mcp-server/dist/services/typesafe.js.map +1 -0
  238. package/mcp-server/dist/services/vector-cache.d.ts +68 -0
  239. package/mcp-server/dist/services/vector-cache.d.ts.map +1 -0
  240. package/mcp-server/dist/services/vector-cache.js +271 -0
  241. package/mcp-server/dist/services/vector-cache.js.map +1 -0
  242. package/mcp-server/dist/services/whiteboard-geometry.d.ts +305 -0
  243. package/mcp-server/dist/services/whiteboard-geometry.d.ts.map +1 -0
  244. package/mcp-server/dist/services/whiteboard-geometry.js +666 -0
  245. package/mcp-server/dist/services/whiteboard-geometry.js.map +1 -0
  246. package/mcp-server/dist/tools/agents.d.ts +212 -0
  247. package/mcp-server/dist/tools/agents.d.ts.map +1 -0
  248. package/mcp-server/dist/tools/agents.js +407 -0
  249. package/mcp-server/dist/tools/agents.js.map +1 -0
  250. package/mcp-server/dist/tools/bluesky.d.ts +39 -0
  251. package/mcp-server/dist/tools/bluesky.d.ts.map +1 -0
  252. package/mcp-server/dist/tools/bluesky.js +1144 -0
  253. package/mcp-server/dist/tools/bluesky.js.map +1 -0
  254. package/mcp-server/dist/tools/browser-advanced.d.ts +58 -0
  255. package/mcp-server/dist/tools/browser-advanced.d.ts.map +1 -0
  256. package/mcp-server/dist/tools/browser-advanced.js +106 -0
  257. package/mcp-server/dist/tools/browser-advanced.js.map +1 -0
  258. package/mcp-server/dist/tools/browser-batch.d.ts +99 -0
  259. package/mcp-server/dist/tools/browser-batch.d.ts.map +1 -0
  260. package/mcp-server/dist/tools/browser-batch.js +187 -0
  261. package/mcp-server/dist/tools/browser-batch.js.map +1 -0
  262. package/mcp-server/dist/tools/browser-cheatsheet.d.ts +21 -0
  263. package/mcp-server/dist/tools/browser-cheatsheet.d.ts.map +1 -0
  264. package/mcp-server/dist/tools/browser-cheatsheet.js +144 -0
  265. package/mcp-server/dist/tools/browser-cheatsheet.js.map +1 -0
  266. package/mcp-server/dist/tools/browser-interact.d.ts +239 -0
  267. package/mcp-server/dist/tools/browser-interact.d.ts.map +1 -0
  268. package/mcp-server/dist/tools/browser-interact.js +300 -0
  269. package/mcp-server/dist/tools/browser-interact.js.map +1 -0
  270. package/mcp-server/dist/tools/browser-navigate.d.ts +90 -0
  271. package/mcp-server/dist/tools/browser-navigate.d.ts.map +1 -0
  272. package/mcp-server/dist/tools/browser-navigate.js +142 -0
  273. package/mcp-server/dist/tools/browser-navigate.js.map +1 -0
  274. package/mcp-server/dist/tools/browser-observe.d.ts +561 -0
  275. package/mcp-server/dist/tools/browser-observe.d.ts.map +1 -0
  276. package/mcp-server/dist/tools/browser-observe.js +2009 -0
  277. package/mcp-server/dist/tools/browser-observe.js.map +1 -0
  278. package/mcp-server/dist/tools/browser.d.ts +34 -0
  279. package/mcp-server/dist/tools/browser.d.ts.map +1 -0
  280. package/mcp-server/dist/tools/browser.js +112 -0
  281. package/mcp-server/dist/tools/browser.js.map +1 -0
  282. package/mcp-server/dist/tools/card-tools.d.ts +82 -0
  283. package/mcp-server/dist/tools/card-tools.d.ts.map +1 -0
  284. package/mcp-server/dist/tools/card-tools.js +137 -0
  285. package/mcp-server/dist/tools/card-tools.js.map +1 -0
  286. package/mcp-server/dist/tools/card.d.ts +8 -0
  287. package/mcp-server/dist/tools/card.d.ts.map +1 -0
  288. package/mcp-server/dist/tools/card.js +16 -0
  289. package/mcp-server/dist/tools/card.js.map +1 -0
  290. package/mcp-server/dist/tools/category.d.ts +32 -0
  291. package/mcp-server/dist/tools/category.d.ts.map +1 -0
  292. package/mcp-server/dist/tools/category.js +290 -0
  293. package/mcp-server/dist/tools/category.js.map +1 -0
  294. package/mcp-server/dist/tools/choice.d.ts +160 -0
  295. package/mcp-server/dist/tools/choice.d.ts.map +1 -0
  296. package/mcp-server/dist/tools/choice.js +80 -0
  297. package/mcp-server/dist/tools/choice.js.map +1 -0
  298. package/mcp-server/dist/tools/computer.d.ts +71 -0
  299. package/mcp-server/dist/tools/computer.d.ts.map +1 -0
  300. package/mcp-server/dist/tools/computer.js +174 -0
  301. package/mcp-server/dist/tools/computer.js.map +1 -0
  302. package/mcp-server/dist/tools/discord-channel.d.ts +44 -0
  303. package/mcp-server/dist/tools/discord-channel.d.ts.map +1 -0
  304. package/mcp-server/dist/tools/discord-channel.js +236 -0
  305. package/mcp-server/dist/tools/discord-channel.js.map +1 -0
  306. package/mcp-server/dist/tools/discord-guild.d.ts +20 -0
  307. package/mcp-server/dist/tools/discord-guild.d.ts.map +1 -0
  308. package/mcp-server/dist/tools/discord-guild.js +138 -0
  309. package/mcp-server/dist/tools/discord-guild.js.map +1 -0
  310. package/mcp-server/dist/tools/discord-member.d.ts +26 -0
  311. package/mcp-server/dist/tools/discord-member.d.ts.map +1 -0
  312. package/mcp-server/dist/tools/discord-member.js +122 -0
  313. package/mcp-server/dist/tools/discord-member.js.map +1 -0
  314. package/mcp-server/dist/tools/discord-message.d.ts +36 -0
  315. package/mcp-server/dist/tools/discord-message.d.ts.map +1 -0
  316. package/mcp-server/dist/tools/discord-message.js +184 -0
  317. package/mcp-server/dist/tools/discord-message.js.map +1 -0
  318. package/mcp-server/dist/tools/discord-onboarding.d.ts +32 -0
  319. package/mcp-server/dist/tools/discord-onboarding.d.ts.map +1 -0
  320. package/mcp-server/dist/tools/discord-onboarding.js +179 -0
  321. package/mcp-server/dist/tools/discord-onboarding.js.map +1 -0
  322. package/mcp-server/dist/tools/discord-role.d.ts +34 -0
  323. package/mcp-server/dist/tools/discord-role.d.ts.map +1 -0
  324. package/mcp-server/dist/tools/discord-role.js +178 -0
  325. package/mcp-server/dist/tools/discord-role.js.map +1 -0
  326. package/mcp-server/dist/tools/discord-thread.d.ts +28 -0
  327. package/mcp-server/dist/tools/discord-thread.d.ts.map +1 -0
  328. package/mcp-server/dist/tools/discord-thread.js +88 -0
  329. package/mcp-server/dist/tools/discord-thread.js.map +1 -0
  330. package/mcp-server/dist/tools/discord-webhook.d.ts +32 -0
  331. package/mcp-server/dist/tools/discord-webhook.d.ts.map +1 -0
  332. package/mcp-server/dist/tools/discord-webhook.js +154 -0
  333. package/mcp-server/dist/tools/discord-webhook.js.map +1 -0
  334. package/mcp-server/dist/tools/discord.d.ts +7 -0
  335. package/mcp-server/dist/tools/discord.d.ts.map +1 -0
  336. package/mcp-server/dist/tools/discord.js +25 -0
  337. package/mcp-server/dist/tools/discord.js.map +1 -0
  338. package/mcp-server/dist/tools/fb-groups.d.ts +98 -0
  339. package/mcp-server/dist/tools/fb-groups.d.ts.map +1 -0
  340. package/mcp-server/dist/tools/fb-groups.js +369 -0
  341. package/mcp-server/dist/tools/fb-groups.js.map +1 -0
  342. package/mcp-server/dist/tools/forget.d.ts +14 -0
  343. package/mcp-server/dist/tools/forget.d.ts.map +1 -0
  344. package/mcp-server/dist/tools/forget.js +26 -0
  345. package/mcp-server/dist/tools/forget.js.map +1 -0
  346. package/mcp-server/dist/tools/git-tools.d.ts +24 -0
  347. package/mcp-server/dist/tools/git-tools.d.ts.map +1 -0
  348. package/mcp-server/dist/tools/git-tools.js +107 -0
  349. package/mcp-server/dist/tools/git-tools.js.map +1 -0
  350. package/mcp-server/dist/tools/git.d.ts +7 -0
  351. package/mcp-server/dist/tools/git.d.ts.map +1 -0
  352. package/mcp-server/dist/tools/git.js +11 -0
  353. package/mcp-server/dist/tools/git.js.map +1 -0
  354. package/mcp-server/dist/tools/gsc/experience.d.ts +135 -0
  355. package/mcp-server/dist/tools/gsc/experience.d.ts.map +1 -0
  356. package/mcp-server/dist/tools/gsc/experience.js +351 -0
  357. package/mcp-server/dist/tools/gsc/experience.js.map +1 -0
  358. package/mcp-server/dist/tools/gsc/helpers.d.ts +78 -0
  359. package/mcp-server/dist/tools/gsc/helpers.d.ts.map +1 -0
  360. package/mcp-server/dist/tools/gsc/helpers.js +258 -0
  361. package/mcp-server/dist/tools/gsc/helpers.js.map +1 -0
  362. package/mcp-server/dist/tools/gsc/indexing.d.ts +121 -0
  363. package/mcp-server/dist/tools/gsc/indexing.d.ts.map +1 -0
  364. package/mcp-server/dist/tools/gsc/indexing.js +357 -0
  365. package/mcp-server/dist/tools/gsc/indexing.js.map +1 -0
  366. package/mcp-server/dist/tools/gsc/inspect.d.ts +78 -0
  367. package/mcp-server/dist/tools/gsc/inspect.d.ts.map +1 -0
  368. package/mcp-server/dist/tools/gsc/inspect.js +321 -0
  369. package/mcp-server/dist/tools/gsc/inspect.js.map +1 -0
  370. package/mcp-server/dist/tools/gsc/navigate.d.ts +44 -0
  371. package/mcp-server/dist/tools/gsc/navigate.d.ts.map +1 -0
  372. package/mcp-server/dist/tools/gsc/navigate.js +197 -0
  373. package/mcp-server/dist/tools/gsc/navigate.js.map +1 -0
  374. package/mcp-server/dist/tools/gsc/performance.d.ts +84 -0
  375. package/mcp-server/dist/tools/gsc/performance.d.ts.map +1 -0
  376. package/mcp-server/dist/tools/gsc/performance.js +274 -0
  377. package/mcp-server/dist/tools/gsc/performance.js.map +1 -0
  378. package/mcp-server/dist/tools/gsc/settings.d.ts +153 -0
  379. package/mcp-server/dist/tools/gsc/settings.d.ts.map +1 -0
  380. package/mcp-server/dist/tools/gsc/settings.js +425 -0
  381. package/mcp-server/dist/tools/gsc/settings.js.map +1 -0
  382. package/mcp-server/dist/tools/gsc.d.ts +7 -0
  383. package/mcp-server/dist/tools/gsc.d.ts.map +1 -0
  384. package/mcp-server/dist/tools/gsc.js +51 -0
  385. package/mcp-server/dist/tools/gsc.js.map +1 -0
  386. package/mcp-server/dist/tools/image-tools.d.ts +24 -0
  387. package/mcp-server/dist/tools/image-tools.d.ts.map +1 -0
  388. package/mcp-server/dist/tools/image-tools.js +80 -0
  389. package/mcp-server/dist/tools/image-tools.js.map +1 -0
  390. package/mcp-server/dist/tools/image.d.ts +10 -0
  391. package/mcp-server/dist/tools/image.d.ts.map +1 -0
  392. package/mcp-server/dist/tools/image.js +14 -0
  393. package/mcp-server/dist/tools/image.js.map +1 -0
  394. package/mcp-server/dist/tools/leonardo-browser-tools.d.ts +97 -0
  395. package/mcp-server/dist/tools/leonardo-browser-tools.d.ts.map +1 -0
  396. package/mcp-server/dist/tools/leonardo-browser-tools.js +228 -0
  397. package/mcp-server/dist/tools/leonardo-browser-tools.js.map +1 -0
  398. package/mcp-server/dist/tools/leonardo.d.ts +13 -0
  399. package/mcp-server/dist/tools/leonardo.d.ts.map +1 -0
  400. package/mcp-server/dist/tools/leonardo.js +21 -0
  401. package/mcp-server/dist/tools/leonardo.js.map +1 -0
  402. package/mcp-server/dist/tools/loop.d.ts +30 -0
  403. package/mcp-server/dist/tools/loop.d.ts.map +1 -0
  404. package/mcp-server/dist/tools/loop.js +401 -0
  405. package/mcp-server/dist/tools/loop.js.map +1 -0
  406. package/mcp-server/dist/tools/memories.d.ts +56 -0
  407. package/mcp-server/dist/tools/memories.d.ts.map +1 -0
  408. package/mcp-server/dist/tools/memories.js +171 -0
  409. package/mcp-server/dist/tools/memories.js.map +1 -0
  410. package/mcp-server/dist/tools/morelogin.d.ts +24 -0
  411. package/mcp-server/dist/tools/morelogin.d.ts.map +1 -0
  412. package/mcp-server/dist/tools/morelogin.js +79 -0
  413. package/mcp-server/dist/tools/morelogin.js.map +1 -0
  414. package/mcp-server/dist/tools/profile.d.ts +17 -0
  415. package/mcp-server/dist/tools/profile.d.ts.map +1 -0
  416. package/mcp-server/dist/tools/profile.js +114 -0
  417. package/mcp-server/dist/tools/profile.js.map +1 -0
  418. package/mcp-server/dist/tools/recall.d.ts +71 -0
  419. package/mcp-server/dist/tools/recall.d.ts.map +1 -0
  420. package/mcp-server/dist/tools/recall.js +147 -0
  421. package/mcp-server/dist/tools/recall.js.map +1 -0
  422. package/mcp-server/dist/tools/reflect.d.ts +56 -0
  423. package/mcp-server/dist/tools/reflect.d.ts.map +1 -0
  424. package/mcp-server/dist/tools/reflect.js +157 -0
  425. package/mcp-server/dist/tools/reflect.js.map +1 -0
  426. package/mcp-server/dist/tools/remember.d.ts +62 -0
  427. package/mcp-server/dist/tools/remember.d.ts.map +1 -0
  428. package/mcp-server/dist/tools/remember.js +201 -0
  429. package/mcp-server/dist/tools/remember.js.map +1 -0
  430. package/mcp-server/dist/tools/response.d.ts +23 -0
  431. package/mcp-server/dist/tools/response.d.ts.map +1 -0
  432. package/mcp-server/dist/tools/response.js +24 -0
  433. package/mcp-server/dist/tools/response.js.map +1 -0
  434. package/mcp-server/dist/tools/restore.d.ts +14 -0
  435. package/mcp-server/dist/tools/restore.d.ts.map +1 -0
  436. package/mcp-server/dist/tools/restore.js +23 -0
  437. package/mcp-server/dist/tools/restore.js.map +1 -0
  438. package/mcp-server/dist/tools/style-guide-tools.d.ts +51 -0
  439. package/mcp-server/dist/tools/style-guide-tools.d.ts.map +1 -0
  440. package/mcp-server/dist/tools/style-guide-tools.js +231 -0
  441. package/mcp-server/dist/tools/style-guide-tools.js.map +1 -0
  442. package/mcp-server/dist/tools/style-guide.d.ts +8 -0
  443. package/mcp-server/dist/tools/style-guide.d.ts.map +1 -0
  444. package/mcp-server/dist/tools/style-guide.js +12 -0
  445. package/mcp-server/dist/tools/style-guide.js.map +1 -0
  446. package/mcp-server/dist/tools/sync.d.ts +20 -0
  447. package/mcp-server/dist/tools/sync.d.ts.map +1 -0
  448. package/mcp-server/dist/tools/sync.js +156 -0
  449. package/mcp-server/dist/tools/sync.js.map +1 -0
  450. package/mcp-server/dist/tools/tictactoe-tools.d.ts +18 -0
  451. package/mcp-server/dist/tools/tictactoe-tools.d.ts.map +1 -0
  452. package/mcp-server/dist/tools/tictactoe-tools.js +78 -0
  453. package/mcp-server/dist/tools/tictactoe-tools.js.map +1 -0
  454. package/mcp-server/dist/tools/tictactoe.d.ts +7 -0
  455. package/mcp-server/dist/tools/tictactoe.d.ts.map +1 -0
  456. package/mcp-server/dist/tools/tictactoe.js +11 -0
  457. package/mcp-server/dist/tools/tictactoe.js.map +1 -0
  458. package/mcp-server/dist/tools/utils.d.ts +19 -0
  459. package/mcp-server/dist/tools/utils.d.ts.map +1 -0
  460. package/mcp-server/dist/tools/utils.js +54 -0
  461. package/mcp-server/dist/tools/utils.js.map +1 -0
  462. package/mcp-server/dist/tools/whiteboard-format.d.ts +88 -0
  463. package/mcp-server/dist/tools/whiteboard-format.d.ts.map +1 -0
  464. package/mcp-server/dist/tools/whiteboard-format.js +337 -0
  465. package/mcp-server/dist/tools/whiteboard-format.js.map +1 -0
  466. package/mcp-server/dist/tools/whiteboard-tools.d.ts +326 -0
  467. package/mcp-server/dist/tools/whiteboard-tools.d.ts.map +1 -0
  468. package/mcp-server/dist/tools/whiteboard-tools.js +222 -0
  469. package/mcp-server/dist/tools/whiteboard-tools.js.map +1 -0
  470. package/mcp-server/dist/tools/whiteboard.d.ts +8 -0
  471. package/mcp-server/dist/tools/whiteboard.d.ts.map +1 -0
  472. package/mcp-server/dist/tools/whiteboard.js +16 -0
  473. package/mcp-server/dist/tools/whiteboard.js.map +1 -0
  474. package/mcp-server/dist/tools/youtube/analytics.d.ts +46 -0
  475. package/mcp-server/dist/tools/youtube/analytics.d.ts.map +1 -0
  476. package/mcp-server/dist/tools/youtube/analytics.js +83 -0
  477. package/mcp-server/dist/tools/youtube/analytics.js.map +1 -0
  478. package/mcp-server/dist/tools/youtube/dedup.d.ts +35 -0
  479. package/mcp-server/dist/tools/youtube/dedup.d.ts.map +1 -0
  480. package/mcp-server/dist/tools/youtube/dedup.js +56 -0
  481. package/mcp-server/dist/tools/youtube/dedup.js.map +1 -0
  482. package/mcp-server/dist/tools/youtube/download.d.ts +31 -0
  483. package/mcp-server/dist/tools/youtube/download.d.ts.map +1 -0
  484. package/mcp-server/dist/tools/youtube/download.js +30 -0
  485. package/mcp-server/dist/tools/youtube/download.js.map +1 -0
  486. package/mcp-server/dist/tools/youtube/helpers.d.ts +66 -0
  487. package/mcp-server/dist/tools/youtube/helpers.d.ts.map +1 -0
  488. package/mcp-server/dist/tools/youtube/helpers.js +143 -0
  489. package/mcp-server/dist/tools/youtube/helpers.js.map +1 -0
  490. package/mcp-server/dist/tools/youtube/media.d.ts +43 -0
  491. package/mcp-server/dist/tools/youtube/media.d.ts.map +1 -0
  492. package/mcp-server/dist/tools/youtube/media.js +97 -0
  493. package/mcp-server/dist/tools/youtube/media.js.map +1 -0
  494. package/mcp-server/dist/tools/youtube/metadata.d.ts +33 -0
  495. package/mcp-server/dist/tools/youtube/metadata.d.ts.map +1 -0
  496. package/mcp-server/dist/tools/youtube/metadata.js +106 -0
  497. package/mcp-server/dist/tools/youtube/metadata.js.map +1 -0
  498. package/mcp-server/dist/tools/youtube/navigate.d.ts +30 -0
  499. package/mcp-server/dist/tools/youtube/navigate.d.ts.map +1 -0
  500. package/mcp-server/dist/tools/youtube/navigate.js +71 -0
  501. package/mcp-server/dist/tools/youtube/navigate.js.map +1 -0
  502. package/mcp-server/dist/tools/youtube/playlist.d.ts +29 -0
  503. package/mcp-server/dist/tools/youtube/playlist.d.ts.map +1 -0
  504. package/mcp-server/dist/tools/youtube/playlist.js +128 -0
  505. package/mcp-server/dist/tools/youtube/playlist.js.map +1 -0
  506. package/mcp-server/dist/tools/youtube/schedule.d.ts +31 -0
  507. package/mcp-server/dist/tools/youtube/schedule.d.ts.map +1 -0
  508. package/mcp-server/dist/tools/youtube/schedule.js +147 -0
  509. package/mcp-server/dist/tools/youtube/schedule.js.map +1 -0
  510. package/mcp-server/dist/tools/youtube/source.d.ts +36 -0
  511. package/mcp-server/dist/tools/youtube/source.d.ts.map +1 -0
  512. package/mcp-server/dist/tools/youtube/source.js +85 -0
  513. package/mcp-server/dist/tools/youtube/source.js.map +1 -0
  514. package/mcp-server/dist/tools/youtube/upload.d.ts +53 -0
  515. package/mcp-server/dist/tools/youtube/upload.d.ts.map +1 -0
  516. package/mcp-server/dist/tools/youtube/upload.js +255 -0
  517. package/mcp-server/dist/tools/youtube/upload.js.map +1 -0
  518. package/mcp-server/dist/tools/youtube.d.ts +12 -0
  519. package/mcp-server/dist/tools/youtube.d.ts.map +1 -0
  520. package/mcp-server/dist/tools/youtube.js +38 -0
  521. package/mcp-server/dist/tools/youtube.js.map +1 -0
  522. package/mcp-server/dist/tui.d.ts +3 -0
  523. package/mcp-server/dist/tui.d.ts.map +1 -0
  524. package/mcp-server/dist/tui.js +806 -0
  525. package/mcp-server/dist/tui.js.map +1 -0
  526. package/mcp-server/dist/types.d.ts +77 -0
  527. package/mcp-server/dist/types.d.ts.map +1 -0
  528. package/mcp-server/dist/types.js +2 -0
  529. package/mcp-server/dist/types.js.map +1 -0
  530. package/mcp-server/package-lock.json +4798 -0
  531. package/mcp-server/package.json +55 -38
  532. package/mcp-server/run.mjs +12 -2
  533. package/mcp-server/src/config.ts +48 -3
  534. package/mcp-server/src/http.ts +296 -93
  535. package/mcp-server/src/index.ts +546 -203
  536. package/mcp-server/src/preload-http.ts +19 -18
  537. package/mcp-server/src/preload.ts +19 -18
  538. package/mcp-server/src/scripts/setup-embeddings.ts +9 -0
  539. package/mcp-server/src/services/assistant-judgments.ts +157 -0
  540. package/mcp-server/src/services/browser-assist-gate.ts +383 -0
  541. package/mcp-server/src/services/browser-assist.ts +267 -0
  542. package/mcp-server/src/services/browser-bench.ts +477 -0
  543. package/mcp-server/src/services/browser-judgments.ts +414 -0
  544. package/mcp-server/src/services/browser-risk.ts +484 -0
  545. package/mcp-server/src/services/categories.ts +36 -19
  546. package/mcp-server/src/services/codex-config-heal.ts +403 -0
  547. package/mcp-server/src/services/desktop-assist-gate.ts +251 -0
  548. package/mcp-server/src/services/desktop-assist.ts +308 -0
  549. package/mcp-server/src/services/desktop-bench.ts +372 -0
  550. package/mcp-server/src/services/desktop-client.ts +76 -0
  551. package/mcp-server/src/services/desktop-judgments.ts +218 -0
  552. package/mcp-server/src/services/desktop-risk.ts +743 -0
  553. package/mcp-server/src/services/edit-stale.ts +299 -0
  554. package/mcp-server/src/services/embedding-service.ts +108 -0
  555. package/mcp-server/src/services/embedding-worker.ts +46 -0
  556. package/mcp-server/src/services/fb-regions.ts +221 -0
  557. package/mcp-server/src/services/file-checksums.ts +26 -9
  558. package/mcp-server/src/services/file-excerpt.ts +59 -0
  559. package/mcp-server/src/services/identity.ts +291 -0
  560. package/mcp-server/src/services/local-embeddings.ts +2 -77
  561. package/mcp-server/src/services/memory-client.ts +23 -0
  562. package/mcp-server/src/services/memory-judge-backfill.ts +462 -0
  563. package/mcp-server/src/services/memory-judgments.ts +901 -0
  564. package/mcp-server/src/services/memory-maintenance.ts +360 -0
  565. package/mcp-server/src/services/memory-retrieval.ts +349 -0
  566. package/mcp-server/src/services/memory-schema.ts +280 -0
  567. package/mcp-server/src/services/memory-sessions.ts +87 -0
  568. package/mcp-server/src/services/memory-triage.ts +182 -0
  569. package/mcp-server/src/services/memory-verification.ts +68 -0
  570. package/mcp-server/src/services/memory-writer.ts +125 -0
  571. package/mcp-server/src/services/neural-interface.ts +1217 -73
  572. package/mcp-server/src/services/profiles.ts +428 -0
  573. package/mcp-server/src/services/secret-gate.ts +52 -0
  574. package/mcp-server/src/services/server-instructions.ts +122 -0
  575. package/mcp-server/src/services/sqlite.ts +1456 -823
  576. package/mcp-server/src/services/typesafe-bench.ts +247 -0
  577. package/mcp-server/src/services/typesafe-config.ts +790 -0
  578. package/mcp-server/src/services/typesafe.ts +390 -0
  579. package/mcp-server/src/services/vector-cache.ts +255 -0
  580. package/mcp-server/src/services/whiteboard-geometry.ts +711 -0
  581. package/mcp-server/src/tools/agents.ts +539 -0
  582. package/mcp-server/src/tools/bluesky.ts +1186 -0
  583. package/mcp-server/src/tools/browser-advanced.ts +15 -6
  584. package/mcp-server/src/tools/browser-batch.ts +178 -0
  585. package/mcp-server/src/tools/browser-cheatsheet.ts +155 -0
  586. package/mcp-server/src/tools/browser-interact.ts +362 -255
  587. package/mcp-server/src/tools/browser-navigate.ts +164 -77
  588. package/mcp-server/src/tools/browser-observe.ts +2222 -1330
  589. package/mcp-server/src/tools/browser.ts +181 -107
  590. package/mcp-server/src/tools/card.ts +7 -5
  591. package/mcp-server/src/tools/choice.ts +103 -0
  592. package/mcp-server/src/tools/computer.ts +190 -0
  593. package/mcp-server/src/tools/discord.ts +10 -23
  594. package/mcp-server/src/tools/fb-groups.ts +387 -0
  595. package/mcp-server/src/tools/forget.ts +1 -1
  596. package/mcp-server/src/tools/git.ts +3 -1
  597. package/mcp-server/src/tools/gsc/experience.ts +440 -0
  598. package/mcp-server/src/tools/gsc/helpers.ts +288 -0
  599. package/mcp-server/src/tools/gsc/indexing.ts +495 -0
  600. package/mcp-server/src/tools/gsc/inspect.ts +391 -0
  601. package/mcp-server/src/tools/gsc/navigate.ts +236 -0
  602. package/mcp-server/src/tools/gsc/performance.ts +352 -0
  603. package/mcp-server/src/tools/gsc/settings.ts +564 -0
  604. package/mcp-server/src/tools/gsc.ts +95 -0
  605. package/mcp-server/src/tools/image.ts +3 -1
  606. package/mcp-server/src/tools/leonardo-browser-tools.ts +47 -37
  607. package/mcp-server/src/tools/leonardo.ts +7 -5
  608. package/mcp-server/src/tools/loop.ts +118 -25
  609. package/mcp-server/src/tools/memories.ts +65 -5
  610. package/mcp-server/src/tools/morelogin.ts +81 -0
  611. package/mcp-server/src/tools/profile.ts +122 -0
  612. package/mcp-server/src/tools/recall.ts +105 -261
  613. package/mcp-server/src/tools/reflect.ts +171 -145
  614. package/mcp-server/src/tools/remember.ts +223 -115
  615. package/mcp-server/src/tools/style-guide-tools.ts +250 -0
  616. package/mcp-server/src/tools/style-guide.ts +16 -0
  617. package/mcp-server/src/tools/sync.ts +66 -9
  618. package/mcp-server/src/tools/tictactoe.ts +3 -1
  619. package/mcp-server/src/tools/utils.ts +22 -0
  620. package/mcp-server/src/tools/whiteboard-format.ts +394 -0
  621. package/mcp-server/src/tools/whiteboard-tools.ts +191 -183
  622. package/mcp-server/src/tools/whiteboard.ts +7 -5
  623. package/mcp-server/src/tools/youtube/analytics.ts +106 -0
  624. package/mcp-server/src/tools/youtube/dedup.ts +57 -0
  625. package/mcp-server/src/tools/youtube/download.ts +41 -0
  626. package/mcp-server/src/tools/youtube/helpers.ts +183 -0
  627. package/mcp-server/src/tools/youtube/media.ts +109 -0
  628. package/mcp-server/src/tools/youtube/metadata.ts +112 -0
  629. package/mcp-server/src/tools/youtube/navigate.ts +81 -0
  630. package/mcp-server/src/tools/youtube/playlist.ts +134 -0
  631. package/mcp-server/src/tools/youtube/schedule.ts +158 -0
  632. package/mcp-server/src/tools/youtube/source.ts +101 -0
  633. package/mcp-server/src/tools/youtube/upload.ts +296 -0
  634. package/mcp-server/src/tools/youtube.ts +47 -0
  635. package/mcp-server/src/tui.ts +8 -9
  636. package/neural-interface/games/TicTacToe/1.svg +1 -0
  637. package/neural-interface/games/TicTacToe/2.svg +1 -0
  638. package/neural-interface/games/TicTacToe/3.svg +1 -0
  639. package/neural-interface/games/TicTacToe/4.svg +1 -0
  640. package/neural-interface/games/TicTacToe/5.svg +1 -0
  641. package/neural-interface/games/TicTacToe/6.svg +1 -0
  642. package/neural-interface/games/TicTacToe/7.svg +1 -0
  643. package/neural-interface/games/TicTacToe/8.svg +1 -0
  644. package/neural-interface/games/TicTacToe/9.svg +1 -0
  645. package/neural-interface/games/TicTacToe/Board.svg +1 -0
  646. package/neural-interface/games/TicTacToe/Circle.svg +1 -0
  647. package/neural-interface/games/TicTacToe/Cross.svg +1 -0
  648. package/neural-interface/i18n/en.json +7022 -0
  649. package/neural-interface/i18n/pt-BR.json +7026 -0
  650. package/neural-interface/lib/assistant-api.js +1005 -0
  651. package/neural-interface/lib/assistant-attachments.js +121 -0
  652. package/neural-interface/lib/assistant-brains/claude.js +207 -0
  653. package/neural-interface/lib/assistant-brains/codex-browser-policy-hook.mjs +43 -0
  654. package/neural-interface/lib/assistant-brains/codex-route-gate-hook.mjs +56 -0
  655. package/neural-interface/lib/assistant-brains/codex.js +213 -0
  656. package/neural-interface/lib/assistant-brains/opencode-browser-policy.js +47 -0
  657. package/neural-interface/lib/assistant-brains/opencode-route-gate.js +62 -0
  658. package/neural-interface/lib/assistant-brains/opencode.js +320 -0
  659. package/neural-interface/lib/assistant-budget.js +318 -0
  660. package/neural-interface/lib/assistant-catalog.js +731 -0
  661. package/neural-interface/lib/assistant-clarify.js +664 -0
  662. package/neural-interface/lib/assistant-config.js +232 -0
  663. package/neural-interface/lib/assistant-dispatch.js +2744 -0
  664. package/neural-interface/lib/assistant-envelope.js +582 -0
  665. package/neural-interface/lib/assistant-evidence.js +236 -0
  666. package/neural-interface/lib/assistant-hidden-models.js +272 -0
  667. package/neural-interface/lib/assistant-jev.js +117 -0
  668. package/neural-interface/lib/assistant-judgments.js +111 -0
  669. package/neural-interface/lib/assistant-media.js +335 -0
  670. package/neural-interface/lib/assistant-memory.js +398 -0
  671. package/neural-interface/lib/assistant-persona.js +444 -0
  672. package/neural-interface/lib/assistant-plan-permissions.js +448 -0
  673. package/neural-interface/lib/assistant-playbooks/design.md +87 -0
  674. package/neural-interface/lib/assistant-playbooks.js +72 -0
  675. package/neural-interface/lib/assistant-pricing.js +159 -0
  676. package/neural-interface/lib/assistant-route-gate-codex.js +163 -0
  677. package/neural-interface/lib/assistant-route-gate.js +212 -0
  678. package/neural-interface/lib/assistant-router.js +1324 -0
  679. package/neural-interface/lib/assistant-runtime.js +3337 -0
  680. package/neural-interface/lib/assistant-task-prompt.js +472 -0
  681. package/neural-interface/lib/assistant-usage.js +1688 -0
  682. package/neural-interface/lib/augmented-path.js +70 -0
  683. package/neural-interface/lib/automation-storage-migration.js +54 -0
  684. package/neural-interface/lib/backup-service.js +851 -0
  685. package/neural-interface/lib/backup-zip-reader.js +262 -0
  686. package/neural-interface/lib/browser-assist-api.js +41 -0
  687. package/neural-interface/lib/browser-console.js +176 -0
  688. package/neural-interface/lib/browser-execution.js +241 -0
  689. package/neural-interface/lib/browser-extraction.js +191 -0
  690. package/neural-interface/lib/browser-language.js +83 -0
  691. package/neural-interface/lib/browser-screenshot.js +399 -0
  692. package/neural-interface/lib/browser-semantic-context.js +639 -0
  693. package/neural-interface/lib/browser-snapshots.js +192 -0
  694. package/neural-interface/lib/browser-tab-recovery.js +119 -0
  695. package/neural-interface/lib/browser-targets.js +70 -0
  696. package/neural-interface/lib/browser-tool-policy.js +926 -0
  697. package/neural-interface/lib/claude-accounts.js +230 -0
  698. package/neural-interface/lib/claude-agent-bridge.js +3203 -0
  699. package/neural-interface/lib/claude-bypass-policy.js +99 -0
  700. package/neural-interface/lib/claude-executable.js +241 -0
  701. package/neural-interface/lib/claude-history.js +429 -0
  702. package/neural-interface/lib/claude-model-catalog.js +444 -0
  703. package/neural-interface/lib/claude-panel-requests.js +165 -0
  704. package/neural-interface/lib/claude-panel-session.js +272 -0
  705. package/neural-interface/lib/claude-permission-rules.js +153 -0
  706. package/neural-interface/lib/claude-session-cleanup.js +71 -0
  707. package/neural-interface/lib/claude-session-list.js +82 -0
  708. package/neural-interface/lib/claude-session-ops.js +140 -0
  709. package/neural-interface/lib/claude-settings-view.js +51 -0
  710. package/neural-interface/lib/claude-skin-connect.js +79 -0
  711. package/neural-interface/lib/claude-stall-policy.js +7 -0
  712. package/neural-interface/lib/claude-temporary.js +435 -0
  713. package/neural-interface/lib/cli-update-planner.js +312 -0
  714. package/neural-interface/lib/codex-capabilities.d.ts +41 -0
  715. package/neural-interface/lib/codex-capabilities.js +148 -0
  716. package/neural-interface/lib/codex-collaboration-mode.js +17 -0
  717. package/neural-interface/lib/codex-compat-rpc.js +241 -0
  718. package/neural-interface/lib/codex-mcp-approvals.js +56 -0
  719. package/neural-interface/lib/codex-mcp-apps.js +47 -0
  720. package/neural-interface/lib/codex-model-context.js +202 -0
  721. package/neural-interface/lib/codex-plan-store.js +71 -0
  722. package/neural-interface/lib/codex-request-journal.js +242 -0
  723. package/neural-interface/lib/codex-runtime-path.js +213 -0
  724. package/neural-interface/lib/codex-session-catalog.js +231 -0
  725. package/neural-interface/lib/codex-session-settings.js +46 -0
  726. package/neural-interface/lib/codex-sidepanel-permissions.js +110 -0
  727. package/neural-interface/lib/codex-thread-history.js +165 -0
  728. package/neural-interface/lib/codex-user-verification.js +34 -0
  729. package/neural-interface/lib/codex-writer-lifecycle.js +152 -0
  730. package/neural-interface/lib/confined-fs.js +256 -0
  731. package/neural-interface/lib/db.js +922 -0
  732. package/neural-interface/lib/design-md.js +9 -0
  733. package/neural-interface/lib/desktop/api.js +92 -0
  734. package/neural-interface/lib/desktop/audit.js +62 -0
  735. package/neural-interface/lib/desktop/build.js +423 -0
  736. package/neural-interface/lib/desktop/config.js +606 -0
  737. package/neural-interface/lib/desktop/coords.js +76 -0
  738. package/neural-interface/lib/desktop/fake-helper.js +1192 -0
  739. package/neural-interface/lib/desktop/frames.js +77 -0
  740. package/neural-interface/lib/desktop/grants.js +84 -0
  741. package/neural-interface/lib/desktop/guards.js +170 -0
  742. package/neural-interface/lib/desktop/helper/SynabunDesktop.swift +3056 -0
  743. package/neural-interface/lib/desktop/lease.js +51 -0
  744. package/neural-interface/lib/desktop/manager.js +545 -0
  745. package/neural-interface/lib/desktop/press-contexts.js +103 -0
  746. package/neural-interface/lib/desktop/protocol.js +495 -0
  747. package/neural-interface/lib/desktop/service.js +1202 -0
  748. package/neural-interface/lib/effort-levels.js +148 -0
  749. package/neural-interface/lib/event-loop-monitor.js +67 -0
  750. package/neural-interface/lib/exec-loop-command.js +209 -0
  751. package/neural-interface/lib/folder-picker.ps1 +77 -0
  752. package/neural-interface/lib/http-guards.js +287 -0
  753. package/neural-interface/lib/jsonl-lines.js +94 -0
  754. package/neural-interface/lib/loop-logger.js +127 -0
  755. package/neural-interface/lib/mcp-client-config.js +133 -0
  756. package/neural-interface/lib/mcp-installer.js +487 -0
  757. package/neural-interface/lib/mcp-profile-refresh-coordinator.js +78 -0
  758. package/neural-interface/lib/mcp-profile-servers.js +120 -0
  759. package/neural-interface/lib/memo-cache.js +84 -0
  760. package/neural-interface/lib/memory-api.js +758 -0
  761. package/neural-interface/lib/memory-map-api.js +1150 -0
  762. package/neural-interface/lib/memory-map-layout.js +872 -0
  763. package/neural-interface/lib/memory-map-worker.js +165 -0
  764. package/neural-interface/lib/memory-session-capture.js +81 -0
  765. package/neural-interface/lib/memory-timers.js +62 -0
  766. package/neural-interface/lib/morelogin.js +301 -0
  767. package/neural-interface/lib/native-binary-runtime.js +382 -0
  768. package/neural-interface/lib/native-loop-goal.js +33 -0
  769. package/neural-interface/lib/native-loop-providers.js +1002 -0
  770. package/neural-interface/lib/native-loop-runtime.js +747 -0
  771. package/neural-interface/lib/offline-page.js +54 -0
  772. package/neural-interface/lib/opencode-hidden-models.js +113 -0
  773. package/neural-interface/lib/opencode-history.js +253 -0
  774. package/neural-interface/lib/opencode-runtime-config.js +58 -0
  775. package/neural-interface/lib/opencode-v2-client.js +486 -0
  776. package/neural-interface/lib/opencode-v2-ws-requests.js +955 -0
  777. package/neural-interface/lib/path-confine.js +213 -0
  778. package/neural-interface/lib/plugin-installer.js +264 -0
  779. package/neural-interface/lib/project-storage.js +766 -0
  780. package/neural-interface/lib/pty-host/core.js +603 -0
  781. package/neural-interface/lib/pty-host/flow.js +43 -0
  782. package/neural-interface/lib/pty-host/host.js +47 -0
  783. package/neural-interface/lib/pty-host/manager.js +360 -0
  784. package/neural-interface/lib/pty-host/protocol.js +81 -0
  785. package/neural-interface/lib/pty-host/snapshot.js +133 -0
  786. package/neural-interface/lib/remote-policy.js +582 -0
  787. package/neural-interface/lib/rulesets/api.js +175 -0
  788. package/neural-interface/lib/rulesets/fs-safe.js +245 -0
  789. package/neural-interface/lib/rulesets/installer.js +1453 -0
  790. package/neural-interface/lib/rulesets/json-edit.js +186 -0
  791. package/neural-interface/lib/rulesets/lock.js +170 -0
  792. package/neural-interface/lib/rulesets/managed-text.js +314 -0
  793. package/neural-interface/lib/rulesets/normalise.js +24 -0
  794. package/neural-interface/lib/rulesets/render.js +84 -0
  795. package/neural-interface/lib/schedule-launch-intent.js +40 -0
  796. package/neural-interface/lib/session-cache-builder.js +524 -0
  797. package/neural-interface/lib/session-chunker.js +344 -0
  798. package/neural-interface/lib/session-indexer.js +327 -0
  799. package/neural-interface/lib/session-title-generator.js +232 -0
  800. package/neural-interface/lib/skill-prompt-bridge.js +11 -0
  801. package/neural-interface/lib/style-guide/api.js +375 -0
  802. package/neural-interface/lib/style-guide/color.js +352 -0
  803. package/neural-interface/lib/style-guide/export-css.js +99 -0
  804. package/neural-interface/lib/style-guide/export-dtcg.js +209 -0
  805. package/neural-interface/lib/style-guide/export-tailwind.js +102 -0
  806. package/neural-interface/lib/style-guide/fonts.json +227 -0
  807. package/neural-interface/lib/style-guide/import.js +1268 -0
  808. package/neural-interface/lib/style-guide/pointers.js +103 -0
  809. package/neural-interface/lib/style-guide/presets.js +232 -0
  810. package/neural-interface/lib/style-guide/render-design-md.js +319 -0
  811. package/neural-interface/lib/style-guide/schema.js +848 -0
  812. package/neural-interface/lib/style-guide/security.js +43 -0
  813. package/neural-interface/lib/style-guide/store.js +491 -0
  814. package/neural-interface/lib/style-guide/summary.js +106 -0
  815. package/neural-interface/lib/style-guide/tokens.js +170 -0
  816. package/neural-interface/lib/synabun-update-plan.js +324 -0
  817. package/neural-interface/lib/synabun-version.js +179 -0
  818. package/neural-interface/lib/system-restore.js +99 -0
  819. package/neural-interface/lib/terminal-env.js +86 -0
  820. package/neural-interface/lib/ui-blob-store.js +106 -0
  821. package/neural-interface/lib/vector-cache.js +2 -0
  822. package/neural-interface/lib/virtual-socket.js +60 -0
  823. package/neural-interface/lib/whatsapp/api.js +146 -0
  824. package/neural-interface/lib/whatsapp/auth-store.js +389 -0
  825. package/neural-interface/lib/whatsapp/baileys-adapter.js +457 -0
  826. package/neural-interface/lib/whatsapp/brain.js +128 -0
  827. package/neural-interface/lib/whatsapp/bridge.js +1499 -0
  828. package/neural-interface/lib/whatsapp/cards.js +519 -0
  829. package/neural-interface/lib/whatsapp/commands.js +92 -0
  830. package/neural-interface/lib/whatsapp/config.js +291 -0
  831. package/neural-interface/lib/whatsapp/connector/entry.mjs +25 -0
  832. package/neural-interface/lib/whatsapp/connector/manifest.json +6 -0
  833. package/neural-interface/lib/whatsapp/connector/npm-shrinkwrap.json +717 -0
  834. package/neural-interface/lib/whatsapp/connector/package.json +8 -0
  835. package/neural-interface/lib/whatsapp/fake-baileys.js +444 -0
  836. package/neural-interface/lib/whatsapp/format.js +552 -0
  837. package/neural-interface/lib/whatsapp/host-core.js +1225 -0
  838. package/neural-interface/lib/whatsapp/host.js +119 -0
  839. package/neural-interface/lib/whatsapp/identity.js +288 -0
  840. package/neural-interface/lib/whatsapp/inbound.js +149 -0
  841. package/neural-interface/lib/whatsapp/installer.js +562 -0
  842. package/neural-interface/lib/whatsapp/manager.js +638 -0
  843. package/neural-interface/lib/whatsapp/paths.js +81 -0
  844. package/neural-interface/lib/whatsapp/protocol.js +327 -0
  845. package/neural-interface/lib/whatsapp/qr-svg.js +57 -0
  846. package/neural-interface/lib/whatsapp/redact.js +130 -0
  847. package/neural-interface/lib/whatsapp/service.js +1305 -0
  848. package/neural-interface/lib/whatsapp/vendor/qrcodegen.js +848 -0
  849. package/neural-interface/package-lock.json +4431 -0
  850. package/neural-interface/package.json +58 -27
  851. package/neural-interface/public/apple-touch-icon.png +0 -0
  852. package/neural-interface/public/category-logos/synabun.png +0 -0
  853. package/neural-interface/public/claude.svg +1 -0
  854. package/neural-interface/public/favicon-16x16.png +0 -0
  855. package/neural-interface/public/favicon-32x32.png +0 -0
  856. package/neural-interface/public/icon-192.png +0 -0
  857. package/neural-interface/public/icon-512.png +0 -0
  858. package/neural-interface/public/index.html +32 -182
  859. package/neural-interface/public/index2d.html +2 -2
  860. package/neural-interface/public/install-app.html +31 -0
  861. package/neural-interface/public/logo-synabun.png +0 -0
  862. package/neural-interface/public/logoHD.png +0 -0
  863. package/neural-interface/public/manifest.json +2 -0
  864. package/neural-interface/public/mcp.svg +4 -0
  865. package/neural-interface/public/offline.html +670 -299
  866. package/neural-interface/public/onboarding.html +804 -161
  867. package/neural-interface/public/opencode-logo-dark-square.svg +1 -0
  868. package/neural-interface/public/shared/agent-runtime-options.js +596 -0
  869. package/neural-interface/public/shared/api.js +339 -28
  870. package/neural-interface/public/shared/assistant/asst-accounts.js +318 -0
  871. package/neural-interface/public/shared/assistant/asst-agents-dock.js +296 -0
  872. package/neural-interface/public/shared/assistant/asst-api.js +270 -0
  873. package/neural-interface/public/shared/assistant/asst-brain-picker.js +744 -0
  874. package/neural-interface/public/shared/assistant/asst-budget.js +333 -0
  875. package/neural-interface/public/shared/assistant/asst-composer.js +656 -0
  876. package/neural-interface/public/shared/assistant/asst-computer.js +803 -0
  877. package/neural-interface/public/shared/assistant/asst-control.js +1018 -0
  878. package/neural-interface/public/shared/assistant/asst-hosts.js +262 -0
  879. package/neural-interface/public/shared/assistant/asst-mascot.js +313 -0
  880. package/neural-interface/public/shared/assistant/asst-menu.js +375 -0
  881. package/neural-interface/public/shared/assistant/asst-panel.js +1845 -0
  882. package/neural-interface/public/shared/assistant/asst-rack-styles.js +586 -0
  883. package/neural-interface/public/shared/assistant/asst-render.js +3305 -0
  884. package/neural-interface/public/shared/assistant/asst-route.js +1653 -0
  885. package/neural-interface/public/shared/assistant/asst-roving.js +75 -0
  886. package/neural-interface/public/shared/assistant/asst-sanitize.js +117 -0
  887. package/neural-interface/public/shared/assistant/asst-sidepanel-styles.js +488 -0
  888. package/neural-interface/public/shared/assistant/asst-sidepanel.js +675 -0
  889. package/neural-interface/public/shared/assistant/asst-slash.js +258 -0
  890. package/neural-interface/public/shared/assistant/asst-state.js +727 -0
  891. package/neural-interface/public/shared/assistant/asst-styles.js +1686 -0
  892. package/neural-interface/public/shared/assistant/asst-tool-kinds.js +1017 -0
  893. package/neural-interface/public/shared/assistant/asst-usage.js +444 -0
  894. package/neural-interface/public/shared/assistant/asst-ws.js +175 -0
  895. package/neural-interface/public/shared/automation-context.js +75 -0
  896. package/neural-interface/public/shared/blob-store.js +99 -0
  897. package/neural-interface/public/shared/cdx/cdx-capabilities.js +118 -0
  898. package/neural-interface/public/shared/cdx/cdx-cards.js +37 -0
  899. package/neural-interface/public/shared/cdx/cdx-commands.js +171 -0
  900. package/neural-interface/public/shared/cdx/cdx-context-menu.js +402 -0
  901. package/neural-interface/public/shared/cdx/cdx-context-model.js +220 -0
  902. package/neural-interface/public/shared/cdx/cdx-features.js +408 -0
  903. package/neural-interface/public/shared/cdx/cdx-icons.js +89 -0
  904. package/neural-interface/public/shared/cdx/cdx-mcp-app-host.js +414 -0
  905. package/neural-interface/public/shared/cdx/cdx-mcp-app-proxy.html +6 -0
  906. package/neural-interface/public/shared/cdx/cdx-mcp-app-proxy.js +56 -0
  907. package/neural-interface/public/shared/cdx/cdx-mcp-app.js +178 -0
  908. package/neural-interface/public/shared/cdx/cdx-model-catalog.js +120 -0
  909. package/neural-interface/public/shared/cdx/cdx-output-model.js +261 -0
  910. package/neural-interface/public/shared/cdx/cdx-output.js +382 -0
  911. package/neural-interface/public/shared/cdx/cdx-panel.js +1372 -0
  912. package/neural-interface/public/shared/cdx/cdx-plan.js +266 -0
  913. package/neural-interface/public/shared/cdx/cdx-protocol.js +682 -0
  914. package/neural-interface/public/shared/cdx/cdx-render.js +3381 -0
  915. package/neural-interface/public/shared/cdx/cdx-requests.js +1136 -0
  916. package/neural-interface/public/shared/cdx/cdx-styles.js +3251 -0
  917. package/neural-interface/public/shared/cdx/cdx-tabs.js +7992 -0
  918. package/neural-interface/public/shared/cdx/cdx-ws.js +363 -0
  919. package/neural-interface/public/shared/cli-status.js +169 -0
  920. package/neural-interface/public/shared/command-runner-model.js +30 -0
  921. package/neural-interface/public/shared/constants.js +22 -5
  922. package/neural-interface/public/shared/cp/cp-agents.js +186 -0
  923. package/neural-interface/public/shared/cp/cp-bash.js +252 -0
  924. package/neural-interface/public/shared/cp/cp-compose.js +159 -0
  925. package/neural-interface/public/shared/cp/cp-context-menu.js +309 -0
  926. package/neural-interface/public/shared/cp/cp-context-model.js +157 -0
  927. package/neural-interface/public/shared/cp/cp-context-window.js +80 -0
  928. package/neural-interface/public/shared/cp/cp-ctx.js +11 -0
  929. package/neural-interface/public/shared/cp/cp-diff.js +255 -0
  930. package/neural-interface/public/shared/cp/cp-event-rows.js +637 -0
  931. package/neural-interface/public/shared/cp/cp-events.js +846 -0
  932. package/neural-interface/public/shared/cp/cp-markdown.js +104 -0
  933. package/neural-interface/public/shared/cp/cp-permission-model.js +905 -0
  934. package/neural-interface/public/shared/cp/cp-permissions.js +626 -0
  935. package/neural-interface/public/shared/cp/cp-rehydrate.js +113 -0
  936. package/neural-interface/public/shared/cp/cp-restore.js +224 -0
  937. package/neural-interface/public/shared/cp/cp-session-model.js +338 -0
  938. package/neural-interface/public/shared/cp/cp-session-settings.js +254 -0
  939. package/neural-interface/public/shared/cp/cp-sessions.js +227 -0
  940. package/neural-interface/public/shared/cp/cp-statusline.js +153 -0
  941. package/neural-interface/public/shared/cp/cp-styles.js +349 -0
  942. package/neural-interface/public/shared/cp/cp-tasks-model.js +282 -0
  943. package/neural-interface/public/shared/cp/cp-tasks.js +107 -0
  944. package/neural-interface/public/shared/cp/cp-temporary-view.js +56 -0
  945. package/neural-interface/public/shared/cp/cp-temporary.js +231 -0
  946. package/neural-interface/public/shared/cp/cp-tool-cards.js +326 -0
  947. package/neural-interface/public/shared/cp/cp-tool-results.js +618 -0
  948. package/neural-interface/public/shared/cp/cp-usage-model.js +124 -0
  949. package/neural-interface/public/shared/graph-activity.js +68 -0
  950. package/neural-interface/public/shared/html-shell.js +116 -17
  951. package/neural-interface/public/shared/i18n.js +132 -34
  952. package/neural-interface/public/shared/node-positions-store.js +44 -0
  953. package/neural-interface/public/shared/ocp-hidden-models.js +116 -0
  954. package/neural-interface/public/shared/ocp-v2/ocp-v2-approvals.js +355 -0
  955. package/neural-interface/public/shared/ocp-v2/ocp-v2-autoaccept-button.js +35 -0
  956. package/neural-interface/public/shared/ocp-v2/ocp-v2-automation-ownership.js +72 -0
  957. package/neural-interface/public/shared/ocp-v2/ocp-v2-binding.js +280 -0
  958. package/neural-interface/public/shared/ocp-v2/ocp-v2-caps.js +67 -0
  959. package/neural-interface/public/shared/ocp-v2/ocp-v2-changes-logic.js +179 -0
  960. package/neural-interface/public/shared/ocp-v2/ocp-v2-changes.js +164 -0
  961. package/neural-interface/public/shared/ocp-v2/ocp-v2-childpanel.js +725 -0
  962. package/neural-interface/public/shared/ocp-v2/ocp-v2-compact-button.js +75 -0
  963. package/neural-interface/public/shared/ocp-v2/ocp-v2-composer-logic.js +1204 -0
  964. package/neural-interface/public/shared/ocp-v2/ocp-v2-confirm-logic.js +117 -0
  965. package/neural-interface/public/shared/ocp-v2/ocp-v2-confirm.js +59 -0
  966. package/neural-interface/public/shared/ocp-v2/ocp-v2-context-gauge.js +252 -0
  967. package/neural-interface/public/shared/ocp-v2/ocp-v2-context-menu.js +478 -0
  968. package/neural-interface/public/shared/ocp-v2/ocp-v2-context-model.js +179 -0
  969. package/neural-interface/public/shared/ocp-v2/ocp-v2-events.js +233 -0
  970. package/neural-interface/public/shared/ocp-v2/ocp-v2-icons.js +30 -0
  971. package/neural-interface/public/shared/ocp-v2/ocp-v2-manager.js +1065 -0
  972. package/neural-interface/public/shared/ocp-v2/ocp-v2-mention-hints.js +182 -0
  973. package/neural-interface/public/shared/ocp-v2/ocp-v2-modelpicker.js +655 -0
  974. package/neural-interface/public/shared/ocp-v2/ocp-v2-panel.js +2636 -0
  975. package/neural-interface/public/shared/ocp-v2/ocp-v2-plan.js +601 -0
  976. package/neural-interface/public/shared/ocp-v2/ocp-v2-profile-inheritance.js +78 -0
  977. package/neural-interface/public/shared/ocp-v2/ocp-v2-projectbar.js +649 -0
  978. package/neural-interface/public/shared/ocp-v2/ocp-v2-providers.js +35 -0
  979. package/neural-interface/public/shared/ocp-v2/ocp-v2-rehydrate.js +357 -0
  980. package/neural-interface/public/shared/ocp-v2/ocp-v2-render-logic.js +189 -0
  981. package/neural-interface/public/shared/ocp-v2/ocp-v2-render.js +1869 -0
  982. package/neural-interface/public/shared/ocp-v2/ocp-v2-select.js +42 -0
  983. package/neural-interface/public/shared/ocp-v2/ocp-v2-send-logic.js +117 -0
  984. package/neural-interface/public/shared/ocp-v2/ocp-v2-send.js +1714 -0
  985. package/neural-interface/public/shared/ocp-v2/ocp-v2-session-actions.js +468 -0
  986. package/neural-interface/public/shared/ocp-v2/ocp-v2-session-widgets.js +63 -0
  987. package/neural-interface/public/shared/ocp-v2/ocp-v2-sessions-logic.js +366 -0
  988. package/neural-interface/public/shared/ocp-v2/ocp-v2-settings-link.js +19 -0
  989. package/neural-interface/public/shared/ocp-v2/ocp-v2-slash-hints.js +343 -0
  990. package/neural-interface/public/shared/ocp-v2/ocp-v2-state.js +746 -0
  991. package/neural-interface/public/shared/ocp-v2/ocp-v2-status-logic.js +347 -0
  992. package/neural-interface/public/shared/ocp-v2/ocp-v2-status.js +338 -0
  993. package/neural-interface/public/shared/ocp-v2/ocp-v2-styles.js +3543 -0
  994. package/neural-interface/public/shared/ocp-v2/ocp-v2-subagents-logic.js +482 -0
  995. package/neural-interface/public/shared/ocp-v2/ocp-v2-tools-logic.js +351 -0
  996. package/neural-interface/public/shared/ocp-v2/ocp-v2-tools.js +168 -0
  997. package/neural-interface/public/shared/ocp-v2/ocp-v2-variantpicker.js +315 -0
  998. package/neural-interface/public/shared/ocp-v2/ocp-v2-ws.js +545 -0
  999. package/neural-interface/public/shared/provider-icons.js +30 -0
  1000. package/neural-interface/public/shared/pwa-install-page.js +34 -0
  1001. package/neural-interface/public/shared/pwa-install.css +12 -0
  1002. package/neural-interface/public/shared/pwa-install.js +28 -0
  1003. package/neural-interface/public/shared/session-title.js +100 -0
  1004. package/neural-interface/public/shared/settings/settings-ia.js +389 -0
  1005. package/neural-interface/public/shared/settings/settings-kit.js +234 -0
  1006. package/neural-interface/public/shared/settings/settings-rules.js +160 -0
  1007. package/neural-interface/public/shared/settings/settings-search.js +463 -0
  1008. package/neural-interface/public/shared/settings/settings-shell.js +570 -0
  1009. package/neural-interface/public/shared/start-bridge.js +583 -0
  1010. package/neural-interface/public/shared/state.js +5 -0
  1011. package/neural-interface/public/shared/storage.js +50 -7
  1012. package/neural-interface/public/shared/styleguide/sg-agents.js +42 -0
  1013. package/neural-interface/public/shared/styleguide/sg-brand.js +4 -0
  1014. package/neural-interface/public/shared/styleguide/sg-color-client.js +388 -0
  1015. package/neural-interface/public/shared/styleguide/sg-colors.js +151 -0
  1016. package/neural-interface/public/shared/styleguide/sg-components.js +35 -0
  1017. package/neural-interface/public/shared/styleguide/sg-history.js +41 -0
  1018. package/neural-interface/public/shared/styleguide/sg-icons-logo.js +38 -0
  1019. package/neural-interface/public/shared/styleguide/sg-imagery.js +4 -0
  1020. package/neural-interface/public/shared/styleguide/sg-import-export.js +48 -0
  1021. package/neural-interface/public/shared/styleguide/sg-kit.js +284 -0
  1022. package/neural-interface/public/shared/styleguide/sg-labels.js +318 -0
  1023. package/neural-interface/public/shared/styleguide/sg-layout.js +4 -0
  1024. package/neural-interface/public/shared/styleguide/sg-motion.js +5 -0
  1025. package/neural-interface/public/shared/styleguide/sg-panel.js +237 -0
  1026. package/neural-interface/public/shared/styleguide/sg-preview.js +138 -0
  1027. package/neural-interface/public/shared/styleguide/sg-rules.js +9 -0
  1028. package/neural-interface/public/shared/styleguide/sg-shape.js +24 -0
  1029. package/neural-interface/public/shared/styleguide/sg-state.js +132 -0
  1030. package/neural-interface/public/shared/styleguide/sg-typography.js +125 -0
  1031. package/neural-interface/public/shared/styles.css +8614 -1477
  1032. package/neural-interface/public/shared/synabun-mascot.js +1625 -0
  1033. package/neural-interface/public/shared/synabun-ticker.js +376 -0
  1034. package/neural-interface/public/shared/term-ready.js +307 -0
  1035. package/neural-interface/public/shared/term-renderer.js +343 -0
  1036. package/neural-interface/public/shared/ui-assistant-panel.js +10 -0
  1037. package/neural-interface/public/shared/ui-assistant.js +77 -0
  1038. package/neural-interface/public/shared/ui-automation-studio.js +5105 -3573
  1039. package/neural-interface/public/shared/ui-claude-panel.js +11685 -6333
  1040. package/neural-interface/public/shared/ui-codex-panel.js +12 -0
  1041. package/neural-interface/public/shared/ui-command-runner.js +251 -468
  1042. package/neural-interface/public/shared/ui-cost-widget.js +55 -34
  1043. package/neural-interface/public/shared/ui-detail.js +77 -14
  1044. package/neural-interface/public/shared/ui-explorer.js +360 -181
  1045. package/neural-interface/public/shared/ui-file-explorer.js +485 -107
  1046. package/neural-interface/public/shared/ui-help.js +2 -2
  1047. package/neural-interface/public/shared/ui-image-gallery.js +45 -21
  1048. package/neural-interface/public/shared/ui-judgments.js +1346 -0
  1049. package/neural-interface/public/shared/ui-keybinds.js +3 -2
  1050. package/neural-interface/public/shared/ui-link.js +105 -51
  1051. package/neural-interface/public/shared/ui-loading.js +222 -11
  1052. package/neural-interface/public/shared/ui-memory-maintenance.js +111 -0
  1053. package/neural-interface/public/shared/ui-menubar.js +100 -31
  1054. package/neural-interface/public/shared/ui-native-loop-router-state.js +94 -0
  1055. package/neural-interface/public/shared/ui-native-loop-router.js +493 -0
  1056. package/neural-interface/public/shared/ui-native-window-id.js +7 -0
  1057. package/neural-interface/public/shared/ui-navbar.js +357 -222
  1058. package/neural-interface/public/shared/ui-notifications.js +58 -12
  1059. package/neural-interface/public/shared/ui-opencode-panel-v2.js +17 -0
  1060. package/neural-interface/public/shared/ui-panels.js +15 -7
  1061. package/neural-interface/public/shared/ui-resume.js +823 -653
  1062. package/neural-interface/public/shared/ui-schedule-queue.js +172 -0
  1063. package/neural-interface/public/shared/ui-schedules-studio.js +1882 -0
  1064. package/neural-interface/public/shared/ui-scroll-follow.js +194 -0
  1065. package/neural-interface/public/shared/ui-sessions.js +789 -34
  1066. package/neural-interface/public/shared/ui-settings.js +5774 -2194
  1067. package/neural-interface/public/shared/ui-sidepanel-layout.js +109 -0
  1068. package/neural-interface/public/shared/ui-sidepanel-runtime.js +19 -0
  1069. package/neural-interface/public/shared/ui-sidepanel-session-entry.js +40 -0
  1070. package/neural-interface/public/shared/ui-sidepanel-sessions.js +221 -0
  1071. package/neural-interface/public/shared/ui-sidepanel-state.js +131 -0
  1072. package/neural-interface/public/shared/ui-sidepanel-tray.js +547 -0
  1073. package/neural-interface/public/shared/ui-sidepanel-windows.js +387 -0
  1074. package/neural-interface/public/shared/ui-skills.js +18 -32
  1075. package/neural-interface/public/shared/ui-styleguide.js +35 -0
  1076. package/neural-interface/public/shared/ui-sync.js +87 -0
  1077. package/neural-interface/public/shared/ui-terminal.js +7863 -6385
  1078. package/neural-interface/public/shared/ui-tool-updates.js +292 -0
  1079. package/neural-interface/public/shared/ui-tooltip.js +156 -15
  1080. package/neural-interface/public/shared/ui-tutorial-draw.js +119 -683
  1081. package/neural-interface/public/shared/ui-tutorial-layout.js +135 -0
  1082. package/neural-interface/public/shared/ui-tutorial-steps.js +59 -2209
  1083. package/neural-interface/public/shared/ui-tutorial.css +102 -0
  1084. package/neural-interface/public/shared/ui-tutorial.js +557 -1076
  1085. package/neural-interface/public/shared/ui-update.js +420 -68
  1086. package/neural-interface/public/shared/ui-whatsapp.js +941 -0
  1087. package/neural-interface/public/shared/ui-whiteboard.js +1076 -233
  1088. package/neural-interface/public/shared/ui-workspaces.js +2 -1
  1089. package/neural-interface/public/shared/utils.js +58 -0
  1090. package/neural-interface/public/shared/vterm-buffer.js +15 -5
  1091. package/neural-interface/public/shared/whatsapp/wa-phone.js +65 -0
  1092. package/neural-interface/public/shared/whatsapp/wa-view.js +411 -0
  1093. package/neural-interface/public/sidepanel-session.html +18 -0
  1094. package/neural-interface/public/sw.js +10 -2
  1095. package/neural-interface/public/synabun.png +0 -0
  1096. package/neural-interface/public/variant/2d/graph.js +1707 -1635
  1097. package/neural-interface/public/variant/2d/lasso.js +2 -1
  1098. package/neural-interface/public/variant/2d/layout.js +595 -594
  1099. package/neural-interface/public/variant/2d/main.js +557 -521
  1100. package/neural-interface/public/variant/2d/settings-gfx.js +18 -24
  1101. package/neural-interface/public/variant/3d/main.js +517 -500
  1102. package/neural-interface/public/variant/3d/map-controls.js +144 -0
  1103. package/neural-interface/public/variant/3d/map-data.js +251 -0
  1104. package/neural-interface/public/variant/3d/map-labels.js +241 -0
  1105. package/neural-interface/public/variant/3d/map-pick.js +90 -0
  1106. package/neural-interface/public/variant/3d/map-renderer.js +521 -0
  1107. package/neural-interface/public/variant/3d/map-shaders.js +158 -0
  1108. package/neural-interface/public/variant/3d/map-view.js +618 -0
  1109. package/neural-interface/public/vendor/three/manifest.json +3 -0
  1110. package/neural-interface/public/vendor/three/three-0.169.0.min.js +3844 -0
  1111. package/neural-interface/public/vendor/xterm/manifest.json +3 -0
  1112. package/neural-interface/public/vendor/xterm/xterm-6.0.0.css +285 -0
  1113. package/neural-interface/public/vendor/xterm/xterm-6.0.0.min.js +192 -0
  1114. package/neural-interface/scripts/build-vendor.mjs +64 -0
  1115. package/neural-interface/scripts/codex-protocol-fixture.mjs +83 -0
  1116. package/neural-interface/scripts/rebuild-pty.js +39 -22
  1117. package/neural-interface/scripts/vendor-three-entry.js +24 -0
  1118. package/neural-interface/scripts/vendor-xterm-entry.js +8 -0
  1119. package/neural-interface/server.js +33983 -15000
  1120. package/neural-interface/skins/default/skin.css +90 -0
  1121. package/neural-interface/skins/default/skin.json +8 -0
  1122. package/neural-interface/templates/rulesets/README.md +9 -0
  1123. package/neural-interface/templates/rulesets/claude.md +3 -0
  1124. package/neural-interface/templates/rulesets/codex.md +2 -0
  1125. package/neural-interface/templates/rulesets/coexistence.md +6 -0
  1126. package/neural-interface/templates/rulesets/core.md +26 -0
  1127. package/neural-interface/templates/rulesets/cursor.md +2 -0
  1128. package/neural-interface/templates/rulesets/gemini.md +2 -0
  1129. package/neural-interface/templates/rulesets/legacy-hashes.json +227 -0
  1130. package/neural-interface/templates/rulesets/manifest.json +12 -0
  1131. package/neural-interface/templates/rulesets/opencode.md +2 -0
  1132. package/package.json +131 -53
  1133. package/postinstall.js +55 -26
  1134. package/preuninstall.js +187 -0
  1135. package/scripts/restore-data-home-from-backup.mjs +239 -0
  1136. package/setup.js +872 -260
  1137. package/skills/leonardo/SKILL.md +19 -10
  1138. package/skills/leonardo/modules/image-prompter.md +18 -12
  1139. package/skills/leonardo/modules/video-prompter.md +19 -13
  1140. package/skills/synabun/SKILL.md +209 -185
  1141. package/skills/synabun/icon.png +0 -0
  1142. package/skills/synabun/modules/audit.md +380 -374
  1143. package/skills/synabun/modules/changelog.md +15 -6
  1144. package/skills/synabun/modules/idea.md +148 -144
  1145. package/skills/synabun/modules/memorize.md +13 -4
  1146. package/skills/synabun/modules/schedule.md +198 -0
  1147. package/skills/synabun/synabunicon.png +0 -0
  1148. package/updater.cmd +31 -0
  1149. package/updater.mjs +382 -0
  1150. package/updater.sh +29 -0
  1151. package/hooks/claude-code/test-user-learning.mjs +0 -531
  1152. package/neural-interface/public/claude-chat.css +0 -878
  1153. package/neural-interface/public/claude-chat.html +0 -92
  1154. package/neural-interface/public/claude-chat.js +0 -1168
  1155. package/neural-interface/public/index-legacy.html +0 -14677
  1156. package/neural-interface/public/index2d-legacy.html +0 -8193
  1157. package/neural-interface/public/shared/html-term-renderer.js +0 -1022
  1158. package/neural-interface/public/variant/3d/background.js +0 -884
  1159. package/neural-interface/public/variant/3d/camera.js +0 -661
  1160. package/neural-interface/public/variant/3d/gfx.js +0 -116
  1161. package/neural-interface/public/variant/3d/graph.js +0 -2399
  1162. package/neural-interface/public/variant/3d/settings-gfx.js +0 -228
  1163. package/neural-interface/templates/CLAUDE-template.md +0 -211
@@ -0,0 +1,3381 @@
1
+ // ═══════════════════════════════════════════
2
+ // SynaBun — Codex Panel: Render Module
3
+ // Text utilities, card rendering, delta appending, and history rendering.
4
+ // ═══════════════════════════════════════════
5
+
6
+ import {
7
+ OPENAI_ICON, ICON_SPARK, ICON_TERMINAL, ICON_TOOL, ICON_FILES,
8
+ ICON_STOP, ICON_EDIT, ICON_SHIELD, ICON_PLAN, ICON_BRAIN,
9
+ SYNABUN_LOGO_ICON,
10
+ CODEX_GREETING_MARKER_START, CODEX_GREETING_MARKER_END,
11
+ CODEX_PLAN_MODE_PREFIXES,
12
+ } from './cdx-icons.js';
13
+ import { buildCodexPlanRevisionPrompt, codexGenericCardHead, codexReadableValue, codexTranscriptPresentation, selectCodexHistoryRenderSource } from './cdx-protocol.js';
14
+ import { getPlanMarkdown, approvePlan, extractProposedPlan, syncPlanMirrors, isRecordedPlanTurn, isPlanMessageCandidate, selectPlanAdoptionMessage } from './cdx-plan.js';
15
+ import { bindExpandableCard, setCardExpanded } from './cdx-cards.js';
16
+ import { mountMcpAppCard } from './cdx-mcp-app-host.js';
17
+ import { OUTPUT_LIMITS, boundedText, jsonAnswer, prettyJsonFences, readableStructuredValue, fileChangeDiffs, parseUnifiedDiff, deduplicateTurnDiff } from './cdx-output-model.js';
18
+ import { renderReadableOutput, renderReadableBlock, cleanOutputBlocks, hydrateOutputControls, observeOutputBlocks, renderDiffView, restoreDiffViews } from './cdx-output.js';
19
+ export { setCardExpanded } from './cdx-cards.js';
20
+
21
+ // ── Shared context (set by cdx-tabs via setRenderContext) ──
22
+ let _ctx = {
23
+ get items() { return new Map(); },
24
+ get requestCards() { return new Map(); },
25
+ get messagesEl() { return null; },
26
+ get project() { return ''; },
27
+ get projects() { return []; },
28
+ get activeItems() { return new Map(); },
29
+ get marked() { return null; },
30
+ get hljs() { return null; },
31
+ get boundTab() { return null; },
32
+ get accountId() { return 'default'; },
33
+ get connectionEpoch() { return ''; },
34
+ get activeTurnId() { return null; },
35
+ get threadId() { return null; },
36
+ get transcriptSourceType() { return ''; },
37
+ get running() { return false; },
38
+ get startingThread() { return false; },
39
+ get compacting() { return false; },
40
+ get threadTokenUsage() { return null; },
41
+ scrollEnd() {},
42
+ sendSocket() {},
43
+ emit() {},
44
+ activeTab() { return null; },
45
+ isActiveTab() { return false; },
46
+ dispatchPrompt() {},
47
+ capturePlanContent() {},
48
+ saveTabs() {},
49
+ hideEmpty() {},
50
+ showEmpty() {},
51
+ repositionThinking() {},
52
+ pruneTranscriptDom() {},
53
+ scheduleThreadSnapshotSave() {},
54
+ flushThreadSnapshotSave() {},
55
+ setTranscriptSourceMeta() {},
56
+ withTab() {},
57
+ createRequestButton() {},
58
+ formatStatus() { return 'pending'; },
59
+ itemHeadline() { return { title: '', detail: '' }; },
60
+ cleanPreview() { return ''; },
61
+ syncToolbarState() {},
62
+ startCompaction() {},
63
+ syncWorkStatus() {},
64
+ clearTranscript() {},
65
+ setThread() {},
66
+ renderStoredTranscript() { return false; },
67
+ updateThreadTokenUsage() {},
68
+ setShowPostCompactionPrompt() {},
69
+ setPostCompactionPending() {},
70
+ setPostCompactionPromptSource() {},
71
+ setPlanFeedbackDraft() {},
72
+ getThreadSnapshot() { return null; },
73
+ normalizeThreadSnapshotEntry() { return null; },
74
+ normalizeSnapshotSourceType() { return ''; },
75
+ normalizeSnapshotItemCount() { return 0; },
76
+ timestampMs() { return 0; },
77
+ setCompactingUI() {},
78
+ renderStatusChrome() {},
79
+ itemOwnershipBelongsToBoundThread() { return false; },
80
+ };
81
+
82
+ export function setRenderContext(ctx) { _ctx = ctx; }
83
+
84
+ // ═══════════════════════════════════════════
85
+ // Text Utilities
86
+ // ═══════════════════════════════════════════
87
+
88
+ export function esc(s) {
89
+ return String(s)
90
+ .replace(/&/g, '&')
91
+ .replace(/</g, '&lt;')
92
+ .replace(/>/g, '&gt;')
93
+ .replace(/"/g, '&quot;');
94
+ }
95
+
96
+ // Codex re-renders every assistant message through marked.parse() on every
97
+ // streaming delta tick. Memoize so identical text — unchanged messages
98
+ // elsewhere in the transcript that get re-rendered anyway — returns cached
99
+ // HTML without re-parsing.
100
+ const _mdCache = new Map();
101
+ const _MD_CACHE_LIMIT = 512;
102
+ let _mdRenderer = null;
103
+ let _mdParser;
104
+ function markdownFallback(text) {
105
+ const source = prettyJsonFences(text);
106
+ const fence = /^(`{3,}|~{3,})([\w-]*)[ \t]*\r?\n([\s\S]*?)\r?\n\1[ \t]*$/gm;
107
+ let html = '', last = 0;
108
+ for (const match of source.matchAll(fence)) {
109
+ html += esc(source.slice(last, match.index)).replace(/\n/g, '<br>');
110
+ html += `<pre><code${match[2] ? ` class="language-${esc(match[2])}"` : ''}>${esc(match[3])}</code></pre>`;
111
+ last = match.index + match[0].length;
112
+ }
113
+ return html + esc(source.slice(last)).replace(/\n/g, '<br>');
114
+ }
115
+ export function md(text) {
116
+ // Contexts expose marked through a live getter: it can arrive after the first
117
+ // render or be replaced without setRenderContext being called again.
118
+ const renderer = _ctx.marked;
119
+ const parser = renderer?.parse;
120
+ if (renderer !== _mdRenderer || parser !== _mdParser) {
121
+ _mdCache.clear();
122
+ _mdRenderer = renderer;
123
+ _mdParser = parser;
124
+ }
125
+ const key = text || '';
126
+ if (key.length > OUTPUT_LIMITS.input) return `<pre class="cxp-card-pre cxp-output-raw"><code>${esc(boundedText(key))}</code></pre>`;
127
+ const cached = _mdCache.get(key);
128
+ if (cached !== undefined) return cached;
129
+ let html;
130
+ if (!renderer) {
131
+ html = markdownFallback(key);
132
+ } else {
133
+ try { html = parser.call(renderer, prettyJsonFences(key)); }
134
+ catch { html = markdownFallback(key); }
135
+ }
136
+ if (_mdCache.size >= _MD_CACHE_LIMIT) {
137
+ const firstKey = _mdCache.keys().next().value;
138
+ if (firstKey !== undefined) _mdCache.delete(firstKey);
139
+ }
140
+ _mdCache.set(key, html);
141
+ return html;
142
+ }
143
+
144
+ export function renderAssistantMarkdown(text, { streaming = false } = {}) {
145
+ const tpl = document.createElement('template');
146
+ if (!renderReadableOutput(tpl.content, text || '', { streaming })) tpl.innerHTML = md(text || '');
147
+ // Codex occasionally emits standalone separators that render as stray rules in the sidepanel.
148
+ tpl.content.querySelectorAll('hr').forEach((node) => node.remove());
149
+ tpl.content.querySelectorAll('p').forEach((node) => {
150
+ if (!node.textContent.trim() && !node.querySelector('img,svg,code,pre,table,ul,ol,blockquote')) node.remove();
151
+ });
152
+ return {
153
+ html: tpl.innerHTML,
154
+ hasContent: Boolean((tpl.content.textContent || '').trim() || tpl.content.querySelector('img,svg,code,pre,table,ul,ol,blockquote')),
155
+ };
156
+ }
157
+
158
+ function extractCompactAssistantSummary(text) {
159
+ const value = String(text || '')
160
+ .replace(/```[\s\S]*?```/g, ' [code] ')
161
+ .replace(/`([^`]+)`/g, '$1')
162
+ .replace(/!\[[^\]]*\]\([^)]+\)/g, ' [image] ')
163
+ .replace(/\[([^\]]+)\]\([^)]+\)/g, '$1')
164
+ .replace(/^[#>\-\*\d\.\s]+/gm, '')
165
+ .replace(/\s+/g, ' ')
166
+ .trim();
167
+ if (!value) return 'Working through the next step';
168
+ return value;
169
+ }
170
+
171
+ // ═══════════════════════════════════════════
172
+ // Choice / Inline Button Utilities
173
+ // ═══════════════════════════════════════════
174
+
175
+ const SYNABUN_CHOICE_LABEL_RE = /\b(Brainstorm Ideas|Audit Memories|Memorize Context|Memory Health|Search Memories|Auto Changelog|More(?:\.\.\.)?|Back|Freeform|Current project|Run full audit|View details|View full|Search again|Save as-is|Edit first|Cancel|Done)\b/i;
176
+
177
+ export function normalizeChoiceLabel(value) {
178
+ return String(value || '')
179
+ .replace(/[`*_]+/g, '')
180
+ .replace(/\s+/g, ' ')
181
+ .trim();
182
+ }
183
+
184
+ export function isChangelogChoiceSet(options = []) {
185
+ const labels = options.map((option) => normalizeChoiceLabel(option?.label || option?.value || option).toLowerCase());
186
+ return labels.includes('save as-is') && labels.includes('edit first');
187
+ }
188
+
189
+ export function isSynaBunChoicePrompt(promptText = '', options = []) {
190
+ if (!options.length) return false;
191
+ const prompt = normalizeChoiceLabel(promptText);
192
+ if (/synabun/i.test(prompt)) return true;
193
+ // Require at least 2 individual labels to match — a single generic hit
194
+ // like "Done" or "Back" in informational lists shouldn't trigger branding.
195
+ const matchCount = options.filter((option) =>
196
+ SYNABUN_CHOICE_LABEL_RE.test(normalizeChoiceLabel(option?.label || option?.value || option)),
197
+ ).length;
198
+ return matchCount >= 2;
199
+ }
200
+
201
+ export function isLikelyInteractivePrompt(promptText = '', options = []) {
202
+ const prompt = normalizeChoiceLabel(promptText);
203
+ if (/^(reply\s+with\s+one\s+of|choose\s+one|pick\s+one|select\s+(one|an?\s+option))/i.test(prompt)) return true;
204
+ if (isSynaBunChoicePrompt(prompt, options)) return true;
205
+ if (!options.length || options.length > 6) return false;
206
+ if (!/[?]$/.test(prompt)) return false;
207
+ return options.every((option) => normalizeChoiceLabel(option?.label || option?.value || option).length <= 48);
208
+ }
209
+
210
+ export function extractChoiceOption(li) {
211
+ if (!li) return null;
212
+ const clone = li.cloneNode(true);
213
+ let label = '';
214
+ const strong = clone.querySelector('strong');
215
+ const code = !strong ? clone.querySelector('code') : null;
216
+ if (strong) {
217
+ label = normalizeChoiceLabel(strong.textContent);
218
+ strong.remove();
219
+ } else if (code) {
220
+ label = normalizeChoiceLabel(code.textContent);
221
+ code.remove();
222
+ }
223
+ let desc = normalizeChoiceLabel(clone.textContent || '');
224
+ desc = desc.replace(/^[\u2014\u2013\-—–:]\s*/, '').trim();
225
+ if (!label) {
226
+ const match = desc.match(/^(.+?)\s+[—–-]\s+(.+)$/);
227
+ if (match) {
228
+ label = normalizeChoiceLabel(match[1]);
229
+ desc = normalizeChoiceLabel(match[2]);
230
+ } else {
231
+ label = desc;
232
+ desc = '';
233
+ }
234
+ }
235
+ if (!label) return null;
236
+ return { label, desc };
237
+ }
238
+
239
+ export function extractChoiceOptionsFromList(listEl) {
240
+ if (!listEl || !/^(UL|OL)$/.test(listEl.tagName)) return [];
241
+ return Array.from(listEl.querySelectorAll(':scope > li')).map(extractChoiceOption).filter(Boolean);
242
+ }
243
+
244
+ export function restoreChangelogButtons(buttons = []) {
245
+ buttons.forEach((button) => {
246
+ if (!button) return;
247
+ button.disabled = false;
248
+ button.classList.remove('selected', 'chosen', 'dismissed');
249
+ });
250
+ }
251
+
252
+ export function buildInlineChoiceButtons(options, {
253
+ synabun = false,
254
+ promptText = '',
255
+ } = {}) {
256
+ const wrap = document.createElement('div');
257
+ wrap.className = `cxp-inline-choices${synabun ? ' cxp-inline-choices-synabun' : ''}`;
258
+ const isChangelogAsk = isChangelogChoiceSet(options);
259
+
260
+ for (const opt of options) {
261
+ const btn = document.createElement('button');
262
+ btn.type = 'button';
263
+ btn.className = 'cxp-inline-choice';
264
+ btn.innerHTML =
265
+ `<span class="cxp-inline-choice-label">${esc(opt.label)}</span>` +
266
+ (opt.desc ? `<span class="cxp-inline-choice-desc">${esc(opt.desc)}</span>` : '');
267
+ btn.addEventListener('click', async () => {
268
+ if (btn.classList.contains('chosen') || btn.classList.contains('dismissed')) return;
269
+ const allButtons = Array.from(wrap.querySelectorAll('.cxp-inline-choice'));
270
+ if (isChangelogAsk && /edit first/i.test(opt.label)) {
271
+ allButtons.forEach((button) => { button.disabled = true; });
272
+ btn.classList.add('chosen');
273
+ const opened = await openChangelogEditorFlow({
274
+ source: synabun ? 'synabun-inline' : 'inline',
275
+ buttons: allButtons,
276
+ });
277
+ if (!opened) restoreChangelogButtons(allButtons);
278
+ return;
279
+ }
280
+ wrap.querySelectorAll('.cxp-inline-choice').forEach((button) => button.classList.add('dismissed'));
281
+ btn.classList.remove('dismissed');
282
+ btn.classList.add('chosen');
283
+ _ctx.dispatchPrompt(opt.label);
284
+ });
285
+ wrap.appendChild(btn);
286
+ }
287
+
288
+ if (!synabun) return wrap;
289
+
290
+ const card = document.createElement('div');
291
+ card.className = 'cxp-synabun-inline-card';
292
+ card.innerHTML = `
293
+ <div class="cxp-synabun-inline-head">
294
+ <div class="cxp-synabun-inline-logo">${SYNABUN_LOGO_ICON}</div>
295
+ <div class="cxp-synabun-inline-title">SynaBun</div>
296
+ </div>
297
+ `;
298
+ const cleanedPrompt = normalizeChoiceLabel(promptText).replace(/^synabun\s*[—-]\s*/i, '').trim();
299
+ if (cleanedPrompt) {
300
+ const promptEl = document.createElement('div');
301
+ promptEl.className = 'cxp-synabun-inline-prompt';
302
+ promptEl.textContent = cleanedPrompt;
303
+ card.appendChild(promptEl);
304
+ }
305
+ card.appendChild(wrap);
306
+ return card;
307
+ }
308
+
309
+ /** Detect choice prompts + adjacent list markup and convert to clickable buttons. */
310
+ export function convertInlineChoices(container) {
311
+ if (!container) return;
312
+ const paragraphs = Array.from(container.querySelectorAll('p'));
313
+ for (const p of paragraphs) {
314
+ const promptText = normalizeChoiceLabel(p.textContent || '');
315
+ // Check direct sibling first; if <p> is inside a <blockquote>, check the blockquote's sibling
316
+ let listEl = p.nextElementSibling;
317
+ const parentBq = (!listEl || !/^(UL|OL)$/.test(listEl.tagName))
318
+ ? p.closest('blockquote')
319
+ : null;
320
+ if (parentBq) listEl = parentBq.nextElementSibling;
321
+ const options = extractChoiceOptionsFromList(listEl);
322
+ if (!isLikelyInteractivePrompt(promptText, options)) continue;
323
+ const choiceEl = buildInlineChoiceButtons(options, {
324
+ synabun: isSynaBunChoicePrompt(promptText, options),
325
+ promptText,
326
+ });
327
+ const trailingInstruction = listEl?.nextElementSibling;
328
+ if (parentBq) parentBq.replaceWith(choiceEl);
329
+ else p.replaceWith(choiceEl);
330
+ listEl?.remove();
331
+ if (trailingInstruction?.tagName === 'P') {
332
+ const trailingText = normalizeChoiceLabel(trailingInstruction.textContent || '');
333
+ if (/^reply\s+with\s+(one|[\d,\s]+or\s+\d+)(\s+option(\s+name\s+or\s+number)?)?\.?/i.test(trailingText)) {
334
+ trailingInstruction.remove();
335
+ }
336
+ }
337
+ }
338
+ }
339
+
340
+ // ═══════════════════════════════════════════
341
+ // Post-processing & HTML utilities
342
+ // ═══════════════════════════════════════════
343
+
344
+ export function postProcessRenderedHtml(container) {
345
+ if (!container) return;
346
+ cleanOutputBlocks(container);
347
+ hydrateOutputControls(container);
348
+ container.querySelectorAll('pre').forEach((pre) => {
349
+ if (pre.closest('.cxp-readable, .cxp-output-source, .cxp-diff-view') || pre.querySelector('.cxp-readable')) return;
350
+ if (pre.querySelector('.cxp-copy-btn')) return;
351
+ const btn = document.createElement('button');
352
+ btn.className = 'cxp-copy-btn';
353
+ btn.textContent = 'Copy';
354
+ const copyText = (pre.querySelector('code') || pre).textContent || '';
355
+ btn.addEventListener('click', () => {
356
+ navigator.clipboard.writeText(copyText).then(() => {
357
+ btn.textContent = 'Copied!';
358
+ setTimeout(() => { btn.textContent = 'Copy'; }, 2000);
359
+ }).catch(() => {});
360
+ });
361
+ pre.style.position = 'relative';
362
+ pre.appendChild(btn);
363
+ });
364
+ if (_ctx.hljs) {
365
+ container.querySelectorAll('pre code[class*="language-"]').forEach((block) => {
366
+ if (block.dataset.highlighted) return;
367
+ try { _ctx.hljs.highlightElement(block); } catch {}
368
+ });
369
+ }
370
+ markProjectFileAnchors(container);
371
+ linkifyFilePaths(container);
372
+ convertInlineChoices(container);
373
+ }
374
+
375
+ // ═══════════════════════════════════════════
376
+ // File Path Linking
377
+ // ═══════════════════════════════════════════
378
+
379
+ export function normalizePathForCompare(path) {
380
+ return String(path || '').replace(/\\/g, '/').replace(/\/+$/, '');
381
+ }
382
+
383
+ export function isProjectFilePath(path) {
384
+ const normalized = normalizePathForCompare(path);
385
+ if (!normalized.startsWith('/')) return false;
386
+ const roots = new Set([
387
+ normalizePathForCompare(_ctx.project),
388
+ ...(_ctx.projects || []).map((entry) => normalizePathForCompare(entry?.path)).filter(Boolean),
389
+ ]);
390
+ for (const root of roots) {
391
+ if (!root) continue;
392
+ if (normalized === root || normalized.startsWith(`${root}/`)) return true;
393
+ }
394
+ return false;
395
+ }
396
+
397
+ export function parseProjectFileTarget(rawTarget) {
398
+ if (!rawTarget) return null;
399
+ let raw = String(rawTarget).trim();
400
+ if (!raw || raw === '#') return null;
401
+ if (raw.startsWith('file://')) {
402
+ try { raw = decodeURIComponent(new URL(raw).pathname || ''); }
403
+ catch { raw = raw.replace(/^file:\/\//, ''); }
404
+ }
405
+
406
+ let pathPart = raw;
407
+ let hash = '';
408
+ if (/^[a-z]+:\/\//i.test(raw)) {
409
+ try {
410
+ const parsed = new URL(raw, window.location.origin);
411
+ if (parsed.origin !== window.location.origin) return null;
412
+ pathPart = parsed.pathname || '';
413
+ hash = parsed.hash || '';
414
+ } catch {
415
+ return null;
416
+ }
417
+ } else {
418
+ const hashIndex = raw.indexOf('#');
419
+ if (hashIndex >= 0) {
420
+ pathPart = raw.slice(0, hashIndex);
421
+ hash = raw.slice(hashIndex);
422
+ }
423
+ }
424
+
425
+ try { pathPart = decodeURIComponent(pathPart); } catch {}
426
+ pathPart = normalizePathForCompare(pathPart);
427
+
428
+ let line = null;
429
+ let column = null;
430
+ const suffixMatch = pathPart.match(/:(\d+)(?::(\d+))?$/);
431
+ if (suffixMatch) {
432
+ line = Number(suffixMatch[1]) || null;
433
+ column = Number(suffixMatch[2]) || null;
434
+ pathPart = normalizePathForCompare(pathPart.slice(0, suffixMatch.index));
435
+ }
436
+ if (!isProjectFilePath(pathPart)) return null;
437
+ const hashMatch = hash.match(/^#L(\d+)(?:C(\d+))?$/i);
438
+ if (hashMatch) {
439
+ line = Number(hashMatch[1]) || line;
440
+ column = Number(hashMatch[2]) || column;
441
+ }
442
+
443
+ return { filePath: pathPart, line, column };
444
+ }
445
+
446
+ export function openProjectFileTarget(target) {
447
+ if (!target?.filePath) return false;
448
+ _ctx.emit('open-file-editor', {
449
+ filePath: target.filePath,
450
+ line: target.line || undefined,
451
+ column: target.column || undefined,
452
+ });
453
+ return true;
454
+ }
455
+
456
+ export function maybeOpenProjectFileLink(rawTarget, event) {
457
+ const target = parseProjectFileTarget(rawTarget);
458
+ if (!target) return false;
459
+ event?.preventDefault?.();
460
+ return openProjectFileTarget(target);
461
+ }
462
+
463
+ export function markProjectFileAnchors(container) {
464
+ if (!container) return;
465
+ container.querySelectorAll('a[href]').forEach((link) => {
466
+ const target = parseProjectFileTarget(link.getAttribute('href'));
467
+ if (!target) return;
468
+ link.classList.add('cxp-file-link');
469
+ link.dataset.projectFilePath = target.filePath;
470
+ link.title = target.line
471
+ ? `Open in editor${target.column ? ` at line ${target.line}, column ${target.column}` : ` at line ${target.line}`}`
472
+ : 'Open in editor';
473
+ });
474
+ }
475
+
476
+ export function linkifyFilePaths(container) {
477
+ if (!container) return;
478
+ const walker = document.createTreeWalker(container, NodeFilter.SHOW_TEXT, {
479
+ acceptNode: (node) => {
480
+ if (node.parentElement?.closest('pre, code, a, .cxp-copy-btn, .cxp-file-link')) return NodeFilter.FILTER_REJECT;
481
+ return NodeFilter.FILTER_ACCEPT;
482
+ }
483
+ });
484
+ const pathRegex = /((?:\/[\w.@~-]+){2,}(?:\.\w+)?(?::\d+(?::\d+)?)?)/g;
485
+ const textNodes = [];
486
+ while (walker.nextNode()) textNodes.push(walker.currentNode);
487
+ for (const textNode of textNodes) {
488
+ const text = textNode.textContent;
489
+ if (!pathRegex.test(text)) { pathRegex.lastIndex = 0; continue; }
490
+ pathRegex.lastIndex = 0;
491
+ const frag = document.createDocumentFragment();
492
+ let lastIndex = 0;
493
+ let match;
494
+ while ((match = pathRegex.exec(text)) !== null) {
495
+ const pathStr = match[1];
496
+ if (match.index > lastIndex) frag.appendChild(document.createTextNode(text.substring(lastIndex, match.index)));
497
+ const link = document.createElement('a');
498
+ link.className = 'cxp-file-link';
499
+ link.textContent = pathStr;
500
+ const target = parseProjectFileTarget(pathStr);
501
+ link.title = target?.line
502
+ ? `Open in editor${target.column ? ` at line ${target.line}, column ${target.column}` : ` at line ${target.line}`}`
503
+ : target ? 'Open in editor' : 'Click to copy path';
504
+ link.href = '#';
505
+ link.addEventListener('click', (e) => {
506
+ if (maybeOpenProjectFileLink(pathStr, e)) return;
507
+ e.preventDefault();
508
+ const cleanPath = pathStr.replace(/:\d+(:\d+)?$/, '');
509
+ navigator.clipboard.writeText(cleanPath).catch(() => {});
510
+ link.classList.add('cxp-file-link-copied');
511
+ setTimeout(() => link.classList.remove('cxp-file-link-copied'), 1500);
512
+ });
513
+ frag.appendChild(link);
514
+ lastIndex = match.index + match[0].length;
515
+ }
516
+ if (lastIndex < text.length) frag.appendChild(document.createTextNode(text.substring(lastIndex)));
517
+ textNode.parentNode.replaceChild(frag, textNode);
518
+ }
519
+ }
520
+
521
+ // ═══════════════════════════════════════════
522
+ // Display Helpers
523
+ // ═══════════════════════════════════════════
524
+
525
+ export function basenamePath(path) {
526
+ if (!path) return '';
527
+ const normalized = String(path).replace(/\\/g, '/').replace(/\/+$/, '');
528
+ const parts = normalized.split('/');
529
+ return parts[parts.length - 1] || normalized;
530
+ }
531
+
532
+ export function pretty(value) {
533
+ if (value == null) return '';
534
+ if (typeof value === 'string') return value;
535
+ try { return JSON.stringify(value, null, 2); }
536
+ catch { return String(value); }
537
+ }
538
+
539
+ export function parseJsonishString(value) {
540
+ if (typeof value !== 'string') return value;
541
+ if (value.length > OUTPUT_LIMITS.input) return value;
542
+ const answer = jsonAnswer(value);
543
+ if (answer?.kind === 'json') return answer.value;
544
+ const trimmed = value.trim();
545
+ if (!trimmed) return value;
546
+ if (!/^(?:\{|\[|")/.test(trimmed)) return value;
547
+ try { return JSON.parse(trimmed); }
548
+ catch { return value; }
549
+ }
550
+
551
+ export function isStructuredEmpty(value, seen = new Set()) {
552
+ const parsed = parseJsonishString(value);
553
+ if (parsed !== value) return isStructuredEmpty(parsed, seen);
554
+ if (parsed == null) return true;
555
+ if (typeof parsed === 'string') return !parsed.trim();
556
+ if (typeof parsed === 'number' || typeof parsed === 'boolean') return false;
557
+ if (Array.isArray(parsed)) return parsed.length === 0 || parsed.every((entry) => isStructuredEmpty(entry, seen));
558
+ if (typeof parsed === 'object') {
559
+ if (seen.has(parsed)) return true;
560
+ seen.add(parsed);
561
+ const values = Object.values(parsed);
562
+ return values.length === 0 || values.every((entry) => isStructuredEmpty(entry, seen));
563
+ }
564
+ return false;
565
+ }
566
+
567
+ function dedupeTextBlocks(blocks) {
568
+ const seen = new Set();
569
+ const out = [];
570
+ for (const block of blocks) {
571
+ const text = String(block || '').trim();
572
+ if (!text) continue;
573
+ if (seen.has(text)) continue;
574
+ seen.add(text);
575
+ out.push(text);
576
+ }
577
+ return out;
578
+ }
579
+
580
+ export function summarizeFileChange(change) {
581
+ if (!change || typeof change !== 'object') return '';
582
+ const path = change.path
583
+ || change.filePath
584
+ || change.targetPath
585
+ || change.newPath
586
+ || change.oldPath
587
+ || change.sourcePath
588
+ || '';
589
+ const fromPath = change.from || change.previousPath || change.oldPath || '';
590
+ const toPath = change.to || change.newPath || change.path || change.filePath || change.targetPath || '';
591
+ const kind = String(
592
+ change.kind?.type
593
+ || change.kind
594
+ || change.type
595
+ || change.operation
596
+ || change.status
597
+ || ''
598
+ ).trim().replace(/_/g, ' ');
599
+ if (fromPath && toPath && fromPath !== toPath) {
600
+ return `${kind || 'rename'}: ${fromPath} -> ${toPath}`;
601
+ }
602
+ if (path) return kind ? `${kind}: ${path}` : path;
603
+ if (typeof change.summary === 'string' && change.summary.trim()) return change.summary.trim();
604
+ if (typeof change.description === 'string' && change.description.trim()) return change.description.trim();
605
+ return '';
606
+ }
607
+
608
+ export function summarizeCommandExecution(command, cwd = '') {
609
+ const raw = String(command || '').trim();
610
+ const unwrapped = raw
611
+ .replace(/^\/bin\/(?:zsh|bash)\s+-lc\s+/, '')
612
+ .replace(/^"(.*)"$/, '$1')
613
+ .replace(/^'(.*)'$/, '$1')
614
+ .trim();
615
+ const normalized = unwrapped.toLowerCase();
616
+ const cwdName = basenamePath(cwd || '') || cwd || '';
617
+
618
+ if (/\b(rg|ripgrep|grep|findstr)\b/.test(normalized)) {
619
+ return { title: 'Searching within files', detail: cwdName ? `Scanning ${cwdName}` : 'Scanning the workspace' };
620
+ }
621
+ if (/\b(fd|find|ls|tree)\b/.test(normalized)) {
622
+ return { title: 'Inspecting files', detail: cwdName ? `Looking through ${cwdName}` : 'Exploring the workspace' };
623
+ }
624
+ if (/\b(cat|sed|head|tail|awk|jq)\b/.test(normalized)) {
625
+ return { title: 'Reading file contents', detail: cwdName ? `Inspecting files in ${cwdName}` : 'Inspecting file contents' };
626
+ }
627
+ if (/\bgit\s+(status|log|branch)\b/.test(normalized)) {
628
+ return { title: 'Inspecting git state', detail: cwdName ? `Checking ${cwdName}` : 'Checking repository state' };
629
+ }
630
+ if (/\bgit\s+(diff|show)\b/.test(normalized)) {
631
+ return { title: 'Inspecting git changes', detail: cwdName ? `Reviewing changes in ${cwdName}` : 'Reviewing repository changes' };
632
+ }
633
+ if (/\b(node\s+--check|tsc\b|eslint\b|biome\b|prettier\b|ruff\b)\b/.test(normalized)) {
634
+ return { title: 'Checking code quality', detail: cwdName ? `Validating ${cwdName}` : 'Validating code' };
635
+ }
636
+ if (/\b(vitest|jest|pytest|cargo test|npm test|pnpm test|yarn test|bun test)\b/.test(normalized)) {
637
+ return { title: 'Running tests', detail: cwdName ? `Testing ${cwdName}` : 'Running the test suite' };
638
+ }
639
+ if (/\b(npm|pnpm|yarn|bun)\s+install\b/.test(normalized)) {
640
+ return { title: 'Installing dependencies', detail: cwdName ? `Updating packages in ${cwdName}` : 'Updating project dependencies' };
641
+ }
642
+
643
+ return {
644
+ title: 'Running shell command',
645
+ detail: cwdName ? `Working in ${cwdName}` : 'Executing a shell command',
646
+ };
647
+ }
648
+
649
+ function computeDiffStats(text) {
650
+ const value = String(text || '').slice(0, OUTPUT_LIMITS.input);
651
+ if (!value.trim()) return '';
652
+ let added = 0;
653
+ let removed = 0;
654
+ for (const line of value.split('\n')) {
655
+ if (line.startsWith('+++') || line.startsWith('---')) continue;
656
+ if (line.startsWith('+')) added += 1;
657
+ else if (line.startsWith('-')) removed += 1;
658
+ }
659
+ if (!added && !removed) return '';
660
+ return `+${added} -${removed}`;
661
+ }
662
+
663
+ function describeFileChange(change) {
664
+ if (!change || typeof change !== 'object') return '';
665
+ const summary = summarizeFileChange(change);
666
+ const detail = String(change.summary || change.description || '').trim();
667
+ const stats = computeDiffStats(change.diff || change.patch || change.output || change.content || '');
668
+ return [summary, detail && detail !== summary ? detail : '', stats].filter(Boolean).join(' · ');
669
+ }
670
+
671
+ function resolveFileChangeSummary(item) {
672
+ const changes = Array.isArray(item?.changes) ? item.changes.slice(0, OUTPUT_LIMITS.files) : [];
673
+ const lines = changes.map((change) => describeFileChange(change)).filter(Boolean);
674
+ if (lines.length) return lines.join('\n');
675
+ return 'Pending file changes';
676
+ }
677
+
678
+ function collectStructuredText(value, seen = new Set()) {
679
+ const parsed = parseJsonishString(value);
680
+ if (parsed !== value) {
681
+ return [readableStructuredValue(parsed)];
682
+ }
683
+ if (value == null) return [];
684
+ if (typeof value === 'string') return value.trim() ? [value] : [];
685
+ if (typeof value === 'number' || typeof value === 'boolean') return [String(value)];
686
+ if (Array.isArray(value)) {
687
+ return dedupeTextBlocks(value.flatMap((entry) => collectStructuredText(entry, seen)));
688
+ }
689
+ if (typeof value !== 'object') return [];
690
+ if (seen.has(value)) return [];
691
+ seen.add(value);
692
+
693
+ const blocks = [];
694
+ const type = String(value.type || '').trim().toLowerCase();
695
+ const imageRef = value.path || value.url || value.imageUrl || value.src || value.name || '';
696
+
697
+ if (type === 'image' || type === 'inputimage' || type === 'localimage') {
698
+ return [`[image]${imageRef ? ` ${imageRef}` : ''}`];
699
+ }
700
+
701
+ const fileSummary = summarizeFileChange(value);
702
+ if (fileSummary) blocks.push(fileSummary);
703
+
704
+ for (const key of ['text', 'message', 'content', 'data', 'output', 'result', 'delta', 'details', 'description']) {
705
+ if (typeof value[key] === 'string') {
706
+ blocks.push(...collectStructuredText(value[key], seen));
707
+ }
708
+ }
709
+
710
+ for (const key of ['content', 'contentItems', 'parts', 'items', 'results', 'resourceTemplates', 'resources', 'changes', 'entries']) {
711
+ if (Array.isArray(value[key])) {
712
+ blocks.push(...collectStructuredText(value[key], seen));
713
+ }
714
+ }
715
+
716
+ for (const key of ['content', 'data', 'output', 'result', 'response', 'error']) {
717
+ if (value[key] && typeof value[key] === 'object' && !Array.isArray(value[key])) {
718
+ blocks.push(...collectStructuredText(value[key], seen));
719
+ }
720
+ }
721
+
722
+ if (!blocks.length && !isStructuredEmpty(value)) {
723
+ const scalarLines = Object.entries(value)
724
+ .filter(([, entry]) => typeof entry === 'string' || typeof entry === 'number' || typeof entry === 'boolean')
725
+ .map(([key, entry]) => `${key}: ${entry}`);
726
+ if (scalarLines.length) blocks.push(...scalarLines);
727
+ }
728
+
729
+ return dedupeTextBlocks(blocks);
730
+ }
731
+
732
+ export function extractStructuredText(value) {
733
+ const parsed = parseJsonishString(value);
734
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed) && !parsed.type && !Array.isArray(parsed.content)) return readableStructuredValue(parsed);
735
+ if (typeof value === 'string') return readableStructuredValue(value);
736
+ if (Array.isArray(parsed) && !parsed.some(entry => entry && typeof entry === 'object' && entry.type)) return readableStructuredValue(parsed);
737
+ return collectStructuredText(value).join('\n\n');
738
+ }
739
+
740
+ export function formatStructuredValue(value) {
741
+ return readableStructuredValue(parseJsonishString(value));
742
+ }
743
+
744
+ export function resolveFinalMarkdownText(state, nextText) {
745
+ // Completed items are authoritative, including explicit empty/shorter text.
746
+ return typeof nextText === 'string' ? nextText : String(state?.buffer || '');
747
+ }
748
+
749
+ // ═══════════════════════════════════════════
750
+ // Greeting / Plan Preamble Stripping
751
+ // ═══════════════════════════════════════════
752
+
753
+ export function stripInjectedCodexGreeting(text) {
754
+ const raw = String(text || '');
755
+ if (!raw) return '';
756
+ const start = raw.indexOf(CODEX_GREETING_MARKER_START);
757
+ const end = raw.indexOf(CODEX_GREETING_MARKER_END);
758
+ if (start === -1 || end === -1 || end < start) return raw.trim();
759
+ const before = raw.slice(0, start).trim();
760
+ const after = raw.slice(end + CODEX_GREETING_MARKER_END.length).trim();
761
+ return [before, after].filter(Boolean).join('\n\n').trim();
762
+ }
763
+
764
+ function isInjectedCodexPlanBlock(block) {
765
+ const normalized = String(block || '').trim().toLowerCase();
766
+ if (!normalized) return false;
767
+ return normalized.startsWith('[plan mode')
768
+ || normalized.startsWith('critical')
769
+ || normalized.includes('do not make code changes')
770
+ || normalized.includes('askuserquestion')
771
+ || normalized.includes('toolsearch')
772
+ || normalized.includes('exitplanmode')
773
+ || normalized.includes('present options')
774
+ || normalized.includes('need clarification')
775
+ || normalized.includes('questions about the approach');
776
+ }
777
+
778
+ export function stripInjectedCodexPlanPreamble(text) {
779
+ let value = String(text || '').trim();
780
+ if (!value) return '';
781
+
782
+ for (const prefix of CODEX_PLAN_MODE_PREFIXES) {
783
+ if (value.startsWith(prefix)) value = value.slice(prefix.length).trim();
784
+ }
785
+
786
+ if (!/^\[PLAN MODE\b/i.test(value)) return value.trim();
787
+
788
+ const parts = value.split(/\n{2,}/).map((part) => part.trim()).filter(Boolean);
789
+ if (!parts.length) return '';
790
+
791
+ let index = 0;
792
+ while (index < parts.length && isInjectedCodexPlanBlock(parts[index])) index += 1;
793
+ if (!index) return value.trim();
794
+ return parts.slice(index).join('\n\n').trim();
795
+ }
796
+
797
+ export function stripInjectedCodexSkillPreamble(text) {
798
+ const raw = String(text || '').trim();
799
+ if (!raw) return '';
800
+ const startTag = '<skill-instructions>';
801
+ const endTag = '</skill-instructions>';
802
+ const start = raw.indexOf(startTag);
803
+ const end = raw.indexOf(endTag);
804
+ if (start === -1 || end === -1 || end < start) return raw;
805
+
806
+ const after = raw.slice(end + endTag.length).trim();
807
+ const invokeMatch = after.match(/^The user invoked the \/([^\s]+) command(?: with arguments: ([\s\S]*?))?\. Follow the skill instructions above exactly\.$/);
808
+ if (invokeMatch) {
809
+ const command = invokeMatch[1];
810
+ const args = String(invokeMatch[2] || '').trim();
811
+ return `/${command}${args ? ` ${args}` : ''}`;
812
+ }
813
+
814
+ const before = raw.slice(0, start).trim();
815
+ return [before, after].filter(Boolean).join('\n\n').trim();
816
+ }
817
+
818
+ export function sanitizeCodexUserFacingText(text) {
819
+ return stripInjectedCodexSkillPreamble(stripInjectedCodexPlanPreamble(stripInjectedCodexGreeting(text)));
820
+ }
821
+
822
+ export function sanitizeStoredTranscriptDom(container) {
823
+ if (!container) return;
824
+ // MCP startup/auth notices describe live app-server state. Restoring them
825
+ // from an HTML snapshot would leave a stale, disabled authentication action.
826
+ container.querySelectorAll('.cxp-mcp-startup-notice').forEach((node) => node.remove());
827
+ // These actions require a live completed item identity; history rendering
828
+ // reinstalls them once the authoritative item is available.
829
+ container.querySelectorAll('.cxp-use-as-plan').forEach((node) => node.remove());
830
+ container.querySelectorAll('.cxp-msg-user .cxp-msg-body').forEach((bodyEl) => {
831
+ const messageEl = bodyEl.closest('.cxp-msg-user');
832
+ const textBlocks = Array.from(bodyEl.querySelectorAll('.cxp-msg-user-text'));
833
+ const imageBlocks = bodyEl.querySelectorAll('.cxp-msg-user-image, .cxp-msg-user-attachment');
834
+ if (textBlocks.length || imageBlocks.length) {
835
+ let hasRenderable = imageBlocks.length > 0;
836
+ textBlocks.forEach((textEl) => {
837
+ const text = sanitizeCodexUserFacingText(textEl.textContent || '');
838
+ if (!text) {
839
+ textEl.remove();
840
+ return;
841
+ }
842
+ textEl.textContent = text;
843
+ hasRenderable = true;
844
+ });
845
+ if (!hasRenderable) messageEl?.remove();
846
+ return;
847
+ }
848
+
849
+ const text = sanitizeCodexUserFacingText(bodyEl.textContent || '');
850
+ if (!text) {
851
+ messageEl?.remove();
852
+ return;
853
+ }
854
+ bodyEl.textContent = text;
855
+ });
856
+ container.querySelectorAll('.cxp-msg-assistant .cxp-msg-rendered').forEach(body => {
857
+ if (body.querySelector('.cxp-readable')) { hydrateOutputControls(body); return; }
858
+ const text = body.textContent || '';
859
+ if (!body.querySelector('pre, code') && jsonAnswer(text)) renderReadableOutput(body, text);
860
+ });
861
+ if (!_ctx.running) container.querySelectorAll('.cxp-reasoning').forEach(card => {
862
+ const body = card.querySelector('.cxp-reasoning-body');
863
+ if (body && /^Waiting for reasoning summary/.test(body.textContent)) body.textContent = 'No reasoning summary was provided.';
864
+ const pill = card.querySelector('.cxp-status-pill');
865
+ if (pill?.textContent === 'live') pill.textContent = 'complete';
866
+ });
867
+ syncStatusPillTones(container);
868
+ cleanOutputBlocks(container);
869
+ // Old snapshots retain their classes; replace just their verbose diff body.
870
+ container.querySelectorAll('[data-item-type="fileChange"]').forEach(card => {
871
+ const fold = card.querySelector('.cxp-fold-diff');
872
+ const raw = fold?.querySelector('.cxp-card-pre-diff')?.textContent;
873
+ if (!raw) return;
874
+ const view = document.createElement('div');
875
+ fold.replaceWith(view);
876
+ renderDiffView(view, parseUnifiedDiff(raw), { lazy: true, cwd: _ctx.boundTab?.project || _ctx.project, summaryEl: card.querySelector('.cxp-card-meta') });
877
+ if (card.dataset.userCollapsed !== '1') setCardExpanded(card, true);
878
+ });
879
+ restoreDiffViews(container, { cwd: _ctx.boundTab?.project || _ctx.project });
880
+ }
881
+
882
+ // ═══════════════════════════════════════════
883
+ // Status pill tone (presentation only)
884
+ // ═══════════════════════════════════════════
885
+
886
+ // Pills get their label from many call sites as plain text. The stylesheet
887
+ // needs a hook to give a status its colour, so the label is mirrored into
888
+ // data-tone here; nothing else reads it.
889
+ const STATUS_PILL_TONES = [
890
+ ['error', /^(failed|failure|error|errored|denied|rejected)\b/],
891
+ ['warning', /^(waiting|declined|cancell?ed|interrupted|aborted|blocked|retry|stale|unsupported|timed ?out|paused)\b/],
892
+ ['running', /^(in ?progress|running|live|working|starting|started|submitting|streaming|active)\b/],
893
+ ['success', /^(allowed|accepted|approved|authenticated|ready)\b/],
894
+ ];
895
+
896
+ export function statusPillTone(text) {
897
+ const label = String(text || '').trim().toLowerCase();
898
+ return STATUS_PILL_TONES.find(([, pattern]) => pattern.test(label))?.[0] || '';
899
+ }
900
+
901
+ function applyStatusPillTone(pill) {
902
+ if (!pill) return;
903
+ const tone = statusPillTone(pill.textContent);
904
+ if (tone) pill.dataset.tone = tone;
905
+ else delete pill.dataset.tone;
906
+ }
907
+
908
+ const _statusPillObserver = typeof MutationObserver === 'function'
909
+ ? new MutationObserver((records) => {
910
+ for (const record of records) {
911
+ const node = record.target.nodeType === 1 ? record.target : record.target.parentElement;
912
+ applyStatusPillTone(node?.closest?.('.cxp-status-pill'));
913
+ }
914
+ })
915
+ : null;
916
+
917
+ export function syncStatusPillTones(root) {
918
+ const pills = root?.querySelectorAll?.('.cxp-status-pill') || [];
919
+ for (const pill of pills) {
920
+ applyStatusPillTone(pill);
921
+ _statusPillObserver?.observe(pill, { childList: true, characterData: true, subtree: true });
922
+ }
923
+ }
924
+
925
+ export function removePostPlanCards(messagesEl = _ctx.messagesEl) {
926
+ messagesEl?.querySelectorAll('.cxp-post-plan-msg').forEach((node) => node.remove());
927
+ messagesEl?.querySelectorAll('.cxp-plan-document-toolbar [data-plan-action="edit"]').forEach((button) => { button.hidden = false; });
928
+ }
929
+
930
+ export function removePostCompactionCards(messagesEl = _ctx.messagesEl) {
931
+ messagesEl?.querySelectorAll('.cxp-post-compact-msg').forEach((node) => node.remove());
932
+ }
933
+
934
+ // ═══════════════════════════════════════════
935
+ // User Message Content
936
+ // ═══════════════════════════════════════════
937
+
938
+ function normalizeUserContentItemType(item) {
939
+ return String(item?.type || '').trim().toLowerCase().replace(/[_-]/g, '');
940
+ }
941
+
942
+ function getUserAttachmentPath(item) {
943
+ return item?.path || item?.filePath || item?.file_path || '';
944
+ }
945
+
946
+ function getUserAttachmentLabel(item) {
947
+ return item?.name || item?.fileId || item?.file_id || basenamePath(getUserAttachmentPath(item)) || 'image';
948
+ }
949
+
950
+ function normalizeRenderableImageUrl(rawUrl) {
951
+ const value = String(rawUrl || '').trim().replace(/^<|>$/g, '').replace(/[),.;!?]+$/g, '');
952
+ if (!value) return '';
953
+ try {
954
+ const url = new URL(value, window.location.origin);
955
+ const path = String(url.pathname || '').toLowerCase();
956
+ if (!/\.(avif|bmp|gif|jpe?g|png|svg|webp)$/i.test(path)) return '';
957
+ return url.href;
958
+ } catch {
959
+ return '';
960
+ }
961
+ }
962
+
963
+ function extractLinkedImageUrls(text) {
964
+ const source = String(text || '');
965
+ if (!source) return [];
966
+ const found = new Set();
967
+ const markdownImageRegex = /!\[[^\]]*]\((https?:\/\/[^)\s>]+)\)/gi;
968
+ const markdownLinkRegex = /(?<!!)\[[^\]]*]\((https?:\/\/[^)\s>]+)\)/gi;
969
+ const plainUrlRegex = /(?:^|\s)(https?:\/\/[^\s<>"']+)/gi;
970
+
971
+ let match;
972
+ while ((match = markdownImageRegex.exec(source))) {
973
+ const normalized = normalizeRenderableImageUrl(match[1]);
974
+ if (normalized) found.add(normalized);
975
+ }
976
+ while ((match = markdownLinkRegex.exec(source))) {
977
+ const normalized = normalizeRenderableImageUrl(match[1]);
978
+ if (normalized) found.add(normalized);
979
+ }
980
+ while ((match = plainUrlRegex.exec(source))) {
981
+ const normalized = normalizeRenderableImageUrl(match[1]);
982
+ if (normalized) found.add(normalized);
983
+ }
984
+ return [...found];
985
+ }
986
+
987
+ function isUserTextItem(item) {
988
+ return normalizeUserContentItemType(item) === 'text' || normalizeUserContentItemType(item) === 'inputtext';
989
+ }
990
+
991
+ function isUserImageItem(item) {
992
+ const type = normalizeUserContentItemType(item);
993
+ return type === 'image' || type === 'inputimage' || type === 'localimage';
994
+ }
995
+
996
+ function isUserLocalImageItem(item) {
997
+ return normalizeUserContentItemType(item) === 'localimage';
998
+ }
999
+
1000
+ export function extractUserText(content) {
1001
+ if (!Array.isArray(content)) return '';
1002
+ return sanitizeCodexUserFacingText(content.map((item) => {
1003
+ if (isUserTextItem(item)) return item.text || '';
1004
+ if (!isUserImageItem(item)) {
1005
+ if (normalizeUserContentItemType(item) === 'mention') return `@${item.name || item.path || 'mention'}`;
1006
+ if (normalizeUserContentItemType(item) === 'skill') return `/${item.name || item.path || 'skill'}`;
1007
+ return '';
1008
+ }
1009
+ if (isUserLocalImageItem(item)) return `[image] ${basenamePath(getUserAttachmentPath(item)) || ''}`.trim();
1010
+ if (item?.imageUrl || item?.image_url || item?.url || item?.src) {
1011
+ return `[image] ${item.imageUrl || item.image_url || item.url || item.src}`;
1012
+ }
1013
+ return `[image] ${getUserAttachmentLabel(item)}`.trim();
1014
+ }).filter(Boolean).join('\n'));
1015
+ }
1016
+
1017
+ export function getUserAttachmentImageSrc(item) {
1018
+ if (!isUserImageItem(item)) return '';
1019
+ const directSrc = item?.dataUrl || item?.url || item?.imageUrl || item?.image_url || item?.src || '';
1020
+ if (directSrc) return directSrc;
1021
+ const fileName = basenamePath(getUserAttachmentPath(item));
1022
+ return fileName ? `/api/images/file/${encodeURIComponent(fileName)}` : '';
1023
+ }
1024
+
1025
+ export function renderUserMessageContent(bodyEl, content) {
1026
+ if (!bodyEl) return;
1027
+ const items = Array.isArray(content) ? content : [];
1028
+ bodyEl.innerHTML = '';
1029
+ let hasRenderable = false;
1030
+
1031
+ for (const item of items) {
1032
+ if (!item || typeof item !== 'object') continue;
1033
+
1034
+ if (isUserImageItem(item)) {
1035
+ const src = getUserAttachmentImageSrc(item);
1036
+ if (src) {
1037
+ const img = document.createElement('img');
1038
+ img.className = 'cxp-msg-user-image';
1039
+ img.alt = getUserAttachmentLabel(item) || 'Attached image';
1040
+ img.src = src;
1041
+ bodyEl.appendChild(img);
1042
+ }
1043
+
1044
+ const chip = document.createElement('div');
1045
+ chip.className = 'cxp-msg-user-attachment';
1046
+ chip.textContent = `Attached image: ${getUserAttachmentLabel(item)}`;
1047
+ bodyEl.appendChild(chip);
1048
+ hasRenderable = true;
1049
+ continue;
1050
+ }
1051
+
1052
+ if (isUserTextItem(item)) {
1053
+ const text = sanitizeCodexUserFacingText(item.text || '');
1054
+ if (!text) continue;
1055
+ const textEl = document.createElement('div');
1056
+ textEl.className = 'cxp-msg-user-text';
1057
+ textEl.textContent = text;
1058
+ bodyEl.appendChild(textEl);
1059
+ extractLinkedImageUrls(text).forEach((url) => {
1060
+ const img = document.createElement('img');
1061
+ img.className = 'cxp-msg-user-image';
1062
+ img.alt = 'Linked image';
1063
+ img.src = url;
1064
+ bodyEl.appendChild(img);
1065
+
1066
+ const chip = document.createElement('div');
1067
+ chip.className = 'cxp-msg-user-attachment';
1068
+ chip.textContent = 'Linked image';
1069
+ bodyEl.appendChild(chip);
1070
+ });
1071
+ hasRenderable = true;
1072
+ continue;
1073
+ }
1074
+ }
1075
+
1076
+ if (!hasRenderable) bodyEl.textContent = extractUserText(items);
1077
+ }
1078
+
1079
+ // ═══════════════════════════════════════════
1080
+ // Tool Result Extraction
1081
+ // ═══════════════════════════════════════════
1082
+
1083
+ export function extractToolResultText(result) {
1084
+ if (result && typeof result === 'object' && hasOwnField(result, 'structuredContent')) return formatStructuredValue(result.structuredContent);
1085
+ return extractStructuredText(result);
1086
+ }
1087
+
1088
+ export function extractDynamicToolResultText(contentItems) {
1089
+ return extractStructuredText(contentItems);
1090
+ }
1091
+
1092
+ // ═══════════════════════════════════════════
1093
+ // Permission / Guardian Formatting
1094
+ // ═══════════════════════════════════════════
1095
+
1096
+ export function formatPermissionProfile(profile) {
1097
+ if (!profile || typeof profile !== 'object') return '(none)';
1098
+ const parts = [];
1099
+ const read = Array.isArray(profile?.fileSystem?.read) ? profile.fileSystem.read.filter(Boolean) : [];
1100
+ const write = Array.isArray(profile?.fileSystem?.write) ? profile.fileSystem.write.filter(Boolean) : [];
1101
+ if (read.length) parts.push(`read ${read.join(', ')}`);
1102
+ if (write.length) parts.push(`write ${write.join(', ')}`);
1103
+ if (profile?.network?.enabled === true) parts.push('network access');
1104
+ if (profile?.network?.enabled === false) parts.push('network disabled');
1105
+ return parts.join(' • ') || pretty(profile);
1106
+ }
1107
+
1108
+ export function formatGuardianReview(review) {
1109
+ if (!review || typeof review !== 'object') return '';
1110
+ return [
1111
+ review.status || 'review',
1112
+ review.riskLevel ? `${review.riskLevel} risk` : '',
1113
+ Number.isFinite(review.riskScore) ? `score ${review.riskScore}` : '',
1114
+ review.rationale || '',
1115
+ ].filter(Boolean).join(' • ');
1116
+ }
1117
+
1118
+ // ═══════════════════════════════════════════
1119
+ // Changelog Editor Flow (hybrid — uses context heavily)
1120
+ // ═══════════════════════════════════════════
1121
+
1122
+ export async function openChangelogEditorFlow({
1123
+ requestId = null,
1124
+ answerKey = '',
1125
+ buttons = [],
1126
+ source = 'inline',
1127
+ responseKind = 'native',
1128
+ responseMeta = null,
1129
+ } = {}) {
1130
+ const tab = _ctx.activeTab();
1131
+ if (!tab) return false;
1132
+ const changelogText = _ctx.capturePlanContent(tab);
1133
+ if (!changelogText) {
1134
+ appendSystem('No changelog content found to edit.', 'error');
1135
+ restoreChangelogButtons(buttons);
1136
+ return false;
1137
+ }
1138
+
1139
+ try {
1140
+ const res = await fetch('/api/create-plan', {
1141
+ method: 'POST',
1142
+ headers: { 'Content-Type': 'application/json' },
1143
+ body: JSON.stringify({ content: changelogText }),
1144
+ });
1145
+ const result = await res.json();
1146
+ if (!res.ok || !result?.ok || !result.path) throw new Error('create-plan failed');
1147
+ tab._changelogRequest = {
1148
+ source,
1149
+ requestId,
1150
+ answerKey,
1151
+ buttons,
1152
+ responseKind,
1153
+ responseMeta,
1154
+ };
1155
+ _ctx.emit('open-changelog-editor', { filePath: result.path, tabId: tab.id });
1156
+ return true;
1157
+ } catch {
1158
+ appendSystem('Failed to create changelog draft.', 'error');
1159
+ restoreChangelogButtons(buttons);
1160
+ if (tab._changelogRequest?.requestId === requestId) tab._changelogRequest = null;
1161
+ return false;
1162
+ }
1163
+ }
1164
+
1165
+ // ═══════════════════════════════════════════
1166
+ // Element Appending
1167
+ // ═══════════════════════════════════════════
1168
+
1169
+ export function appendElement(el) {
1170
+ if (!_ctx.messagesEl) {
1171
+ console.warn('[cdx-render] appendElement skipped — _ctx.messagesEl is null', { hasItems: !!_ctx.items, boundTab: !!_ctx.boundTab });
1172
+ return;
1173
+ }
1174
+ _ctx.hideEmpty();
1175
+ observeOutputBlocks(_ctx.messagesEl);
1176
+ if (_ctx.threadId) {
1177
+ el.dataset.codexAccountId = String(_ctx.accountId || 'default');
1178
+ el.dataset.codexThreadId = String(_ctx.threadId);
1179
+ el.dataset.codexSessionId = String(_ctx.boundTab?.id || '');
1180
+ el.dataset.codexConnectionEpoch = String(_ctx.connectionEpoch || '');
1181
+ if (_ctx.activeTurnId) el.dataset.codexTurnId = String(_ctx.activeTurnId);
1182
+ }
1183
+ _ctx.messagesEl.appendChild(el);
1184
+ syncStatusPillTones(el);
1185
+ _ctx.pruneTranscriptDom(_ctx.messagesEl);
1186
+ if (_ctx.threadId) {
1187
+ _ctx.setTranscriptSourceMeta({
1188
+ sourceType: _ctx.transcriptSourceType && _ctx.transcriptSourceType !== 'snapshot'
1189
+ ? _ctx.transcriptSourceType
1190
+ : 'live',
1191
+ itemCount: countRenderableTranscriptNodes(_ctx.messagesEl),
1192
+ sourceUpdatedAt: Date.now(),
1193
+ });
1194
+ }
1195
+ _ctx.repositionThinking();
1196
+ _ctx.scrollEnd();
1197
+ _ctx.scheduleThreadSnapshotSave(_ctx.boundTab);
1198
+ }
1199
+
1200
+ export function appendSystem(text, tone = 'muted') {
1201
+ const el = document.createElement('div');
1202
+ el.className = `cxp-system${tone === 'error' ? ' error' : tone === 'working' ? ' working' : ''}`;
1203
+ el.textContent = text;
1204
+ appendElement(el);
1205
+ }
1206
+
1207
+ function findMcpStartupNotice(name) {
1208
+ const serverName = String(name || '');
1209
+ return [...(_ctx.messagesEl?.querySelectorAll('.cxp-mcp-startup-notice') || [])]
1210
+ .find((node) => node.dataset.mcpServer === serverName) || null;
1211
+ }
1212
+
1213
+ function applyMcpStartupNoticeTone(card, state) {
1214
+ card.dataset.state = state;
1215
+ card.classList.toggle('error', state === 'failed' || state === 'error');
1216
+ card.classList.toggle('working', state === 'opening' || state === 'waiting');
1217
+ card.classList.toggle('success', state === 'authenticated' || state === 'ready');
1218
+ }
1219
+
1220
+ export function updateMcpStartupNotice(name, {
1221
+ state = 'failed',
1222
+ title,
1223
+ error,
1224
+ statusText = '',
1225
+ buttonLabel = 'Authenticate',
1226
+ buttonDisabled = false,
1227
+ buttonHidden = false,
1228
+ onAuthenticate,
1229
+ } = {}) {
1230
+ const card = findMcpStartupNotice(name);
1231
+ if (!card) return false;
1232
+
1233
+ const titleEl = card.querySelector('.cxp-mcp-startup-title');
1234
+ const detailEl = card.querySelector('.cxp-mcp-startup-error');
1235
+ const statusEl = card.querySelector('.cxp-mcp-startup-status');
1236
+ const button = card.querySelector('.cxp-mcp-startup-auth');
1237
+ applyMcpStartupNoticeTone(card, state);
1238
+
1239
+ if (title != null && titleEl) titleEl.textContent = title;
1240
+ if (error !== undefined && detailEl) {
1241
+ detailEl.textContent = String(error || 'The MCP server could not start.');
1242
+ detailEl.hidden = state === 'ready';
1243
+ } else if (detailEl && state === 'ready') {
1244
+ detailEl.hidden = true;
1245
+ }
1246
+ if (statusEl) {
1247
+ statusEl.textContent = statusText;
1248
+ statusEl.hidden = !statusText;
1249
+ }
1250
+ if (button) {
1251
+ button.textContent = buttonLabel;
1252
+ button.disabled = !!buttonDisabled;
1253
+ button.hidden = !!buttonHidden;
1254
+ if (typeof onAuthenticate === 'function') button.onclick = onAuthenticate;
1255
+ }
1256
+
1257
+ _ctx.scheduleThreadSnapshotSave(_ctx.boundTab);
1258
+ _ctx.scrollEnd();
1259
+ return true;
1260
+ }
1261
+
1262
+ export function upsertMcpStartupNotice({
1263
+ name,
1264
+ error,
1265
+ canAuthenticate = false,
1266
+ onAuthenticate,
1267
+ } = {}) {
1268
+ const serverName = String(name || 'MCP');
1269
+ let card = findMcpStartupNotice(serverName);
1270
+ const isNew = !card;
1271
+ if (!card) {
1272
+ card = document.createElement('section');
1273
+ card.className = 'cxp-system cxp-mcp-startup-notice error';
1274
+ card.dataset.mcpServer = serverName;
1275
+ card.setAttribute('role', 'alert');
1276
+ card.setAttribute('aria-live', 'polite');
1277
+
1278
+ const titleEl = document.createElement('div');
1279
+ titleEl.className = 'cxp-mcp-startup-title';
1280
+ const detailEl = document.createElement('div');
1281
+ detailEl.className = 'cxp-mcp-startup-error';
1282
+ const actionsEl = document.createElement('div');
1283
+ actionsEl.className = 'cxp-mcp-startup-actions';
1284
+ const statusEl = document.createElement('span');
1285
+ statusEl.className = 'cxp-mcp-startup-status';
1286
+ const button = document.createElement('button');
1287
+ button.type = 'button';
1288
+ button.className = 'cxp-mcp-startup-auth';
1289
+ button.setAttribute('aria-label', `Authenticate ${serverName} MCP server`);
1290
+ actionsEl.append(statusEl, button);
1291
+ card.append(titleEl, detailEl, actionsEl);
1292
+ }
1293
+
1294
+ card.dataset.authAvailable = canAuthenticate ? '1' : '0';
1295
+ if (isNew) appendElement(card);
1296
+ updateMcpStartupNotice(serverName, {
1297
+ state: 'failed',
1298
+ title: `MCP startup failed · ${serverName}`,
1299
+ error,
1300
+ statusText: canAuthenticate ? 'Authentication required' : '',
1301
+ buttonLabel: 'Authenticate',
1302
+ buttonDisabled: false,
1303
+ buttonHidden: !canAuthenticate,
1304
+ onAuthenticate,
1305
+ });
1306
+ return card;
1307
+ }
1308
+
1309
+ function normalizeAssistantPhase(phase) {
1310
+ const value = String(phase || '').trim().toLowerCase();
1311
+ if (value === 'commentary') return 'commentary';
1312
+ if (value === 'final_answer') return 'final_answer';
1313
+ return '';
1314
+ }
1315
+
1316
+ export function createMessageShell(kind, label, { phase = '' } = {}) {
1317
+ const el = document.createElement('div');
1318
+ el.className = `cxp-msg cxp-msg-${kind}`;
1319
+ const normalizedPhase = kind === 'assistant' ? normalizeAssistantPhase(phase) : '';
1320
+ if (normalizedPhase) el.dataset.phase = normalizedPhase;
1321
+
1322
+ const avatar = document.createElement('div');
1323
+ avatar.className = 'cxp-msg-avatar';
1324
+ avatar.innerHTML = kind === 'assistant' ? OPENAI_ICON : ICON_SPARK;
1325
+ el.appendChild(avatar);
1326
+
1327
+ const card = document.createElement('div');
1328
+ card.className = 'cxp-msg-card';
1329
+
1330
+ const head = document.createElement('div');
1331
+ head.className = 'cxp-msg-head';
1332
+ const labelEl = document.createElement('div');
1333
+ labelEl.className = 'cxp-msg-label';
1334
+ labelEl.textContent = label;
1335
+ head.appendChild(labelEl);
1336
+ const phaseEl = document.createElement('div');
1337
+ phaseEl.className = 'cxp-msg-phase';
1338
+ phaseEl.hidden = normalizedPhase !== 'final_answer';
1339
+ phaseEl.textContent = normalizedPhase === 'final_answer' ? 'Final' : '';
1340
+ head.appendChild(phaseEl);
1341
+ card.appendChild(head);
1342
+
1343
+ const body = document.createElement('div');
1344
+ body.className = 'cxp-msg-body';
1345
+ card.appendChild(body);
1346
+
1347
+ el.appendChild(card);
1348
+ appendElement(el);
1349
+ return { el, head, body, labelEl, phaseEl };
1350
+ }
1351
+
1352
+ export function appendAssistantMarkdownMessage(tab, markdown) {
1353
+ if (!tab?.messagesEl || !markdown) return;
1354
+ _ctx.withTab(tab, () => {
1355
+ const shell = createMessageShell('assistant', 'Codex');
1356
+ shell.body.innerHTML = md(markdown);
1357
+ postProcessRenderedHtml(shell.body);
1358
+ });
1359
+ }
1360
+
1361
+ // ═══════════════════════════════════════════
1362
+ // Post-Plan Transcript Meta & Actions
1363
+ // ═══════════════════════════════════════════
1364
+
1365
+ export function updatePostPlanTranscriptMeta(tab = _ctx.boundTab) {
1366
+ if (!tab?.messagesEl) return;
1367
+ _ctx.withTab(tab, () => {
1368
+ if (_ctx.threadId) {
1369
+ _ctx.setTranscriptSourceMeta({
1370
+ sourceType: _ctx.transcriptSourceType && _ctx.transcriptSourceType !== 'snapshot'
1371
+ ? _ctx.transcriptSourceType
1372
+ : 'live',
1373
+ itemCount: countRenderableTranscriptNodes(_ctx.messagesEl),
1374
+ sourceUpdatedAt: Date.now(),
1375
+ });
1376
+ }
1377
+ _ctx.scheduleThreadSnapshotSave(tab);
1378
+ });
1379
+ }
1380
+
1381
+ export function syncPlanEditorActions(tab = _ctx.boundTab) {
1382
+ if (!tab?.messagesEl) return;
1383
+ syncMessagePlanActions(tab);
1384
+ const blocked = !!(tab.planEditor?.dirty || tab.planEditor?.saving);
1385
+ tab.messagesEl.querySelectorAll('[data-plan-action]').forEach((button) => {
1386
+ const action = button.dataset.planAction;
1387
+ const card = button.closest('.cxp-post-plan-card');
1388
+ button.disabled = !!(tab.running || tab.startingThread || tab.compacting)
1389
+ || !!card?.classList.contains('is-busy')
1390
+ || (action === 'feedback' && !card?.querySelector('.cxp-post-plan-feedback')?.value.trim())
1391
+ || ((action === 'continue' || action === 'feedback' || action === 'compact' || action === 'adopt') && blocked)
1392
+ || (action === 'edit' && !!tab.planEditor?.saving);
1393
+ if (action === 'continue') button.title = blocked ? 'Save or cancel the open plan edits first.' : '';
1394
+ if (action === 'edit' && button.closest('.cxp-plan-document-toolbar')) {
1395
+ button.hidden = !!tab.messagesEl.querySelector('.cxp-post-plan-msg [data-plan-action="edit"]');
1396
+ }
1397
+ });
1398
+ const notice = tab.messagesEl.querySelector('.cxp-plan-editor-notice');
1399
+ if (notice) {
1400
+ notice.hidden = !blocked;
1401
+ notice.textContent = tab.planEditor?.saving ? 'Saving plan…' : 'Save or cancel your editor changes before continuing.';
1402
+ }
1403
+ }
1404
+
1405
+ export async function openCodexPlanEditor(tab = _ctx.boundTab) {
1406
+ if (!tab?.planDocument || tab.closed || tab.running || tab.startingThread || tab.compacting || tab.planEditor?.saving) return false;
1407
+ const document = tab.planDocument;
1408
+ const identity = {
1409
+ source: 'codex', tabId: tab.id, planId: document.id, revision: document.revision,
1410
+ accountId: document.accountId, threadId: document.threadId,
1411
+ };
1412
+ try {
1413
+ // Materialize and verify every revision; a cached path can contain old text.
1414
+ const filePath = await _ctx.ensurePlanFile?.(tab);
1415
+ if (tab.closed) return false;
1416
+ if (!filePath) throw new Error('The complete plan could not be saved for editing.');
1417
+ if (tab.planDocument?.id !== document.id || tab.planDocument?.revision !== document.revision) {
1418
+ throw new Error('The plan changed while opening the editor. Open the latest revision again.');
1419
+ }
1420
+ _ctx.emit('open-plan-editor', {
1421
+ ...identity, filePath, expectedMarkdown: document.markdown,
1422
+ save: (edit) => tab.closed
1423
+ ? { ok: false, reason: 'This conversation was closed. Your edits are still available to copy.' }
1424
+ : _ctx.savePlanEdit(tab, { ...identity, ...edit }),
1425
+ });
1426
+ return true;
1427
+ } catch (error) {
1428
+ _ctx.withTab(tab, () => appendSystem(error?.message || 'Could not open the plan editor.', 'error'));
1429
+ return false;
1430
+ }
1431
+ }
1432
+
1433
+ function renderPlanDocument(tab) {
1434
+ const plan = tab.planDocument;
1435
+ let el = tab.messagesEl.querySelector('.cxp-plan-document-msg');
1436
+ if (!getPlanMarkdown(tab)) {
1437
+ el?.remove();
1438
+ tab.messagesEl.querySelectorAll('[data-plan-source-hidden]').forEach((node) => {
1439
+ node.hidden = false;
1440
+ delete node.dataset.planSourceHidden;
1441
+ });
1442
+ return null;
1443
+ }
1444
+ if (!el) {
1445
+ _ctx.withTab(tab, () => {
1446
+ const shell = createMessageShell('assistant', 'Codex');
1447
+ el = shell.el;
1448
+ el.classList.add('cxp-plan-document-msg');
1449
+ });
1450
+ }
1451
+ const body = el.querySelector('.cxp-msg-body');
1452
+ const changed = el.dataset.planId !== plan.id || Number(el.dataset.planRevision) !== plan.revision;
1453
+ el.dataset.planId = plan.id;
1454
+ el.dataset.planRevision = String(plan.revision);
1455
+ el.dataset.codexThreadId = plan.threadId || '';
1456
+ el.dataset.codexAccountId = plan.accountId || 'default';
1457
+ el.dataset.codexTurnId = plan.turnId || '';
1458
+ // Snapshot HTML has no event handlers; rebind toolbar on every restoration.
1459
+ let toolbar = body.querySelector('.cxp-plan-document-toolbar');
1460
+ if (!toolbar) {
1461
+ toolbar = document.createElement('div');
1462
+ toolbar.className = 'cxp-plan-document-toolbar';
1463
+ }
1464
+ toolbar.replaceChildren();
1465
+ let label = body.querySelector('.cxp-plan-document-label');
1466
+ if (!label) {
1467
+ label = document.createElement('div');
1468
+ label.className = 'cxp-plan-document-label';
1469
+ }
1470
+ label.textContent = `${plan.status === 'complete' ? 'Plan' : 'Draft plan'} · revision ${plan.revision}`;
1471
+ body.prepend(label);
1472
+ const copy = _ctx.createRequestButton('Copy plan', 'secondary');
1473
+ copy.addEventListener('click', async () => {
1474
+ try { await navigator.clipboard.writeText(getPlanMarkdown(tab)); copy.textContent = 'Copied'; }
1475
+ catch { _ctx.withTab(tab, () => appendSystem('Could not copy the plan.', 'error')); }
1476
+ });
1477
+ const edit = _ctx.createRequestButton(tab.planEditor?.open ? 'Reopen editor' : 'Edit plan', 'secondary');
1478
+ edit.dataset.planAction = 'edit';
1479
+ edit.addEventListener('click', () => openCodexPlanEditor(tab));
1480
+ const collapse = _ctx.createRequestButton(el.dataset.planCollapsed === '1' ? 'Expand plan' : 'Collapse plan', 'secondary');
1481
+ collapse.addEventListener('click', () => {
1482
+ const collapsed = el.dataset.planCollapsed !== '1';
1483
+ el.dataset.planCollapsed = collapsed ? '1' : '0';
1484
+ content.hidden = collapsed;
1485
+ collapse.textContent = collapsed ? 'Expand plan' : 'Collapse plan';
1486
+ _ctx.scheduleThreadSnapshotSave(tab);
1487
+ });
1488
+ toolbar.append(copy, edit, collapse);
1489
+ let content = body.querySelector('.cxp-plan-document-content');
1490
+ if (!content) {
1491
+ content = document.createElement('div');
1492
+ content.className = 'cxp-plan-document-content';
1493
+ }
1494
+ // Re-establish document-first order for live updates and old saved snapshots.
1495
+ body.append(content, toolbar);
1496
+ if (changed || !el._planDocumentBound || !content.childNodes.length) {
1497
+ content.innerHTML = md(plan.markdown);
1498
+ postProcessRenderedHtml(content);
1499
+ }
1500
+ el._planDocumentBound = true;
1501
+ content.hidden = el.dataset.planCollapsed === '1';
1502
+ tab.messagesEl.querySelectorAll('[data-item-id]').forEach((node) => {
1503
+ const sameSource = node.dataset.itemId === plan.itemId;
1504
+ if (sameSource || node.dataset.planSourceHidden) {
1505
+ node.hidden = sameSource;
1506
+ if (sameSource) node.dataset.planSourceHidden = '1';
1507
+ else delete node.dataset.planSourceHidden;
1508
+ }
1509
+ // A regenerated plan can arrive later in the transcript. Keep its document
1510
+ // and the following review controls beside that current source item.
1511
+ if (sameSource && node.parentElement === tab.messagesEl && node !== el && node.nextElementSibling !== el) node.after(el);
1512
+ });
1513
+ syncPlanEditorActions(tab);
1514
+ return el;
1515
+ }
1516
+
1517
+ function renderRecoverablePlanDraft(tab) {
1518
+ tab.messagesEl.querySelectorAll('.cxp-plan-draft-msg').forEach((node) => node.remove());
1519
+ const draft = tab.planDraft;
1520
+ if (!draft?.markdown) return;
1521
+ _ctx.withTab(tab, () => {
1522
+ const shell = createMessageShell('assistant', 'Draft plan');
1523
+ shell.el.classList.add('cxp-plan-draft-msg');
1524
+ const note = document.createElement('p');
1525
+ note.textContent = 'This draft needs review before it can become the implementation plan.';
1526
+ const content = document.createElement('div');
1527
+ content.innerHTML = md(draft.markdown);
1528
+ postProcessRenderedHtml(content);
1529
+ const adopt = _ctx.createRequestButton('Use as plan', 'secondary');
1530
+ adopt.dataset.planAction = 'adopt';
1531
+ adopt.disabled = !!(tab.running || tab.planEditor?.dirty || tab.planEditor?.saving);
1532
+ adopt.addEventListener('click', () => {
1533
+ if (tab.running || tab.planEditor?.dirty || tab.planEditor?.saving) return;
1534
+ _ctx.adoptMessageAsPlan?.(tab, {
1535
+ itemId: draft.itemId || 'recovered-draft', turnId: draft.turnId || '', markdown: draft.markdown,
1536
+ });
1537
+ });
1538
+ shell.body.append(note, content, adopt);
1539
+ });
1540
+ }
1541
+
1542
+ export function renderPostPlanActions(tab = _ctx.boundTab, headerText = null) {
1543
+ if (!tab?.messagesEl) return;
1544
+ syncPlanMirrors(tab);
1545
+ syncMessagePlanActions(tab);
1546
+ tab.postPlanHeader = headerText || tab.postPlanHeader || 'PLAN COMPLETE';
1547
+ if (_ctx.boundTab === tab) {
1548
+ // Sync the module-level _postPlanHeader via context
1549
+ if (typeof _ctx.setPostPlanHeader === 'function') _ctx.setPostPlanHeader(tab.postPlanHeader);
1550
+ }
1551
+
1552
+ const planEl = renderPlanDocument(tab);
1553
+ renderRecoverablePlanDraft(tab);
1554
+ removePostPlanCards(tab.messagesEl);
1555
+ if (!tab.showPostPlanActions || tab.planDocument?.status !== 'complete' || !getPlanMarkdown(tab)) {
1556
+ updatePostPlanTranscriptMeta(tab);
1557
+ return;
1558
+ }
1559
+
1560
+ const el = document.createElement('div');
1561
+ el.className = 'cxp-msg cxp-msg-assistant cxp-post-plan-msg';
1562
+
1563
+ const cardWrap = document.createElement('div');
1564
+ cardWrap.className = 'cxp-msg-card';
1565
+
1566
+ const body = document.createElement('div');
1567
+ body.className = 'cxp-msg-body';
1568
+
1569
+ const card = document.createElement('div');
1570
+ card.className = 'cxp-post-plan-card';
1571
+ card.setAttribute('role', 'group');
1572
+ card.setAttribute('aria-label', 'Plan review');
1573
+
1574
+ const continueBtn = _ctx.createRequestButton('Continue with implementation', 'primary');
1575
+ const compactBtn = _ctx.createRequestButton('Compact context', 'secondary');
1576
+ const editBtn = _ctx.createRequestButton('Edit plan', 'secondary');
1577
+ continueBtn.dataset.planAction = 'continue';
1578
+ compactBtn.dataset.planAction = 'compact';
1579
+ editBtn.dataset.planAction = 'edit';
1580
+ const editorNotice = document.createElement('div');
1581
+ editorNotice.className = 'cxp-plan-editor-notice';
1582
+ editorNotice.setAttribute('role', 'status');
1583
+ editorNotice.hidden = true;
1584
+ card.appendChild(editorNotice);
1585
+
1586
+ const decision = document.createElement('div');
1587
+ decision.className = 'cxp-post-plan-decision';
1588
+ decision.appendChild(continueBtn);
1589
+
1590
+ const feedbackGroup = document.createElement('div');
1591
+ feedbackGroup.className = 'cxp-post-plan-feedback-group';
1592
+ const feedbackId = `cxp-plan-feedback-${tab.id || 'active'}`;
1593
+ const feedbackLabel = document.createElement('label');
1594
+ feedbackLabel.className = 'cxp-post-plan-feedback-label';
1595
+ feedbackLabel.htmlFor = feedbackId;
1596
+ feedbackLabel.textContent = 'Suggest changes';
1597
+ const feedback = document.createElement('textarea');
1598
+ feedback.id = feedbackId;
1599
+ feedback.className = 'cxp-post-plan-feedback';
1600
+ feedback.rows = 2;
1601
+ feedback.placeholder = 'Add a requirement, change the approach, remove a step…';
1602
+ feedback.value = tab.planFeedbackDraft || '';
1603
+ feedback.setAttribute('aria-describedby', `${feedbackId}-hint ${feedbackId}-error`);
1604
+
1605
+ const feedbackFooter = document.createElement('div');
1606
+ feedbackFooter.className = 'cxp-post-plan-feedback-footer';
1607
+ const feedbackHint = document.createElement('span');
1608
+ feedbackHint.id = `${feedbackId}-hint`;
1609
+ feedbackHint.className = 'cxp-post-plan-feedback-hint';
1610
+ feedbackHint.textContent = '⌘/Ctrl + Enter to update';
1611
+ const updateBtn = _ctx.createRequestButton('Update plan', 'secondary');
1612
+ updateBtn.dataset.planAction = 'feedback';
1613
+ updateBtn.classList.add('cxp-post-plan-update');
1614
+ feedbackFooter.append(feedbackHint, updateBtn);
1615
+
1616
+ const feedbackError = document.createElement('div');
1617
+ feedbackError.id = `${feedbackId}-error`;
1618
+ feedbackError.className = 'cxp-post-plan-feedback-error';
1619
+ feedbackError.setAttribute('role', 'status');
1620
+ feedbackError.hidden = true;
1621
+
1622
+ feedbackGroup.append(feedbackLabel, feedback, feedbackFooter, feedbackError);
1623
+
1624
+ const utilities = document.createElement('div');
1625
+ utilities.className = 'cxp-post-plan-utilities';
1626
+ utilities.append(editBtn, compactBtn);
1627
+
1628
+ let busy = false;
1629
+ const autosizeFeedback = () => {
1630
+ feedback.style.height = 'auto';
1631
+ feedback.style.height = `${Math.min(feedback.scrollHeight, 132)}px`;
1632
+ };
1633
+ const syncFeedbackAction = () => {
1634
+ updateBtn.disabled = busy
1635
+ || !feedback.value.trim()
1636
+ || _ctx.running
1637
+ || _ctx.startingThread
1638
+ || _ctx.compacting
1639
+ || tab.planEditor?.dirty
1640
+ || tab.planEditor?.saving;
1641
+ };
1642
+ const showFeedbackError = (message = '') => {
1643
+ feedbackError.textContent = message;
1644
+ feedbackError.hidden = !message;
1645
+ };
1646
+
1647
+ const setBusy = (nextBusy) => {
1648
+ busy = !!nextBusy;
1649
+ card.classList.toggle('is-busy', busy);
1650
+ card.setAttribute('aria-busy', busy ? 'true' : 'false');
1651
+ [continueBtn, compactBtn, editBtn].forEach((btn) => { btn.disabled = busy; });
1652
+ feedback.disabled = busy;
1653
+ if (!busy) syncPlanEditorActions(tab);
1654
+ syncFeedbackAction();
1655
+ };
1656
+
1657
+ feedback.addEventListener('input', () => {
1658
+ tab.planFeedbackDraft = feedback.value;
1659
+ if (_ctx.isActiveTab(tab) && typeof _ctx.setPlanFeedbackDraft === 'function') {
1660
+ _ctx.setPlanFeedbackDraft(feedback.value);
1661
+ }
1662
+ autosizeFeedback();
1663
+ showFeedbackError();
1664
+ syncFeedbackAction();
1665
+ _ctx.saveTabs();
1666
+ });
1667
+
1668
+ const submitFeedback = () => {
1669
+ _ctx.withTab(tab, () => {
1670
+ if (busy || _ctx.running || _ctx.startingThread || _ctx.compacting || tab.planEditor?.dirty || tab.planEditor?.saving) return;
1671
+ const requestedChanges = feedback.value.trim();
1672
+ if (!requestedChanges) {
1673
+ syncFeedbackAction();
1674
+ feedback.focus();
1675
+ return;
1676
+ }
1677
+ const planText = getPlanMarkdown(tab);
1678
+ const prompt = buildCodexPlanRevisionPrompt(planText, requestedChanges);
1679
+ if (!prompt) return;
1680
+
1681
+ showFeedbackError();
1682
+ setBusy(true);
1683
+ const previousPlanMode = !!tab.planMode;
1684
+ tab.planMode = true;
1685
+ _ctx.syncToolbarState();
1686
+ const sent = _ctx.dispatchPrompt(prompt, { tab, forcePlanModePrefix: true });
1687
+ if (!sent) {
1688
+ tab.planMode = previousPlanMode;
1689
+ _ctx.syncToolbarState();
1690
+ setBusy(false);
1691
+ showFeedbackError('Could not send these changes. Check the Codex connection and try again.');
1692
+ feedback.focus();
1693
+ _ctx.saveTabs();
1694
+ return;
1695
+ }
1696
+
1697
+ tab.planFeedbackDraft = '';
1698
+ tab.showPostPlanActions = false;
1699
+ tab.planApprovalPending = false;
1700
+ if (typeof _ctx.setPlanFeedbackDraft === 'function') _ctx.setPlanFeedbackDraft('');
1701
+ if (typeof _ctx.setShowPostPlanActions === 'function') _ctx.setShowPostPlanActions(false);
1702
+ if (typeof _ctx.setPlanApprovalPending === 'function') _ctx.setPlanApprovalPending(false);
1703
+ removePostPlanCards(tab.messagesEl);
1704
+ updatePostPlanTranscriptMeta(tab);
1705
+ _ctx.saveTabs();
1706
+ });
1707
+ };
1708
+
1709
+ feedback.addEventListener('keydown', (event) => {
1710
+ if (event.key === 'Enter' && (event.metaKey || event.ctrlKey)) {
1711
+ event.preventDefault();
1712
+ submitFeedback();
1713
+ }
1714
+ });
1715
+ updateBtn.addEventListener('click', submitFeedback);
1716
+
1717
+ continueBtn.addEventListener('click', async () => {
1718
+ if (busy) return;
1719
+ const selectedPlan = tab.planDocument;
1720
+ const initialApproval = approvePlan(tab);
1721
+ if (!initialApproval.ok) { showFeedbackError(initialApproval.reason); return; }
1722
+ setBusy(true);
1723
+ try {
1724
+ await _ctx.persistPlanDocument(tab);
1725
+ if (tab.closed) { setBusy(false); return; }
1726
+ if (tab.planDocument !== selectedPlan) throw new Error('The plan changed. Review the latest revision before continuing.');
1727
+ } catch (error) {
1728
+ setBusy(false);
1729
+ showFeedbackError(error?.message || 'Could not verify the approved plan.');
1730
+ return;
1731
+ }
1732
+ _ctx.withTab(tab, () => {
1733
+ if (_ctx.running || _ctx.startingThread || _ctx.compacting) { setBusy(false); return; }
1734
+ const approval = approvePlan(tab);
1735
+ if (!approval.ok) { setBusy(false); showFeedbackError(approval.reason); return; }
1736
+ const finalPrompt = approval.prompt;
1737
+ const previousPlanMode = !!tab.planMode;
1738
+ tab.planMode = false;
1739
+ tab.showPostPlanActions = false;
1740
+ tab.planApprovalPending = false;
1741
+ tab.planTurnActive = false;
1742
+ tab.lastPlanTurnId = '';
1743
+ tab.postPlanHeader = 'PLAN COMPLETE';
1744
+ if (typeof _ctx.setShowPostPlanActions === 'function') _ctx.setShowPostPlanActions(false);
1745
+ if (typeof _ctx.setPlanApprovalPending === 'function') _ctx.setPlanApprovalPending(false);
1746
+ if (typeof _ctx.setPlanTurnActive === 'function') _ctx.setPlanTurnActive(false);
1747
+ if (typeof _ctx.setLastPlanTurnId === 'function') _ctx.setLastPlanTurnId('');
1748
+ if (typeof _ctx.setPostPlanHeader === 'function') _ctx.setPostPlanHeader('PLAN COMPLETE');
1749
+ _ctx.syncToolbarState();
1750
+ const sent = _ctx.dispatchPrompt(finalPrompt, { tab, forcePlanModePrefix: false });
1751
+ if (!sent) {
1752
+ tab.planMode = previousPlanMode;
1753
+ tab.showPostPlanActions = true;
1754
+ tab.planApprovalPending = true;
1755
+ if (typeof _ctx.setShowPostPlanActions === 'function') _ctx.setShowPostPlanActions(true);
1756
+ if (typeof _ctx.setPlanApprovalPending === 'function') _ctx.setPlanApprovalPending(true);
1757
+ _ctx.syncToolbarState();
1758
+ renderPostPlanActions(tab);
1759
+ return;
1760
+ }
1761
+ _ctx.markPlanImplementationSubmitted?.(tab, selectedPlan);
1762
+ selectedPlan.approvedRevision = selectedPlan.revision;
1763
+ _ctx.persistPlanDocument(tab).catch((error) => {
1764
+ _ctx.withTab(tab, () => appendSystem(error?.message || 'Could not save the plan approval.', 'error'));
1765
+ });
1766
+ tab.planFeedbackDraft = '';
1767
+ if (typeof _ctx.setPlanFeedbackDraft === 'function') _ctx.setPlanFeedbackDraft('');
1768
+ removePostPlanCards(tab.messagesEl);
1769
+ updatePostPlanTranscriptMeta(tab);
1770
+ _ctx.saveTabs();
1771
+ });
1772
+ });
1773
+
1774
+ compactBtn.addEventListener('click', () => {
1775
+ _ctx.withTab(tab, () => {
1776
+ if (_ctx.running || _ctx.startingThread || _ctx.compacting || tab.planEditor?.dirty || tab.planEditor?.saving) return;
1777
+ _ctx.startCompaction();
1778
+ });
1779
+ });
1780
+
1781
+ editBtn.addEventListener('click', async () => {
1782
+ setBusy(true);
1783
+ await openCodexPlanEditor(tab);
1784
+ setBusy(false);
1785
+ });
1786
+
1787
+ card.append(decision, feedbackGroup, utilities);
1788
+ body.appendChild(card);
1789
+ cardWrap.appendChild(body);
1790
+ el.appendChild(cardWrap);
1791
+
1792
+ // Actions belong immediately below their document, including after a save.
1793
+ if (planEl?.parentElement === tab.messagesEl) planEl.after(el);
1794
+ else _ctx.withTab(tab, () => appendElement(el));
1795
+ autosizeFeedback();
1796
+ syncPlanEditorActions(tab);
1797
+ syncFeedbackAction();
1798
+ updatePostPlanTranscriptMeta(tab);
1799
+ }
1800
+
1801
+ export function renderPostCompactionActions(tab = _ctx.boundTab) {
1802
+ if (!tab?.messagesEl) return;
1803
+
1804
+ removePostCompactionCards(tab.messagesEl);
1805
+ if (!tab.showPostCompactionPrompt || tab.showPostPlanActions || tab.postCompactionPromptSource !== 'manual') {
1806
+ updatePostPlanTranscriptMeta(tab);
1807
+ return;
1808
+ }
1809
+
1810
+ const el = document.createElement('div');
1811
+ el.className = 'cxp-msg cxp-msg-assistant cxp-post-compact-msg';
1812
+
1813
+ const avatar = document.createElement('div');
1814
+ avatar.className = 'cxp-msg-avatar';
1815
+ avatar.innerHTML = OPENAI_ICON;
1816
+ el.appendChild(avatar);
1817
+
1818
+ const cardWrap = document.createElement('div');
1819
+ cardWrap.className = 'cxp-msg-card';
1820
+
1821
+ const head = document.createElement('div');
1822
+ head.className = 'cxp-msg-head';
1823
+ const labelEl = document.createElement('div');
1824
+ labelEl.className = 'cxp-msg-label';
1825
+ labelEl.textContent = 'Codex';
1826
+ head.appendChild(labelEl);
1827
+ cardWrap.appendChild(head);
1828
+
1829
+ const body = document.createElement('div');
1830
+ body.className = 'cxp-msg-body';
1831
+
1832
+ const card = document.createElement('div');
1833
+ card.className = 'cxp-post-plan-card cxp-post-compact-card';
1834
+
1835
+ const header = document.createElement('div');
1836
+ header.className = 'cxp-post-plan-header';
1837
+ header.textContent = 'CONTEXT COMPACTED';
1838
+ card.appendChild(header);
1839
+
1840
+ const note = document.createElement('div');
1841
+ note.className = 'cxp-post-plan-note';
1842
+ note.textContent = 'Context was compacted. Continue when you are ready and Codex will resume from the summarized conversation.';
1843
+ card.appendChild(note);
1844
+
1845
+ const actions = document.createElement('div');
1846
+ actions.className = 'cxp-post-plan-actions';
1847
+
1848
+ const continueBtn = _ctx.createRequestButton('Continue', 'primary');
1849
+ const dismissBtn = _ctx.createRequestButton('Not now', 'secondary');
1850
+
1851
+ const syncHiddenState = () => {
1852
+ tab.showPostCompactionPrompt = false;
1853
+ tab.postCompactionPending = false;
1854
+ tab.postCompactionPromptSource = '';
1855
+ if (_ctx.isActiveTab(tab)) {
1856
+ if (typeof _ctx.setShowPostCompactionPrompt === 'function') _ctx.setShowPostCompactionPrompt(false);
1857
+ if (typeof _ctx.setPostCompactionPending === 'function') _ctx.setPostCompactionPending(false);
1858
+ if (typeof _ctx.setPostCompactionPromptSource === 'function') _ctx.setPostCompactionPromptSource('');
1859
+ }
1860
+ };
1861
+
1862
+ const setBusy = (busy) => {
1863
+ [continueBtn, dismissBtn].forEach((btn) => { btn.disabled = busy; });
1864
+ card.style.opacity = busy ? '0.55' : '1';
1865
+ card.style.pointerEvents = busy ? 'none' : 'auto';
1866
+ };
1867
+
1868
+ continueBtn.addEventListener('click', () => {
1869
+ _ctx.withTab(tab, () => {
1870
+ if (_ctx.running || _ctx.startingThread) return;
1871
+ syncHiddenState();
1872
+ const sent = _ctx.dispatchPrompt('Please continue from the compacted conversation where you left off.', {
1873
+ tab,
1874
+ forcePlanModePrefix: false,
1875
+ });
1876
+ if (!sent) {
1877
+ tab.showPostCompactionPrompt = true;
1878
+ tab.postCompactionPromptSource = 'manual';
1879
+ if (_ctx.isActiveTab(tab) && typeof _ctx.setShowPostCompactionPrompt === 'function') {
1880
+ _ctx.setShowPostCompactionPrompt(true);
1881
+ }
1882
+ if (_ctx.isActiveTab(tab) && typeof _ctx.setPostCompactionPromptSource === 'function') {
1883
+ _ctx.setPostCompactionPromptSource('manual');
1884
+ }
1885
+ renderPostCompactionActions(tab);
1886
+ return;
1887
+ }
1888
+ removePostCompactionCards(tab.messagesEl);
1889
+ updatePostPlanTranscriptMeta(tab);
1890
+ _ctx.saveTabs();
1891
+ });
1892
+ });
1893
+
1894
+ dismissBtn.addEventListener('click', () => {
1895
+ setBusy(true);
1896
+ _ctx.withTab(tab, () => {
1897
+ syncHiddenState();
1898
+ removePostCompactionCards(tab.messagesEl);
1899
+ updatePostPlanTranscriptMeta(tab);
1900
+ _ctx.saveTabs();
1901
+ });
1902
+ });
1903
+
1904
+ actions.append(continueBtn, dismissBtn);
1905
+ card.appendChild(actions);
1906
+ body.appendChild(card);
1907
+ cardWrap.appendChild(body);
1908
+ el.appendChild(cardWrap);
1909
+
1910
+ _ctx.withTab(tab, () => appendElement(el));
1911
+ }
1912
+
1913
+ // ═══════════════════════════════════════════
1914
+ // Card System
1915
+ // ═══════════════════════════════════════════
1916
+
1917
+ export function createCard(item, iconSvg, title, subtitle = '') {
1918
+ const el = document.createElement('div');
1919
+ el.className = 'cxp-card cxp-collapsed';
1920
+ el.dataset.itemId = item.id || '';
1921
+ el.dataset.expanded = '0';
1922
+ if (item?.type) el.dataset.itemType = item.type;
1923
+ if (item?.type === 'mcpToolCall' && isSynaBunMcpTool(item)) el.classList.add('cxp-card-synabun-tool');
1924
+
1925
+ const head = document.createElement('div');
1926
+ head.className = 'cxp-card-head';
1927
+
1928
+ const icon = document.createElement('div');
1929
+ icon.className = 'cxp-card-icon';
1930
+ icon.innerHTML = iconSvg;
1931
+ head.appendChild(icon);
1932
+
1933
+ const titles = document.createElement('div');
1934
+ titles.className = 'cxp-card-titles';
1935
+ const titleEl = document.createElement('div');
1936
+ titleEl.className = 'cxp-card-title';
1937
+ titleEl.textContent = title;
1938
+ titles.appendChild(titleEl);
1939
+ const subtitleEl = document.createElement('div');
1940
+ subtitleEl.className = 'cxp-card-subtitle';
1941
+ subtitleEl.textContent = subtitle;
1942
+ titles.appendChild(subtitleEl);
1943
+ head.appendChild(titles);
1944
+
1945
+ const pill = document.createElement('div');
1946
+ pill.className = 'cxp-status-pill';
1947
+ head.appendChild(pill);
1948
+
1949
+ const chevron = document.createElement('div');
1950
+ chevron.className = 'cxp-card-chevron';
1951
+ head.appendChild(chevron);
1952
+
1953
+ const body = document.createElement('div');
1954
+ body.className = 'cxp-card-body';
1955
+
1956
+ el.append(head, body);
1957
+ bindCardHead(el);
1958
+ appendElement(el);
1959
+ return { el, head, body, titleEl, subtitleEl, pill };
1960
+ }
1961
+
1962
+ export function bindCardHead(card) {
1963
+ bindExpandableCard(card, (node, expanded) => {
1964
+ node.dataset.userCollapsed = expanded ? '0' : '1';
1965
+ const state = expanded && node.dataset.itemId ? _ctx.items.get(node.dataset.itemId) : null;
1966
+ if (state) flushCardBody(state);
1967
+ _ctx.scheduleThreadSnapshotSave(_ctx.boundTab);
1968
+ _ctx.saveTabs();
1969
+ });
1970
+ }
1971
+
1972
+ export function createFoldSection(label, {
1973
+ className = '',
1974
+ bodyClassName = '',
1975
+ preformatted = true,
1976
+ open = false,
1977
+ } = {}) {
1978
+ const details = document.createElement('details');
1979
+ details.className = `cxp-fold${className ? ` ${className}` : ''}`;
1980
+ details.hidden = true;
1981
+ details.open = !!open;
1982
+ details.dataset.expanded = open ? '1' : '0';
1983
+
1984
+ const summary = document.createElement('summary');
1985
+ summary.className = 'cxp-fold-summary';
1986
+ const labelEl = document.createElement('span');
1987
+ labelEl.className = 'cxp-fold-label';
1988
+ labelEl.textContent = label;
1989
+ summary.appendChild(labelEl);
1990
+ const chevron = document.createElement('span');
1991
+ chevron.className = 'cxp-card-chevron';
1992
+ summary.appendChild(chevron);
1993
+ details.appendChild(summary);
1994
+
1995
+ const bodyWrap = document.createElement('div');
1996
+ bodyWrap.className = 'cxp-fold-body';
1997
+ const bodyEl = preformatted ? document.createElement('pre') : document.createElement('div');
1998
+ bodyEl.className = bodyClassName || (preformatted ? 'cxp-card-pre' : '');
1999
+ bodyWrap.appendChild(bodyEl);
2000
+ details.appendChild(bodyWrap);
2001
+
2002
+ details.addEventListener('toggle', () => {
2003
+ details.dataset.expanded = details.open ? '1' : '0';
2004
+ _ctx.scheduleThreadSnapshotSave(_ctx.boundTab);
2005
+ });
2006
+
2007
+ return { sectionEl: details, bodyEl, labelEl };
2008
+ }
2009
+
2010
+ export function isSynaBunMcpTool(item) {
2011
+ return String(item?.server || '').trim().toLowerCase() === 'synabun';
2012
+ }
2013
+
2014
+ export function getMcpToolIcon(item) {
2015
+ return isSynaBunMcpTool(item) ? SYNABUN_LOGO_ICON : ICON_TOOL;
2016
+ }
2017
+
2018
+ export function isCardBodyHidden(state) {
2019
+ return !!state?.el?.classList.contains('cxp-collapsed');
2020
+ }
2021
+
2022
+ function hasOwnField(value, key) {
2023
+ return !!value && Object.prototype.hasOwnProperty.call(value, key);
2024
+ }
2025
+
2026
+ function normalizeReasoningText(value) {
2027
+ if (typeof value === 'string') return value;
2028
+ if (value == null) return '';
2029
+ if (Array.isArray(value)) {
2030
+ return value.map(normalizeReasoningText).filter(Boolean).join('\n');
2031
+ }
2032
+ if (typeof value === 'object') {
2033
+ for (const key of ['text', 'summaryText', 'summary_text', 'content', 'message']) {
2034
+ if (typeof value[key] === 'string') return value[key];
2035
+ }
2036
+ return extractStructuredText(value);
2037
+ }
2038
+ return String(value);
2039
+ }
2040
+
2041
+ function normalizeReasoningLines(value) {
2042
+ if (value == null) return [];
2043
+ const values = Array.isArray(value) ? value : [value];
2044
+ return values
2045
+ .map((entry) => normalizeReasoningText(entry).trim())
2046
+ .filter(Boolean);
2047
+ }
2048
+
2049
+ function reasoningTextFromKeys(item, keys) {
2050
+ if (!item || typeof item !== 'object') return [];
2051
+ for (const key of keys) {
2052
+ if (!hasOwnField(item, key)) continue;
2053
+ const lines = normalizeReasoningLines(item[key]);
2054
+ if (lines.length) return lines;
2055
+ }
2056
+ return [];
2057
+ }
2058
+
2059
+ function isCompleteStatus(status) {
2060
+ if (!status) return false;
2061
+ if (typeof status === 'string') {
2062
+ return /^(complete|completed|done|success|failed|errored)$/i.test(status.trim());
2063
+ }
2064
+ if (typeof status === 'object') {
2065
+ return Object.keys(status).some((key) => isCompleteStatus(key));
2066
+ }
2067
+ return false;
2068
+ }
2069
+
2070
+ function isReasoningComplete(item, hasIncomingText = false) {
2071
+ return isCompleteStatus(item?.status)
2072
+ || !!item?._synabunCompleted
2073
+ || (!_ctx.running && item?._synabunOwnership?.source === 'history')
2074
+ || !!item?.completedAt
2075
+ || !!item?.completed_at
2076
+ || item?.durationMs != null
2077
+ || item?.duration_ms != null
2078
+ || (hasIncomingText && !_ctx.running);
2079
+ }
2080
+
2081
+ function getReasoningPreviewText(state) {
2082
+ const summaryText = Array.isArray(state?.summaryLines)
2083
+ ? state.summaryLines.map((line) => String(line || '').trim()).filter(Boolean).join(' ')
2084
+ : '';
2085
+ const contentText = Array.isArray(state?.contentLines)
2086
+ ? state.contentLines.map((line) => String(line || '').trim()).filter(Boolean).join(' ')
2087
+ : '';
2088
+ return _ctx.cleanPreview(summaryText || contentText || 'Working through the next step');
2089
+ }
2090
+
2091
+ function updateReasoningChrome(state, { complete = false } = {}) {
2092
+ if (!state) return;
2093
+ if (state.summaryEl) state.summaryEl.textContent = complete && !state.summaryLines?.some(line => String(line).trim()) && !state.contentLines?.some(line => String(line).trim()) ? 'No reasoning summary was provided.' : getReasoningPreviewText(state);
2094
+ if (state.pillEl) state.pillEl.textContent = complete ? 'complete' : 'live';
2095
+ if (state.el) state.el.classList.toggle('cxp-reasoning-done', !!complete);
2096
+ }
2097
+
2098
+ function cacheCollapsedCardState(state, item, itemType) {
2099
+ if (!state || !item) return;
2100
+ switch (itemType) {
2101
+ case 'plan':
2102
+ state.buffer = resolveFinalMarkdownText(state, item.text);
2103
+ break;
2104
+ case 'commandExecution': {
2105
+ if (hasOwnField(item, 'command')) {
2106
+ state.commandText = typeof item.command === 'string'
2107
+ ? item.command
2108
+ : (item.command == null ? '' : String(item.command));
2109
+ }
2110
+ if (hasOwnField(item, 'cwd')) state.commandCwd = item.cwd || '';
2111
+ const summary = summarizeCommandExecution(state.commandText || '', state.commandCwd || '');
2112
+ state.commandTitle = summary.title;
2113
+ state.commandSubtitle = summary.detail;
2114
+ const metaText = [
2115
+ item.processId ? `PID ${item.processId}` : '',
2116
+ item.durationMs != null ? `${item.durationMs}ms` : '',
2117
+ item.exitCode != null ? `exit ${item.exitCode}` : '',
2118
+ ].filter(Boolean).join(' • ');
2119
+ if (metaText || !state.metaText) state.metaText = metaText;
2120
+ if (item.aggregatedOutput != null) state.outputBuf = item.aggregatedOutput || '';
2121
+ break;
2122
+ }
2123
+ case 'mcpToolCall': {
2124
+ if (hasOwnField(item, 'server')) state.toolServer = item.server || '';
2125
+ if (hasOwnField(item, 'tool')) state.toolName = item.tool || '';
2126
+ const ui = item.mcpAppUi;
2127
+ const metadata = [item.durationMs != null ? `${item.durationMs}ms` : '', ui?.resourceUri || item.mcpAppResourceUri || '', ui?.preferredModelDisplayMode ? `Display: ${ui.preferredModelDisplayMode}` : ''].filter(Boolean).join(' · ');
2128
+ if (metadata) state.toolSubtitle = metadata;
2129
+ else if (!state.toolSubtitle) state.toolSubtitle = 'Tool call';
2130
+ if (hasOwnField(item, 'arguments')) state.argsText = formatStructuredValue(item.arguments);
2131
+ if (hasOwnField(item, 'error')) {
2132
+ state.progressText = item.error
2133
+ ? `Error: ${extractStructuredText(item.error) || formatStructuredValue(item.error)}`
2134
+ : '';
2135
+ }
2136
+ if (hasOwnField(item, 'result') || hasOwnField(item, 'error')) {
2137
+ state.resultText = extractToolResultText(item.result) || (item.error ? formatStructuredValue(item.error) : '');
2138
+ }
2139
+ break;
2140
+ }
2141
+ case 'dynamicToolCall': {
2142
+ if (hasOwnField(item, 'tool')) state.dynamicToolName = [item.namespace, item.tool].filter(Boolean).join(' / ');
2143
+ if (item.durationMs != null) state.dynamicSubtitle = `${item.durationMs}ms`;
2144
+ else if (!state.dynamicSubtitle) state.dynamicSubtitle = 'Client tool call';
2145
+ if (hasOwnField(item, 'arguments')) state.argsText = formatStructuredValue(item.arguments);
2146
+ if (hasOwnField(item, 'contentItems') || hasOwnField(item, 'success')) {
2147
+ state.resultText = extractDynamicToolResultText(item.contentItems)
2148
+ || (item.success === false ? 'Client tool call failed' : '');
2149
+ }
2150
+ break;
2151
+ }
2152
+ case 'fileChange': {
2153
+ const changes = Array.isArray(item.changes) ? item.changes : [];
2154
+ if (hasOwnField(item, 'changes')) {
2155
+ state.fileMetaText = changes.length ? `${changes.length} file change${changes.length > 1 ? 's' : ''}` : 'Pending file changes';
2156
+ state.filesText = resolveFileChangeSummary(item);
2157
+ const diffText = changes
2158
+ .map((change) => extractStructuredText(change?.diff || change?.patch || change?.content || change?.output || ''))
2159
+ .filter(Boolean)
2160
+ .join('\n\n');
2161
+ if (item.aggregatedOutput != null || diffText) {
2162
+ state.outputBuf = extractStructuredText(item.aggregatedOutput) || item.aggregatedOutput || diffText;
2163
+ }
2164
+ } else if (item.aggregatedOutput != null) {
2165
+ state.outputBuf = extractStructuredText(item.aggregatedOutput) || item.aggregatedOutput || '';
2166
+ }
2167
+ break;
2168
+ }
2169
+ case 'collabAgentToolCall': {
2170
+ if (hasOwnField(item, 'tool')) state.collabToolName = item.tool || '';
2171
+ const subtitle = [
2172
+ item.model || state.collabModel || '',
2173
+ Array.isArray(item.receiverThreadIds) && item.receiverThreadIds.length
2174
+ ? `${item.receiverThreadIds.length} agent${item.receiverThreadIds.length > 1 ? 's' : ''}`
2175
+ : '',
2176
+ ].filter(Boolean).join(' · ') || 'Sub-agent delegation';
2177
+ if (subtitle || !state.collabSubtitle) state.collabSubtitle = subtitle;
2178
+ if (hasOwnField(item, 'model')) state.collabModel = item.model || '';
2179
+ if (hasOwnField(item, 'prompt')) state.promptText = item.prompt || '';
2180
+ if (hasOwnField(item, 'agentsStates')) state.collabResultText = item.agentsStates ? pretty(item.agentsStates) : '';
2181
+ break;
2182
+ }
2183
+ default:
2184
+ break;
2185
+ }
2186
+ }
2187
+
2188
+ export function flushCardBody(state) {
2189
+ if (!state?._bodyDirty) return;
2190
+ state._bodyDirty = false;
2191
+ switch (state.type) {
2192
+ case 'plan':
2193
+ setMarkdownBuffer(state, state.buffer || '', true);
2194
+ break;
2195
+ case 'commandExecution':
2196
+ if (state.titleEl) state.titleEl.textContent = state.commandTitle || 'Running shell command';
2197
+ if (state.subtitleEl) state.subtitleEl.textContent = state.commandSubtitle || 'Executing a shell command';
2198
+ if (state.metaEl) state.metaEl.textContent = state.metaText || '';
2199
+ setOptionalSection(state.commandSectionEl, state.commandEl, state.commandText || '');
2200
+ if (state.interactionEl) state.interactionEl.hidden = !state.interactionEl.textContent;
2201
+ setOptionalSection(state.outputSectionEl, state.outputEl, state.outputBuf || '');
2202
+ break;
2203
+ case 'mcpToolCall':
2204
+ if (state.titleEl) state.titleEl.textContent = `${state.toolServer || 'MCP'} • ${state.toolName || 'tool'}`;
2205
+ if (state.subtitleEl) state.subtitleEl.textContent = state.toolSubtitle || 'Tool call';
2206
+ setOptionalSection(state.argsSectionEl, state.argsEl, state.argsText || '');
2207
+ setOptionalText(state.progressEl, state.progressText || '');
2208
+ setOptionalSection(state.resultSectionEl, state.resultEl, state.resultText || '');
2209
+ break;
2210
+ case 'dynamicToolCall':
2211
+ if (state.titleEl) state.titleEl.textContent = state.dynamicToolName || 'Dynamic tool';
2212
+ if (state.subtitleEl) state.subtitleEl.textContent = state.dynamicSubtitle || 'Client tool call';
2213
+ setOptionalSection(state.argsSectionEl, state.argsEl, state.argsText || '');
2214
+ setOptionalSection(state.resultSectionEl, state.resultEl, state.resultText || '');
2215
+ break;
2216
+ case 'fileChange':
2217
+ if (state.metaEl) state.metaEl.textContent = state.fileMetaText || 'Pending file changes';
2218
+ if (state.filesEl) state.filesEl.textContent = state.filesText || 'Pending file changes';
2219
+ refreshFileChangeView(state);
2220
+ break;
2221
+ case 'collabAgentToolCall':
2222
+ if (state.titleEl) state.titleEl.textContent = state.collabToolName || 'Agent collaboration';
2223
+ if (state.subtitleEl) state.subtitleEl.textContent = state.collabSubtitle || 'Sub-agent delegation';
2224
+ setOptionalSection(state.promptSectionEl, state.promptEl, state.promptText || '');
2225
+ setOptionalSection(state.resultSectionEl, state.resultEl, state.collabResultText || '');
2226
+ break;
2227
+ default:
2228
+ if (state._lastItem) updateItemFromData(state._lastItem);
2229
+ break;
2230
+ }
2231
+ }
2232
+
2233
+ export function setOptionalSection(sectionEl, bodyEl, text) {
2234
+ const value = typeof text === 'string' ? text : '';
2235
+ if (bodyEl) renderReadableBlock(bodyEl, value);
2236
+ if (sectionEl) sectionEl.hidden = !value.trim();
2237
+ }
2238
+
2239
+ export function setOptionalText(el, text) {
2240
+ const value = typeof text === 'string' ? text : '';
2241
+ if (el) {
2242
+ el.textContent = value;
2243
+ el.hidden = !value.trim();
2244
+ }
2245
+ }
2246
+
2247
+ export function queueMarkdownRender(state) {
2248
+ if (state.renderTimer) return;
2249
+ state.renderTimer = setTimeout(() => {
2250
+ state.renderTimer = null;
2251
+ if (!state.bodyEl) return;
2252
+ if (state.type === 'agentMessage') {
2253
+ renderAssistantMessage(state);
2254
+ } else {
2255
+ state.bodyEl.innerHTML = md(state.buffer || '');
2256
+ postProcessRenderedHtml(state.bodyEl);
2257
+ }
2258
+ _ctx.scrollEnd();
2259
+ _ctx.scheduleThreadSnapshotSave(_ctx.boundTab);
2260
+ }, 32);
2261
+ }
2262
+
2263
+ function planMessageModels(tab) {
2264
+ const items = tab === _ctx.boundTab ? (_ctx.items || tab.items) : tab.items;
2265
+ return [...tab.messagesEl.querySelectorAll('.cxp-msg-assistant[data-plan-message-candidate="1"]')].map(el => {
2266
+ const state = items?.get(el.dataset.itemId);
2267
+ return { id: el.dataset.itemId, type: 'agentMessage', turnId: el.dataset.codexTurnId || '',
2268
+ threadId: el.dataset.codexThreadId || tab.threadId, phase: state?.phase || el.dataset.phase || '',
2269
+ text: state?.buffer ?? el.dataset.planSourceText ?? '', completed: state?.completed ?? el.dataset.messageCompleted === '1',
2270
+ planTurn: el.dataset.planTurn === '1', el, bodyEl: state?.bodyEl || el.querySelector('.cxp-msg-body') };
2271
+ });
2272
+ }
2273
+
2274
+ export function syncMessagePlanActions(tab = _ctx.boundTab) {
2275
+ if (!tab?.messagesEl) return;
2276
+ const messages = planMessageModels(tab);
2277
+ const nativePlans = [...tab.messagesEl.querySelectorAll('[data-item-type="plan"]')].map(el => ({ itemId: el.dataset.itemId, turnId: el.dataset.codexTurnId || '' }));
2278
+ const selected = selectPlanAdoptionMessage(tab, messages, nativePlans);
2279
+ tab.messagesEl.querySelectorAll('.cxp-use-as-plan').forEach(button => {
2280
+ if (button.closest('.cxp-msg-assistant')?.dataset.itemId !== selected) button.remove();
2281
+ });
2282
+ const message = messages.find(entry => entry.id === selected);
2283
+ if (!message?.bodyEl) return;
2284
+ let button = message.bodyEl.querySelector('.cxp-use-as-plan');
2285
+ if (!button) {
2286
+ button = _ctx.createRequestButton('Use as plan', 'secondary');
2287
+ button.dataset.planAction = 'adopt';
2288
+ button.classList.add('cxp-use-as-plan');
2289
+ button.addEventListener('click', async () => {
2290
+ const current = planMessageModels(tab);
2291
+ const plans = [...tab.messagesEl.querySelectorAll('[data-item-type="plan"]')].map(el => ({ itemId: el.dataset.itemId, turnId: el.dataset.codexTurnId || '' }));
2292
+ if (selectPlanAdoptionMessage(tab, current, plans) !== message.id || tab.planEditor?.dirty || tab.planEditor?.saving) return;
2293
+ const candidate = current.find(entry => entry.id === message.id);
2294
+ button.disabled = true;
2295
+ try {
2296
+ await _ctx.adoptMessageAsPlan?.(tab, { itemId: candidate.id, turnId: candidate.turnId,
2297
+ markdown: extractProposedPlan(candidate.text) || candidate.text });
2298
+ renderPostPlanActions(tab);
2299
+ } finally { syncMessagePlanActions(tab); }
2300
+ });
2301
+ message.bodyEl.appendChild(button);
2302
+ }
2303
+ button.hidden = false;
2304
+ button.disabled = !!(tab.planEditor?.dirty || tab.planEditor?.saving);
2305
+ }
2306
+
2307
+ function renderAssistantMessage(state) {
2308
+ const text = String(state.buffer || '').replace(/<\/?proposed_plan>/g, '');
2309
+ const streaming = !state.completed;
2310
+ const parser = _ctx.marked?.parse;
2311
+ const tab = _ctx.boundTab;
2312
+ const turnId = state.el?.dataset.codexTurnId || '';
2313
+ const candidate = isPlanMessageCandidate(tab, { type: 'agentMessage', text: state.buffer, phase: state.phase,
2314
+ turnId, planTurn: state.el?.dataset.planTurn === '1' || isRecordedPlanTurn(tab, turnId) });
2315
+ if (state.el) {
2316
+ state.el.dataset.messageCompleted = state.completed ? '1' : '0';
2317
+ if (candidate) {
2318
+ state.el.dataset.planMessageCandidate = '1';
2319
+ if (isRecordedPlanTurn(tab, turnId)) state.el.dataset.planTurn = '1';
2320
+ if (state.buffer.length <= OUTPUT_LIMITS.input) state.el.dataset.planSourceText = state.buffer;
2321
+ else delete state.el.dataset.planSourceText;
2322
+ } else { delete state.el.dataset.planMessageCandidate; delete state.el.dataset.planSourceText; }
2323
+ }
2324
+ if (state._renderedText === text && state._renderedStreaming === streaming && state._renderedParser === parser && state._renderedPhase === state.phase) { syncMessagePlanActions(tab); return; }
2325
+ state._renderedText = text; state._renderedStreaming = streaming; state._renderedParser = parser; state._renderedPhase = state.phase;
2326
+ const phase = normalizeAssistantPhase(state.phase || '');
2327
+ if (phase === 'commentary' && state.summaryBodyEl && state.verboseSectionEl && state.verboseBodyEl && state.renderedEl) {
2328
+ state.renderedEl.hidden = true;
2329
+ state.summaryBodyEl.hidden = false;
2330
+ state.summaryBodyEl.textContent = extractCompactAssistantSummary(state.buffer || '');
2331
+ state.verboseSectionEl.hidden = true;
2332
+ state.verboseBodyEl.innerHTML = '';
2333
+ if (state.el) state.el.classList.toggle('cxp-msg-empty', !state.summaryBodyEl.textContent.trim());
2334
+ syncMessagePlanActions(tab);
2335
+ return;
2336
+ }
2337
+
2338
+ const target = state.renderedEl || state.bodyEl;
2339
+ let hasContent = false;
2340
+ if (target) {
2341
+ target.hidden = false;
2342
+ if (renderReadableOutput(target, text, { streaming })) hasContent = true;
2343
+ else {
2344
+ const rendered = renderAssistantMarkdown(text, { streaming });
2345
+ target.innerHTML = rendered.html; hasContent = rendered.hasContent;
2346
+ }
2347
+ postProcessRenderedHtml(target);
2348
+ }
2349
+ if (state.summaryBodyEl) {
2350
+ state.summaryBodyEl.hidden = true;
2351
+ state.summaryBodyEl.textContent = '';
2352
+ }
2353
+ if (state.verboseSectionEl) {
2354
+ state.verboseSectionEl.hidden = true;
2355
+ }
2356
+ if (state.verboseBodyEl) state.verboseBodyEl.innerHTML = '';
2357
+ if (state.el) state.el.classList.toggle('cxp-msg-empty', !hasContent);
2358
+ syncMessagePlanActions(tab);
2359
+ }
2360
+
2361
+ export function setMarkdownBuffer(state, text, final = false) {
2362
+ state.buffer = text || '';
2363
+ if (final) {
2364
+ if (state.renderTimer) clearTimeout(state.renderTimer);
2365
+ state.renderTimer = null;
2366
+ if (state.bodyEl) {
2367
+ if (state.type === 'agentMessage') {
2368
+ renderAssistantMessage(state);
2369
+ } else {
2370
+ state.bodyEl.innerHTML = md(state.buffer);
2371
+ postProcessRenderedHtml(state.bodyEl);
2372
+ }
2373
+ }
2374
+ _ctx.scrollEnd();
2375
+ _ctx.scheduleThreadSnapshotSave(_ctx.boundTab, { force: true });
2376
+ return;
2377
+ }
2378
+ queueMarkdownRender(state);
2379
+ }
2380
+
2381
+ // ═══════════════════════════════════════════
2382
+ // Ensure*State — Item state initializers
2383
+ // ═══════════════════════════════════════════
2384
+
2385
+ export function ensureUserState(item) {
2386
+ let state = _ctx.items.get(item.id);
2387
+ if (state) return state;
2388
+ const shell = createMessageShell('user', 'User');
2389
+ shell.el.dataset.itemId = item.id || '';
2390
+ state = {
2391
+ type: 'userMessage',
2392
+ el: shell.el,
2393
+ bodyEl: shell.body,
2394
+ };
2395
+ _ctx.items.set(item.id, state);
2396
+ return state;
2397
+ }
2398
+
2399
+ export function ensureAssistantState(itemId) {
2400
+ let state = _ctx.items.get(itemId);
2401
+ if (state) return state;
2402
+ const shell = createMessageShell('assistant', 'Codex');
2403
+ shell.el.dataset.itemId = itemId || '';
2404
+ const rendered = document.createElement('div');
2405
+ rendered.className = 'cxp-msg-rendered';
2406
+ shell.body.appendChild(rendered);
2407
+ const summary = document.createElement('div');
2408
+ summary.className = 'cxp-msg-summary';
2409
+ summary.hidden = true;
2410
+ shell.body.appendChild(summary);
2411
+ const verboseSection = createFoldSection('Verbose', {
2412
+ className: 'cxp-msg-verbose-fold',
2413
+ preformatted: false,
2414
+ bodyClassName: 'cxp-msg-verbose-body',
2415
+ });
2416
+ verboseSection.sectionEl.hidden = true;
2417
+ shell.body.appendChild(verboseSection.sectionEl);
2418
+ state = {
2419
+ type: 'agentMessage',
2420
+ el: shell.el,
2421
+ bodyEl: shell.body,
2422
+ renderedEl: rendered,
2423
+ summaryBodyEl: summary,
2424
+ verboseSectionEl: verboseSection.sectionEl,
2425
+ verboseBodyEl: verboseSection.bodyEl,
2426
+ labelEl: shell.labelEl,
2427
+ phaseEl: shell.phaseEl,
2428
+ buffer: '',
2429
+ renderTimer: null,
2430
+ phase: '',
2431
+ };
2432
+ state.el.classList.add('cxp-msg-empty');
2433
+ _ctx.items.set(itemId, state);
2434
+ return state;
2435
+ }
2436
+
2437
+ export function ensureReasoningState(item) {
2438
+ let state = _ctx.items.get(item.id);
2439
+ if (state) return state;
2440
+
2441
+ const details = document.createElement('details');
2442
+ details.className = 'cxp-card cxp-reasoning';
2443
+ details.dataset.itemId = item.id || '';
2444
+ details.dataset.expanded = '1';
2445
+
2446
+ const summary = document.createElement('summary');
2447
+ summary.innerHTML = `
2448
+ <span class="cxp-card-icon">${ICON_SPARK}</span>
2449
+ <span class="cxp-card-titles">
2450
+ <span class="cxp-card-title">Reasoning Summary</span>
2451
+ <span class="cxp-card-subtitle">Working through the next step</span>
2452
+ </span>
2453
+ <span class="cxp-think-dots"><span></span><span></span><span></span></span>
2454
+ <span class="cxp-status-pill">live</span>
2455
+ <span class="cxp-card-chevron"></span>
2456
+ `;
2457
+ details.appendChild(summary);
2458
+
2459
+ const body = document.createElement('div');
2460
+ body.className = 'cxp-reasoning-body';
2461
+ details.appendChild(body);
2462
+ details.open = true;
2463
+ details.addEventListener('toggle', () => {
2464
+ details.dataset.expanded = details.open ? '1' : '0';
2465
+ if (state && !state.suppressToggleTracking) state.userToggled = true;
2466
+ _ctx.scheduleThreadSnapshotSave(_ctx.boundTab);
2467
+ });
2468
+
2469
+ appendElement(details);
2470
+ state = {
2471
+ type: 'reasoning',
2472
+ el: details,
2473
+ bodyEl: body,
2474
+ summaryEl: summary.querySelector('.cxp-card-subtitle'),
2475
+ pillEl: summary.querySelector('.cxp-status-pill'),
2476
+ summaryLines: [],
2477
+ contentLines: [],
2478
+ userToggled: false,
2479
+ suppressToggleTracking: false,
2480
+ };
2481
+ _ctx.items.set(item.id, state);
2482
+ return state;
2483
+ }
2484
+
2485
+ export function ensurePlanState(item) {
2486
+ let state = _ctx.items.get(item.id);
2487
+ if (state) return state;
2488
+ const card = createCard(item, ICON_SPARK, 'Plan', 'Structured plan output');
2489
+ card.el.classList.add('cxp-plan-card');
2490
+ setCardExpanded(card.el, true);
2491
+ card.el.dataset.itemId = item.id || '';
2492
+ const body = document.createElement('div');
2493
+ body.className = 'cxp-msg-body';
2494
+ card.body.appendChild(body);
2495
+ state = {
2496
+ type: 'plan',
2497
+ el: card.el,
2498
+ bodyEl: body,
2499
+ buffer: '',
2500
+ renderTimer: null,
2501
+ pillEl: card.pill,
2502
+ };
2503
+ _ctx.items.set(item.id, state);
2504
+ return state;
2505
+ }
2506
+
2507
+ export function ensureCommandState(item) {
2508
+ let state = _ctx.items.get(item.id);
2509
+ if (state) return state;
2510
+ const summary = summarizeCommandExecution(item.command || '', item.cwd || '');
2511
+ const card = createCard(item, ICON_TERMINAL, summary.title, summary.detail);
2512
+
2513
+ const meta = document.createElement('div');
2514
+ meta.className = 'cxp-card-meta';
2515
+ card.body.appendChild(meta);
2516
+
2517
+ const commandSection = createFoldSection('Shell command', {
2518
+ className: 'cxp-fold-command',
2519
+ bodyClassName: 'cxp-card-pre cxp-card-pre-log',
2520
+ });
2521
+ card.body.appendChild(commandSection.sectionEl);
2522
+
2523
+ const interaction = document.createElement('div');
2524
+ interaction.className = 'cxp-card-meta';
2525
+ interaction.hidden = true;
2526
+ card.body.appendChild(interaction);
2527
+
2528
+ const outputSection = createFoldSection('Output', {
2529
+ className: 'cxp-fold-output',
2530
+ bodyClassName: 'cxp-card-pre cxp-card-pre-log',
2531
+ });
2532
+ card.body.appendChild(outputSection.sectionEl);
2533
+
2534
+ state = {
2535
+ type: 'commandExecution',
2536
+ el: card.el,
2537
+ titleEl: card.titleEl,
2538
+ subtitleEl: card.subtitleEl,
2539
+ pillEl: card.pill,
2540
+ metaEl: meta,
2541
+ commandSectionEl: commandSection.sectionEl,
2542
+ commandEl: commandSection.bodyEl,
2543
+ interactionEl: interaction,
2544
+ outputSectionEl: outputSection.sectionEl,
2545
+ outputEl: outputSection.bodyEl,
2546
+ outputBuf: '',
2547
+ };
2548
+ _ctx.items.set(item.id, state);
2549
+ return state;
2550
+ }
2551
+
2552
+ export function ensureToolState(item) {
2553
+ let state = _ctx.items.get(item.id);
2554
+ if (state) return state;
2555
+ const card = createCard(item, getMcpToolIcon(item), `${item.server || 'MCP'} • ${item.tool || 'tool'}`, 'Tool call');
2556
+
2557
+ const argsSection = createFoldSection('Arguments', {
2558
+ className: 'cxp-fold-args',
2559
+ bodyClassName: 'cxp-card-pre cxp-card-pre-json',
2560
+ });
2561
+ card.body.appendChild(argsSection.sectionEl);
2562
+
2563
+ const progress = document.createElement('div');
2564
+ progress.className = 'cxp-card-meta';
2565
+ progress.hidden = true;
2566
+ card.body.appendChild(progress);
2567
+
2568
+ const resultSection = createFoldSection('Result', {
2569
+ className: 'cxp-fold-result',
2570
+ bodyClassName: 'cxp-card-pre cxp-card-pre-result',
2571
+ });
2572
+ card.body.appendChild(resultSection.sectionEl);
2573
+
2574
+ state = {
2575
+ type: 'mcpToolCall',
2576
+ el: card.el,
2577
+ titleEl: card.titleEl,
2578
+ subtitleEl: card.subtitleEl,
2579
+ pillEl: card.pill,
2580
+ argsSectionEl: argsSection.sectionEl,
2581
+ argsEl: argsSection.bodyEl,
2582
+ progressEl: progress,
2583
+ resultSectionEl: resultSection.sectionEl,
2584
+ resultEl: resultSection.bodyEl,
2585
+ };
2586
+ _ctx.items.set(item.id, state);
2587
+ return state;
2588
+ }
2589
+
2590
+ export function ensureDynamicToolState(item) {
2591
+ let state = _ctx.items.get(item.id);
2592
+ if (state) return state;
2593
+ const card = createCard(item, ICON_TOOL, item.tool || 'Dynamic tool', 'Client tool call');
2594
+
2595
+ const argsSection = createFoldSection('Arguments', {
2596
+ className: 'cxp-fold-args',
2597
+ bodyClassName: 'cxp-card-pre cxp-card-pre-json',
2598
+ });
2599
+ card.body.appendChild(argsSection.sectionEl);
2600
+
2601
+ const resultSection = createFoldSection('Result', {
2602
+ className: 'cxp-fold-result',
2603
+ bodyClassName: 'cxp-card-pre cxp-card-pre-result',
2604
+ });
2605
+ card.body.appendChild(resultSection.sectionEl);
2606
+
2607
+ state = {
2608
+ type: 'dynamicToolCall',
2609
+ el: card.el,
2610
+ titleEl: card.titleEl,
2611
+ subtitleEl: card.subtitleEl,
2612
+ pillEl: card.pill,
2613
+ argsSectionEl: argsSection.sectionEl,
2614
+ argsEl: argsSection.bodyEl,
2615
+ resultSectionEl: resultSection.sectionEl,
2616
+ resultEl: resultSection.bodyEl,
2617
+ };
2618
+ _ctx.items.set(item.id, state);
2619
+ return state;
2620
+ }
2621
+
2622
+ function refreshFileChangeView(state) {
2623
+ if (!state?.outputEl) return;
2624
+ const item = { ...state._lastItem, aggregatedOutput: state.outputBuf || state._lastItem?.aggregatedOutput || '' };
2625
+ const files = fileChangeDiffs(item);
2626
+ state.diffFiles = files;
2627
+ renderDiffView(state.outputEl, files, { lazy: state._lastItem?._synabunOwnership?.source === 'history', cwd: _ctx.boundTab?.project || _ctx.project, summaryEl: state.metaEl });
2628
+ state.outputEl.hidden = !files.length;
2629
+ const turnId = state.el?.dataset.codexTurnId;
2630
+ const latest = _ctx.boundTab?.latestTurnDiff;
2631
+ if (latest?.turnId === turnId) renderTurnDiff(turnId, latest.diff);
2632
+ const itemId = state.el?.dataset.itemId;
2633
+ for (const view of _ctx.messagesEl?.querySelectorAll('[data-approval-diff-item]') || []) {
2634
+ if (view.dataset.approvalDiffItem !== itemId || !files.length) continue;
2635
+ view.classList.remove('cxp-output-note');
2636
+ renderDiffView(view, files, { cwd: _ctx.boundTab?.project || _ctx.project });
2637
+ }
2638
+ }
2639
+
2640
+ // Aggregate events include shell edits. Only omit a file when an item card in
2641
+ // this same turn already contains the same patch; the synthetic card is reused.
2642
+ export function renderTurnDiff(turnId, diff, { lazy = false } = {}) {
2643
+ if (!turnId || !_ctx.messagesEl) return;
2644
+ const id = `turn-diff:${turnId}`;
2645
+ const shown = [];
2646
+ for (const [itemId, state] of _ctx.items) {
2647
+ if (itemId === id || state.type !== 'fileChange' || state.el?.dataset.codexTurnId !== String(turnId)) continue;
2648
+ shown.push(...(state.diffFiles || fileChangeDiffs({ ...state._lastItem, aggregatedOutput: state.outputBuf || '' })));
2649
+ }
2650
+ // HTML snapshots may not have item states until native history catches up.
2651
+ for (const card of _ctx.messagesEl.querySelectorAll('[data-item-type="fileChange"]')) {
2652
+ if (card.dataset.itemId === id || _ctx.items.has(card.dataset.itemId) || card.dataset.codexTurnId !== String(turnId)) continue;
2653
+ const raw = card.querySelector('.cxp-diff-source')?.textContent;
2654
+ if (raw) shown.push(...parseUnifiedDiff(raw));
2655
+ }
2656
+ const files = deduplicateTurnDiff(parseUnifiedDiff(diff), shown);
2657
+ let state = _ctx.items.get(id);
2658
+ if (!state) {
2659
+ const old = [..._ctx.messagesEl.querySelectorAll('[data-turn-diff]')].find(card => card.dataset.codexTurnId === String(turnId));
2660
+ if (old) {
2661
+ state = { type: 'fileChange', el: old, pillEl: old.querySelector('.cxp-status-pill'), metaEl: old.querySelector('.cxp-card-meta'), outputEl: old.querySelector('.cxp-diff-view'), outputBuf: '' };
2662
+ _ctx.items.set(id, state);
2663
+ }
2664
+ }
2665
+ if (!files.length) {
2666
+ if (state) { state.el.hidden = true; _ctx.scheduleThreadSnapshotSave(_ctx.boundTab, { force: true }); }
2667
+ return;
2668
+ }
2669
+ if (!state) {
2670
+ state = ensureFileChangeState({ id, type: 'fileChange' });
2671
+ state.el.dataset.codexTurnId = String(turnId);
2672
+ state.el.dataset.turnDiff = '1';
2673
+ }
2674
+ state.el.hidden = false;
2675
+ state.pillEl.textContent = 'completed';
2676
+ state.metaEl.textContent = `${files.length} file change${files.length === 1 ? '' : 's'}`;
2677
+ state.diffFiles = files;
2678
+ renderDiffView(state.outputEl, files, { lazy, cwd: _ctx.boundTab?.project || _ctx.project, summaryEl: state.metaEl });
2679
+ const anchor = [..._ctx.messagesEl.children].filter(node => node !== state.el && node.dataset?.codexTurnId === String(turnId)).at(-1);
2680
+ if (anchor && anchor.nextElementSibling !== state.el) anchor.after(state.el);
2681
+ _ctx.scheduleThreadSnapshotSave(_ctx.boundTab, { force: true });
2682
+ }
2683
+
2684
+ export function completeTurnOutputs(turnId) {
2685
+ for (const state of _ctx.items.values()) {
2686
+ if (turnId && state.el?.dataset.codexTurnId !== String(turnId)) continue;
2687
+ if (state.type === 'reasoning') {
2688
+ updateReasoningChrome(state, { complete: true });
2689
+ if (state.bodyEl) state.bodyEl.textContent = getReasoningBodyText(state, { complete: true });
2690
+ if (!state.userToggled && state.el) { state.el.open = false; state.el.dataset.expanded = '0'; }
2691
+ } else if (state.type === 'agentMessage' && !state.completed) {
2692
+ state.completed = true;
2693
+ renderAssistantMessage(state);
2694
+ }
2695
+ }
2696
+ }
2697
+
2698
+ export function ensureFileChangeState(item) {
2699
+ let state = _ctx.items.get(item.id);
2700
+ if (state) return state;
2701
+ const card = createCard(item, ICON_FILES, 'File change', 'Proposed file updates');
2702
+ setCardExpanded(card.el, true);
2703
+
2704
+ const summary = document.createElement('div');
2705
+ summary.className = 'cxp-card-meta';
2706
+ card.body.appendChild(summary);
2707
+
2708
+ const filesSection = document.createElement('div');
2709
+ filesSection.className = 'cxp-card-section';
2710
+ const filesLabel = document.createElement('div');
2711
+ filesLabel.className = 'cxp-card-section-label';
2712
+ filesLabel.textContent = 'Files changed';
2713
+ const filesBody = document.createElement('div');
2714
+ filesBody.className = 'cxp-card-meta';
2715
+ filesSection.append(filesLabel, filesBody);
2716
+ card.body.appendChild(filesSection);
2717
+
2718
+ const outputEl = document.createElement('div');
2719
+ outputEl.className = 'cxp-card-pre-diff';
2720
+ card.body.appendChild(outputEl);
2721
+ filesSection.hidden = true;
2722
+
2723
+ state = {
2724
+ type: 'fileChange',
2725
+ el: card.el,
2726
+ pillEl: card.pill,
2727
+ metaEl: summary,
2728
+ filesEl: filesBody,
2729
+ outputSectionEl: outputEl,
2730
+ outputEl,
2731
+ outputBuf: '',
2732
+ };
2733
+ _ctx.items.set(item.id, state);
2734
+ return state;
2735
+ }
2736
+
2737
+ export function ensureCollabAgentState(item) {
2738
+ let state = _ctx.items.get(item.id);
2739
+ if (state) return state;
2740
+ const card = createCard(item, ICON_BRAIN, item.tool || 'Agent collaboration', 'Sub-agent delegation');
2741
+
2742
+ const promptSection = createFoldSection('Delegation prompt', {
2743
+ className: 'cxp-fold-prompt',
2744
+ bodyClassName: 'cxp-card-pre cxp-card-pre-prompt',
2745
+ });
2746
+ card.body.appendChild(promptSection.sectionEl);
2747
+
2748
+ const resultSection = createFoldSection('Agent result', {
2749
+ className: 'cxp-fold-result',
2750
+ bodyClassName: 'cxp-card-pre cxp-card-pre-json',
2751
+ });
2752
+ card.body.appendChild(resultSection.sectionEl);
2753
+
2754
+ state = {
2755
+ type: 'collabAgentToolCall',
2756
+ el: card.el,
2757
+ titleEl: card.titleEl,
2758
+ subtitleEl: card.subtitleEl,
2759
+ pillEl: card.pill,
2760
+ promptSectionEl: promptSection.sectionEl,
2761
+ promptEl: promptSection.bodyEl,
2762
+ resultSectionEl: resultSection.sectionEl,
2763
+ resultEl: resultSection.bodyEl,
2764
+ };
2765
+ _ctx.items.set(item.id, state);
2766
+ return state;
2767
+ }
2768
+
2769
+ export function ensureGenericState(item) {
2770
+ let state = _ctx.items.get(item.id);
2771
+ if (state) return state;
2772
+ const summary = _ctx.itemHeadline(item);
2773
+ const head = codexGenericCardHead(item, summary);
2774
+ const card = createCard(item, ICON_TOOL, head.title, head.subtitle || 'Codex item');
2775
+ const pre = document.createElement('pre');
2776
+ pre.className = 'cxp-card-pre';
2777
+ card.body.appendChild(pre);
2778
+ state = {
2779
+ type: item.type || 'item',
2780
+ el: card.el,
2781
+ titleEl: card.titleEl,
2782
+ subtitleEl: card.subtitleEl,
2783
+ pillEl: card.pill,
2784
+ preEl: pre,
2785
+ };
2786
+ _ctx.items.set(item.id, state);
2787
+ return state;
2788
+ }
2789
+
2790
+ // ═══════════════════════════════════════════
2791
+ // Interaction & Guardian Review
2792
+ // ═══════════════════════════════════════════
2793
+
2794
+ export function setCommandInteraction(itemId, processId) {
2795
+ const state = _ctx.items.get(itemId) || ensureCommandState({ id: itemId });
2796
+ if (!state.interactionEl) return;
2797
+ state.interactionEl.hidden = false;
2798
+ state.interactionEl.innerHTML = '';
2799
+ const label = document.createElement('div');
2800
+ label.className = 'cxp-card-section-label';
2801
+ label.textContent = `Terminal interaction · PID ${processId || itemId}`;
2802
+ state.interactionEl.appendChild(label);
2803
+ const inputWrap = document.createElement('div');
2804
+ inputWrap.className = 'cxp-terminal-input-wrap';
2805
+ const termInput = document.createElement('input');
2806
+ termInput.type = 'text';
2807
+ termInput.className = 'cxp-terminal-input';
2808
+ termInput.placeholder = 'Type terminal input and press Enter…';
2809
+ const sendBtn = document.createElement('button');
2810
+ sendBtn.className = 'cxp-btn cxp-btn-sm';
2811
+ sendBtn.textContent = 'Send';
2812
+ const doSend = () => {
2813
+ const val = termInput.value;
2814
+ if (!val && val !== '') return;
2815
+ _ctx.sendSocket({ type: 'terminal_input', itemId, processId, input: val + '\n' });
2816
+ termInput.value = '';
2817
+ };
2818
+ termInput.addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.preventDefault(); doSend(); } });
2819
+ sendBtn.addEventListener('click', doSend);
2820
+ inputWrap.append(termInput, sendBtn);
2821
+ state.interactionEl.appendChild(inputWrap);
2822
+ _ctx.scrollEnd();
2823
+ _ctx.scheduleThreadSnapshotSave(_ctx.boundTab);
2824
+ }
2825
+
2826
+ export function setGuardianReviewState(targetItemId, review) {
2827
+ const state = _ctx.items.get(targetItemId);
2828
+ const text = formatGuardianReview(review);
2829
+ if (!state?.el) {
2830
+ if (text) appendSystem(`Approval review · ${text}`, review?.status === 'approved' ? 'muted' : 'working');
2831
+ return;
2832
+ }
2833
+
2834
+ const body = state.bodyEl || state.el.querySelector('.cxp-card-body');
2835
+ if (!body) return;
2836
+ if (!state.reviewEl) {
2837
+ const meta = document.createElement('div');
2838
+ meta.className = 'cxp-card-meta';
2839
+ body.prepend(meta);
2840
+ state.reviewEl = meta;
2841
+ }
2842
+ state.reviewEl.textContent = text;
2843
+ _ctx.scheduleThreadSnapshotSave(_ctx.boundTab);
2844
+ }
2845
+
2846
+ // ═══════════════════════════════════════════
2847
+ // updateItemFromData — Master item updater
2848
+ // ═══════════════════════════════════════════
2849
+
2850
+ function getRenderableItemType(item) {
2851
+ if (!item || typeof item !== 'object') return '';
2852
+ if (item.type === 'collabToolCall') return 'collabAgentToolCall';
2853
+ return item.type || '';
2854
+ }
2855
+
2856
+ function renderAgentMetadata(state, item) {
2857
+ const metadata = { delivery: item.delivery, questions: item.questions, memoryCitation: item.memoryCitation };
2858
+ const signature = JSON.stringify(metadata);
2859
+ if (state.agentMetadataSignature === signature) return;
2860
+ state.agentMetadataSignature = signature;
2861
+ state.agentMetadataEl?.remove();
2862
+ if (!item.delivery && !item.questions?.length && !item.memoryCitation) return;
2863
+ const section = document.createElement('div');
2864
+ section.className = 'cxp-card-section cxp-ask';
2865
+ if (item.memoryCitation) {
2866
+ const note = document.createElement('div'); note.className = 'cxp-card-meta';
2867
+ note.textContent = `Memory references\n${codexReadableValue(item.memoryCitation)}`;
2868
+ section.appendChild(note);
2869
+ }
2870
+ if (item.questions?.length) {
2871
+ const note = document.createElement('div'); note.className = 'cxp-request-note';
2872
+ note.textContent = 'Codex can continue working while you answer.'; section.appendChild(note);
2873
+ const fields = item.questions.map(question => {
2874
+ const label = document.createElement('label'); label.className = 'cxp-config-row';
2875
+ const title = document.createElement('span'); title.className = 'cxp-config-label'; title.textContent = question.title;
2876
+ const input = document.createElement('input'); input.className = 'cxp-config-input';
2877
+ label.append(title, input); section.appendChild(label);
2878
+ for (const option of question.options || []) {
2879
+ const button = _ctx.createRequestButton(option, 'secondary');
2880
+ button.addEventListener('click', () => { input.value = option; }); section.appendChild(button);
2881
+ }
2882
+ return { question, input };
2883
+ });
2884
+ const send = _ctx.createRequestButton('Send answers');
2885
+ const tab = _ctx.boundTab;
2886
+ send.addEventListener('click', () => {
2887
+ const answers = fields.filter(field => field.input.value.trim()).map(field => `${field.question.title}\n${field.input.value.trim()}`);
2888
+ if (!answers.length) return;
2889
+ if (_ctx.dispatchPrompt(answers.join('\n\n'), { tab })) {
2890
+ send.disabled = true; fields.forEach(field => { field.input.disabled = true; });
2891
+ }
2892
+ });
2893
+ section.appendChild(send);
2894
+ }
2895
+ state.agentMetadataEl = section;
2896
+ state.bodyEl.appendChild(section);
2897
+ }
2898
+
2899
+ export function updateItemFromData(item) {
2900
+ if (!item?.id) return;
2901
+ if (!_ctx.itemOwnershipBelongsToBoundThread(item)) return;
2902
+ const itemType = getRenderableItemType(item);
2903
+ if (itemType === 'contextCompaction') {
2904
+ return;
2905
+ }
2906
+
2907
+ let state = _ctx.items.get(item.id);
2908
+ if (!state) {
2909
+ switch (itemType) {
2910
+ case 'userMessage': state = ensureUserState(item); break;
2911
+ case 'hookPrompt': state = ensureGenericState(item); break;
2912
+ case 'agentMessage': state = ensureAssistantState(item.id); break;
2913
+ case 'reasoning': state = ensureReasoningState(item); break;
2914
+ case 'plan': state = ensurePlanState(item); break;
2915
+ case 'commandExecution': state = ensureCommandState(item); break;
2916
+ case 'mcpToolCall': state = ensureToolState(item); break;
2917
+ case 'dynamicToolCall': state = ensureDynamicToolState(item); break;
2918
+ case 'fileChange': state = ensureFileChangeState(item); break;
2919
+ case 'collabAgentToolCall': state = ensureCollabAgentState(item); break;
2920
+ default: state = ensureGenericState(item); break;
2921
+ }
2922
+ }
2923
+
2924
+ if (itemType === 'agentMessage' || itemType === 'plan') {
2925
+ state.completed = !!(state.completed || item._synabunCompleted || isCompleteStatus(item.status) || (!_ctx.running && typeof item.text === 'string'));
2926
+ }
2927
+ if (state.el && (item._synabunOwnership?.turnId || item.turnId)) state.el.dataset.codexTurnId = String(item._synabunOwnership?.turnId || item.turnId);
2928
+ if (itemType === 'plan') syncMessagePlanActions(_ctx.boundTab);
2929
+
2930
+ // Store last item for lazy flush; skip body DOM writes for collapsed cards
2931
+ const _isCard = state.el?.classList.contains('cxp-card');
2932
+ if (_isCard) {
2933
+ state._lastItem = state._lastItem ? { ...state._lastItem, ...item } : item;
2934
+ cacheCollapsedCardState(state, state._lastItem, itemType);
2935
+ if (itemType === 'mcpToolCall') mountMcpAppCard({ card: state.el, item: state._lastItem,
2936
+ tab: _ctx.boundTab, request: _ctx.requestForTab, draft: _ctx.setMcpAppDraft });
2937
+ // Always update head elements (visible even when collapsed)
2938
+ if (state.titleEl) {
2939
+ const h = _ctx.itemHeadline(state._lastItem);
2940
+ const head = state.preEl ? codexGenericCardHead(state._lastItem, h) : null;
2941
+ state.titleEl.textContent = head?.title || h.detail || item.type || 'Item';
2942
+ if (head && state.subtitleEl) state.subtitleEl.textContent = head.subtitle;
2943
+ }
2944
+ if (state.pillEl && item.status != null) state.pillEl.textContent = _ctx.formatStatus(item.status);
2945
+ if (isCardBodyHidden(state)) { state._bodyDirty = true; _ctx.scheduleThreadSnapshotSave(_ctx.boundTab); return; }
2946
+ }
2947
+
2948
+ switch (itemType) {
2949
+ case 'userMessage':
2950
+ renderUserMessageContent(state.bodyEl, item.content);
2951
+ break;
2952
+ case 'agentMessage': {
2953
+ const phase = normalizeAssistantPhase(item.phase || state.phase || '');
2954
+ state.phase = phase;
2955
+ if (state.el) {
2956
+ if (phase) state.el.dataset.phase = phase;
2957
+ else delete state.el.dataset.phase;
2958
+ }
2959
+ if (state.labelEl) state.labelEl.textContent = 'Codex';
2960
+ if (state.phaseEl) {
2961
+ state.phaseEl.hidden = phase !== 'final_answer';
2962
+ state.phaseEl.textContent = phase === 'final_answer' ? 'Final' : '';
2963
+ }
2964
+ setMarkdownBuffer(state, resolveFinalMarkdownText(state, item.text), true);
2965
+ renderAgentMetadata(state, item);
2966
+ break;
2967
+ }
2968
+ case 'reasoning': {
2969
+ const incomingSummaryLines = reasoningTextFromKeys(item, ['summary', 'summaryText', 'summary_text']);
2970
+ const incomingContentLines = reasoningTextFromKeys(item, ['content', 'text']);
2971
+ const hasIncomingText = incomingSummaryLines.length > 0 || incomingContentLines.length > 0;
2972
+ if (incomingSummaryLines.length) state.summaryLines = incomingSummaryLines;
2973
+ if (incomingContentLines.length) state.contentLines = incomingContentLines;
2974
+ const complete = isReasoningComplete(item, hasIncomingText);
2975
+ updateReasoningChrome(state, { complete });
2976
+ if (state.bodyEl) state.bodyEl.textContent = getReasoningBodyText(state, { complete });
2977
+ if (state.el && complete && !state.userToggled) {
2978
+ state.suppressToggleTracking = true;
2979
+ state.el.open = false;
2980
+ state.el.dataset.expanded = '0';
2981
+ setTimeout(() => { state.suppressToggleTracking = false; }, 0);
2982
+ } else if (state.el && !complete) {
2983
+ state.el.classList.remove('cxp-reasoning-done');
2984
+ if (!state.userToggled) {
2985
+ state.suppressToggleTracking = true;
2986
+ state.el.open = true;
2987
+ state.el.dataset.expanded = '1';
2988
+ setTimeout(() => { state.suppressToggleTracking = false; }, 0);
2989
+ }
2990
+ }
2991
+ break;
2992
+ }
2993
+ case 'plan':
2994
+ if (state.pillEl) state.pillEl.textContent = state.completed ? 'complete' : 'draft';
2995
+ setMarkdownBuffer(state, resolveFinalMarkdownText(state, item.text), true);
2996
+ break;
2997
+ case 'commandExecution': {
2998
+ state.titleEl.textContent = state.commandTitle || 'Running shell command';
2999
+ state.subtitleEl.textContent = state.commandSubtitle || 'Executing a shell command';
3000
+ state.pillEl.textContent = _ctx.formatStatus(item.status);
3001
+ state.metaEl.textContent = state.metaText || '';
3002
+ setOptionalSection(state.commandSectionEl, state.commandEl, state.commandText || '');
3003
+ if (item.aggregatedOutput != null) {
3004
+ state.outputBuf = item.aggregatedOutput || '';
3005
+ setOptionalSection(state.outputSectionEl, state.outputEl, state.outputBuf);
3006
+ } else if (state.outputBuf) {
3007
+ setOptionalSection(state.outputSectionEl, state.outputEl, state.outputBuf);
3008
+ }
3009
+ if (state.interactionEl) {
3010
+ state.interactionEl.hidden = !state.interactionEl.textContent;
3011
+ }
3012
+ break;
3013
+ }
3014
+ case 'mcpToolCall': {
3015
+ state.titleEl.textContent = `${state.toolServer || 'MCP'} • ${state.toolName || 'tool'}`;
3016
+ state.subtitleEl.textContent = state.toolSubtitle || 'Tool call';
3017
+ state.pillEl.textContent = _ctx.formatStatus(item.status);
3018
+ setOptionalSection(state.argsSectionEl, state.argsEl, state.argsText || '');
3019
+ setOptionalText(state.progressEl, state.progressText || '');
3020
+ setOptionalSection(state.resultSectionEl, state.resultEl, state.resultText || '');
3021
+ break;
3022
+ }
3023
+ case 'dynamicToolCall': {
3024
+ state.titleEl.textContent = state.dynamicToolName || 'Dynamic tool';
3025
+ state.subtitleEl.textContent = state.dynamicSubtitle || 'Client tool call';
3026
+ state.pillEl.textContent = _ctx.formatStatus(item.status);
3027
+ setOptionalSection(state.argsSectionEl, state.argsEl, state.argsText || '');
3028
+ setOptionalSection(state.resultSectionEl, state.resultEl, state.resultText || '');
3029
+ break;
3030
+ }
3031
+ case 'fileChange': {
3032
+ state.pillEl.textContent = _ctx.formatStatus(item.status);
3033
+ state.metaEl.textContent = state.fileMetaText || 'Pending file changes';
3034
+ if (state.filesEl) state.filesEl.textContent = state.filesText || 'Pending file changes';
3035
+ if (item.aggregatedOutput != null) {
3036
+ state.outputBuf = extractStructuredText(item.aggregatedOutput) || item.aggregatedOutput || '';
3037
+ }
3038
+ refreshFileChangeView(state);
3039
+ break;
3040
+ }
3041
+ case 'collabAgentToolCall': {
3042
+ if (state.titleEl) state.titleEl.textContent = state.collabToolName || 'Agent collaboration';
3043
+ if (state.subtitleEl) state.subtitleEl.textContent = state.collabSubtitle || 'Sub-agent delegation';
3044
+ if (state.pillEl) state.pillEl.textContent = _ctx.formatStatus(item.status);
3045
+ if (state.promptSectionEl && state.promptEl) {
3046
+ setOptionalSection(state.promptSectionEl, state.promptEl, state.promptText || '');
3047
+ }
3048
+ if (state.resultSectionEl && state.resultEl) {
3049
+ setOptionalSection(state.resultSectionEl, state.resultEl, state.collabResultText || '');
3050
+ }
3051
+ break;
3052
+ }
3053
+ case 'webSearch':
3054
+ if (state.titleEl || state.subtitleEl) {
3055
+ if (state.titleEl) state.titleEl.textContent = 'Web search';
3056
+ if (state.subtitleEl) state.subtitleEl.textContent = item.query || '';
3057
+ }
3058
+ if (state.pillEl) state.pillEl.textContent = item.action?.type || 'search';
3059
+ if (state.preEl) {
3060
+ state.preEl.textContent = codexReadableValue({ action: item.action, results: item.results, query: item.query });
3061
+ }
3062
+ break;
3063
+ case 'imageView':
3064
+ if (state.titleEl || state.subtitleEl) {
3065
+ if (state.titleEl) state.titleEl.textContent = 'Viewing image';
3066
+ if (state.subtitleEl) state.subtitleEl.textContent = _ctx.cleanPreview(item.path || '');
3067
+ }
3068
+ if (state.pillEl) state.pillEl.textContent = 'complete';
3069
+ if (state.preEl) {
3070
+ state.preEl.textContent = item.path || '';
3071
+ if (item.path && !state._imgRendered) {
3072
+ state._imgRendered = true;
3073
+ const imgWrap = document.createElement('div');
3074
+ imgWrap.className = 'cxp-image-preview';
3075
+ const img = document.createElement('img');
3076
+ img.src = item.url || item.path;
3077
+ img.alt = item.path;
3078
+ img.style.maxWidth = '100%';
3079
+ img.style.maxHeight = '300px';
3080
+ img.style.borderRadius = '6px';
3081
+ img.style.marginTop = '8px';
3082
+ img.style.cursor = 'pointer';
3083
+ img.addEventListener('click', () => {
3084
+ const overlay = document.createElement('div');
3085
+ overlay.className = 'cxp-lightbox';
3086
+ const fullImg = document.createElement('img');
3087
+ fullImg.src = img.src;
3088
+ overlay.appendChild(fullImg);
3089
+ overlay.addEventListener('click', () => overlay.remove());
3090
+ document.body.appendChild(overlay);
3091
+ });
3092
+ img.onerror = () => { imgWrap.remove(); };
3093
+ imgWrap.appendChild(img);
3094
+ state.preEl.after(imgWrap);
3095
+ }
3096
+ }
3097
+ break;
3098
+ case 'imageGeneration':
3099
+ if (state.titleEl || state.subtitleEl) {
3100
+ if (state.titleEl) state.titleEl.textContent = 'Image generation';
3101
+ if (state.subtitleEl) state.subtitleEl.textContent = item.status || '';
3102
+ }
3103
+ if (state.pillEl) state.pillEl.textContent = item.status || 'complete';
3104
+ if (state.preEl) {
3105
+ state.preEl.textContent = [
3106
+ item.revisedPrompt ? `Prompt\n${item.revisedPrompt}` : '',
3107
+ item.savedPath ? `Saved path\n${item.savedPath}` : '',
3108
+ item.result ? `Result\n${item.result}` : '',
3109
+ item.failure ? `Failure\n${codexReadableValue(item.failure)}` : '',
3110
+ item.transparentBackground != null ? `Transparent background: ${item.transparentBackground ? 'yes' : 'no'}` : '',
3111
+ ].filter(Boolean).join('\n\n');
3112
+ }
3113
+ break;
3114
+ case 'hookPrompt':
3115
+ if (state.titleEl || state.subtitleEl) {
3116
+ if (state.titleEl) state.titleEl.textContent = 'Hook prompt';
3117
+ if (state.subtitleEl) state.subtitleEl.textContent = 'Injected context';
3118
+ }
3119
+ if (state.pillEl) state.pillEl.textContent = 'complete';
3120
+ if (state.preEl) state.preEl.textContent = Array.isArray(item.fragments)
3121
+ ? item.fragments.map((fragment) => fragment?.text || '').filter(Boolean).join('\n\n')
3122
+ : pretty(item.fragments);
3123
+ break;
3124
+ case 'enteredReviewMode':
3125
+ case 'exitedReviewMode':
3126
+ if (state.titleEl || state.subtitleEl) {
3127
+ if (state.titleEl) state.titleEl.textContent = item.type === 'enteredReviewMode' ? 'Entered review mode' : 'Exited review mode';
3128
+ if (state.subtitleEl) state.subtitleEl.textContent = '';
3129
+ }
3130
+ if (state.pillEl) state.pillEl.textContent = 'complete';
3131
+ if (state.preEl) state.preEl.textContent = item.review || '';
3132
+ break;
3133
+ default: {
3134
+ const presentation = codexTranscriptPresentation(item);
3135
+ if (state.titleEl || state.subtitleEl) {
3136
+ if (state.titleEl) state.titleEl.textContent = presentation.title;
3137
+ if (state.subtitleEl) state.subtitleEl.textContent = presentation.subtitle;
3138
+ }
3139
+ if (state.pillEl) state.pillEl.textContent = _ctx.formatStatus(presentation.status);
3140
+ if (state.preEl) state.preEl.textContent = presentation.text;
3141
+ break;
3142
+ }
3143
+ }
3144
+ _ctx.scheduleThreadSnapshotSave(_ctx.boundTab);
3145
+ }
3146
+
3147
+ // ═══════════════════════════════════════════
3148
+ // Delta Appending
3149
+ // ═══════════════════════════════════════════
3150
+
3151
+ export function appendAgentDelta(itemId, delta) {
3152
+ const state = ensureAssistantState(itemId);
3153
+ state.buffer += delta || '';
3154
+ setMarkdownBuffer(state, state.buffer, false);
3155
+ }
3156
+
3157
+ export function appendPlanDelta(itemId, delta) {
3158
+ const state = ensurePlanState({ id: itemId });
3159
+ if (state.pillEl) state.pillEl.textContent = 'live';
3160
+ state.buffer += delta || '';
3161
+ if (isCardBodyHidden(state)) { state._bodyDirty = true; return; }
3162
+ setMarkdownBuffer(state, state.buffer, false);
3163
+ }
3164
+
3165
+ export function getReasoningBodyText(state, { complete = false } = {}) {
3166
+ if (!state) return complete ? 'No reasoning summary was provided.' : 'Waiting for reasoning summary...';
3167
+ const contentText = Array.isArray(state.contentLines) ? state.contentLines.join('').trim() : '';
3168
+ if (contentText) return contentText;
3169
+ const summaryText = Array.isArray(state.summaryLines)
3170
+ ? state.summaryLines.map((line) => String(line || '').trim()).filter(Boolean).join('\n')
3171
+ : '';
3172
+ return summaryText || (complete ? 'No reasoning summary was provided.' : 'Waiting for reasoning summary...');
3173
+ }
3174
+
3175
+ export function appendReasoningDelta(itemId, delta, isSummary = false, summaryIndex = null) {
3176
+ const state = ensureReasoningState({ id: itemId });
3177
+ state.el?.classList.remove('cxp-reasoning-done');
3178
+ if (state.pillEl) state.pillEl.textContent = 'live';
3179
+ if (!state.userToggled && state.el && !state.el.open) {
3180
+ state.suppressToggleTracking = true;
3181
+ state.el.open = true;
3182
+ state.el.dataset.expanded = '1';
3183
+ setTimeout(() => { state.suppressToggleTracking = false; }, 0);
3184
+ }
3185
+ if (isSummary) {
3186
+ const idx = Number.isFinite(summaryIndex) ? Math.max(0, summaryIndex) : state.summaryLines.length;
3187
+ state.summaryLines[idx] = (state.summaryLines[idx] || '') + (delta || '');
3188
+ } else {
3189
+ state.contentLines.push(delta || '');
3190
+ }
3191
+ updateReasoningChrome(state, { complete: false });
3192
+ state.bodyEl.textContent = getReasoningBodyText(state);
3193
+ _ctx.scrollEnd();
3194
+ _ctx.scheduleThreadSnapshotSave(_ctx.boundTab);
3195
+ }
3196
+
3197
+ export function appendReasoningSummaryPart(itemId, summaryIndex) {
3198
+ const state = ensureReasoningState({ id: itemId });
3199
+ const idx = Number.isFinite(summaryIndex) ? Math.max(0, summaryIndex) : state.summaryLines.length;
3200
+ if (state.summaryLines[idx] == null) state.summaryLines[idx] = '';
3201
+ updateReasoningChrome(state, { complete: false });
3202
+ state.bodyEl.textContent = getReasoningBodyText(state);
3203
+ }
3204
+
3205
+ export function appendOutputDelta(itemId, delta, fallbackType = 'commandExecution') {
3206
+ const state = _ctx.items.get(itemId) || (fallbackType === 'fileChange'
3207
+ ? ensureFileChangeState({ id: itemId })
3208
+ : ensureCommandState({ id: itemId }));
3209
+ if (!state.outputEl) return;
3210
+ state.outputBuf = (state.outputBuf || '') + (delta || '');
3211
+ if (isCardBodyHidden(state)) { state._bodyDirty = true; return; }
3212
+ if (state.type === 'fileChange') refreshFileChangeView(state);
3213
+ else setOptionalSection(state.outputSectionEl, state.outputEl, state.outputBuf);
3214
+ _ctx.scrollEnd();
3215
+ _ctx.scheduleThreadSnapshotSave(_ctx.boundTab);
3216
+ }
3217
+
3218
+ export function appendToolProgress(itemId, message) {
3219
+ const state = ensureToolState({ id: itemId, server: 'MCP', tool: 'tool' });
3220
+ state._lastProgress = message || '';
3221
+ state.progressText = state._lastProgress;
3222
+ if (isCardBodyHidden(state)) { state._bodyDirty = true; return; }
3223
+ setOptionalText(state.progressEl, state._lastProgress);
3224
+ _ctx.scrollEnd();
3225
+ _ctx.scheduleThreadSnapshotSave(_ctx.boundTab);
3226
+ }
3227
+
3228
+ // ═══════════════════════════════════════════
3229
+ // Active Item Tracking
3230
+ // ═══════════════════════════════════════════
3231
+
3232
+ export function trackActiveItemStart(item) {
3233
+ if (!item?.id || item.type === 'userMessage' || item.type === 'contextCompaction') return;
3234
+ const existing = _ctx.activeItems.get(item.id);
3235
+ _ctx.activeItems.set(item.id, {
3236
+ id: item.id,
3237
+ item: existing?.item ? { ...existing.item, ...item } : item,
3238
+ startedAt: existing?.startedAt || Date.now(),
3239
+ });
3240
+ if (_ctx.boundTab) _ctx.boundTab.activeItems = _ctx.activeItems;
3241
+ _ctx.syncWorkStatus();
3242
+ }
3243
+
3244
+ export function trackActiveItemCompletion(item) {
3245
+ if (!item?.id) return;
3246
+ const existing = _ctx.activeItems.get(item.id);
3247
+ if (existing) _ctx.activeItems.delete(item.id);
3248
+ if (_ctx.boundTab) _ctx.boundTab.activeItems = _ctx.activeItems;
3249
+ _ctx.syncWorkStatus();
3250
+ }
3251
+
3252
+ // ═══════════════════════════════════════════
3253
+ // Transcript Node Counting & History Helpers
3254
+ // ═══════════════════════════════════════════
3255
+
3256
+ export function countRenderableTranscriptNodes(container) {
3257
+ if (!container) return 0;
3258
+ return [...container.children].filter((node) => !(
3259
+ node.classList?.contains('cxp-empty')
3260
+ || node.classList?.contains('cxp-thinking')
3261
+ )).length;
3262
+ }
3263
+
3264
+ export function flattenThreadHistoryItems(thread) {
3265
+ if (!Array.isArray(thread?.turns)) return [];
3266
+ return thread.turns.flatMap((turn) => Array.isArray(turn?.items) ? turn.items.filter(Boolean) : []);
3267
+ }
3268
+
3269
+ export function resolveHistoryRenderSource(thread, fallbackItems = null, storedSnapshot = null) {
3270
+ const sdkItems = flattenThreadHistoryItems(thread);
3271
+ const fallbackHistoryItems = Array.isArray(fallbackItems) ? fallbackItems.filter(Boolean) : [];
3272
+ const snapshot = _ctx.normalizeThreadSnapshotEntry(storedSnapshot, thread?.id || null);
3273
+ const threadUpdatedAt = _ctx.timestampMs(thread?.updatedAt || thread?.createdAt);
3274
+ // Structured Codex history is authoritative. A rendered-HTML snapshot is a
3275
+ // presentation cache only; extra cards must never make it "more complete".
3276
+ const selected = selectCodexHistoryRenderSource({
3277
+ sdkItems,
3278
+ fallbackItems: fallbackHistoryItems,
3279
+ snapshot,
3280
+ });
3281
+ return {
3282
+ ...selected,
3283
+ sourceUpdatedAt: selected.sourceType === 'snapshot'
3284
+ ? (snapshot.sourceUpdatedAt || snapshot.updatedAt || Date.now())
3285
+ : (threadUpdatedAt || Date.now()),
3286
+ counts: {
3287
+ sdkItems: sdkItems.length,
3288
+ fallbackItems: fallbackHistoryItems.length,
3289
+ snapshotItems: snapshot?.itemCount || 0,
3290
+ },
3291
+ };
3292
+ }
3293
+
3294
+ // ═══════════════════════════════════════════
3295
+ // History Rendering
3296
+ // ═══════════════════════════════════════════
3297
+
3298
+ // `authoritative`: the thread's turns are its whole history (a revert reply),
3299
+ // so the saved rendering is never used in their place, even when none remain.
3300
+ export function renderHistory(thread, fallbackItems = null, { authoritative = false } = {}) {
3301
+ _ctx.flushThreadSnapshotSave(_ctx.boundTab);
3302
+ const preservedTokenUsage = thread?.id && _ctx.threadId && thread.id === _ctx.threadId
3303
+ ? _ctx.threadTokenUsage
3304
+ : null;
3305
+ const storedSnapshot = _ctx.getThreadSnapshot(thread?.id || null);
3306
+ const historySource = resolveHistoryRenderSource(
3307
+ thread,
3308
+ fallbackItems,
3309
+ authoritative ? null : storedSnapshot,
3310
+ );
3311
+ _ctx.clearTranscript(historySource.items.length || historySource.snapshot
3312
+ ? 'Rendering saved Codex thread…'
3313
+ : 'Continue this Codex thread or start a fresh one.');
3314
+ if (!thread) return;
3315
+ _ctx.setThread(thread);
3316
+ console.info('[codex-panel] restore history source', {
3317
+ threadId: thread.id,
3318
+ selected: historySource.sourceType,
3319
+ sdkItems: historySource.counts.sdkItems,
3320
+ fallbackItems: historySource.counts.fallbackItems,
3321
+ snapshotItems: historySource.counts.snapshotItems,
3322
+ snapshotSourceType: historySource.snapshot?.sourceType || '',
3323
+ });
3324
+ if (historySource.snapshot && _ctx.renderStoredTranscript(historySource.snapshot, 'Continue this Codex thread or start a fresh one.')) {
3325
+ _ctx.updateThreadTokenUsage(preservedTokenUsage || historySource.snapshot?.threadTokenUsage || null);
3326
+ renderPostPlanActions(_ctx.boundTab);
3327
+ renderPostCompactionActions(_ctx.boundTab);
3328
+ return;
3329
+ }
3330
+ if (preservedTokenUsage || storedSnapshot?.threadTokenUsage) {
3331
+ _ctx.updateThreadTokenUsage(preservedTokenUsage || storedSnapshot?.threadTokenUsage || null);
3332
+ }
3333
+ if (!historySource.items.length) {
3334
+ _ctx.showEmpty('Continue this Codex thread or start a fresh one.');
3335
+ return;
3336
+ }
3337
+ const historyTurns = new Map((thread.turns || []).flatMap(turn => (turn.items || []).map(item => [item?.id, turn.id])));
3338
+ for (const item of historySource.items) {
3339
+ updateItemFromData({
3340
+ ...item,
3341
+ _synabunOwnership: {
3342
+ accountId: _ctx.accountId || 'default',
3343
+ threadId: thread.id,
3344
+ turnId: item.turnId || historyTurns.get(item.id) || '',
3345
+ source: 'history',
3346
+ },
3347
+ });
3348
+ }
3349
+ // Native items do not include the aggregated shell-edit diff. Retain only
3350
+ // locally saved turn-diff text with the same ownership manifest, rebuilding
3351
+ // it through the normal renderer after authoritative items have been shown.
3352
+ const normalizedSnapshot = _ctx.normalizeThreadSnapshotEntry(storedSnapshot, thread.id, _ctx.accountId || 'default');
3353
+ if (normalizedSnapshot?.html) {
3354
+ const tpl = document.createElement('template');
3355
+ tpl.innerHTML = normalizedSnapshot.html;
3356
+ const acceptedItems = new Set(normalizedSnapshot.acceptedItemIds || []);
3357
+ const acceptedTurns = new Set(normalizedSnapshot.acceptedTurnIds || []);
3358
+ const nativeTurns = new Set((thread.turns || []).map(turn => String(turn.id)));
3359
+ for (const saved of tpl.content.querySelectorAll('[data-turn-diff="1"]')) {
3360
+ const turnId = saved.dataset.codexTurnId;
3361
+ if (!turnId || saved.dataset.codexThreadId !== String(thread.id) || saved.dataset.codexAccountId !== String(_ctx.accountId || 'default')
3362
+ || saved.dataset.itemId !== `turn-diff:${turnId}` || !acceptedItems.has(saved.dataset.itemId)
3363
+ || !acceptedTurns.has(turnId) || nativeTurns.size && !nativeTurns.has(turnId)) continue;
3364
+ const raw = saved.querySelector('.cxp-diff-source')?.textContent;
3365
+ if (!raw) continue;
3366
+ renderTurnDiff(turnId, raw, { lazy: true });
3367
+ const restored = _ctx.items.get(`turn-diff:${turnId}`);
3368
+ if (restored && saved.dataset.userCollapsed === '1') { setCardExpanded(restored.el, false); restored.el.dataset.userCollapsed = '1'; }
3369
+ }
3370
+ }
3371
+ _ctx.setTranscriptSourceMeta({
3372
+ sourceType: historySource.sourceType,
3373
+ itemCount: historySource.items.length,
3374
+ sourceUpdatedAt: historySource.sourceUpdatedAt,
3375
+ });
3376
+ _ctx.hideEmpty();
3377
+ _ctx.scrollEnd(true);
3378
+ _ctx.flushThreadSnapshotSave(_ctx.boundTab);
3379
+ renderPostPlanActions(_ctx.boundTab);
3380
+ renderPostCompactionActions(_ctx.boundTab);
3381
+ }