@walkhi/code-relax 0.1.0-beta.6 → 0.1.0-beta.65

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 (855) hide show
  1. package/README.md +67 -45
  2. package/dist/bin/cleanup-accounts.mjs +63 -0
  3. package/dist/bin/codex-remote.mjs +362 -227
  4. package/dist/bin/manage-relay-admin.mjs +20 -0
  5. package/dist/bin/migrate-relay-db.mjs +109 -0
  6. package/dist/bin/publish-android-release.mjs +63 -0
  7. package/dist/bin/publish-harmony-release.mjs +46 -0
  8. package/dist/bin/release-upload-server.mjs +190 -0
  9. package/dist/bin/self-relay-admin.mjs +8 -0
  10. package/dist/bin/self-relay-demo.mjs +2 -2
  11. package/dist/bin/self-relay-server.mjs +43 -30
  12. package/dist/bin/set-relay-admin-password.mjs +11 -0
  13. package/dist/build-platform.json +1 -0
  14. package/dist/get-shared-service-url.ps1 +12 -4
  15. package/dist/shared/app-server-events.cjs +457 -238
  16. package/dist/shared/code-browser.cjs +159 -0
  17. package/dist/shared/file-changes.cjs +31 -0
  18. package/dist/shared/file-citations.cjs +117 -0
  19. package/dist/shared/file-mentions.cjs +92 -0
  20. package/dist/shared/message-delivery.cjs +115 -0
  21. package/dist/shared/p2p-data-channel.cjs +22 -0
  22. package/dist/shared/p2p-diagnostics.cjs +1 -1
  23. package/dist/shared/p2p-file-channel.cjs +87 -0
  24. package/dist/shared/p2p-ice-policy.cjs +22 -0
  25. package/dist/shared/plan-pricing.cjs +32 -0
  26. package/dist/shared/question-attention.cjs +69 -0
  27. package/dist/shared/release-version.cjs +31 -0
  28. package/dist/shared/server-requests.cjs +311 -67
  29. package/dist/shared/service-endpoints.cjs +21 -0
  30. package/dist/shared/timeline-sync.cjs +173 -67
  31. package/dist/skills/code-relax/SKILL.md +22 -17
  32. package/dist/src/archived-threads.mjs +22 -0
  33. package/dist/src/bounded-process-log.mjs +63 -0
  34. package/dist/src/celsius-client.mjs +107 -0
  35. package/dist/src/code-browser.mjs +100 -0
  36. package/dist/src/desktop-launcher.mjs +53 -42
  37. package/dist/src/dots-approval-timeline.mjs +99 -0
  38. package/dist/src/dots-avatar.mjs +42 -0
  39. package/dist/src/dots-chat-projection.mjs +50 -0
  40. package/dist/src/dots-chat-server.mjs +498 -0
  41. package/dist/src/dots-message-images.mjs +319 -0
  42. package/dist/src/dots-profile.mjs +77 -0
  43. package/dist/src/dots-room-activity.mjs +178 -0
  44. package/dist/src/dots-room-client.mjs +137 -0
  45. package/dist/src/dots-room-projection.mjs +94 -0
  46. package/dist/src/dots-schedules.mjs +66 -0
  47. package/dist/src/dots-summary-tasks.mjs +32 -0
  48. package/dist/src/dots-task-catalog.mjs +430 -0
  49. package/dist/src/dots-task-files.mjs +52 -0
  50. package/dist/src/file-transfer-range.mjs +22 -0
  51. package/dist/src/file-upload-session.mjs +146 -0
  52. package/dist/src/hosted-history-app-server.mjs +103 -0
  53. package/dist/src/hosted-history-cache.mjs +113 -0
  54. package/dist/src/lan-socket-server.mjs +266 -37
  55. package/dist/src/managed-app-server.mjs +373 -213
  56. package/dist/src/managed-endpoint.mjs +25 -0
  57. package/dist/src/message-images.mjs +0 -18
  58. package/dist/src/message-upload-files.mjs +109 -0
  59. package/dist/src/onboarding.mjs +9 -28
  60. package/dist/src/plan-pricing.mjs +47 -0
  61. package/dist/src/platform/README.md +7 -12
  62. package/dist/src/platform/codex-protocol.mjs +46 -0
  63. package/dist/src/platform/codex-runtime.mjs +3 -0
  64. package/dist/src/platform/credential-store.mjs +19 -0
  65. package/dist/src/platform/desktop-host.mjs +3 -0
  66. package/dist/src/platform/desktop-permission-mode.mjs +17 -0
  67. package/dist/src/platform/desktop-project-registration.mjs +3 -0
  68. package/dist/src/platform/desktop-project-store.mjs +219 -0
  69. package/dist/src/platform/file-opener.mjs +9 -0
  70. package/dist/src/platform/linux/codex-runtime.mjs +56 -0
  71. package/dist/src/platform/linux/desktop-host.mjs +87 -0
  72. package/dist/src/platform/linux/desktop-installation.mjs +36 -0
  73. package/dist/src/platform/linux/desktop-project-registration.mjs +11 -0
  74. package/dist/src/platform/linux/network-info.mjs +11 -0
  75. package/dist/src/platform/linux/process.mjs +53 -0
  76. package/dist/src/platform/linux/project-locations.mjs +17 -0
  77. package/dist/src/platform/linux/resident-startup.mjs +52 -0
  78. package/dist/src/platform/linux/retained-runtime.mjs +76 -0
  79. package/dist/src/platform/linux/service-host.mjs +18 -0
  80. package/dist/src/platform/linux/system.mjs +50 -0
  81. package/dist/src/platform/linux/user-environment.mjs +9 -0
  82. package/dist/src/platform/macos/codex-runtime.mjs +125 -0
  83. package/dist/src/platform/macos/desktop-host.mjs +86 -0
  84. package/dist/src/platform/macos/desktop-project-registration.mjs +11 -0
  85. package/dist/src/platform/macos/network-info.mjs +8 -0
  86. package/dist/src/platform/macos/project-locations.mjs +8 -0
  87. package/dist/src/platform/macos/resident-startup.mjs +84 -0
  88. package/dist/src/platform/macos/retained-runtime.mjs +127 -0
  89. package/dist/src/platform/macos/service-host.mjs +18 -0
  90. package/dist/src/platform/macos/system.mjs +29 -0
  91. package/dist/src/platform/macos/user-environment.mjs +9 -0
  92. package/dist/src/platform/native-package.mjs +58 -0
  93. package/dist/src/platform/network-info.mjs +3 -0
  94. package/dist/src/platform/path-identity.mjs +5 -0
  95. package/dist/src/platform/posix/network-candidates.mjs +23 -0
  96. package/dist/src/platform/posix/runtime-paths.mjs +25 -0
  97. package/dist/src/platform/power-inhibitor.mjs +22 -0
  98. package/dist/src/platform/project-locations.mjs +3 -0
  99. package/dist/src/platform/runtime-paths.mjs +8 -0
  100. package/dist/src/platform/select.mjs +5 -0
  101. package/dist/src/platform/service-host.mjs +3 -0
  102. package/dist/src/platform/user-environment.mjs +3 -0
  103. package/dist/src/platform/windows/activate-desktop.ps1 +49 -0
  104. package/dist/src/platform/windows/codex-runtime.mjs +215 -0
  105. package/dist/src/platform/windows/desktop-helper.mjs +179 -0
  106. package/dist/src/platform/windows/desktop-host.mjs +45 -18
  107. package/dist/src/platform/windows/desktop-info.ps1 +27 -5
  108. package/dist/src/platform/windows/desktop-project-registration.mjs +4 -0
  109. package/dist/src/platform/windows/desktop-runtime.ps1 +10 -0
  110. package/dist/src/platform/windows/desktop-shortcuts.ps1 +7 -14
  111. package/dist/src/platform/windows/dots-profile.mjs +49 -0
  112. package/dist/src/platform/windows/keep-awake.ps1 +20 -0
  113. package/dist/src/platform/windows/launch-worker.mjs +41 -42
  114. package/dist/src/platform/windows/project-locations.mjs +16 -0
  115. package/dist/src/platform/windows/resident-start.js +20 -0
  116. package/dist/src/platform/windows/resident-startup.ps1 +76 -20
  117. package/dist/src/platform/windows/rustdesk-video-probe.mjs +168 -0
  118. package/dist/src/platform/windows/service-host.mjs +15 -23
  119. package/dist/src/platform/windows/start-hidden-console.ps1 +11 -2
  120. package/dist/src/platform/windows/stop-desktop.ps1 +7 -4
  121. package/dist/src/platform/windows/user-environment.mjs +52 -50
  122. package/dist/src/project-create.mjs +121 -0
  123. package/dist/src/project-management.mjs +59 -0
  124. package/dist/src/project-remove.mjs +55 -0
  125. package/dist/src/project-update.mjs +59 -0
  126. package/dist/src/relay-config-import.mjs +45 -0
  127. package/dist/src/runtime-discovery.mjs +47 -0
  128. package/dist/src/self-relay/account-cleanup.mjs +30 -0
  129. package/dist/src/self-relay/account-connections.mjs +52 -0
  130. package/dist/src/self-relay/admin-auth.mjs +59 -0
  131. package/dist/src/self-relay/admin-cloud.mjs +101 -0
  132. package/dist/src/self-relay/admin-login-page.mjs +7 -0
  133. package/dist/src/self-relay/admin-page.mjs +87 -0
  134. package/dist/src/self-relay/admin-server.mjs +105 -0
  135. package/dist/src/self-relay/admin-state.mjs +85 -19
  136. package/dist/src/self-relay/admin-style.mjs +58 -0
  137. package/dist/src/self-relay/admin-workstations-page.mjs +95 -0
  138. package/dist/src/self-relay/admin-workstations.mjs +143 -0
  139. package/dist/src/self-relay/android-releases.mjs +61 -0
  140. package/dist/src/self-relay/client.mjs +45 -45
  141. package/dist/src/self-relay/connection-reporting.mjs +38 -0
  142. package/dist/src/self-relay/connector.mjs +331 -49
  143. package/dist/src/self-relay/demo.mjs +3 -4
  144. package/dist/src/self-relay/device-info.mjs +27 -0
  145. package/dist/src/self-relay/device-store.mjs +10 -2
  146. package/dist/src/self-relay/diagnostics.mjs +54 -0
  147. package/dist/src/self-relay/email-accounts.mjs +183 -0
  148. package/dist/src/self-relay/file-wire.mjs +13 -0
  149. package/dist/src/self-relay/lan-authorizations.mjs +44 -2
  150. package/dist/src/self-relay/lifecycle.mjs +255 -262
  151. package/dist/src/self-relay/long-pairing.mjs +40 -0
  152. package/dist/src/self-relay/mysql-state.mjs +118 -0
  153. package/dist/src/self-relay/p2p-probe.mjs +168 -123
  154. package/dist/src/self-relay/phone-connections.mjs +55 -0
  155. package/dist/src/self-relay/privacy-page.mjs +26 -0
  156. package/dist/src/self-relay/public-releases.mjs +67 -0
  157. package/dist/src/self-relay/rtc-file-bridge.mjs +46 -0
  158. package/dist/src/self-relay/server.mjs +714 -116
  159. package/dist/src/self-relay/usage-daily.mjs +55 -0
  160. package/dist/src/self-relay/usage-meter.mjs +141 -0
  161. package/dist/src/server.mjs +1566 -1793
  162. package/dist/src/service-control.mjs +17 -7
  163. package/dist/src/service-doctor.mjs +148 -65
  164. package/dist/src/service-health.mjs +2 -2
  165. package/dist/src/service-install.mjs +16 -10
  166. package/dist/src/service-lifecycle.mjs +51 -47
  167. package/dist/src/service-power.mjs +58 -0
  168. package/dist/src/service-restart.mjs +45 -12
  169. package/dist/src/service-update-worker.mjs +42 -0
  170. package/dist/src/service-update.mjs +80 -0
  171. package/dist/src/shared-app-server.mjs +489 -305
  172. package/dist/src/shared-catalog.mjs +132 -50
  173. package/dist/src/shared-recovery.mjs +24 -14
  174. package/dist/src/shared-thread-stream.mjs +136 -51
  175. package/dist/src/skill-install.mjs +0 -13
  176. package/dist/src/turn-model-history.mjs +47 -0
  177. package/dist/src/usage-reset.mjs +17 -0
  178. package/dist/src/workstation-downloads.mjs +120 -0
  179. package/package.json +35 -37
  180. package/tools/postinstall.mjs +186 -118
  181. package/dist/src/app-server-client.mjs +0 -468
  182. package/dist/src/app-server-tasks.mjs +0 -358
  183. package/dist/src/debug-ui.mjs +0 -131
  184. package/dist/src/platform/windows/credential-store.mjs +0 -27
  185. package/dist/src/platform/windows/credential-store.ps1 +0 -49
  186. package/dist/src/platform/windows/desktop-monitor.mjs +0 -34
  187. package/dist/src/platform/windows/desktop-shortcut-run.ps1 +0 -5
  188. package/dist/src/platform/windows/desktop-tools.mjs +0 -48
  189. package/dist/src/thread-catalog.mjs +0 -47
  190. package/dist/tools/find-desktop-pipe.mjs +0 -10
  191. package/dist/web/activity-view.js +0 -283
  192. package/dist/web/approval-ui.js +0 -188
  193. package/dist/web/capabilities.js +0 -57
  194. package/dist/web/chat-transport.js +0 -202
  195. package/dist/web/chat.css +0 -782
  196. package/dist/web/chat.js +0 -3830
  197. package/dist/web/client-platform.js +0 -11
  198. package/dist/web/community-view.js +0 -20
  199. package/dist/web/community.css +0 -77
  200. package/dist/web/community.html +0 -37
  201. package/dist/web/composer-controller.js +0 -101
  202. package/dist/web/conversation-controller.js +0 -99
  203. package/dist/web/disclosure-state-controller.js +0 -95
  204. package/dist/web/draft-controller.js +0 -103
  205. package/dist/web/harmony-platform.js +0 -52
  206. package/dist/web/highlight-powershell.min.js +0 -39
  207. package/dist/web/highlight.min.js +0 -1262
  208. package/dist/web/history-cache.js +0 -240
  209. package/dist/web/history-controller.js +0 -167
  210. package/dist/web/index.html +0 -325
  211. package/dist/web/lan-session.js +0 -122
  212. package/dist/web/link-action-controller.js +0 -212
  213. package/dist/web/message-send-controller.js +0 -177
  214. package/dist/web/message-view.js +0 -98
  215. package/dist/web/p2p-data-channel.js +0 -50
  216. package/dist/web/p2p-diagnostics.js +0 -28
  217. package/dist/web/p2p-probe.js +0 -123
  218. package/dist/web/page-resume.js +0 -28
  219. package/dist/web/pending-message-store.js +0 -108
  220. package/dist/web/queue-controller.js +0 -82
  221. package/dist/web/resources.json +0 -1
  222. package/dist/web/self-relay-session.js +0 -129
  223. package/dist/web/server-requests.js +0 -67
  224. package/dist/web/station-connection-controller.js +0 -75
  225. package/dist/web/task-list-view.js +0 -296
  226. package/dist/web/thread-attention-controller.js +0 -124
  227. package/dist/web/thread-context-controller.js +0 -30
  228. package/dist/web/thread-list-controller.js +0 -61
  229. package/dist/web/thread-list-sync.js +0 -86
  230. package/dist/web/thread-title-controller.js +0 -57
  231. package/dist/web/timeline-formatters.js +0 -249
  232. package/dist/web/timeline-reducer.js +0 -81
  233. package/dist/web/timeline-renderer.js +0 -161
  234. package/dist/web/timeline-scroll-controller.js +0 -50
  235. package/dist/web/timeline-sync.js +0 -67
  236. package/dist/web/usage-controller.js +0 -258
  237. package/dist/web/vendor/README.md +0 -14
  238. package/dist/web/vendor/highlight-powershell.min.js +0 -39
  239. package/dist/web/vendor/highlight.LICENSE.txt +0 -29
  240. package/dist/web/vendor/highlight.min.js +0 -1262
  241. package/dist/web/vendor/lucide.LICENSE.txt +0 -17
  242. package/node_modules/@img/colour/LICENSE.md +0 -82
  243. package/node_modules/@img/colour/README.md +0 -15
  244. package/node_modules/@img/colour/color.cjs +0 -1596
  245. package/node_modules/@img/colour/index.cjs +0 -1
  246. package/node_modules/@img/colour/index.d.ts +0 -929
  247. package/node_modules/@img/colour/package.json +0 -58
  248. package/node_modules/@img/sharp-win32-x64/LICENSE +0 -191
  249. package/node_modules/@img/sharp-win32-x64/README.md +0 -59
  250. package/node_modules/@img/sharp-win32-x64/index.cjs +0 -1
  251. package/node_modules/@img/sharp-win32-x64/lib/libvips-42.dll +0 -0
  252. package/node_modules/@img/sharp-win32-x64/lib/libvips-cpp-8.18.6.dll +0 -0
  253. package/node_modules/@img/sharp-win32-x64/lib/sharp-win32-x64-0.35.4.node +0 -0
  254. package/node_modules/@img/sharp-win32-x64/package.json +0 -40
  255. package/node_modules/@img/sharp-win32-x64/versions.json +0 -30
  256. package/node_modules/@node-datachannel/win32-x64-msvc/node_datachannel.node +0 -0
  257. package/node_modules/@node-datachannel/win32-x64-msvc/package.json +0 -20
  258. package/node_modules/ansi-regex/index.d.ts +0 -37
  259. package/node_modules/ansi-regex/index.js +0 -10
  260. package/node_modules/ansi-regex/license +0 -9
  261. package/node_modules/ansi-regex/package.json +0 -55
  262. package/node_modules/ansi-regex/readme.md +0 -78
  263. package/node_modules/ansi-styles/index.d.ts +0 -345
  264. package/node_modules/ansi-styles/index.js +0 -163
  265. package/node_modules/ansi-styles/license +0 -9
  266. package/node_modules/ansi-styles/package.json +0 -56
  267. package/node_modules/ansi-styles/readme.md +0 -152
  268. package/node_modules/camelcase/index.d.ts +0 -63
  269. package/node_modules/camelcase/index.js +0 -76
  270. package/node_modules/camelcase/license +0 -9
  271. package/node_modules/camelcase/package.json +0 -43
  272. package/node_modules/camelcase/readme.md +0 -99
  273. package/node_modules/cliui/CHANGELOG.md +0 -76
  274. package/node_modules/cliui/LICENSE.txt +0 -14
  275. package/node_modules/cliui/README.md +0 -115
  276. package/node_modules/cliui/index.js +0 -354
  277. package/node_modules/cliui/package.json +0 -65
  278. package/node_modules/color-convert/CHANGELOG.md +0 -54
  279. package/node_modules/color-convert/LICENSE +0 -21
  280. package/node_modules/color-convert/README.md +0 -68
  281. package/node_modules/color-convert/conversions.js +0 -839
  282. package/node_modules/color-convert/index.js +0 -81
  283. package/node_modules/color-convert/package.json +0 -48
  284. package/node_modules/color-convert/route.js +0 -97
  285. package/node_modules/color-name/LICENSE +0 -8
  286. package/node_modules/color-name/README.md +0 -11
  287. package/node_modules/color-name/index.js +0 -152
  288. package/node_modules/color-name/package.json +0 -28
  289. package/node_modules/decamelize/index.js +0 -13
  290. package/node_modules/decamelize/license +0 -21
  291. package/node_modules/decamelize/package.json +0 -38
  292. package/node_modules/decamelize/readme.md +0 -48
  293. package/node_modules/detect-libc/LICENSE +0 -201
  294. package/node_modules/detect-libc/README.md +0 -163
  295. package/node_modules/detect-libc/index.d.ts +0 -14
  296. package/node_modules/detect-libc/lib/detect-libc.js +0 -313
  297. package/node_modules/detect-libc/lib/elf.js +0 -39
  298. package/node_modules/detect-libc/lib/filesystem.js +0 -51
  299. package/node_modules/detect-libc/lib/process.js +0 -24
  300. package/node_modules/detect-libc/package.json +0 -44
  301. package/node_modules/dijkstrajs/.travis.yml +0 -4
  302. package/node_modules/dijkstrajs/CONTRIBUTING.md +0 -8
  303. package/node_modules/dijkstrajs/LICENSE.md +0 -19
  304. package/node_modules/dijkstrajs/README.md +0 -18
  305. package/node_modules/dijkstrajs/dijkstra.js +0 -165
  306. package/node_modules/dijkstrajs/package.json +0 -60
  307. package/node_modules/dijkstrajs/test/dijkstra.test.js +0 -96
  308. package/node_modules/emoji-regex/LICENSE-MIT.txt +0 -20
  309. package/node_modules/emoji-regex/README.md +0 -73
  310. package/node_modules/emoji-regex/es2015/index.js +0 -6
  311. package/node_modules/emoji-regex/es2015/text.js +0 -6
  312. package/node_modules/emoji-regex/index.d.ts +0 -23
  313. package/node_modules/emoji-regex/index.js +0 -6
  314. package/node_modules/emoji-regex/package.json +0 -50
  315. package/node_modules/emoji-regex/text.js +0 -6
  316. package/node_modules/find-up/index.d.ts +0 -137
  317. package/node_modules/find-up/index.js +0 -89
  318. package/node_modules/find-up/license +0 -9
  319. package/node_modules/find-up/package.json +0 -53
  320. package/node_modules/find-up/readme.md +0 -156
  321. package/node_modules/get-caller-file/LICENSE.md +0 -6
  322. package/node_modules/get-caller-file/README.md +0 -41
  323. package/node_modules/get-caller-file/index.d.ts +0 -2
  324. package/node_modules/get-caller-file/index.js +0 -22
  325. package/node_modules/get-caller-file/index.js.map +0 -1
  326. package/node_modules/get-caller-file/package.json +0 -42
  327. package/node_modules/is-fullwidth-code-point/index.d.ts +0 -17
  328. package/node_modules/is-fullwidth-code-point/index.js +0 -50
  329. package/node_modules/is-fullwidth-code-point/license +0 -9
  330. package/node_modules/is-fullwidth-code-point/package.json +0 -42
  331. package/node_modules/is-fullwidth-code-point/readme.md +0 -39
  332. package/node_modules/locate-path/index.d.ts +0 -83
  333. package/node_modules/locate-path/index.js +0 -65
  334. package/node_modules/locate-path/license +0 -9
  335. package/node_modules/locate-path/package.json +0 -45
  336. package/node_modules/locate-path/readme.md +0 -122
  337. package/node_modules/node-datachannel/.clang-format +0 -17
  338. package/node_modules/node-datachannel/.editorconfig +0 -12
  339. package/node_modules/node-datachannel/.eslintignore +0 -8
  340. package/node_modules/node-datachannel/.eslintrc.json +0 -27
  341. package/node_modules/node-datachannel/.gitmodules +0 -3
  342. package/node_modules/node-datachannel/.prettierignore +0 -7
  343. package/node_modules/node-datachannel/.prettierrc +0 -13
  344. package/node_modules/node-datachannel/API.md +0 -247
  345. package/node_modules/node-datachannel/BULDING.md +0 -26
  346. package/node_modules/node-datachannel/CMakeLists.txt +0 -153
  347. package/node_modules/node-datachannel/LICENSE +0 -373
  348. package/node_modules/node-datachannel/README.md +0 -129
  349. package/node_modules/node-datachannel/dist/cjs/index.cjs +0 -11
  350. package/node_modules/node-datachannel/dist/cjs/index.cjs.map +0 -1
  351. package/node_modules/node-datachannel/dist/cjs/lib/datachannel-stream.cjs +0 -101
  352. package/node_modules/node-datachannel/dist/cjs/lib/datachannel-stream.cjs.map +0 -1
  353. package/node_modules/node-datachannel/dist/cjs/lib/index.cjs +0 -91
  354. package/node_modules/node-datachannel/dist/cjs/lib/index.cjs.map +0 -1
  355. package/node_modules/node-datachannel/dist/cjs/lib/node-datachannel.cjs +0 -94
  356. package/node_modules/node-datachannel/dist/cjs/lib/node-datachannel.cjs.map +0 -1
  357. package/node_modules/node-datachannel/dist/cjs/lib/websocket-server.cjs +0 -45
  358. package/node_modules/node-datachannel/dist/cjs/lib/websocket-server.cjs.map +0 -1
  359. package/node_modules/node-datachannel/dist/cjs/lib/websocket.cjs +0 -8
  360. package/node_modules/node-datachannel/dist/cjs/lib/websocket.cjs.map +0 -1
  361. package/node_modules/node-datachannel/dist/cjs/polyfill/Events.cjs +0 -86
  362. package/node_modules/node-datachannel/dist/cjs/polyfill/Events.cjs.map +0 -1
  363. package/node_modules/node-datachannel/dist/cjs/polyfill/Exception.cjs +0 -34
  364. package/node_modules/node-datachannel/dist/cjs/polyfill/Exception.cjs.map +0 -1
  365. package/node_modules/node-datachannel/dist/cjs/polyfill/RTCCertificate.cjs +0 -34
  366. package/node_modules/node-datachannel/dist/cjs/polyfill/RTCCertificate.cjs.map +0 -1
  367. package/node_modules/node-datachannel/dist/cjs/polyfill/RTCDataChannel.cjs +0 -214
  368. package/node_modules/node-datachannel/dist/cjs/polyfill/RTCDataChannel.cjs.map +0 -1
  369. package/node_modules/node-datachannel/dist/cjs/polyfill/RTCDtlsTransport.cjs +0 -53
  370. package/node_modules/node-datachannel/dist/cjs/polyfill/RTCDtlsTransport.cjs.map +0 -1
  371. package/node_modules/node-datachannel/dist/cjs/polyfill/RTCError.cjs +0 -83
  372. package/node_modules/node-datachannel/dist/cjs/polyfill/RTCError.cjs.map +0 -1
  373. package/node_modules/node-datachannel/dist/cjs/polyfill/RTCIceCandidate.cjs +0 -132
  374. package/node_modules/node-datachannel/dist/cjs/polyfill/RTCIceCandidate.cjs.map +0 -1
  375. package/node_modules/node-datachannel/dist/cjs/polyfill/RTCIceTransport.cjs +0 -94
  376. package/node_modules/node-datachannel/dist/cjs/polyfill/RTCIceTransport.cjs.map +0 -1
  377. package/node_modules/node-datachannel/dist/cjs/polyfill/RTCPeerConnection.cjs +0 -434
  378. package/node_modules/node-datachannel/dist/cjs/polyfill/RTCPeerConnection.cjs.map +0 -1
  379. package/node_modules/node-datachannel/dist/cjs/polyfill/RTCSctpTransport.cjs +0 -59
  380. package/node_modules/node-datachannel/dist/cjs/polyfill/RTCSctpTransport.cjs.map +0 -1
  381. package/node_modules/node-datachannel/dist/cjs/polyfill/RTCSessionDescription.cjs +0 -45
  382. package/node_modules/node-datachannel/dist/cjs/polyfill/RTCSessionDescription.cjs.map +0 -1
  383. package/node_modules/node-datachannel/dist/cjs/polyfill/index.cjs +0 -42
  384. package/node_modules/node-datachannel/dist/cjs/polyfill/index.cjs.map +0 -1
  385. package/node_modules/node-datachannel/dist/esm/index.mjs +0 -8
  386. package/node_modules/node-datachannel/dist/esm/index.mjs.map +0 -1
  387. package/node_modules/node-datachannel/dist/esm/lib/datachannel-stream.mjs +0 -78
  388. package/node_modules/node-datachannel/dist/esm/lib/datachannel-stream.mjs.map +0 -1
  389. package/node_modules/node-datachannel/dist/esm/lib/index.mjs +0 -64
  390. package/node_modules/node-datachannel/dist/esm/lib/index.mjs.map +0 -1
  391. package/node_modules/node-datachannel/dist/esm/lib/node-datachannel.mjs +0 -78
  392. package/node_modules/node-datachannel/dist/esm/lib/node-datachannel.mjs.map +0 -1
  393. package/node_modules/node-datachannel/dist/esm/lib/websocket-server.mjs +0 -43
  394. package/node_modules/node-datachannel/dist/esm/lib/websocket-server.mjs.map +0 -1
  395. package/node_modules/node-datachannel/dist/esm/lib/websocket.mjs +0 -6
  396. package/node_modules/node-datachannel/dist/esm/lib/websocket.mjs.map +0 -1
  397. package/node_modules/node-datachannel/dist/esm/polyfill/Events.mjs +0 -82
  398. package/node_modules/node-datachannel/dist/esm/polyfill/Events.mjs.map +0 -1
  399. package/node_modules/node-datachannel/dist/esm/polyfill/Exception.mjs +0 -28
  400. package/node_modules/node-datachannel/dist/esm/polyfill/Exception.mjs.map +0 -1
  401. package/node_modules/node-datachannel/dist/esm/polyfill/RTCCertificate.mjs +0 -30
  402. package/node_modules/node-datachannel/dist/esm/polyfill/RTCCertificate.mjs.map +0 -1
  403. package/node_modules/node-datachannel/dist/esm/polyfill/RTCDataChannel.mjs +0 -210
  404. package/node_modules/node-datachannel/dist/esm/polyfill/RTCDataChannel.mjs.map +0 -1
  405. package/node_modules/node-datachannel/dist/esm/polyfill/RTCDtlsTransport.mjs +0 -49
  406. package/node_modules/node-datachannel/dist/esm/polyfill/RTCDtlsTransport.mjs.map +0 -1
  407. package/node_modules/node-datachannel/dist/esm/polyfill/RTCError.mjs +0 -79
  408. package/node_modules/node-datachannel/dist/esm/polyfill/RTCError.mjs.map +0 -1
  409. package/node_modules/node-datachannel/dist/esm/polyfill/RTCIceCandidate.mjs +0 -128
  410. package/node_modules/node-datachannel/dist/esm/polyfill/RTCIceCandidate.mjs.map +0 -1
  411. package/node_modules/node-datachannel/dist/esm/polyfill/RTCIceTransport.mjs +0 -89
  412. package/node_modules/node-datachannel/dist/esm/polyfill/RTCIceTransport.mjs.map +0 -1
  413. package/node_modules/node-datachannel/dist/esm/polyfill/RTCPeerConnection.mjs +0 -430
  414. package/node_modules/node-datachannel/dist/esm/polyfill/RTCPeerConnection.mjs.map +0 -1
  415. package/node_modules/node-datachannel/dist/esm/polyfill/RTCSctpTransport.mjs +0 -55
  416. package/node_modules/node-datachannel/dist/esm/polyfill/RTCSctpTransport.mjs.map +0 -1
  417. package/node_modules/node-datachannel/dist/esm/polyfill/RTCSessionDescription.mjs +0 -41
  418. package/node_modules/node-datachannel/dist/esm/polyfill/RTCSessionDescription.mjs.map +0 -1
  419. package/node_modules/node-datachannel/dist/esm/polyfill/index.mjs +0 -27
  420. package/node_modules/node-datachannel/dist/esm/polyfill/index.mjs.map +0 -1
  421. package/node_modules/node-datachannel/dist/types/lib/datachannel-stream.d.ts +0 -24
  422. package/node_modules/node-datachannel/dist/types/lib/index.d.ts +0 -241
  423. package/node_modules/node-datachannel/dist/types/lib/types.d.ts +0 -119
  424. package/node_modules/node-datachannel/dist/types/lib/websocket-server.d.ts +0 -13
  425. package/node_modules/node-datachannel/dist/types/lib/websocket.d.ts +0 -25
  426. package/node_modules/node-datachannel/dist/types/polyfill/Events.d.ts +0 -15
  427. package/node_modules/node-datachannel/dist/types/polyfill/RTCCertificate.d.ts +0 -9
  428. package/node_modules/node-datachannel/dist/types/polyfill/RTCDataChannel.d.ts +0 -29
  429. package/node_modules/node-datachannel/dist/types/polyfill/RTCDtlsTransport.d.ts +0 -15
  430. package/node_modules/node-datachannel/dist/types/polyfill/RTCError.d.ts +0 -17
  431. package/node_modules/node-datachannel/dist/types/polyfill/RTCIceCandidate.d.ts +0 -21
  432. package/node_modules/node-datachannel/dist/types/polyfill/RTCIceTransport.d.ts +0 -30
  433. package/node_modules/node-datachannel/dist/types/polyfill/RTCPeerConnection.d.ts +0 -64
  434. package/node_modules/node-datachannel/dist/types/polyfill/RTCSctpTransport.d.ts +0 -15
  435. package/node_modules/node-datachannel/dist/types/polyfill/RTCSessionDescription.d.ts +0 -10
  436. package/node_modules/node-datachannel/dist/types/polyfill/index.d.ts +0 -26
  437. package/node_modules/node-datachannel/jest.config.ts +0 -14
  438. package/node_modules/node-datachannel/package.json +0 -133
  439. package/node_modules/node-datachannel/rollup.config.mjs +0 -67
  440. package/node_modules/node-datachannel/src/cpp/data-channel-wrapper.cpp +0 -530
  441. package/node_modules/node-datachannel/src/cpp/data-channel-wrapper.h +0 -63
  442. package/node_modules/node-datachannel/src/cpp/ice-udp-mux-listener-wrapper.cpp +0 -172
  443. package/node_modules/node-datachannel/src/cpp/ice-udp-mux-listener-wrapper.h +0 -43
  444. package/node_modules/node-datachannel/src/cpp/main.cpp +0 -58
  445. package/node_modules/node-datachannel/src/cpp/media-audio-wrapper.cpp +0 -457
  446. package/node_modules/node-datachannel/src/cpp/media-audio-wrapper.h +0 -52
  447. package/node_modules/node-datachannel/src/cpp/media-av1packetization.cpp +0 -24
  448. package/node_modules/node-datachannel/src/cpp/media-av1packetization.h +0 -11
  449. package/node_modules/node-datachannel/src/cpp/media-av1rtppacketizer-wrapper.cpp +0 -126
  450. package/node_modules/node-datachannel/src/cpp/media-av1rtppacketizer-wrapper.h +0 -29
  451. package/node_modules/node-datachannel/src/cpp/media-direction.cpp +0 -43
  452. package/node_modules/node-datachannel/src/cpp/media-direction.h +0 -10
  453. package/node_modules/node-datachannel/src/cpp/media-h264rtppacketizer-wrapper.cpp +0 -126
  454. package/node_modules/node-datachannel/src/cpp/media-h264rtppacketizer-wrapper.h +0 -30
  455. package/node_modules/node-datachannel/src/cpp/media-h265rtppacketizer-wrapper.cpp +0 -126
  456. package/node_modules/node-datachannel/src/cpp/media-h265rtppacketizer-wrapper.h +0 -30
  457. package/node_modules/node-datachannel/src/cpp/media-h26xseparator.cpp +0 -32
  458. package/node_modules/node-datachannel/src/cpp/media-h26xseparator.h +0 -11
  459. package/node_modules/node-datachannel/src/cpp/media-mediahandler-helper.cpp +0 -31
  460. package/node_modules/node-datachannel/src/cpp/media-mediahandler-helper.h +0 -11
  461. package/node_modules/node-datachannel/src/cpp/media-pacinghandler-wrapper.cpp +0 -79
  462. package/node_modules/node-datachannel/src/cpp/media-pacinghandler-wrapper.h +0 -28
  463. package/node_modules/node-datachannel/src/cpp/media-rtcpnackresponder-wrapper.cpp +0 -68
  464. package/node_modules/node-datachannel/src/cpp/media-rtcpnackresponder-wrapper.h +0 -28
  465. package/node_modules/node-datachannel/src/cpp/media-rtcpreceivingsession-wrapper.cpp +0 -57
  466. package/node_modules/node-datachannel/src/cpp/media-rtcpreceivingsession-wrapper.h +0 -28
  467. package/node_modules/node-datachannel/src/cpp/media-rtcpsrreporter-wrapper.cpp +0 -93
  468. package/node_modules/node-datachannel/src/cpp/media-rtcpsrreporter-wrapper.h +0 -30
  469. package/node_modules/node-datachannel/src/cpp/media-rtppacketizationconfig-wrapper.cpp +0 -167
  470. package/node_modules/node-datachannel/src/cpp/media-rtppacketizationconfig-wrapper.h +0 -35
  471. package/node_modules/node-datachannel/src/cpp/media-rtppacketizer-wrapper.cpp +0 -95
  472. package/node_modules/node-datachannel/src/cpp/media-rtppacketizer-wrapper.h +0 -30
  473. package/node_modules/node-datachannel/src/cpp/media-track-wrapper.cpp +0 -458
  474. package/node_modules/node-datachannel/src/cpp/media-track-wrapper.h +0 -61
  475. package/node_modules/node-datachannel/src/cpp/media-video-wrapper.cpp +0 -526
  476. package/node_modules/node-datachannel/src/cpp/media-video-wrapper.h +0 -56
  477. package/node_modules/node-datachannel/src/cpp/peer-connection-wrapper.cpp +0 -1298
  478. package/node_modules/node-datachannel/src/cpp/peer-connection-wrapper.h +0 -89
  479. package/node_modules/node-datachannel/src/cpp/rtc-wrapper.cpp +0 -205
  480. package/node_modules/node-datachannel/src/cpp/rtc-wrapper.h +0 -24
  481. package/node_modules/node-datachannel/src/cpp/thread-safe-callback.cpp +0 -57
  482. package/node_modules/node-datachannel/src/cpp/thread-safe-callback.h +0 -47
  483. package/node_modules/node-datachannel/src/cpp/web-socket-server-wrapper.cpp +0 -275
  484. package/node_modules/node-datachannel/src/cpp/web-socket-server-wrapper.h +0 -41
  485. package/node_modules/node-datachannel/src/cpp/web-socket-wrapper.cpp +0 -796
  486. package/node_modules/node-datachannel/src/cpp/web-socket-wrapper.h +0 -63
  487. package/node_modules/node-datachannel/src/index.ts +0 -9
  488. package/node_modules/node-datachannel/src/lib/datachannel-stream.ts +0 -100
  489. package/node_modules/node-datachannel/src/lib/index.ts +0 -290
  490. package/node_modules/node-datachannel/src/lib/node-datachannel.ts +0 -84
  491. package/node_modules/node-datachannel/src/lib/types.ts +0 -169
  492. package/node_modules/node-datachannel/src/lib/websocket-server.ts +0 -37
  493. package/node_modules/node-datachannel/src/lib/websocket.ts +0 -26
  494. package/node_modules/node-datachannel/src/polyfill/Events.ts +0 -82
  495. package/node_modules/node-datachannel/src/polyfill/Exception.ts +0 -37
  496. package/node_modules/node-datachannel/src/polyfill/README.md +0 -41
  497. package/node_modules/node-datachannel/src/polyfill/RTCCertificate.ts +0 -21
  498. package/node_modules/node-datachannel/src/polyfill/RTCDataChannel.ts +0 -225
  499. package/node_modules/node-datachannel/src/polyfill/RTCDtlsTransport.ts +0 -46
  500. package/node_modules/node-datachannel/src/polyfill/RTCError.ts +0 -78
  501. package/node_modules/node-datachannel/src/polyfill/RTCIceCandidate.ts +0 -128
  502. package/node_modules/node-datachannel/src/polyfill/RTCIceTransport.ts +0 -90
  503. package/node_modules/node-datachannel/src/polyfill/RTCPeerConnection.ts +0 -527
  504. package/node_modules/node-datachannel/src/polyfill/RTCSctpTransport.ts +0 -51
  505. package/node_modules/node-datachannel/src/polyfill/RTCSessionDescription.ts +0 -41
  506. package/node_modules/node-datachannel/src/polyfill/index.ts +0 -38
  507. package/node_modules/node-datachannel/tsconfig.json +0 -21
  508. package/node_modules/p-limit/index.d.ts +0 -38
  509. package/node_modules/p-limit/index.js +0 -57
  510. package/node_modules/p-limit/license +0 -9
  511. package/node_modules/p-limit/package.json +0 -52
  512. package/node_modules/p-limit/readme.md +0 -101
  513. package/node_modules/p-locate/index.d.ts +0 -64
  514. package/node_modules/p-locate/index.js +0 -52
  515. package/node_modules/p-locate/license +0 -9
  516. package/node_modules/p-locate/package.json +0 -53
  517. package/node_modules/p-locate/readme.md +0 -90
  518. package/node_modules/p-try/index.d.ts +0 -39
  519. package/node_modules/p-try/index.js +0 -9
  520. package/node_modules/p-try/license +0 -9
  521. package/node_modules/p-try/package.json +0 -42
  522. package/node_modules/p-try/readme.md +0 -58
  523. package/node_modules/path-exists/index.d.ts +0 -28
  524. package/node_modules/path-exists/index.js +0 -23
  525. package/node_modules/path-exists/license +0 -9
  526. package/node_modules/path-exists/package.json +0 -39
  527. package/node_modules/path-exists/readme.md +0 -52
  528. package/node_modules/pngjs/.eslintignore +0 -2
  529. package/node_modules/pngjs/.eslintrc.json +0 -19
  530. package/node_modules/pngjs/.prettierignore +0 -3
  531. package/node_modules/pngjs/LICENSE +0 -20
  532. package/node_modules/pngjs/README.md +0 -429
  533. package/node_modules/pngjs/browser.js +0 -16096
  534. package/node_modules/pngjs/coverage/lcov-report/base.css +0 -354
  535. package/node_modules/pngjs/coverage/lcov-report/bitmapper.js.html +0 -881
  536. package/node_modules/pngjs/coverage/lcov-report/bitpacker.js.html +0 -554
  537. package/node_modules/pngjs/coverage/lcov-report/block-navigation.js +0 -77
  538. package/node_modules/pngjs/coverage/lcov-report/chunkstream.js.html +0 -647
  539. package/node_modules/pngjs/coverage/lcov-report/constants.js.html +0 -176
  540. package/node_modules/pngjs/coverage/lcov-report/crc.js.html +0 -200
  541. package/node_modules/pngjs/coverage/lcov-report/favicon.png +0 -0
  542. package/node_modules/pngjs/coverage/lcov-report/filter-pack.js.html +0 -593
  543. package/node_modules/pngjs/coverage/lcov-report/filter-parse-async.js.html +0 -152
  544. package/node_modules/pngjs/coverage/lcov-report/filter-parse-sync.js.html +0 -143
  545. package/node_modules/pngjs/coverage/lcov-report/filter-parse.js.html +0 -611
  546. package/node_modules/pngjs/coverage/lcov-report/format-normaliser.js.html +0 -359
  547. package/node_modules/pngjs/coverage/lcov-report/index.html +0 -426
  548. package/node_modules/pngjs/coverage/lcov-report/interlace.js.html +0 -365
  549. package/node_modules/pngjs/coverage/lcov-report/packer-async.js.html +0 -230
  550. package/node_modules/pngjs/coverage/lcov-report/packer-sync.js.html +0 -248
  551. package/node_modules/pngjs/coverage/lcov-report/packer.js.html +0 -467
  552. package/node_modules/pngjs/coverage/lcov-report/paeth-predictor.js.html +0 -128
  553. package/node_modules/pngjs/coverage/lcov-report/parser-async.js.html +0 -575
  554. package/node_modules/pngjs/coverage/lcov-report/parser-sync.js.html +0 -404
  555. package/node_modules/pngjs/coverage/lcov-report/parser.js.html +0 -950
  556. package/node_modules/pngjs/coverage/lcov-report/png-sync.js.html +0 -116
  557. package/node_modules/pngjs/coverage/lcov-report/png.js.html +0 -662
  558. package/node_modules/pngjs/coverage/lcov-report/prettify.css +0 -101
  559. package/node_modules/pngjs/coverage/lcov-report/prettify.js +0 -1007
  560. package/node_modules/pngjs/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  561. package/node_modules/pngjs/coverage/lcov-report/sorter.js +0 -169
  562. package/node_modules/pngjs/coverage/lcov-report/sync-inflate.js.html +0 -584
  563. package/node_modules/pngjs/coverage/lcov-report/sync-reader.js.html +0 -215
  564. package/node_modules/pngjs/coverage/lcov.info +0 -2177
  565. package/node_modules/pngjs/lib/bitmapper.js +0 -267
  566. package/node_modules/pngjs/lib/bitpacker.js +0 -158
  567. package/node_modules/pngjs/lib/chunkstream.js +0 -189
  568. package/node_modules/pngjs/lib/constants.js +0 -32
  569. package/node_modules/pngjs/lib/crc.js +0 -40
  570. package/node_modules/pngjs/lib/filter-pack.js +0 -171
  571. package/node_modules/pngjs/lib/filter-parse-async.js +0 -24
  572. package/node_modules/pngjs/lib/filter-parse-sync.js +0 -21
  573. package/node_modules/pngjs/lib/filter-parse.js +0 -177
  574. package/node_modules/pngjs/lib/format-normaliser.js +0 -93
  575. package/node_modules/pngjs/lib/interlace.js +0 -95
  576. package/node_modules/pngjs/lib/packer-async.js +0 -50
  577. package/node_modules/pngjs/lib/packer-sync.js +0 -56
  578. package/node_modules/pngjs/lib/packer.js +0 -129
  579. package/node_modules/pngjs/lib/paeth-predictor.js +0 -16
  580. package/node_modules/pngjs/lib/parser-async.js +0 -165
  581. package/node_modules/pngjs/lib/parser-sync.js +0 -108
  582. package/node_modules/pngjs/lib/parser.js +0 -290
  583. package/node_modules/pngjs/lib/png-sync.js +0 -12
  584. package/node_modules/pngjs/lib/png.js +0 -194
  585. package/node_modules/pngjs/lib/sync-inflate.js +0 -168
  586. package/node_modules/pngjs/lib/sync-reader.js +0 -45
  587. package/node_modules/pngjs/package.json +0 -73
  588. package/node_modules/qrcode/README.md +0 -770
  589. package/node_modules/qrcode/bin/qrcode +0 -159
  590. package/node_modules/qrcode/helper/to-sjis-browser.js +0 -2
  591. package/node_modules/qrcode/helper/to-sjis.js +0 -105
  592. package/node_modules/qrcode/lib/browser.js +0 -76
  593. package/node_modules/qrcode/lib/can-promise.js +0 -7
  594. package/node_modules/qrcode/lib/core/alignment-pattern.js +0 -83
  595. package/node_modules/qrcode/lib/core/alphanumeric-data.js +0 -59
  596. package/node_modules/qrcode/lib/core/bit-buffer.js +0 -37
  597. package/node_modules/qrcode/lib/core/bit-matrix.js +0 -65
  598. package/node_modules/qrcode/lib/core/byte-data.js +0 -30
  599. package/node_modules/qrcode/lib/core/error-correction-code.js +0 -135
  600. package/node_modules/qrcode/lib/core/error-correction-level.js +0 -50
  601. package/node_modules/qrcode/lib/core/finder-pattern.js +0 -22
  602. package/node_modules/qrcode/lib/core/format-info.js +0 -29
  603. package/node_modules/qrcode/lib/core/galois-field.js +0 -69
  604. package/node_modules/qrcode/lib/core/kanji-data.js +0 -54
  605. package/node_modules/qrcode/lib/core/mask-pattern.js +0 -234
  606. package/node_modules/qrcode/lib/core/mode.js +0 -167
  607. package/node_modules/qrcode/lib/core/numeric-data.js +0 -43
  608. package/node_modules/qrcode/lib/core/polynomial.js +0 -62
  609. package/node_modules/qrcode/lib/core/qrcode.js +0 -495
  610. package/node_modules/qrcode/lib/core/reed-solomon-encoder.js +0 -56
  611. package/node_modules/qrcode/lib/core/regex.js +0 -31
  612. package/node_modules/qrcode/lib/core/segments.js +0 -330
  613. package/node_modules/qrcode/lib/core/utils.js +0 -63
  614. package/node_modules/qrcode/lib/core/version-check.js +0 -9
  615. package/node_modules/qrcode/lib/core/version.js +0 -163
  616. package/node_modules/qrcode/lib/index.js +0 -12
  617. package/node_modules/qrcode/lib/renderer/canvas.js +0 -63
  618. package/node_modules/qrcode/lib/renderer/png.js +0 -78
  619. package/node_modules/qrcode/lib/renderer/svg-tag.js +0 -81
  620. package/node_modules/qrcode/lib/renderer/svg.js +0 -19
  621. package/node_modules/qrcode/lib/renderer/terminal/terminal-small.js +0 -85
  622. package/node_modules/qrcode/lib/renderer/terminal/terminal.js +0 -49
  623. package/node_modules/qrcode/lib/renderer/terminal.js +0 -9
  624. package/node_modules/qrcode/lib/renderer/utf8.js +0 -71
  625. package/node_modules/qrcode/lib/renderer/utils.js +0 -99
  626. package/node_modules/qrcode/lib/server.js +0 -138
  627. package/node_modules/qrcode/license +0 -10
  628. package/node_modules/qrcode/package.json +0 -77
  629. package/node_modules/require-directory/.jshintrc +0 -67
  630. package/node_modules/require-directory/.npmignore +0 -1
  631. package/node_modules/require-directory/.travis.yml +0 -3
  632. package/node_modules/require-directory/LICENSE +0 -22
  633. package/node_modules/require-directory/README.markdown +0 -184
  634. package/node_modules/require-directory/index.js +0 -86
  635. package/node_modules/require-directory/package.json +0 -40
  636. package/node_modules/require-main-filename/CHANGELOG.md +0 -26
  637. package/node_modules/require-main-filename/LICENSE.txt +0 -14
  638. package/node_modules/require-main-filename/README.md +0 -26
  639. package/node_modules/require-main-filename/index.js +0 -18
  640. package/node_modules/require-main-filename/package.json +0 -35
  641. package/node_modules/semver/LICENSE +0 -15
  642. package/node_modules/semver/README.md +0 -680
  643. package/node_modules/semver/bin/semver.js +0 -195
  644. package/node_modules/semver/classes/comparator.js +0 -143
  645. package/node_modules/semver/classes/index.js +0 -7
  646. package/node_modules/semver/classes/range.js +0 -577
  647. package/node_modules/semver/classes/semver.js +0 -350
  648. package/node_modules/semver/functions/clean.js +0 -8
  649. package/node_modules/semver/functions/cmp.js +0 -54
  650. package/node_modules/semver/functions/coerce.js +0 -62
  651. package/node_modules/semver/functions/compare-build.js +0 -9
  652. package/node_modules/semver/functions/compare-loose.js +0 -5
  653. package/node_modules/semver/functions/compare.js +0 -7
  654. package/node_modules/semver/functions/diff.js +0 -60
  655. package/node_modules/semver/functions/eq.js +0 -5
  656. package/node_modules/semver/functions/gt.js +0 -5
  657. package/node_modules/semver/functions/gte.js +0 -5
  658. package/node_modules/semver/functions/inc.js +0 -21
  659. package/node_modules/semver/functions/lt.js +0 -5
  660. package/node_modules/semver/functions/lte.js +0 -5
  661. package/node_modules/semver/functions/major.js +0 -5
  662. package/node_modules/semver/functions/minor.js +0 -5
  663. package/node_modules/semver/functions/neq.js +0 -5
  664. package/node_modules/semver/functions/parse.js +0 -18
  665. package/node_modules/semver/functions/patch.js +0 -5
  666. package/node_modules/semver/functions/prerelease.js +0 -8
  667. package/node_modules/semver/functions/rcompare.js +0 -5
  668. package/node_modules/semver/functions/rsort.js +0 -5
  669. package/node_modules/semver/functions/satisfies.js +0 -12
  670. package/node_modules/semver/functions/sort.js +0 -5
  671. package/node_modules/semver/functions/truncate.js +0 -48
  672. package/node_modules/semver/functions/valid.js +0 -8
  673. package/node_modules/semver/index.js +0 -93
  674. package/node_modules/semver/internal/constants.js +0 -37
  675. package/node_modules/semver/internal/debug.js +0 -11
  676. package/node_modules/semver/internal/identifiers.js +0 -29
  677. package/node_modules/semver/internal/lrucache.js +0 -42
  678. package/node_modules/semver/internal/parse-options.js +0 -17
  679. package/node_modules/semver/internal/re.js +0 -223
  680. package/node_modules/semver/package.json +0 -78
  681. package/node_modules/semver/preload.js +0 -4
  682. package/node_modules/semver/range.bnf +0 -17
  683. package/node_modules/semver/ranges/gtr.js +0 -6
  684. package/node_modules/semver/ranges/intersects.js +0 -9
  685. package/node_modules/semver/ranges/ltr.js +0 -6
  686. package/node_modules/semver/ranges/max-satisfying.js +0 -27
  687. package/node_modules/semver/ranges/min-satisfying.js +0 -26
  688. package/node_modules/semver/ranges/min-version.js +0 -63
  689. package/node_modules/semver/ranges/outside.js +0 -82
  690. package/node_modules/semver/ranges/simplify.js +0 -49
  691. package/node_modules/semver/ranges/subset.js +0 -249
  692. package/node_modules/semver/ranges/to-comparators.js +0 -10
  693. package/node_modules/semver/ranges/valid.js +0 -13
  694. package/node_modules/set-blocking/CHANGELOG.md +0 -26
  695. package/node_modules/set-blocking/LICENSE.txt +0 -14
  696. package/node_modules/set-blocking/README.md +0 -31
  697. package/node_modules/set-blocking/index.js +0 -7
  698. package/node_modules/set-blocking/package.json +0 -42
  699. package/node_modules/sharp/LICENSE +0 -191
  700. package/node_modules/sharp/README.md +0 -112
  701. package/node_modules/sharp/dist/channel.cjs +0 -177
  702. package/node_modules/sharp/dist/channel.mjs +0 -177
  703. package/node_modules/sharp/dist/colour.cjs +0 -199
  704. package/node_modules/sharp/dist/colour.mjs +0 -199
  705. package/node_modules/sharp/dist/composite.cjs +0 -213
  706. package/node_modules/sharp/dist/composite.mjs +0 -213
  707. package/node_modules/sharp/dist/constructor.cjs +0 -511
  708. package/node_modules/sharp/dist/constructor.mjs +0 -511
  709. package/node_modules/sharp/dist/index.cjs +0 -25
  710. package/node_modules/sharp/dist/index.d.cts +0 -1999
  711. package/node_modules/sharp/dist/index.d.mts +0 -2046
  712. package/node_modules/sharp/dist/index.mjs +0 -25
  713. package/node_modules/sharp/dist/input.cjs +0 -819
  714. package/node_modules/sharp/dist/input.mjs +0 -819
  715. package/node_modules/sharp/dist/is.cjs +0 -143
  716. package/node_modules/sharp/dist/is.mjs +0 -143
  717. package/node_modules/sharp/dist/libvips.cjs +0 -212
  718. package/node_modules/sharp/dist/libvips.mjs +0 -212
  719. package/node_modules/sharp/dist/operation.cjs +0 -1002
  720. package/node_modules/sharp/dist/operation.mjs +0 -1002
  721. package/node_modules/sharp/dist/output.cjs +0 -1785
  722. package/node_modules/sharp/dist/output.mjs +0 -1785
  723. package/node_modules/sharp/dist/resize.cjs +0 -617
  724. package/node_modules/sharp/dist/resize.mjs +0 -617
  725. package/node_modules/sharp/dist/sharp.cjs +0 -174
  726. package/node_modules/sharp/dist/sharp.mjs +0 -174
  727. package/node_modules/sharp/dist/utility.cjs +0 -301
  728. package/node_modules/sharp/dist/utility.mjs +0 -301
  729. package/node_modules/sharp/install/build.js +0 -38
  730. package/node_modules/sharp/lib/index.d.ts +0 -1999
  731. package/node_modules/sharp/package.json +0 -227
  732. package/node_modules/sharp/src/binding.gyp +0 -303
  733. package/node_modules/sharp/src/common.cc +0 -1186
  734. package/node_modules/sharp/src/common.h +0 -421
  735. package/node_modules/sharp/src/metadata.cc +0 -404
  736. package/node_modules/sharp/src/metadata.h +0 -95
  737. package/node_modules/sharp/src/operations.cc +0 -516
  738. package/node_modules/sharp/src/operations.h +0 -137
  739. package/node_modules/sharp/src/pipeline.cc +0 -1948
  740. package/node_modules/sharp/src/pipeline.h +0 -422
  741. package/node_modules/sharp/src/sharp.cc +0 -43
  742. package/node_modules/sharp/src/stats.cc +0 -186
  743. package/node_modules/sharp/src/stats.h +0 -62
  744. package/node_modules/sharp/src/utilities.cc +0 -289
  745. package/node_modules/sharp/src/utilities.h +0 -22
  746. package/node_modules/string-width/index.d.ts +0 -29
  747. package/node_modules/string-width/index.js +0 -47
  748. package/node_modules/string-width/license +0 -9
  749. package/node_modules/string-width/package.json +0 -56
  750. package/node_modules/string-width/readme.md +0 -50
  751. package/node_modules/strip-ansi/index.d.ts +0 -17
  752. package/node_modules/strip-ansi/index.js +0 -4
  753. package/node_modules/strip-ansi/license +0 -9
  754. package/node_modules/strip-ansi/package.json +0 -54
  755. package/node_modules/strip-ansi/readme.md +0 -46
  756. package/node_modules/which-module/LICENSE +0 -13
  757. package/node_modules/which-module/README.md +0 -58
  758. package/node_modules/which-module/index.js +0 -9
  759. package/node_modules/which-module/package.json +0 -41
  760. package/node_modules/wrap-ansi/index.js +0 -186
  761. package/node_modules/wrap-ansi/license +0 -9
  762. package/node_modules/wrap-ansi/package.json +0 -61
  763. package/node_modules/wrap-ansi/readme.md +0 -97
  764. package/node_modules/ws/LICENSE +0 -20
  765. package/node_modules/ws/README.md +0 -548
  766. package/node_modules/ws/browser.js +0 -8
  767. package/node_modules/ws/index.js +0 -22
  768. package/node_modules/ws/lib/buffer-util.js +0 -131
  769. package/node_modules/ws/lib/constants.js +0 -19
  770. package/node_modules/ws/lib/event-target.js +0 -292
  771. package/node_modules/ws/lib/extension.js +0 -203
  772. package/node_modules/ws/lib/limiter.js +0 -55
  773. package/node_modules/ws/lib/permessage-deflate.js +0 -530
  774. package/node_modules/ws/lib/receiver.js +0 -743
  775. package/node_modules/ws/lib/sender.js +0 -607
  776. package/node_modules/ws/lib/stream.js +0 -161
  777. package/node_modules/ws/lib/subprotocol.js +0 -62
  778. package/node_modules/ws/lib/validation.js +0 -152
  779. package/node_modules/ws/lib/websocket-server.js +0 -562
  780. package/node_modules/ws/lib/websocket.js +0 -1407
  781. package/node_modules/ws/package.json +0 -74
  782. package/node_modules/ws/wrapper.mjs +0 -21
  783. package/node_modules/y18n/CHANGELOG.md +0 -35
  784. package/node_modules/y18n/LICENSE +0 -13
  785. package/node_modules/y18n/README.md +0 -109
  786. package/node_modules/y18n/index.js +0 -188
  787. package/node_modules/y18n/package.json +0 -39
  788. package/node_modules/yargs/CHANGELOG.md +0 -420
  789. package/node_modules/yargs/LICENSE +0 -21
  790. package/node_modules/yargs/README.md +0 -140
  791. package/node_modules/yargs/build/lib/apply-extends.d.ts +0 -2
  792. package/node_modules/yargs/build/lib/apply-extends.js +0 -65
  793. package/node_modules/yargs/build/lib/argsert.d.ts +0 -2
  794. package/node_modules/yargs/build/lib/argsert.js +0 -65
  795. package/node_modules/yargs/build/lib/command.d.ts +0 -64
  796. package/node_modules/yargs/build/lib/command.js +0 -416
  797. package/node_modules/yargs/build/lib/common-types.d.ts +0 -36
  798. package/node_modules/yargs/build/lib/common-types.js +0 -25
  799. package/node_modules/yargs/build/lib/completion-templates.d.ts +0 -2
  800. package/node_modules/yargs/build/lib/completion-templates.js +0 -50
  801. package/node_modules/yargs/build/lib/completion.d.ts +0 -21
  802. package/node_modules/yargs/build/lib/completion.js +0 -135
  803. package/node_modules/yargs/build/lib/is-promise.d.ts +0 -1
  804. package/node_modules/yargs/build/lib/is-promise.js +0 -9
  805. package/node_modules/yargs/build/lib/levenshtein.d.ts +0 -1
  806. package/node_modules/yargs/build/lib/levenshtein.js +0 -58
  807. package/node_modules/yargs/build/lib/middleware.d.ts +0 -10
  808. package/node_modules/yargs/build/lib/middleware.js +0 -57
  809. package/node_modules/yargs/build/lib/obj-filter.d.ts +0 -1
  810. package/node_modules/yargs/build/lib/obj-filter.js +0 -14
  811. package/node_modules/yargs/build/lib/parse-command.d.ts +0 -11
  812. package/node_modules/yargs/build/lib/parse-command.js +0 -36
  813. package/node_modules/yargs/build/lib/process-argv.d.ts +0 -2
  814. package/node_modules/yargs/build/lib/process-argv.js +0 -31
  815. package/node_modules/yargs/build/lib/usage.d.ts +0 -49
  816. package/node_modules/yargs/build/lib/usage.js +0 -540
  817. package/node_modules/yargs/build/lib/validation.d.ts +0 -34
  818. package/node_modules/yargs/build/lib/validation.js +0 -330
  819. package/node_modules/yargs/build/lib/yargs.d.ts +0 -274
  820. package/node_modules/yargs/build/lib/yargs.js +0 -1190
  821. package/node_modules/yargs/build/lib/yerror.d.ts +0 -4
  822. package/node_modules/yargs/build/lib/yerror.js +0 -11
  823. package/node_modules/yargs/index.js +0 -40
  824. package/node_modules/yargs/locales/be.json +0 -46
  825. package/node_modules/yargs/locales/de.json +0 -46
  826. package/node_modules/yargs/locales/en.json +0 -51
  827. package/node_modules/yargs/locales/es.json +0 -46
  828. package/node_modules/yargs/locales/fi.json +0 -49
  829. package/node_modules/yargs/locales/fr.json +0 -53
  830. package/node_modules/yargs/locales/hi.json +0 -49
  831. package/node_modules/yargs/locales/hu.json +0 -46
  832. package/node_modules/yargs/locales/id.json +0 -50
  833. package/node_modules/yargs/locales/it.json +0 -46
  834. package/node_modules/yargs/locales/ja.json +0 -51
  835. package/node_modules/yargs/locales/ko.json +0 -49
  836. package/node_modules/yargs/locales/nb.json +0 -44
  837. package/node_modules/yargs/locales/nl.json +0 -49
  838. package/node_modules/yargs/locales/nn.json +0 -44
  839. package/node_modules/yargs/locales/pirate.json +0 -13
  840. package/node_modules/yargs/locales/pl.json +0 -49
  841. package/node_modules/yargs/locales/pt.json +0 -45
  842. package/node_modules/yargs/locales/pt_BR.json +0 -48
  843. package/node_modules/yargs/locales/ru.json +0 -46
  844. package/node_modules/yargs/locales/th.json +0 -46
  845. package/node_modules/yargs/locales/tr.json +0 -48
  846. package/node_modules/yargs/locales/zh_CN.json +0 -48
  847. package/node_modules/yargs/locales/zh_TW.json +0 -47
  848. package/node_modules/yargs/package.json +0 -92
  849. package/node_modules/yargs/yargs.js +0 -14
  850. package/node_modules/yargs-parser/CHANGELOG.md +0 -601
  851. package/node_modules/yargs-parser/LICENSE.txt +0 -14
  852. package/node_modules/yargs-parser/README.md +0 -449
  853. package/node_modules/yargs-parser/index.js +0 -1032
  854. package/node_modules/yargs-parser/lib/tokenize-arg-string.js +0 -40
  855. package/node_modules/yargs-parser/package.json +0 -46
@@ -1,186 +1,291 @@
1
- import crypto from 'node:crypto';
2
- import fs from 'node:fs';
3
- import http from 'node:http';
4
- import net from 'node:net';
5
- import path from 'node:path';
6
- import os from 'node:os';
7
- import { fileURLToPath } from 'node:url';
1
+ import crypto from 'node:crypto';
2
+ import { RustDeskVideoProbe } from './platform/windows/rustdesk-video-probe.mjs';
3
+ import fs from 'node:fs';
4
+ import http from 'node:http';
5
+ import { createPowerManager } from './service-power.mjs';
6
+ import path from 'node:path';
7
+ import os from 'node:os';
8
+ import { fileURLToPath } from 'node:url';
8
9
  import vm from 'node:vm';
9
10
  import sharp from 'sharp';
10
- import { attachLanSocket } from './lan-socket-server.mjs';
11
- import { validateInlineImage, persistLegacyImage } from './message-images.mjs';
11
+ import { attachLanSocket } from './lan-socket-server.mjs';
12
+ import { connectionReporting } from './self-relay/connection-reporting.mjs';
13
+ import fileMentions from '../shared/file-mentions.cjs';
14
+ import fileCitations from '../shared/file-citations.cjs';
15
+ import { validateInlineImage } from './message-images.mjs';
12
16
  import { ThreadWriterConflictError } from './shared-app-server.mjs';
13
- import { ManagedAppServer, managedAppServerUrl } from './managed-app-server.mjs';
14
- import { streamSharedThread } from './shared-thread-stream.mjs';
15
- import { AppServerClient } from './app-server-client.mjs';
16
- import { AppServerTaskStore, AppServerTaskTransport } from './app-server-tasks.mjs';
17
- import { supplementThreadCatalog } from './thread-catalog.mjs';
18
- import appServerEvents from '../shared/app-server-events.cjs';
19
- import { runtimePaths } from './platform/windows/runtime-paths.mjs';
20
- import { openControl } from './service-control.mjs';
21
- import { writeRecord, readRecord } from './service-lifecycle.mjs';
22
- import { ExecutionHealth } from './execution-health.mjs';
23
- import { activeDebugPort, serveDebugResource } from './debug-ui.mjs';
24
- import { authorizeLanDevice } from './self-relay/lan-authorizations.mjs';
25
- import { generateAndSetThreadTitle } from './thread-title-generation.mjs';
26
- import { DesktopMonitor } from './platform/windows/desktop-monitor.mjs';
27
- import { desktopHost } from './platform/windows/desktop-host.mjs';
28
- import { SharedCatalog } from './shared-catalog.mjs';
29
- import { requestRecovery, recoveryStatus } from './shared-recovery.mjs';
30
- import { requestServiceRestart } from './service-restart.mjs';
31
-
32
- const MAX_FRAME_BYTES = 32 * 1024 * 1024;
33
- const MAX_ATTACHMENT_BYTES = 16 * 1024 * 1024;
34
- const MAX_CACHED_ATTACHMENTS = 32;
35
- const MAX_CACHED_FILES = 64;
36
- const MAX_CONTEXT_TAIL_BYTES = 4 * 1024 * 1024;
17
+ import { ManagedAppServer } from './managed-app-server.mjs';
18
+ import { RuntimeDiscovery } from './runtime-discovery.mjs';
19
+ import { streamSharedThread, threadListNotificationMethods } from './shared-thread-stream.mjs';
20
+ import appServerEvents from '../shared/app-server-events.cjs';
21
+ import questionAttention from '../shared/question-attention.cjs';
22
+ import messageDelivery from '../shared/message-delivery.cjs';
23
+ import fileChanges from '../shared/file-changes.cjs';
24
+ import { runtimePaths } from './platform/runtime-paths.mjs';
25
+ import { openControl } from './service-control.mjs';
26
+ import { writeRecord, readRecord } from './service-lifecycle.mjs';
27
+ import { ExecutionHealth } from './execution-health.mjs';
28
+ import { authorizeLanDevice, onLanAuthorizationsChanged, recordLanConnection } from './self-relay/lan-authorizations.mjs';
29
+ import { createRelayManager } from './self-relay/lifecycle.mjs';
30
+ import { generateAndSetThreadTitle } from './thread-title-generation.mjs';
31
+ import { desktopHost } from './platform/desktop-host.mjs';
32
+ import { SharedCatalog } from './shared-catalog.mjs';
33
+ import { DotsAppServer } from './dots-task-catalog.mjs';
34
+ import { DotsChatServer } from './dots-chat-server.mjs';
35
+ import { HostedHistoryCache } from './hosted-history-cache.mjs';
36
+ import { readDotsSummaryTasks } from './dots-summary-tasks.mjs';
37
+ import { readDotsSchedules } from './dots-schedules.mjs';
38
+ import { DotsMessageImages } from './dots-message-images.mjs';
39
+ import { desktopDotsProfiles } from './platform/windows/dots-profile.mjs';
40
+ import { readPlanPricing } from './plan-pricing.mjs';
41
+ import { requestRecovery, recoveryStatus } from './shared-recovery.mjs';
42
+ import { startDesktop } from './desktop-launcher.mjs';
43
+ import { requestServiceRestart } from './service-restart.mjs';
44
+ import { checkServiceUpdate, requestServiceUpdate, serviceUpdateStatus } from './service-update.mjs';
45
+ import { TurnModelHistory } from './turn-model-history.mjs';
46
+ import { browseCode } from './code-browser.mjs';
47
+ import { fileRange, fileRevision, FileRangeError } from './file-transfer-range.mjs';
48
+ import { browseProjectFolders, createProjectFolder } from './project-management.mjs';
49
+ import { projectLocations } from './platform/project-locations.mjs';
50
+ import { createWorkstationProject } from './project-create.mjs';
51
+ import { removeWorkstationProject } from './project-remove.mjs';
52
+ import { archivedThreadsPage } from './archived-threads.mjs';
53
+ import { updateWorkstationProject } from './project-update.mjs';
54
+ import { WorkstationDownloads } from './workstation-downloads.mjs';
55
+ import { stageWorkstationMessageFile } from './message-upload-files.mjs';
56
+ import { consumeUsageReset } from './usage-reset.mjs';
57
+ import { DesktopHelper } from './platform/windows/desktop-helper.mjs';
58
+
59
+ const turnModelHistory = new TurnModelHistory();
60
+ const messageReceipts = new messageDelivery.Store();
61
+ const desktopHelper = process.platform === 'win32' ? new DesktopHelper() : null;
62
+
63
+ const MAX_ATTACHMENT_BYTES = 20 * 1024 * 1024;
64
+ const MAX_FILE_DOWNLOAD_BYTES = 20 * 1024 * 1024;
65
+ const MAX_CACHED_ATTACHMENTS = 32;
66
+ const MAX_CACHED_FILES = 64;
67
+ const MAX_CONTEXT_TAIL_BYTES = 4 * 1024 * 1024;
37
68
  const MAX_LIVE_ACTIVITY_TAIL_BYTES = 1024 * 1024;
38
69
  const SHARED_HISTORY_PAGE_BYTES = 500 * 1024;
39
70
  const SHARED_HISTORY_PAGE_TURNS = 8;
40
- const MAX_SETTINGS_SCAN_BYTES = 64 * 1024 * 1024;
41
- const ALLOWED_MODELS = new Set([
42
- 'gpt-6-astra',
43
- 'gpt-5.6-sol',
44
- 'gpt-5.6-terra',
45
- 'gpt-5.6-luna',
46
- 'gpt-5.5',
47
- 'gpt-5.3-codex-spark',
48
- ]);
49
- const ALLOWED_THINKING = new Set(['low', 'medium', 'high', 'xhigh', 'max', 'ultra']);
50
- const ALLOWED_TOOLS = new Set([
51
- 'create_thread',
52
- 'get_usage_limits',
53
- 'list_projects',
54
- 'list_threads',
55
- 'read_thread',
56
- 'send_message_to_thread',
57
- 'wait_threads',
58
- ]);
59
-
60
- const options = parseOptions(process.argv.slice(2));
61
- const pipePath = process.env.CODEX_APP_TOOLS_PIPE_PATH?.trim();
62
- let sourceThreadId = (process.env.CODEX_THREAD_ID || process.env.CODEX_SESSION_ID)?.trim();
63
-
64
- const webRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', 'web');
65
- const repositoryRoot = path.resolve(webRoot, '..', '..');
66
- const projectRoot = os.homedir();
67
- const { codexDataRoot, stateRoot, sessionsRoot, uploadsRoot } = runtimePaths();
68
- const markdownItScript = fs.readFileSync(path.join(webRoot, 'vendor', 'markdown-it.umd.min.js'));
69
- const markdownSandbox = { atob };
70
- vm.runInNewContext(markdownItScript.toString(), markdownSandbox);
71
- const imageMarkdown = markdownSandbox.markdownit({ html: false });
72
- let nextRequestId = 0;
71
+ const MAX_SETTINGS_SCAN_BYTES = 64 * 1024 * 1024;
72
+ const ALLOWED_THINKING = new Set(['low', 'medium', 'high', 'xhigh', 'max', 'ultra']);
73
+
74
+ class RequestError extends Error {
75
+ constructor(statusCode, message, details) {
76
+ super(message);
77
+ this.statusCode = statusCode;
78
+ this.details = details;
79
+ this.code = details?.code;
80
+ }
81
+ }
82
+
83
+ function readRuntimeVersion() {
84
+ let directory = path.dirname(fileURLToPath(import.meta.url));
85
+ while (true) {
86
+ const manifest = readRecord(path.join(directory, 'package.json'));
87
+ if (manifest?.name === '@walkhi/code-relax') return manifest.version || '未知';
88
+ const parent = path.dirname(directory);
89
+ if (parent === directory) return '未知';
90
+ directory = parent;
91
+ }
92
+ }
93
+
94
+ const options = parseOptions(process.argv.slice(2));
95
+ const runtimeVersion = readRuntimeVersion();
96
+ const serviceInstanceId = crypto.randomUUID();
97
+
98
+ const webRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', 'web');
99
+ const projectRoot = os.homedir();
100
+ const { codexDataRoot, stateRoot, sessionsRoot, uploadsRoot } = runtimePaths();
101
+ const desktopVideoProbe = process.platform === 'win32' ? new RustDeskVideoProbe({ stateRoot }) : null;
102
+ process.once('exit', () => desktopVideoProbe?.stop());
103
+ const workstationDownloads = new WorkstationDownloads(stateRoot);
104
+ const markdownItScript = fs.readFileSync(path.join(webRoot, 'vendor', 'markdown-it.umd.min.js'));
105
+ const markdownSandbox = { atob };
106
+ vm.runInNewContext(markdownItScript.toString(), markdownSandbox);
107
+ const imageMarkdown = markdownSandbox.markdownit({ html: false });
108
+ fileCitations.install(imageMarkdown);
73
109
  const attachmentCache = new Map();
74
110
  const fileCache = new Map();
75
111
  const fileCapabilitySecret = crypto.randomBytes(32);
76
112
  const imageCapabilityKey = crypto.createHash('sha256')
77
113
  .update('code-relax-image-capability\0').update(options.token || fileCapabilitySecret).digest();
78
- const legacyUploadsRoot = path.resolve(webRoot, '..', 'uploads');
79
- const allowedUploadsRoots = [uploadsRoot, legacyUploadsRoot,
80
- ...JSON.parse(process.env.CODEX_REMOTE_LEGACY_UPLOADS_ROOTS || '[]')];
81
- const sessionPathCache = new Map();
82
- const sessionSettingsCache = new Map();
83
- const liveActivityCache = new Map();
84
- const messageQueues = new Map();
114
+ const sessionPathCache = new Map();
115
+ const sessionSettingsCache = new Map();
116
+ const liveActivityCache = new Map();
85
117
  let sharedServer = null;
86
- const desktopMonitor = new DesktopMonitor({
87
- read: (tool, args, pipe) => callTool(tool, args, 5000, pipe),
88
- });
89
118
  const managedAppServer = new ManagedAppServer({ stateRoot, host: desktopHost });
119
+ const hostedHistoryCache = new HostedHistoryCache(stateRoot);
120
+ const dotsServer = new DotsAppServer(managedAppServer.client, desktopDotsProfiles(codexDataRoot, stateRoot), hostedHistoryCache, { uploadsRoot });
121
+ const dotsImages = new DotsMessageImages({ localServer: managedAppServer.client, identity: () => dotsServer.profileIdentity,
122
+ cache: attachmentCache, capabilityKey: imageCapabilityKey, maxEntries: MAX_CACHED_ATTACHMENTS, maxBytes: MAX_ATTACHMENT_BYTES });
123
+ const dotsChat = new DotsChatServer(dotsServer, undefined, { uploadsRoot });
124
+ const runtimeDiscovery = new RuntimeDiscovery({ stateRoot, host: desktopHost, url: () => managedAppServer.snapshot().url });
125
+ const relayManager = createRelayManager(stateRoot);
90
126
  let sharedCatalog = null, executionMode = 'ordinary';
91
127
  function selectSharedExecution(enabled) {
92
128
  const next = enabled ? managedAppServer.client : null;
93
129
  if (sharedServer === next) return;
94
130
  sharedServer = next;
95
- sharedCatalog = sharedServer ? new SharedCatalog(sharedServer) : null;
131
+ sharedCatalog = sharedServer ? new SharedCatalog(sharedServer, dotsServer, dotsChat) : null;
96
132
  }
97
133
  const executionHealth = new ExecutionHealth(async () => {
98
134
  await managedAppServer.ensure();
99
- const [desktop, attachedUrl] = await Promise.all([desktopHost.inspect(), desktopHost.sharedServiceUrl()]);
100
- const attached = Boolean(desktop.running && attachedUrl === managedAppServerUrl);
101
135
  const managed = managedAppServer.snapshot();
102
136
  // The managed app-server owns execution. Desktop is another client of the
103
137
  // same service, so closing Desktop must not disable phone writes.
104
138
  selectSharedExecution(managed.ready);
105
139
  executionMode = sharedServer ? 'shared' : 'ordinary';
106
- const basicReady = Boolean(desktop.running && sourceThreadId && desktopMonitor.pipe);
107
140
  const managedRestarting = managed.state === 'restarting';
108
- const ready = !managedRestarting && (sharedServer ? sharedServer.ready === true : basicReady);
141
+ const ready = !managedRestarting && sharedServer?.ready === true;
109
142
  const state = managedRestarting ? 'restarting'
110
- : ready ? (sharedServer ? 'full' : 'basic') : !desktop.running ? 'offline' : 'unavailable';
111
- return { state, ready, canWrite: ready, mode: executionMode,
112
- desktop: { state: !desktop.running ? 'stopped' : attached ? 'managed' : 'ordinary', attachedToManagedServer: attached } };
143
+ : ready ? 'full' : 'unavailable';
144
+ return { state, ready, canWrite: ready, mode: executionMode };
113
145
  });
114
146
  async function refreshExecution() {
115
- sourceThreadId = readRecord(path.join(stateRoot, 'bridge-startup-context.json'))?.sourceThreadId || sourceThreadId;
116
147
  await executionHealth.refresh();
117
148
  return { mode: executionMode };
118
149
  }
119
- async function requireExecutionWritable(threadId) {
120
- const recovery = readRecord(path.join(stateRoot, 'shared-recovery.json'));
121
- if (['scheduled', 'running'].includes(recovery?.status)) {
122
- throw new RequestError(409, 'Codex 正在重新启动,当前不能提交写操作。', { code: 'desktop_recovering' });
123
- }
150
+ async function requireExecutionWritable(threadId, client = sharedServer) {
124
151
  const execution = await executionHealth.refresh();
125
152
  if (!execution.canWrite) {
126
- const message = execution.mode === 'shared'
127
- ? 'Codex 内核尚未建立可写连接,请稍后重新检测。'
128
- : 'Codex Desktop 尚未建立基础控制连接,请稍后重新检测。';
129
- throw new RequestError(409, message, { code: 'execution_not_writable', execution });
153
+ throw new RequestError(503, 'Codex 内核未就绪。请重启工作站服务;若仍失败,运行 relax doctor。',
154
+ { code: 'full_control_required', execution });
130
155
  }
131
- if (threadId && sharedServer) sharedServer.requireWritable(threadId);
156
+ if (threadId && client) client.requireWritable(threadId);
132
157
  return execution;
133
158
  }
134
- managedAppServer.client.on('subscription-error', error => console.error('完整控制任务取消订阅失败:', error.message));
135
- const queueWorkers = new Map();
136
- const appServer = new AppServerClient({ cwd: projectRoot });
137
- const appServerTasks = new AppServerTaskTransport({
138
- client: appServer,
139
- store: new AppServerTaskStore(path.join(stateRoot, 'app-server-tasks.json')),
140
- });
141
- appServer.on('protocol-error', error => console.error(`app-server 协议错误:${error.message}`));
142
- appServer.on('server-request', request => {
143
- console.log(`app-server 等待客户端处理反向请求:${request.method} (${request.id})`);
144
- });
145
-
146
- async function handleRequest(request, response) {
147
- try {
148
- const url = new URL(request.url || '/', `http://${request.headers.host || 'localhost'}`);
149
-
150
- if (!request.socketRequest) {
151
- if (serveDebugResource(request, response, stateRoot, options.port)) return;
152
- sendJson(response, 404, { error: 'Bridge 调试页面未开启,或请求的资源不存在。' });
153
- return;
154
- }
155
-
156
- // These routes never wait on Desktop tools or a stuck thread. They remain
157
- // available to authenticated paired controllers during an app-server stall.
158
- if (request.method === 'GET' && url.pathname === '/api/shared-recovery/events') {
159
- response.startEvents();
160
- let stopped = false, timer, previous = '';
161
- response.once('close', () => { stopped = true; clearTimeout(timer); });
162
- const publish = async () => {
163
- try {
164
- const state = await recoveryStatus(stateRoot);
165
- const serialized = JSON.stringify(state);
166
- if (!stopped && serialized !== previous) { previous = serialized; response.event('progress', state); }
167
- } catch (error) { if (!stopped) response.event('progress-error', { error: error.message }); }
168
- if (!stopped) timer = setTimeout(publish, 1000);
169
- };
170
- void publish(); return;
171
- }
172
- if (request.method === 'GET' && url.pathname === '/api/shared-recovery') {
173
- sendJson(response, 200, await recoveryStatus(stateRoot)); return;
174
- }
175
- if (request.method === 'POST' && url.pathname === '/api/shared-recovery') {
176
- const body = await readJsonBody(request);
177
- if (body.confirmed !== true || typeof body.operationId !== 'string') throw new RequestError(400, '请确认重新打开当前 Codex 并启用完整控制后再继续。');
178
- sendJson(response, 202, await requestRecovery(stateRoot, body)); return;
179
- }
159
+ managedAppServer.client.on('subscription-error', error => console.error('Codex 内核任务取消订阅失败:', error.message));
160
+ dotsServer.on('subscription-error', error => console.error('Dots 任务取消订阅失败:', error.message));
161
+ dotsChat.on('subscription-error', error => console.error('Dots 对话取消订阅失败:', error.message));
162
+ dotsChat.on('activity-error', error => console.error('点点状态通道不可用:', error.message));
163
+
164
+ dotsServer.on('profile-error', error => console.error(error.message));
165
+ async function handleRequest(request, response) {
166
+ let messageSubmissionStarted = false;
167
+ try {
168
+ const url = new URL(request.url || '/', `http://${request.headers.host || 'localhost'}`);
169
+
170
+ if (!request.socketRequest) {
171
+ sendJson(response, 404, { error: '此端口不提供网页。' });
172
+ return;
173
+ }
174
+
175
+ if (process.platform !== 'win32' && url.pathname.startsWith('/api/desktop/'))
176
+ throw new RequestError(501, 'Mac 版不提供远程桌面控制。', { code: 'desktop_control_unsupported' });
177
+
178
+ if (url.pathname === '/api/desktop/video-probe') {
179
+ if (request.method === 'POST') {
180
+ try { sendJson(response, 200, await desktopVideoProbe.begin()); }
181
+ catch (error) { throw new RequestError(error.statusCode || 502, error.message, { code: error.code || 'desktop_video_failed' }); }
182
+ }
183
+ else if (request.method === 'PUT' || request.method === 'DELETE') {
184
+ const body = await readJsonBody(request);
185
+ if (typeof body.leaseId !== 'string' || !/^[a-f0-9]{32}$/.test(body.leaseId)) throw new RequestError(400, '无效的视频会话');
186
+ sendJson(response, 200, request.method === 'PUT' ? desktopVideoProbe.renew(body.leaseId) : desktopVideoProbe.release(body.leaseId));
187
+ } else throw new RequestError(405, '不支持的操作');
188
+ return;
189
+ }
190
+ if (request.method === 'GET' && url.pathname === '/api/desktop/frame') {
191
+ const started = Date.now();
192
+ try {
193
+ const preview = url.searchParams.get('preview') === '1';
194
+ const frame = await desktopHelper.frame({ preview });
195
+ if (!preview) console.info(JSON.stringify({ time: new Date().toISOString(), event: 'desktop-frame', state: frame.state,
196
+ kind: frame.kind, width: frame.width, height: frame.height, bytes: frame.size, ms: Date.now() - started }));
197
+ if (response.writableEnded) { desktopHelper.release(frame.frameId); return; }
198
+ sendJson(response, 200, frame);
199
+ } catch (error) {
200
+ console.error(JSON.stringify({ time: new Date().toISOString(), event: 'desktop-frame', state: 'failed',
201
+ error: error.message.slice(0, 160), ms: Date.now() - started }));
202
+ throw error;
203
+ }
204
+ return;
205
+ }
206
+ const desktopImage = /^\/api\/desktop\/frames\/([a-f0-9]{32})$/.exec(url.pathname);
207
+ if (request.method === 'GET' && desktopImage) {
208
+ if (!request.fileTransfer) throw new RequestError(400, '请通过文件通道读取桌面画面。');
209
+ const image = desktopHelper.image(desktopImage[1]);
210
+ const { offset, length } = fileRange(url.searchParams, image.length, desktopImage[1]);
211
+ const mimeType = desktopHelper.active.frame.mimeType;
212
+ response.filePart({ name: mimeType === 'image/jpeg' ? 'desktop.jpg' : 'desktop.png', size: image.length, offset, revision: desktopImage[1], mimeType },
213
+ image.subarray(offset, offset + length));
214
+ return;
215
+ }
216
+ if (request.method === 'POST' && url.pathname === '/api/desktop/move') {
217
+ try { sendJson(response, 200, await desktopHelper.move(await readJsonBody(request))); }
218
+ catch (error) {
219
+ console.error(JSON.stringify({ time: new Date().toISOString(), event: 'desktop-move', state: 'failed',
220
+ code: error.code, error: error.message.slice(0, 160) }));
221
+ throw error;
222
+ }
223
+ return;
224
+ }
225
+ if (request.method === 'POST' && url.pathname === '/api/desktop/click') {
226
+ const started = Date.now();
227
+ console.info(JSON.stringify({ time: new Date().toISOString(), event: 'desktop-click', state: 'received' }));
228
+ try {
229
+ const result = await desktopHelper.click(await readJsonBody(request));
230
+ console.info(JSON.stringify({ time: new Date().toISOString(), event: 'desktop-click', state: result.state,
231
+ inputCount: result.inputCount, ms: Date.now() - started }));
232
+ sendJson(response, 200, result);
233
+ } catch (error) {
234
+ console.error(JSON.stringify({ time: new Date().toISOString(), event: 'desktop-click', state: 'failed',
235
+ code: error.code, error: error.message.slice(0, 160), ms: Date.now() - started }));
236
+ throw error;
237
+ }
238
+ return;
239
+ }
240
+ if (request.method === 'DELETE' && url.pathname === '/api/desktop/frame') {
241
+ const body = await readJsonBody(request);
242
+ if (typeof body.sessionId !== 'string' || !/^[a-f0-9]{32}$/.test(body.sessionId)) throw new RequestError(400, '无效的桌面会话');
243
+ desktopHelper.release(undefined, body.sessionId);
244
+ sendJson(response, 200, { released: true });
245
+ return;
246
+ }
247
+
248
+ // These routes never wait on Desktop tools or a stuck thread. They remain
249
+ // available to authenticated paired controllers during an app-server stall.
250
+ if (request.method === 'GET' && url.pathname === '/api/shared-recovery/events') {
251
+ response.startEvents();
252
+ let stopped = false, timer, previous = '';
253
+ response.once('close', () => { stopped = true; clearTimeout(timer); });
254
+ const publish = async () => {
255
+ try {
256
+ const state = await recoveryStatus(stateRoot);
257
+ const serialized = JSON.stringify(state);
258
+ if (!stopped && serialized !== previous) { previous = serialized; response.event('progress', state); }
259
+ } catch (error) { if (!stopped) response.event('progress-error', { error: error.message }); }
260
+ if (!stopped) timer = setTimeout(publish, 1000);
261
+ };
262
+ void publish(); return;
263
+ }
264
+ if (request.method === 'GET' && url.pathname === '/api/shared-recovery') {
265
+ sendJson(response, 200, await recoveryStatus(stateRoot)); return;
266
+ }
267
+ if (request.method === 'POST' && ['/api/desktop/start', '/api/desktop/restart'].includes(url.pathname)) {
268
+ const body = await readJsonBody(request);
269
+ if (body.confirmed !== true) throw new RequestError(400, '请确认启动桌面端。');
270
+ const result = await startDesktop({ stateRoot, host: desktopHost, restart: url.pathname === '/api/desktop/restart' });
271
+ const observed = await runtimeDiscovery.sample();
272
+ sendJson(response, 202, { ...result, desktop: observed.desktop, checkedAt: observed.checkedAt }); return;
273
+ }
274
+ if (request.method === 'POST' && url.pathname === '/api/shared-recovery') {
275
+ const body = await readJsonBody(request);
276
+ if (body.confirmed !== true || typeof body.operationId !== 'string') throw new RequestError(400, '请确认重新打开当前 Codex 并接入 Codex 内核后再继续。');
277
+ sendJson(response, 202, await requestRecovery(stateRoot, body)); return;
278
+ }
279
+ if (url.pathname === '/api/service/update' && request.method === 'GET') {
280
+ sendJson(response, 200, await checkServiceUpdate(stateRoot)); return;
281
+ }
282
+ if (url.pathname === '/api/service/update' && request.method === 'POST') {
283
+ sendJson(response, 202, await requestServiceUpdate(stateRoot, await readJsonBody(request))); return;
284
+ }
180
285
  if (request.method === 'POST' && url.pathname === '/api/service/restart') {
181
286
  const body = await readJsonBody(request);
182
287
  if (body.confirmed !== true) throw new RequestError(400, '请确认重启工作站服务后再继续。');
183
- sendJson(response, 202, await requestServiceRestart(stateRoot)); return;
288
+ sendJson(response, 202, { ...await requestServiceRestart(stateRoot), serviceInstanceId }); return;
184
289
  }
185
290
  if (request.method === 'POST' && url.pathname === '/api/managed-app-server/restart') {
186
291
  const body = await readJsonBody(request);
@@ -196,42 +301,59 @@ async function handleRequest(request, response) {
196
301
  return;
197
302
  }
198
303
 
304
+ if (request.method === 'GET' && url.pathname === '/api/official-remote/status') {
305
+ if (!managedAppServer.client.ready) throw new RequestError(503, 'Codex 内核未就绪,无法读取官方工作站状态。');
306
+ const result = await managedAppServer.client.request('remoteControl/status/read', {});
307
+ sendJson(response, 200, { status: result.status, environmentId: result.environmentId });
308
+ return;
309
+ }
310
+ if (request.method === 'POST' && url.pathname === '/api/official-remote/pairing') {
311
+ if (!managedAppServer.client.ready) throw new RequestError(503, 'Codex 内核未就绪,无法生成官方配对码。');
312
+ const status = await managedAppServer.client.request('remoteControl/status/read', {});
313
+ if (status.status !== 'connected') throw new RequestError(409, '当前 Codex 内核未连接 OpenAI Remote,无法配对。');
314
+ const result = await managedAppServer.client.request('remoteControl/pairing/start', { manualCode: true });
315
+ if (!result.manualPairingCode || !result.environmentId) throw new RequestError(502, 'Codex 内核未返回有效的官方配对码。');
316
+ // One-time code: return only to the authenticated station controller; never persist or log it.
317
+ sendJson(response, 200, { manualPairingCode: result.manualPairingCode,
318
+ environmentId: result.environmentId, expiresAt: result.expiresAt });
319
+ return;
320
+ }
321
+
199
322
  if (request.method === 'GET' && url.pathname === '/api/health') {
200
323
  // Status readers consume the workstation's cached sample. A phone query
201
324
  // must not trigger PowerShell, process-identity, or app-server checks.
202
- const recovery = readRecord(path.join(stateRoot, 'shared-recovery.json'));
203
- const recovering = ['scheduled', 'running'].includes(recovery?.status);
204
- sendJson(response, 200, {
205
- status: 'ok', resident: true,
206
- execution: { ...executionHealth.state, ready: executionHealth.state.ready && (!sharedServer || sharedServer.ready),
207
- ...(recovering ? { state: 'recovering' } : {}), mode: executionMode },
325
+ const executionReady = executionHealth.state.ready && sharedServer?.ready === true;
326
+ sendJson(response, 200, {
327
+ status: 'ok', resident: true, version: runtimeVersion, serviceInstanceId,
328
+ keepAwake: powerManager.snapshot(),
329
+ serviceUpdate: serviceUpdateStatus(stateRoot),
330
+ execution: { ...executionHealth.state, ready: executionReady, canWrite: executionReady,
331
+ mode: executionMode },
208
332
  managedAppServer: managedAppServer.snapshot(),
209
- desktop: executionHealth.state.desktop || { state: 'stopped', attachedToManagedServer: false },
210
- desktopRestartRecommended: executionHealth.state.desktop?.state === 'ordinary' && managedAppServer.snapshot().ready,
211
- recovery: recovery ? { operationId: recovery.operationId, status: recovery.status, stage: recovery.stage, error: recovery.error } : null,
212
- debugUiPort: await activeDebugPort(stateRoot, options.port),
213
- desktopConnected: Boolean(desktopMonitor.pipe),
214
- sharedAppServerConnected: sharedServer?.ready === true,
215
- sharedRecoveryAvailable: true,
216
- desktopOrderingReady: Boolean(desktopMonitor.snapshot),
217
- toolCount: 0,
218
- mode: options.lan ? 'lan' : 'loopback',
219
- tokenRequired: Boolean(options.token),
220
- nativeImageTransport: sharedServer ? 'shared-app-server' : null,
221
- transportMode: sharedServer ? 'lan-shared' : 'lan-legacy',
222
- appServer: {
223
- state: appServer.state,
224
- cliVersion: appServer.cliVersion,
225
- taskCount: appServerTasks.store.list().length,
226
- },
227
- });
228
- return;
229
- }
230
-
231
-
333
+ ...runtimeDiscovery.state,
334
+ sharedAppServerConnected: sharedServer?.ready === true,
335
+ sharedRecoveryAvailable: runtimeDiscovery.state.desktop?.installed === true,
336
+ mode: options.lan ? 'lan' : 'loopback',
337
+ tokenRequired: Boolean(options.token),
338
+ nativeImageTransport: sharedServer ? 'shared-app-server' : null,
339
+ transportMode: sharedServer?.ready === true ? 'lan-shared' : 'unavailable',
340
+ });
341
+ return;
342
+ }
343
+
344
+ const fileLinkMatch = /^\/api\/files\/([a-f0-9]{64})\/link$/.exec(url.pathname);
345
+ if (request.method === 'GET' && fileLinkMatch) {
346
+ const attachment = attachmentCache.get(fileLinkMatch[1])
347
+ || dotsImages.restore(fileLinkMatch[1], url.searchParams.get('recovery') || '');
348
+ if (!attachment?.dotsFile) throw new RequestError(404, '此附件没有网络链接,请重新读取对话。');
349
+ sendJson(response, 200, { url: await dotsImages.fileLink(attachment) });
350
+ return;
351
+ }
352
+
232
353
  const imageMatch = /^\/api\/images\/([a-f0-9]{64})$/.exec(url.pathname);
233
354
  if (request.method === 'GET' && imageMatch) {
234
355
  let image = attachmentCache.get(imageMatch[1])
356
+ || dotsImages.restore(imageMatch[1], url.searchParams.get('recovery') || '')
235
357
  || recoverLocalImage(imageMatch[1], url.searchParams.get('recovery') || '');
236
358
  const contextThreadId = url.searchParams.get('threadId') || '';
237
359
  const contextTurnId = url.searchParams.get('turnId') || '';
@@ -243,1336 +365,1010 @@ async function handleRequest(request, response) {
243
365
  } catch {}
244
366
  }
245
367
  if (!image) throw new RequestError(404, '这张图片已失效,无法从工作站重新读取。');
246
- const thumbnail = url.searchParams.get('variant') === 'thumbnail';
247
- const asset = thumbnail ? await attachmentThumbnail(image) : { buffer: image.buffer, mimeType: image.mimeType };
248
- sendJson(response, 200, { url: `data:${asset.mimeType};base64,${asset.buffer.toString('base64')}` });
249
- return;
250
- }
251
-
252
- const fileMatch = /^\/api\/files\/([a-f0-9]{64})$/.exec(url.pathname);
253
- if (request.method === 'GET' && fileMatch) {
254
- const file = fileCache.get(fileMatch[1]);
255
- if (!file) throw new RequestError(404, '文件授权已失效,请重新读取对话。');
256
- let stat;
257
- try { stat = fs.statSync(file.path); } catch { throw new RequestError(404, '电脑上的文件已不存在。'); }
258
- if (!stat.isFile() || stat.size !== file.size || stat.mtimeMs !== file.mtimeMs) {
259
- fileCache.delete(fileMatch[1]);
260
- throw new RequestError(409, '电脑上的文件已发生变化,请重新读取对话。');
261
- }
262
- if (stat.size > MAX_ATTACHMENT_BYTES) throw new RequestError(413, '暂只支持下载不超过 16 MiB 的文件。');
263
- sendJson(response, 200, { name: file.name, size: file.size, dataBase64: fs.readFileSync(file.path).toString('base64') });
264
- return;
265
- }
266
-
267
- if (request.method === 'GET' && url.pathname === '/api/models') {
268
- if (!usesSharedTask({ hostId: url.searchParams.get('hostId') })) throw new RequestError(400, '当前连接不支持即时模型设置。');
269
- sendJson(response, 200, await sharedServer.models());
270
- return;
271
- }
272
-
273
- if (request.method === 'GET' && url.pathname === '/api/threads') {
274
- const limit = boundedInteger(url.searchParams.get('limit'), 20, 1, 200);
275
- const archived = url.searchParams.get('archived') === 'true';
276
- const catalog = sharedCatalog;
277
- const sharedSnapshot = Boolean(catalog);
278
- let desktopCatalog;
279
- if (catalog) desktopCatalog = await catalog.threads(limit, archived);
280
- else {
281
- if (archived) throw new RequestError(400, '当前连接不支持归档对话。请启用完整控制。');
282
- const listing = await callTool('list_threads', { limit: Math.min(limit, 50) });
283
- if (!listing.success) { sendToolResult(response, listing); return; }
284
- const projects = await callTool('list_projects', {});
285
- if (!projects.success) { sendToolResult(response, projects); return; }
286
- const catalogProjects = projects.data.projects || [];
287
- desktopCatalog = { ...supplementThreadCatalog(listing.data, catalogProjects, codexDataRoot, limit),
288
- projects: catalogProjects };
289
- }
290
- let appServerThreads = archived ? [] : appServerTasks.store.list().map(record => ({
291
- id: record.id,
292
- kind: 'codex',
293
- hostId: 'app-server',
294
- title: record.title,
295
- cwd: record.cwd,
296
- projectId: record.projectId,
297
- updatedAt: record.updatedAt,
298
- recencyAt: record.recencyAt ?? record.updatedAt,
299
- status: 'unknown',
300
- transport: 'app-server',
301
- }));
302
- if (appServerThreads.length) {
303
- try { appServerThreads = await appServerTasks.listTasks(); } catch {}
304
- }
305
- sendJson(response, 200, mergeThreadCatalog(desktopCatalog, appServerThreads, limit, sharedSnapshot));
306
- return;
307
- }
308
-
309
- if (request.method === 'GET' && url.pathname === '/api/projects') {
310
- if (sharedCatalog) {
311
- sendJson(response, 200, { projects: (await sharedCatalog.projects()).projects });
312
- return;
313
- }
314
- const result = await callTool('list_projects', {});
315
- sendToolResult(response, result);
316
- return;
317
- }
318
-
319
- if (request.method === 'GET' && url.pathname === '/api/usage-limits') {
320
- if (sharedServer) sendJson(response, 200, await sharedServer.usage());
321
- else sendToolResult(response, await callTool('get_usage_limits', {}));
322
- return;
323
- }
324
-
325
- if (request.method === 'GET' && url.pathname === '/api/usage-events' && sharedServer) {
326
- const execution = sharedServer;
327
- response.startEvents();
328
- const emit = value => { if (!response.destroyed) response.event('usage', value); };
329
- const failed = error => { if (!response.destroyed) response.event('usage-error', { error: error.message }); };
330
- const disconnected = () => response.end();
331
- execution.on('usage', emit); execution.on('usage-error', failed); execution.on('disconnected', disconnected);
332
- response.once('close', () => { execution.off('usage', emit); execution.off('usage-error', failed); execution.off('disconnected', disconnected); });
333
- try { emit(await execution.usage()); } catch (error) { failed(error); }
334
- return;
335
- }
336
-
337
- if (request.method === 'GET' && url.pathname === '/api/app-server/status') {
338
- sendJson(response, 200, appServerBridgeStatus());
339
- return;
340
- }
341
-
342
- if (request.method === 'POST' && url.pathname === '/api/app-server/start') {
368
+ if (image.dotsFile) await dotsImages.load(image);
369
+ if (url.searchParams.get('variant') === 'thumbnail') {
370
+ const asset = await attachmentThumbnail(image);
371
+ sendJson(response, 200, { url: `data:${asset.mimeType};base64,${asset.buffer.toString('base64')}` });
372
+ return;
373
+ }
374
+ const size = image.buffer.length;
375
+ if (size > MAX_ATTACHMENT_BYTES) throw new RequestError(413, '图片超过下载上限(20 MiB),无法下载。');
376
+ if (!size) throw new RequestError(400, '图片为空,无法下载。');
377
+ if (!request.fileTransfer) throw new RequestError(400, '请通过文件通道读取原图。');
378
+ const revision = imageMatch[1];
379
+ const { offset, length } = fileRange(url.searchParams, size, revision);
380
+ response.filePart({ name: image.name || '图片', size, offset, revision, mimeType: image.mimeType },
381
+ image.buffer.subarray(offset, offset + length));
382
+ return;
383
+ }
384
+
385
+ const localFileRequest = url.pathname === '/api/files/local';
386
+ const fileMatch = /^\/api\/files\/([a-f0-9]{64})$/.exec(url.pathname);
387
+ if (request.method === 'GET' && (fileMatch || localFileRequest)) {
388
+ if (!request.fileTransfer) throw new RequestError(400, '请通过文件通道下载附件。');
389
+ const hosted = !localFileRequest && (attachmentCache.get(fileMatch[1])
390
+ || dotsImages.restore(fileMatch[1], url.searchParams.get('recovery') || ''));
391
+ if (hosted?.dotsFile) {
392
+ const { bytes, ...metadata } = await dotsImages.readPart(hosted, url.searchParams);
393
+ response.filePart(metadata, bytes);
394
+ return;
395
+ }
396
+ const localPath = url.searchParams.get('path') || '';
397
+ if (localFileRequest && (/[\x00-\x1f]/.test(localPath)
398
+ || !(process.platform === 'win32' ? /^[a-z]:[\\/]/i : /^\/(?!\/)/).test(localPath))) {
399
+ throw new RequestError(400, '无效的工作站文件路径。');
400
+ }
401
+ const file = localFileRequest ? describeLocalFile(localPath) : fileCache.get(fileMatch[1]);
402
+ if (!file) throw new RequestError(404, '电脑上的文件不存在或无法读取。');
403
+ if (file.externalIdentity) await dotsImages.authorization(file.externalIdentity);
404
+ const { offset, length } = fileRange(url.searchParams, file.size, file.revision);
405
+ let handle;
406
+ try { handle = fs.openSync(file.path, 'r'); }
407
+ catch (error) {
408
+ if (error.code === 'ENOENT') throw new RequestError(404, '电脑上的文件已不存在。');
409
+ throw error;
410
+ }
411
+ try {
412
+ const validate = () => {
413
+ const stat = fs.fstatSync(handle);
414
+ if (!stat.isFile() || fileRevision(stat) !== file.revision || fileRevision(fs.statSync(file.path)) !== file.revision) {
415
+ if (fileMatch) fileCache.delete(fileMatch[1]);
416
+ throw new RequestError(409, '电脑上的文件已发生变化,请重新读取对话。');
417
+ }
418
+ if (stat.size > MAX_FILE_DOWNLOAD_BYTES) throw new RequestError(413, '文件超过下载上限(20 MiB),无法下载。');
419
+ if (!stat.size) throw new RequestError(400, '文件为空,无法下载。');
420
+ };
421
+ validate();
422
+ const data = Buffer.alloc(length);
423
+ let bytesRead = 0;
424
+ while (bytesRead < data.length) {
425
+ const count = fs.readSync(handle, data, bytesRead, data.length - bytesRead, offset + bytesRead);
426
+ if (!count) throw new RequestError(409, '电脑上的文件读取不完整,请重新读取对话。');
427
+ bytesRead += count;
428
+ }
429
+ validate();
430
+ response.filePart({ name: file.name, size: file.size, offset, revision: file.revision }, data);
431
+ } finally { fs.closeSync(handle); }
432
+ return;
433
+ }
434
+
435
+ if (url.pathname.startsWith('/api/app-server/') || url.pathname === '/api/wait') {
436
+ throw new RequestError(410, '此连接方式已停用。请检查 Codex 内核状态。', { code: 'unsupported_control' });
437
+ }
438
+ const taskPath = /^\/api\/threads\/([^/]+)/.exec(url.pathname);
439
+ if (taskPath && (url.pathname.endsWith('/takeover') || url.pathname === `/api/threads/${taskPath[1]}`)) {
440
+ throw new RequestError(410, '此连接方式已停用。', { code: 'unsupported_control' });
441
+ }
442
+ const hostId = url.searchParams.get('hostId') || 'local';
443
+ if (!['local', 'durable'].includes(hostId)) {
444
+ throw new RequestError(410, '此对话的连接方式已停用,请检查 Codex 内核状态。', { code: 'unsupported_control' });
445
+ }
446
+ if (!executionHealth.state.ready || sharedServer?.ready !== true) {
447
+ throw new RequestError(503, 'Codex 内核未就绪。请重启工作站服务;若仍失败,运行 relax doctor。',
448
+ { code: 'full_control_required' });
449
+ }
450
+ const rootChat = hostId === 'durable' && taskPath
451
+ && await dotsServer.source(decodeURIComponent(taskPath[1])) === 'aeon';
452
+ const execution = rootChat ? dotsChat : hostId === 'durable' ? dotsServer : sharedServer;
453
+
454
+ if (url.pathname === '/api/permissions' && ['GET', 'POST'].includes(request.method)) {
455
+ if (hostId !== 'local') throw new RequestError(405, '当前来源不提供工作站全局审批方式。');
456
+ const body = request.method === 'POST' ? await readJsonBody(request) : {};
457
+ if (request.method === 'POST' && typeof body.mode !== 'string') throw new RequestError(400, '缺少审批方式。');
458
+ sendJson(response, 200, await sharedServer.globalPermissions(body.mode, {
459
+ threadId: url.searchParams.get('threadId') || '', cwd: url.searchParams.get('cwd') || '',
460
+ }));
461
+ return;
462
+ }
463
+
464
+ const dotsActivityMatch = /^\/api\/threads\/([^/]+)\/dots-activity-events$/.exec(url.pathname);
465
+ if (request.method === 'GET' && dotsActivityMatch) {
466
+ if (!rootChat) throw new RequestError(405, '只有点点主聊天提供此活动面板。');
467
+ response.startEvents();
468
+ const disconnected = () => response.end();
469
+ dotsChat.on('disconnected', disconnected);
470
+ const release = dotsChat.acquirePanelActivity(decodeURIComponent(dotsActivityMatch[1]), value => {
471
+ if (!response.destroyed) response.event('dots-activity', { threadId: value.threadId, activity: value.activity });
472
+ });
473
+ response.once('close', () => { release(); dotsChat.off('disconnected', disconnected); });
474
+ return;
475
+ }
476
+
477
+ const dotsTasksMatch = /^\/api\/threads\/([^/]+)\/dots-tasks$/.exec(url.pathname);
478
+ if (request.method === 'GET' && dotsTasksMatch) {
479
+ if (!rootChat) throw new RequestError(405, '只有点点主聊天提供此任务面板。');
480
+ sendJson(response, 200, await readDotsSummaryTasks(dotsServer, decodeURIComponent(dotsTasksMatch[1])));
481
+ return;
482
+ }
483
+
484
+ const dotsSchedulesMatch = /^\/api\/threads\/([^/]+)\/dots-schedules$/.exec(url.pathname);
485
+ if (request.method === 'GET' && dotsSchedulesMatch) {
486
+ if (!rootChat) throw new RequestError(405, '只有点点主聊天提供此定时任务列表。');
487
+ sendJson(response, 200, await readDotsSchedules(dotsServer, decodeURIComponent(dotsSchedulesMatch[1])));
488
+ return;
489
+ }
490
+
491
+ if (request.method === 'GET' && url.pathname.startsWith('/api/code/')) {
492
+ const operation = url.pathname.slice('/api/code/'.length);
493
+ if (hostId === 'durable' && operation !== 'change') throw new RequestError(405, 'Dots 委派任务尚未接入代码浏览。');
494
+ try {
495
+ if ((operation === 'image' || operation === 'binary') && !request.fileTransfer) throw new RequestError(400, '请通过文件通道读取预览文件。');
496
+ const result = await browseCode((method, params) => execution.request(method, params), operation, url.searchParams);
497
+ if (operation === 'image' || operation === 'binary') {
498
+ const { data, ...metadata } = result;
499
+ response.filePart(metadata, data);
500
+ } else sendJson(response, 200, result);
501
+ } catch (error) { throw new RequestError(error.statusCode || 502, error.message); }
502
+ return;
503
+ }
504
+
505
+ if (request.method === 'GET' && url.pathname === '/api/models') {
506
+ sendJson(response, 200, await execution.models());
507
+ return;
508
+ }
509
+
510
+ if (request.method === 'GET' && url.pathname === '/api/archived-threads') {
511
+ sendJson(response, 200, await archivedThreadsPage(execution, sharedServer, hostId, url.searchParams.get('cursor')));
512
+ return;
513
+ }
514
+
515
+ if (request.method === 'GET' && url.pathname === '/api/threads') {
516
+ const limit = boundedInteger(url.searchParams.get('limit'), 20, 1, 200);
517
+ const archived = url.searchParams.get('archived') === 'true';
518
+ const scope = url.searchParams.get('scope') || 'all';
519
+ if (!['all', 'local', 'durable'].includes(scope)) { sendJson(response, 400, { error: '无效的会话列表范围' }); return; }
520
+ const catalog = await sharedCatalog.threads(limit, archived, scope, url.searchParams.get('refresh') === '1');
521
+ const creations = await messageReceipts.creationBindings();
522
+ for (const thread of [...(catalog.threads || []), ...(catalog.pinnedThreads || [])]) {
523
+ const clientId = creations.get(thread.id);
524
+ if (clientId && (thread.hostId || 'local') === 'local') thread.creationClientId = clientId;
525
+ }
526
+ sendJson(response, 200, catalog);
527
+ return;
528
+ }
529
+
530
+ if (request.method === 'GET' && url.pathname === '/api/file-local-copy') {
531
+ const value = url.searchParams.get('source') || '';
532
+ const match = /^([a-f0-9]{64})(?:\?recovery=([A-Za-z0-9_-]{1,4096}))?$/.exec(value);
533
+ if (!match) throw new RequestError(400, '无效的文件标识。');
534
+ const asset = attachmentCache.get(match[1])
535
+ || dotsImages.restore(match[1], match[2] || url.searchParams.get('recovery') || '');
536
+ // A missing process cache does not make a local image an external file.
537
+ // The actual image endpoint owns recovery and download authorization.
538
+ if (!asset?.dotsFile) {
539
+ sendJson(response, 200, { external: false, localCopy: null }); return;
540
+ }
541
+ await dotsImages.authorization(asset.identity);
542
+ const copy = await workstationDownloads.copy(asset.id);
543
+ sendJson(response, 200, { external: true, localCopy: copy ? workstationCopyReference(copy, asset) : null,
544
+ cachedContent: asset.buffer ? { remotePath: asset.id, name: asset.name, size: asset.buffer.length, mimeType: asset.mimeType } : null });
545
+ return;
546
+ }
547
+
548
+ if (request.method === 'POST' && url.pathname === '/api/workstation-file-attachments') {
343
549
  await requireExecutionWritable();
344
- await appServer.start();
345
- sendJson(response, 200, appServerBridgeStatus());
346
- return;
347
- }
348
-
349
- const createThreadMatch = /^\/api\/projects\/([^/]+)\/threads$/.exec(url.pathname);
350
- if (request.method === 'POST' && createThreadMatch) {
550
+ const body = await readJsonBody(request);
551
+ const source = body.source;
552
+ const file = typeof source === 'string' && /^[a-z]:[\\/]/i.test(source) ? describeLocalFile(source)
553
+ : typeof source === 'string' && /^[a-f0-9]{64}$/.test(source) ? fileCache.get(source) : null;
554
+ if (!file) throw new RequestError(404, '电脑文件不可用,请从原文件入口重新添加。');
555
+ if (file.externalIdentity) await dotsImages.authorization(file.externalIdentity);
556
+ sendJson(response, 201, { file: await stageWorkstationMessageFile(file, uploadsRoot) });
557
+ return;
558
+ }
559
+
560
+ if (request.method === 'GET' && url.pathname === '/api/file-save-locations') {
351
561
  await requireExecutionWritable();
352
- const projectId = decodeURIComponent(createThreadMatch[1]);
353
- const body = createThreadBody(await readJsonBody(request));
354
- let projects = [];
355
- if (body.transport !== 'app-server' || projectId !== '__unassigned__') {
356
- const projectsResult = sharedCatalog
357
- ? { success: true, data: { projects: (await sharedCatalog.projects()).projects } }
358
- : await callTool('list_projects', {});
359
- if (!projectsResult.success) {
360
- sendJson(response, 502, { error: '无法读取 Codex Desktop 项目。', details: projectsResult.data });
361
- return;
362
- }
363
- projects = Array.isArray(projectsResult.data?.projects) ? projectsResult.data.projects : [];
364
- }
365
- if (body.transport === 'app-server') {
366
- const target = appServerProjectTarget(projectId, projects);
367
- const result = await appServerTasks.createTask({
368
- cwd: target.cwd,
369
- projectId: target.projectId,
370
- input: appServerTurnArguments('', body).input,
371
- });
372
- sendJson(response, 201, {
373
- threadId: result.record.id,
374
- hostId: 'app-server',
375
- transport: 'app-server',
376
- turnId: result.turn.id,
377
- });
378
- return;
379
- }
380
- const project = projects.find(item => item.projectId === projectId);
381
- if (usesSharedTask({ hostId:project?.hostId || body.hostId })) {
382
- if (projectId !== '__unassigned__' && !project) throw new RequestError(404, '没有找到对应项目。');
383
- const execution = sharedServer;
384
- await execution.connect();
385
- const rpc = (method, params) => execution.request(method, params);
386
- let target = appServerEvents.projectlessThreadTarget(projectRoot);
387
- if (project) {
388
- const nativeProjects = await appServerEvents.readProjects(rpc);
389
- const nativeId = nativeProjects.find(item => item.id === project.projectId)?.id;
390
- if (!nativeId) throw new RequestError(404, '完整控制中没有找到对应项目,请刷新项目列表。');
391
- target = { projectId:nativeId, cwd:project.path };
392
- }
393
- const { input } = appServerTurnArguments('', body);
394
- const created = await appServerEvents.createThread(rpc, target, input, body);
395
- const prompt = input.filter(part => part.type === 'text').map(part => part.text).join('\n');
396
- void generateAndSetThreadTitle({
397
- request: rpc,
398
- subscribe: listener => {
399
- execution.on('notification', listener);
400
- return () => execution.off('notification', listener);
401
- },
402
- threadId: created.threadId,
403
- prompt,
404
- cwd: target.cwd || null,
405
- }).catch(error => console.error(`生成对话标题失败:${error.message}`));
406
- sendJson(response, 201, { ...created, hostId:'local' });
407
- return;
408
- }
409
- const argumentsValue = await createThreadArguments(projectId, body, projects);
410
- sendToolResult(response, await callTool('create_thread', argumentsValue, 90_000));
411
- return;
412
- }
413
-
414
- const timelineMatch = /^\/api\/threads\/([^/]+)\/timeline$/.exec(url.pathname);
562
+ const locations = await projectLocations();
563
+ const projects = (await sharedCatalog.projects()).projects.filter(project => project.hostId === 'local' && project.roots?.length);
564
+ sendJson(response, 200, { projects, downloads: locations.downloads });
565
+ return;
566
+ }
567
+
568
+ if (request.method === 'GET' && url.pathname === '/api/file-save-folders') {
569
+ await requireExecutionWritable();
570
+ sendJson(response, 200, await workstationDownloads.browse((method, params) => sharedServer.request(method, params),
571
+ await projectLocations(), Object.fromEntries(url.searchParams)));
572
+ return;
573
+ }
574
+
575
+ if (request.method === 'POST' && url.pathname === '/api/file-saves') {
576
+ await requireExecutionWritable();
577
+ const body = await readJsonBody(request);
578
+ const asset = await externalDownloadAsset(body.source, body.recovery);
579
+ const controller = new AbortController(), cancel = () => controller.abort();
580
+ response.once('close', cancel);
581
+ try {
582
+ const result = await workstationDownloads.save((method, params) => sharedServer.request(method, params),
583
+ await projectLocations(), body, asset, (file, query) => dotsImages.readPart(file, query), controller.signal);
584
+ sendJson(response, 201, { localCopy: workstationCopyReference(result, asset), warning: result.warning || '' });
585
+ } finally { response.off('close', cancel); }
586
+ return;
587
+ }
588
+
589
+ if (request.method === 'GET' && url.pathname === '/api/projects') {
590
+ sendJson(response, 200, { projects: (await sharedCatalog.projects()).projects });
591
+ return;
592
+ }
593
+
594
+ if (url.pathname === '/api/project-folders' && ['GET', 'POST'].includes(request.method)) {
595
+ await requireExecutionWritable();
596
+ try {
597
+ const rpc = (method, params) => sharedServer.request(method, params);
598
+ const result = request.method === 'GET'
599
+ ? await browseProjectFolders(rpc, !url.searchParams.get('path') || url.searchParams.get('locations') === '1' ? await projectLocations() : null, url.searchParams.get('path'))
600
+ : await createProjectFolder(rpc, await readJsonBody(request));
601
+ sendJson(response, request.method === 'GET' ? 200 : 201, result);
602
+ } catch (error) { throw new RequestError(error.statusCode || 502, error.message); }
603
+ return;
604
+ }
605
+
606
+ const projectActionMatch = /^\/api\/projects\/([^/]+)\/(remove|move)$/.exec(url.pathname);
607
+ if (projectActionMatch) {
608
+ await requireExecutionWritable();
609
+ const projectId = decodeURIComponent(projectActionMatch[1]);
610
+ if (request.method === 'POST' && projectActionMatch[2] === 'move') {
611
+ const { beforeProjectId } = await readJsonBody(request);
612
+ if (beforeProjectId !== null && (typeof beforeProjectId !== 'string' || !beforeProjectId.trim()) || beforeProjectId === projectId) {
613
+ throw new RequestError(400, '排序目标无效。');
614
+ }
615
+ await sharedServer.request('project/move', { projectId, beforeProjectId });
616
+ sendJson(response, 200, await sharedCatalog.projects().then(({ projects }) => ({ projects })));
617
+ return;
618
+ }
619
+ if (request.method === 'POST' && projectActionMatch[2] === 'remove') {
620
+ const result = await removeWorkstationProject(sharedServer, stateRoot, codexDataRoot, projectId, await readJsonBody(request));
621
+ sendJson(response, 200, result);
622
+ return;
623
+ }
624
+ throw new RequestError(405, '不支持的项目操作。');
625
+ }
626
+
627
+ const updateProjectMatch = /^\/api\/projects\/([^/]+)$/.exec(url.pathname);
628
+ if (request.method === 'POST' && (url.pathname === '/api/projects' || updateProjectMatch)) {
629
+ await requireExecutionWritable();
630
+ const body = await readJsonBody(request);
631
+ try {
632
+ const result = !updateProjectMatch ? await createWorkstationProject(sharedServer, stateRoot, codexDataRoot, body)
633
+ : await updateWorkstationProject(sharedServer, stateRoot, codexDataRoot, decodeURIComponent(updateProjectMatch[1]), body);
634
+ sendJson(response, updateProjectMatch ? 200 : 201, result);
635
+ } catch (error) { throw new RequestError(error.statusCode || 502, error.message, error.details); }
636
+ return;
637
+ }
638
+
639
+ if (request.method === 'GET' && url.pathname === '/api/plan-pricing') {
640
+ sendJson(response, 200, await readPlanPricing(sharedServer));
641
+ return;
642
+ }
643
+
644
+ if (request.method === 'GET' && url.pathname === '/api/usage-limits') {
645
+ sendJson(response, 200, await sharedServer.usage());
646
+ return;
647
+ }
648
+
649
+ if (request.method === 'POST' && url.pathname === '/api/usage-reset') {
650
+ await requireExecutionWritable();
651
+ const body = await readJsonBody(request);
652
+ try { sendJson(response, 200, await consumeUsageReset(sharedServer, body)); }
653
+ catch (error) { throw new RequestError(error.statusCode || 502, error.message,
654
+ error.code === 'usage_reset_not_submitted' ? { code: error.code, deliveryUnknown: false } : undefined); }
655
+ return;
656
+ }
657
+
658
+ if (request.method === 'GET' && url.pathname === '/api/usage-events') {
659
+ const execution = sharedServer;
660
+ response.startEvents();
661
+ const emit = value => { if (!response.destroyed) response.event('usage', value); };
662
+ const failed = error => { if (!response.destroyed) response.event('usage-error', { error: error.message }); };
663
+ const disconnected = () => response.end();
664
+ execution.on('usage', emit); execution.on('usage-error', failed); execution.on('disconnected', disconnected);
665
+ response.once('close', () => { execution.off('usage', emit); execution.off('usage-error', failed); execution.off('disconnected', disconnected); });
666
+ try { emit(await execution.usage()); } catch (error) { failed(error); }
667
+ return;
668
+ }
669
+
670
+ if (request.method === 'GET' && url.pathname === '/api/thread-events') {
671
+ await sharedServer.connect();
672
+ const clients = [sharedServer, dotsServer, dotsChat];
673
+ response.startEvents();
674
+ const listener = ({ method, params }) => {
675
+ const questionChange = questionAttention.fromNotification(method, params);
676
+ if ((!threadListNotificationMethods.has(method) && !questionChange) || response.destroyed) return;
677
+ response.event('thread-list-changed', { method, threadId: params.threadId || params.thread?.id || '',
678
+ ...(questionChange ? { questionChange } : {}),
679
+ ...(params.hostId ? { hostId: params.hostId } : {}),
680
+ ...(params.chatRevision !== undefined ? { chatRevision: params.chatRevision } : {}),
681
+ ...(params.updatedAt !== undefined ? { updatedAt: params.updatedAt } : {}),
682
+ ...(params.chatMessageRevision !== undefined ? { chatMessageRevision: params.chatMessageRevision } : {}),
683
+ ...(params.status ? { status: typeof params.status === 'string' ? params.status : params.status.type } : {}) });
684
+ };
685
+ const disconnected = () => response.end();
686
+ for (const client of clients) {
687
+ client.on('notification', listener);
688
+ client.on('disconnected', disconnected);
689
+ }
690
+ response.once('close', () => {
691
+ for (const client of clients) {
692
+ client.off('notification', listener);
693
+ client.off('disconnected', disconnected);
694
+ }
695
+ });
696
+ response.event('ready', {});
697
+ return;
698
+ }
699
+
700
+ if (request.method === 'GET' && url.pathname === '/api/turn-events') {
701
+ const clients = [sharedServer, dotsServer, dotsChat];
702
+ response.startEvents();
703
+ const listener = ({ method, params }) => {
704
+ if (!['turn/started', 'turn/completed'].includes(method)) return;
705
+ const threadId = params?.threadId, turnId = params?.turn?.id;
706
+ if (typeof threadId === 'string' && typeof turnId === 'string' && !response.destroyed) {
707
+ response.event('turn', { method, threadId, turnId });
708
+ }
709
+ };
710
+ const disconnected = () => response.end();
711
+ for (const client of clients) {
712
+ client.on('notification', listener);
713
+ client.on('disconnected', disconnected);
714
+ }
715
+ response.once('close', () => {
716
+ for (const client of clients) {
717
+ client.off('notification', listener);
718
+ client.off('disconnected', disconnected);
719
+ }
720
+ });
721
+ return;
722
+ }
723
+
724
+ const activeTurnMatch = /^\/api\/threads\/([^/]+)\/active-turn$/.exec(url.pathname);
725
+ if (request.method === 'GET' && activeTurnMatch) {
726
+ if (rootChat) { sendJson(response, 200, { turnId: null }); return; }
727
+ const threadId = decodeURIComponent(activeTurnMatch[1]);
728
+ await execution.connect();
729
+ const { thread } = await execution.request('thread/read', { threadId, includeTurns: false });
730
+ const status = typeof thread.status === 'string' ? thread.status : thread.status?.type;
731
+ let turnId = null;
732
+ if (status === 'active') {
733
+ const page = await execution.request('thread/turns/list', {
734
+ threadId, limit: 1, sortDirection: 'desc', itemsView: 'summary',
735
+ });
736
+ const latest = page.data?.[0];
737
+ if (latest?.status === 'inProgress') turnId = latest.id;
738
+ }
739
+ sendJson(response, 200, { turnId });
740
+ return;
741
+ }
742
+
743
+ const createStatusMatch = /^\/api\/projects\/([^/]+)\/threads\/([a-f0-9]{32})\/status$/.exec(url.pathname);
744
+ if (request.method === 'GET' && createStatusMatch) {
745
+ const creationScope = ['project', decodeURIComponent(createStatusMatch[1])], clientId = createStatusMatch[2];
746
+ let status = messageReceipts.status(creationScope, clientId);
747
+ if (status.state === 'unknown' && status.createdThreadId) {
748
+ const threadId = status.createdThreadId;
749
+ status = await messageReceipts.reconcile(creationScope, clientId, async () => {
750
+ const found = await sharedServer.findSentMessage(threadId, clientId);
751
+ return found ? { threadId, hostId: 'local', turnId: found.data?.turnId } : null;
752
+ });
753
+ }
754
+ sendJson(response, 200, status); return;
755
+ }
756
+ const createThreadMatch = /^\/api\/projects\/([^/]+)\/threads$/.exec(url.pathname);
757
+ if (request.method === 'POST' && createThreadMatch) {
758
+ const projectId = decodeURIComponent(createThreadMatch[1]);
759
+ const body = await readJsonBody(request);
760
+ const result = await messageReceipts.submit(['project', projectId], body.clientUserMessageId, async () => {
761
+ const bindCreation = messageReceipts.beginCreation(['project', projectId], body.clientUserMessageId);
762
+ try {
763
+ await requireExecutionWritable();
764
+ if (body.transport === 'app-server' || (body.hostId && body.hostId !== 'local')) {
765
+ throw new RequestError(410, '此连接方式已停用,请检查 Codex 内核状态。', { code: 'unsupported_control' });
766
+ }
767
+ const projects = (await sharedCatalog.projects()).projects;
768
+ const project = projects.find(item => item.projectId === projectId);
769
+ if (projectId !== '__unassigned__' && !project) throw new RequestError(404, '没有找到对应项目。');
770
+ const execution = sharedServer;
771
+ await execution.connect();
772
+ const rpc = (method, params) => execution.request(method, params);
773
+ let target = appServerEvents.projectlessThreadTarget(projectRoot);
774
+ if (project) {
775
+ const nativeProjects = await appServerEvents.readProjects(rpc);
776
+ const nativeProject = nativeProjects.find(item => item.id === project.projectId);
777
+ if (!nativeProject) throw new RequestError(404, 'Codex 内核中没有找到对应项目,请刷新项目列表。');
778
+ target = appServerEvents.projectThreadTarget(nativeProject);
779
+ }
780
+ const { input } = appServerTurnArguments('', body);
781
+ const preference = await execution.globalPermissions(undefined, { cwd: target.cwd });
782
+ messageSubmissionStarted = true;
783
+ const createRpc = (method, params) => method === 'thread/start'
784
+ ? execution.startThread(params) : rpc(method, params);
785
+ const created = await appServerEvents.createThread(createRpc, target, input,
786
+ { ...body, desktopPermissionMode: preference.globalPermissionMode }, undefined, bindCreation);
787
+ const prompt = input.filter(part => part.type === 'text').map(part => part.text).join('\n');
788
+ void generateAndSetThreadTitle({
789
+ request: rpc,
790
+ subscribe: listener => {
791
+ execution.on('notification', listener);
792
+ return () => execution.off('notification', listener);
793
+ },
794
+ threadId: created.threadId,
795
+ prompt,
796
+ cwd: target.cwd || null,
797
+ }).catch(error => console.error(`生成对话标题失败:${error.message}`));
798
+ return { ...created, hostId:'local' };
799
+ } catch (error) {
800
+ error.deliveryUnknown = Boolean(messageReceipts.status(['project', projectId], body.clientUserMessageId).createdThreadId)
801
+ || messageSubmissionStarted && !messageDelivery.rejectedBeforeSubmission(error);
802
+ if (error.deliveryResult) error.deliveryResult = error.deliveryResult.then(created => ({ ...created, hostId: 'local' })).finally(() => bindCreation());
803
+ else bindCreation();
804
+ throw error;
805
+ }
806
+ });
807
+ sendJson(response, 201, result);
808
+ return;
809
+ }
810
+
811
+ const timelineMatch = /^\/api\/threads\/([^/]+)\/timeline$/.exec(url.pathname);
415
812
  if (request.method === 'GET' && timelineMatch) {
416
813
  const threadId = decodeURIComponent(timelineMatch[1]);
417
- if (!appServerTasks.owns(threadId) && usesSharedTask({ hostId: url.searchParams.get('hostId') })) {
418
- const timeline = normalizeSharedTimeline(await sharedServer.timeline(threadId, sharedHistoryPageOptions()));
419
- const knownUpdatedAt = Number(url.searchParams.get('knownUpdatedAt'));
420
- const knownTurnId = url.searchParams.get('knownTurnId') || '';
421
- const knownTurnStatus = url.searchParams.get('knownTurnStatus') || '';
422
- const knownHistoryView = url.searchParams.get('knownHistoryView') || '';
423
- const latest = timeline.turns.at(-1);
424
- const knownIndex = knownTurnId ? timeline.turns.findIndex(turn => turn.id === knownTurnId) : -1;
425
- const sameHistoryView = knownHistoryView === timeline.historyView;
426
- const unchanged = sameHistoryView
427
- && knownIndex === timeline.turns.length - 1
428
- && timeline.thread.status === 'idle' && latest?.status !== 'inProgress'
429
- && knownTurnStatus && knownTurnStatus !== 'inProgress'
430
- && (!Number.isFinite(knownUpdatedAt) || knownUpdatedAt <= 0 || timeline.thread.updatedAt === knownUpdatedAt);
431
- const partial = !unchanged && sameHistoryView && knownIndex >= 0
432
- ? { ...timeline, turns: timeline.turns.slice(knownIndex), partialFromTurnId: knownTurnId }
433
- : timeline;
434
- sendJson(response, 200, unchanged ? { ...timeline, turns: [], notModified: true } : partial);
435
- return;
814
+ const knownSeq = url.searchParams.get('knownSeq');
815
+ const knownSync = { id: url.searchParams.get('knownSyncId'),
816
+ epoch: url.searchParams.get('knownEpoch'), seq: knownSeq === null ? undefined : Number(knownSeq),
817
+ updatedAt: Number(url.searchParams.get('knownUpdatedAt')), chatRevision: url.searchParams.get('knownChatRevision') };
818
+ const pageOptions = sharedHistoryPageOptions();
819
+ pageOptions.reconcileObserved = url.searchParams.get('refresh') === '1';
820
+ if (request.socketRequest) {
821
+ pageOptions.onWindow = async window => {
822
+ if (response.writableEnded) return;
823
+ await response.historyPage(await prepareSharedTimeline(window));
824
+ };
825
+ }
826
+ const timeline = await prepareSharedTimeline(await execution.timeline(threadId, pageOptions, knownSync));
827
+ if (request.socketRequest) {
828
+ response.on('progress', () => execution.keepSnapshotAlive(threadId, timeline.sync));
829
+ response.once('close', () => execution.keepSnapshotAlive(threadId, timeline.sync));
436
830
  }
437
- if (appServerTasks.owns(threadId)) {
438
- const thread = await appServerTasks.readTask(threadId, {
439
- includeTurns: true,
440
- turnLimit: boundedInteger(url.searchParams.get('turnLimit'), 10, 1, 10),
441
- });
442
- sendJson(response, 200, normalizeAppServerTimeline(thread));
443
- return;
444
- }
445
- const argumentsValue = readThreadArguments(url, threadId, 10);
446
- const result = await callTool('read_thread', argumentsValue);
447
- if (result.success) {
448
- const timeline = normalizeTimeline(result.data);
449
- enrichTimelineWithLocalActivity(timeline, threadId);
450
- sendJson(response, 200, timeline);
451
- } else {
452
- sendJson(response, 502, { error: 'Codex Desktop 工具返回失败。', details: result.data });
453
- }
454
- return;
455
- }
456
-
457
- const approvalMatch = /^\/api\/threads\/([^/]+)\/approvals$/.exec(url.pathname);
831
+ sendJson(response, 200, timeline);
832
+ return;
833
+ }
834
+
835
+ const approvalMatch = /^\/api\/threads\/([^/]+)\/approvals$/.exec(url.pathname);
836
+ if (request.method === 'GET' && approvalMatch && rootChat) {
837
+ const threadId = decodeURIComponent(approvalMatch[1]);
838
+ sendJson(response, 200, await execution.inspectApproval(threadId, {
839
+ id: url.searchParams.get('requestId'), roomMessageId: url.searchParams.get('roomMessageId'),
840
+ })); return;
841
+ }
458
842
  if (request.method === 'POST' && approvalMatch) {
459
843
  const threadId = decodeURIComponent(approvalMatch[1]);
460
- await requireExecutionWritable(threadId);
461
- if (!usesSharedTask({ hostId: url.searchParams.get('hostId') }) || appServerTasks.owns(threadId)) {
462
- sendJson(response, 409, { error: '当前执行模式不支持手机审批,请在工作站处理。' }); return;
463
- }
464
- const body = await readJsonBody(request);
465
- sendJson(response, 200, await sharedServer.respondApproval(threadId, body)); return;
466
- }
467
-
468
- const eventsMatch = /^\/api\/threads\/([^/]+)\/events$/.exec(url.pathname);
469
- if (request.method === 'GET' && eventsMatch) {
470
- const threadId = decodeURIComponent(eventsMatch[1]);
471
- if (!appServerTasks.owns(threadId) && usesSharedTask({ hostId: url.searchParams.get('hostId') })) {
472
- const after = Number(url.searchParams.get('afterSeq'));
473
- const requestedSync = url.searchParams.get('syncId') && url.searchParams.get('syncEpoch') && Number.isSafeInteger(after) && after >= 0
474
- ? { id: url.searchParams.get('syncId'), epoch: url.searchParams.get('syncEpoch'), after } : undefined;
475
- const knownUpdatedAt = Number(url.searchParams.get('knownUpdatedAt'));
476
- const known = url.searchParams.get('knownTurnId') ? {
477
- updatedAt: Number.isFinite(knownUpdatedAt) ? knownUpdatedAt : 0,
478
- turnId: url.searchParams.get('knownTurnId'),
479
- turnStatus: url.searchParams.get('knownTurnStatus') || '',
480
- } : undefined;
481
- await streamSharedThread(response, threadId, sharedServer, normalizeSharedTimeline, requestedSync, known);
482
- return;
483
- }
484
- if (appServerTasks.owns(threadId)) {
485
- await streamAppServerThreadEvents(request, response, threadId);
486
- return;
487
- }
488
- await streamThreadEvents(
489
- request,
490
- response,
491
- threadId,
492
- url.searchParams.get('hostId')?.trim(),
493
- );
494
- return;
495
- }
496
-
497
- const contextMatch = /^\/api\/threads\/([^/]+)\/context$/.exec(url.pathname);
498
- if (request.method === 'GET' && contextMatch) {
499
- const threadId = decodeURIComponent(contextMatch[1]);
500
- const current = readContextUsage(threadId);
501
- const native = usesSharedTask({ hostId: url.searchParams.get('hostId') }) ? await sharedServer.readContext(threadId) : {};
502
- sendJson(response, 200, { ...current, ...(native.model ? { model: native.model, thinking: native.thinking, serviceTier: native.serviceTier,
503
- permissionMode: native.permissionMode, permissionModes: native.permissionModes } : {}), ...(native.available ? native : {}) });
504
- return;
505
- }
506
-
507
- const sharedAction = /^\/api\/threads\/([^/]+)\/(history|settings|permissions)$/.exec(url.pathname);
844
+ await requireExecutionWritable(threadId, execution);
845
+ const body = await readJsonBody(request);
846
+ sendJson(response, 200, await execution.respondApproval(threadId, body)); return;
847
+ }
848
+
849
+ const eventsMatch = /^\/api\/threads\/([^/]+)\/events$/.exec(url.pathname);
850
+ if (request.method === 'GET' && eventsMatch) {
851
+ const threadId = decodeURIComponent(eventsMatch[1]);
852
+ const after = Number(url.searchParams.get('afterSeq'));
853
+ const requestedSync = { id: url.searchParams.get('syncId'), epoch: url.searchParams.get('syncEpoch'),
854
+ after: Number.isSafeInteger(after) && after >= 0 ? after : undefined,
855
+ updatedAt: Number(url.searchParams.get('knownUpdatedAt')), chatRevision: url.searchParams.get('knownChatRevision') };
856
+ await streamSharedThread(response, threadId, execution,
857
+ prepareSharedTimeline, requestedSync);
858
+ return;
859
+ }
860
+
861
+ const modelChangesMatch = /^\/api\/threads\/([^/]+)\/model-changes$/.exec(url.pathname);
862
+ if (request.method === 'GET' && modelChangesMatch) {
863
+ const threadId = decodeURIComponent(modelChangesMatch[1]);
864
+ if (!/^[0-9a-f]{8}-[0-9a-f-]{20,55}$/i.test(threadId)) throw new RequestError(400, '对话 ID 无效。');
865
+ sendJson(response, 200, hostId === 'durable' ? [] : await turnModelHistory.read(findSessionPath(threadId), (url.searchParams.get('turnIds') || '').split(',').filter(Boolean)));
866
+ return;
867
+ }
868
+ const contextMatch = /^\/api\/threads\/([^/]+)\/context$/.exec(url.pathname);
869
+ if (request.method === 'GET' && contextMatch) {
870
+ const threadId = decodeURIComponent(contextMatch[1]);
871
+ const current = hostId === 'durable' ? { available: false } : readContextUsage(threadId);
872
+ const native = await execution.readContext(threadId, url.searchParams.get('refreshSettings') === 'true');
873
+ sendJson(response, 200, { ...current, ...(native.model ? { model: native.model, thinking: native.thinking, serviceTier: native.serviceTier,
874
+ permissionMode: native.permissionMode, permissionModes: native.permissionModes, globalPermissionMode: native.globalPermissionMode } : {}), ...(native.available ? native : {}) });
875
+ return;
876
+ }
877
+
878
+ const sharedAction = /^\/api\/threads\/([^/]+)\/(history|settings|permissions)$/.exec(url.pathname);
508
879
  if (sharedAction) {
509
- const id = decodeURIComponent(sharedAction[1]);
510
- if (!usesSharedTask({ hostId: url.searchParams.get('hostId') }) || appServerTasks.owns(id)) throw new RequestError(400, '此连接不支持该操作。');
511
- if (request.method === 'GET' && sharedAction[2] === 'history') {
512
- sendJson(response, 200, normalizeSharedTimeline(await sharedServer.history(id, url.searchParams.get('cursor'), sharedHistoryPageOptions())));
513
- return;
514
- }
880
+ const id = decodeURIComponent(sharedAction[1]);
881
+ if (request.method === 'GET' && sharedAction[2] === 'history') {
882
+ const pageOptions = sharedHistoryPageOptions();
883
+ if (url.searchParams.has('itemLimit')) {
884
+ const itemLimit = Number(url.searchParams.get('itemLimit'));
885
+ if (!Number.isInteger(itemLimit) || itemLimit < 1 || itemLimit > 100) throw new RequestError(400, '历史条目数量无效。');
886
+ pageOptions.itemLimit = itemLimit;
887
+ }
888
+ const history = await execution.history(id, url.searchParams.get('cursor'), pageOptions);
889
+ sendJson(response, 200, await prepareSharedTimeline(history, hostId));
890
+ return;
891
+ }
515
892
  if (request.method === 'POST' && sharedAction[2] === 'settings') {
516
- await requireExecutionWritable(id);
517
- const body = await readJsonBody(request);
518
- sendJson(response, 200, await sharedServer.settings(id, body.model, body.thinking, body.serviceTier));
519
- return;
520
- }
893
+ if (rootChat) throw new RequestError(405, 'Dots 主对话不提供 Codex 模型设置。');
894
+ await requireExecutionWritable(id, execution);
895
+ const body = await readJsonBody(request);
896
+ sendJson(response, 200, await execution.settings(id, body.model, body.thinking, body.serviceTier));
897
+ return;
898
+ }
521
899
  if (request.method === 'POST' && sharedAction[2] === 'permissions') {
522
- await requireExecutionWritable(id);
523
- const body = await readJsonBody(request);
524
- sendJson(response, 200, await sharedServer.permissions(id, body.mode));
525
- return;
900
+ if (rootChat) throw new RequestError(405, 'Dots 主对话不提供 Codex 权限设置。');
901
+ await requireExecutionWritable(id, execution);
902
+ const body = await readJsonBody(request);
903
+ sendJson(response, 200, await execution.permissions(id, body.mode));
904
+ return;
526
905
  }
527
906
  }
528
907
 
529
- const sharedOperationGroup = /^\/api\/threads\/([^/]+)\/turns\/([^/]+)\/groups\/([^/]+)\/items$/.exec(url.pathname);
530
- if (request.method === 'GET' && sharedOperationGroup) {
531
- const threadId = decodeURIComponent(sharedOperationGroup[1]);
532
- const turnId = decodeURIComponent(sharedOperationGroup[2]);
533
- const groupId = decodeURIComponent(sharedOperationGroup[3]);
534
- if (!usesSharedTask({ hostId: url.searchParams.get('hostId') }) || appServerTasks.owns(threadId)) {
535
- throw new RequestError(400, '此连接不支持操作组分页。');
536
- }
537
- const page = await sharedServer.operationGroupItems(threadId, turnId, groupId, url.searchParams.get('offset'));
538
- sendJson(response, 200, { turnId: page.turnId, groupId: page.groupId,
539
- entries: page.items.map(normalizeItem).filter(Boolean), nextOffset: page.nextOffset, total: page.total });
908
+ const renameMatch = /^\/api\/threads\/([^/]+)\/name$/.exec(url.pathname);
909
+ if (request.method === 'POST' && renameMatch) {
910
+ if (rootChat) throw new RequestError(405, '点点主聊天名称由点点资料管理。');
911
+ const threadId = decodeURIComponent(renameMatch[1]);
912
+ await requireExecutionWritable(threadId, execution);
913
+ const body = await readJsonBody(request);
914
+ const name = typeof body.name === 'string' ? body.name.trim() : '';
915
+ if (!name || /[\r\n]/.test(name)) throw new RequestError(400, '请输入单行对话名称。');
916
+ await execution.request('thread/name/set', { threadId, name });
917
+ sendJson(response, 200, { threadId, name });
540
918
  return;
541
919
  }
542
920
 
543
921
  const archiveMatch = /^\/api\/threads\/([^/]+)\/(archive|unarchive)$/.exec(url.pathname);
544
922
  if (request.method === 'POST' && archiveMatch) {
923
+ if (rootChat) throw new RequestError(405, 'Dots 主对话不提供任务归档。');
545
924
  const threadId = decodeURIComponent(archiveMatch[1]);
546
- await requireExecutionWritable(threadId);
547
- if (!usesSharedTask({ hostId: url.searchParams.get('hostId') }) || appServerTasks.owns(threadId)) {
548
- throw new RequestError(400, '当前连接不支持归档对话。请启用完整控制。');
549
- }
550
- await sharedServer.request(`thread/${archiveMatch[2]}`, { threadId });
551
- sendJson(response, 200, { threadId, archived: archiveMatch[2] === 'archive' });
552
- return;
553
- }
554
-
555
- const readMatch = /^\/api\/threads\/([^/]+)$/.exec(url.pathname);
556
- if (request.method === 'DELETE' && readMatch) {
557
- const threadId = decodeURIComponent(readMatch[1]);
558
- await requireExecutionWritable(threadId);
559
- if (!appServerTasks.owns(threadId)) {
560
- throw new RequestError(400, '仅支持删除由独立 app-server 通道创建的任务。');
561
- }
562
- await appServerTasks.deleteTask(threadId);
563
- messageQueues.delete(threadId);
564
- sendJson(response, 200, { deleted: true, threadId, transport: 'app-server' });
565
- return;
566
- }
567
- if (request.method === 'GET' && readMatch) {
568
- const threadId = decodeURIComponent(readMatch[1]);
569
- if (appServerTasks.owns(threadId)) {
570
- sendJson(response, 200, { thread: await appServerTasks.readTask(threadId, {
571
- includeTurns: true,
572
- turnLimit: boundedInteger(url.searchParams.get('turnLimit'), 10, 1, 10),
573
- }) });
574
- return;
575
- }
576
- const argumentsValue = readThreadArguments(url, threadId, 10, 12_000);
577
- sendToolResult(response, await callTool('read_thread', argumentsValue));
578
- return;
579
- }
580
-
581
- const queueMatch = /^\/api\/threads\/([^/]+)\/queue$/.exec(url.pathname);
582
- if (queueMatch) {
583
- const threadId = decodeURIComponent(queueMatch[1]);
584
- if (!appServerTasks.owns(threadId) && usesSharedTask({ hostId: url.searchParams.get('hostId') })) {
585
- await sharedServer.connect();
586
- const rpc = (method, params) => sharedServer.request(method, params);
587
- if (request.method === 'GET') { sendJson(response, 200, await appServerEvents.readQueue(rpc, threadId)); return; }
588
- if (request.method === 'POST') {
589
- await requireExecutionWritable(threadId);
590
- const args = appServerTurnArguments(threadId, await readJsonBody(request));
591
- sendJson(response, 202, await appServerEvents.addQueue(rpc, threadId, args.input, crypto.randomUUID())); return;
592
- }
593
- }
594
- if (request.method === 'GET') {
595
- sendJson(response, 200, { items: publicQueue(threadId) });
596
- return;
597
- }
925
+ await requireExecutionWritable(threadId, execution);
926
+ if (archiveMatch[2] === 'archive') {
927
+ const { thread } = await execution.request('thread/read', { threadId, includeTurns: false });
928
+ const status = typeof thread.status === 'string' ? thread.status : thread.status?.type;
929
+ if (['active', 'inProgress'].includes(status)) throw new RequestError(409, '对话正在运行,请先停止任务。');
930
+ }
931
+ await execution.request(`thread/${archiveMatch[2]}`, { threadId });
932
+ sendJson(response, 200, { threadId, archived: archiveMatch[2] === 'archive' });
933
+ return;
934
+ }
935
+
936
+ const queueMatch = /^\/api\/threads\/([^/]+)\/queue$/.exec(url.pathname);
937
+ if (queueMatch) {
938
+ if (hostId === 'durable') throw new RequestError(405, '当前托管连接不提供排队消息。');
939
+ const threadId = decodeURIComponent(queueMatch[1]);
940
+ await sharedServer.connect();
941
+ const rpc = (method, params) => sharedServer.request(method, params);
942
+ if (request.method === 'GET') { sendJson(response, 200, await appServerEvents.readQueue(rpc, threadId)); return; }
598
943
  if (request.method === 'POST') {
599
- await requireExecutionWritable(threadId);
600
- const body = await readJsonBody(request);
601
- const argumentsValue = appServerTasks.owns(threadId)
602
- ? appServerTurnArguments(threadId, body)
603
- : await messageArguments(threadId, body);
604
- const item = {
605
- id: crypto.randomUUID(),
606
- ...argumentsValue,
607
- transport: appServerTasks.owns(threadId) ? 'app-server' : 'desktop-tools',
608
- displayPrompt: messageDisplayText(body),
609
- text: typeof body.prompt === 'string' ? body.prompt.trim() : '',
610
- attachments: (body.images || []).map(id => ({ type:'image', available:true, url:id.startsWith('data:') ? id : `/api/attachments/${id}`, thumbnailUrl:id.startsWith('data:') ? id : `/api/attachments/${id}/thumbnail` })),
611
- createdAt: new Date().toISOString(),
612
- error: '',
613
- };
614
- const queue = messageQueues.get(threadId) || [];
615
- queue.push(item);
616
- messageQueues.set(threadId, queue);
617
- ensureQueueWorker(threadId);
618
- sendJson(response, 202, { items: publicQueue(threadId) });
619
- return;
620
- }
621
- }
622
-
623
- const queueItemMatch = /^\/api\/threads\/([^/]+)\/queue\/([a-f0-9-]+)(?:\/(steer))?$/.exec(url.pathname);
624
- if (queueItemMatch) {
625
- const threadId = decodeURIComponent(queueItemMatch[1]);
626
- const itemId = queueItemMatch[2];
627
- if (!appServerTasks.owns(threadId) && usesSharedTask({ hostId: url.searchParams.get('hostId') })) {
628
- await sharedServer.connect();
629
- const action = request.method === 'DELETE' ? 'delete' : queueItemMatch[3] === 'steer' ? 'steer' : null;
630
- if (!action) throw new RequestError(405, '不支持的队列操作');
631
- await requireExecutionWritable(threadId);
632
- sendJson(response, 200, await appServerEvents.changeQueue((method, params) => sharedServer.request(method, params), threadId, itemId, action)); return;
633
- }
634
- const queue = messageQueues.get(threadId) || [];
635
- const index = queue.findIndex(item => item.id === itemId);
636
- if (index < 0) {
637
- sendJson(response, 404, { error: '没有找到该排队消息。' });
638
- return;
639
- }
640
- if (request.method === 'DELETE' && !queueItemMatch[3]) {
641
- await requireExecutionWritable(threadId);
642
- queue.splice(index, 1);
643
- sendJson(response, 200, { items: publicQueue(threadId) });
644
- return;
645
- }
646
- if (request.method === 'POST' && queueItemMatch[3] === 'steer') {
647
- await requireExecutionWritable(threadId);
648
- const [item] = queue.splice(index, 1);
649
- const result = await steerQueuedItem(threadId, item);
650
- if (!result.success) {
651
- queue.splice(index, 0, item);
652
- sendJson(response, 502, { error: '插队发送失败。', details: result.data });
653
- return;
654
- }
655
- sendJson(response, 200, { items: publicQueue(threadId) });
656
- return;
657
- }
658
- }
659
-
660
- const takeoverMatch = /^\/api\/threads\/([^/]+)\/takeover$/.exec(url.pathname);
661
- if (request.method === 'POST' && takeoverMatch) {
662
- const threadId = decodeURIComponent(takeoverMatch[1]);
944
+ const body = await readJsonBody(request);
945
+ const result = await messageReceipts.submit([hostId, threadId], body.clientUserMessageId, async () => {
946
+ let args;
947
+ try { await requireExecutionWritable(threadId); args = appServerTurnArguments(threadId, body); }
948
+ catch (error) { error.deliveryUnknown = false; throw error; }
949
+ messageSubmissionStarted = true;
950
+ try { return await appServerEvents.addQueue(rpc, threadId, args.input, body.clientUserMessageId, body.containsVoiceInput); }
951
+ catch (error) { if (messageDelivery.rejectedBeforeSubmission(error)) error.deliveryUnknown = false; throw error; }
952
+ });
953
+ sendJson(response, 202, result); return;
954
+ }
955
+ }
956
+
957
+ const queueItemMatch = /^\/api\/threads\/([^/]+)\/queue\/([a-f0-9-]+)(?:\/(steer))?$/.exec(url.pathname);
958
+ if (queueItemMatch) {
959
+ if (hostId === 'durable') throw new RequestError(405, '当前托管连接不提供排队消息。');
960
+ const threadId = decodeURIComponent(queueItemMatch[1]);
961
+ const itemId = queueItemMatch[2];
962
+ await sharedServer.connect();
963
+ const action = request.method === 'DELETE' ? 'delete' : queueItemMatch[3] === 'steer' ? 'steer' : null;
964
+ if (!action) throw new RequestError(405, '不支持的队列操作');
663
965
  await requireExecutionWritable(threadId);
664
- if (appServerTasks.owns(threadId)) {
665
- sendJson(response, 200, { thread: (await appServerTasks.listTasks()).find(t => t.id === threadId) });
666
- return;
667
- }
668
- if (messageQueues.get(threadId)?.length || queueWorkers.has(threadId)) throw new RequestError(409, '请先处理此聊天的排队消息。');
669
- const result = await callTool('read_thread', { threadId, turnLimit: 1, includeOutputs: false });
670
- if (!result.success) throw new RequestError(502, '无法确认 Desktop 聊天状态。');
671
- const timeline = normalizeTimeline(result.data);
672
- const thread = timeline.thread;
673
- if (thread.hostId && thread.hostId !== 'local') throw new RequestError(400, '只支持本机聊天。');
674
- if (!['idle', 'notLoaded'].includes(thread.status) || timeline.turns.some(t => t.status === 'inProgress')) throw new RequestError(409, '只允许接管已空闲的聊天,请等当前轮次结束。');
675
- if (!findSessionPath(threadId)) throw new RequestError(400, '未找到此聊天的本机历史。');
676
- sendJson(response, 200, { thread: await appServerTasks.adoptTask({ ...thread, id: threadId }) });
677
- return;
678
- }
679
-
680
- const interruptMatch = /^\/api\/threads\/([^/]+)\/interrupt$/.exec(url.pathname);
966
+ sendJson(response, 200, await appServerEvents.changeQueue((method, params) => sharedServer.request(method, params), threadId, itemId, action)); return;
967
+ }
968
+
969
+ const interruptMatch = /^\/api\/threads\/([^/]+)\/interrupt$/.exec(url.pathname);
681
970
  if (request.method === 'POST' && interruptMatch) {
971
+ if (rootChat) throw new RequestError(405, 'Dots 主对话不提供停止 Codex 任务的操作。');
682
972
  const threadId = decodeURIComponent(interruptMatch[1]);
683
- await requireExecutionWritable(threadId);
684
- if (!appServerTasks.owns(threadId) && usesSharedTask({ hostId: url.searchParams.get('hostId') })) {
685
- const body = await readJsonBody(request);
686
- if (!body.turnId) throw new RequestError(400, '缺少活动轮次 ID。');
687
- await sharedServer.interrupt(threadId, body.turnId);
688
- sendJson(response, 200, { requested: true });
689
- return;
690
- }
691
- if (!appServerTasks.owns(threadId)) throw new RequestError(409, '请先接管空闲聊天。');
692
- const body = await readJsonBody(request);
693
- const thread = await appServerTasks.readTask(threadId, { turnLimit: 1 });
694
- const active = thread.turns?.at(-1);
695
- if (!body.turnId || active?.id !== body.turnId || statusText(active.status) !== 'inProgress') throw new RequestError(409, '此轮次已经结束或发生变化。');
696
- // Stop also clears pending messages so the queue cannot restart work.
697
- messageQueues.delete(threadId);
698
- await appServerTasks.interruptTurn(threadId, body.turnId);
699
- sendJson(response, 200, { requested: true });
700
- return;
701
- }
702
-
703
- const messageMatch = /^\/api\/threads\/([^/]+)\/messages$/.exec(url.pathname);
973
+ await requireExecutionWritable(threadId, execution);
974
+ const body = await readJsonBody(request);
975
+ if (!body.turnId) throw new RequestError(400, '缺少活动轮次 ID。');
976
+ await execution.interrupt(threadId, body.turnId);
977
+ sendJson(response, 200, { requested: true });
978
+ return;
979
+ }
980
+
981
+ const roomReadMatch = /^\/api\/threads\/([^/]+)\/read$/.exec(url.pathname);
982
+ if (request.method === 'POST' && roomReadMatch) {
983
+ if (!rootChat) throw new RequestError(405, '此对话不提供消息房间已读操作。');
984
+ await dotsChat.markRead(decodeURIComponent(roomReadMatch[1]), await readJsonBody(request));
985
+ sendJson(response, 200, { success: true });
986
+ return;
987
+ }
988
+
989
+ const messageStatusMatch = /^\/api\/threads\/([^/]+)\/messages\/([a-f0-9]{32})\/status$/.exec(url.pathname);
990
+ if (request.method === 'GET' && messageStatusMatch) {
991
+ const threadId = decodeURIComponent(messageStatusMatch[1]), clientId = messageStatusMatch[2];
992
+ const result = await messageReceipts.reconcile([hostId, threadId], clientId, async () => {
993
+ if (hostId !== 'durable') {
994
+ await sharedServer.connect();
995
+ const queue = await appServerEvents.readQueue((method, params) => sharedServer.request(method, params), threadId);
996
+ if (queue.items.some(item => messageDelivery.normalizeId(item.clientUserMessageId) === clientId)) return queue;
997
+ }
998
+ return execution.findSentMessage(threadId, clientId, url.searchParams.get('anchorItemId') || '');
999
+ });
1000
+ sendJson(response, 200, result);
1001
+ return;
1002
+ }
1003
+ const messageMatch = /^\/api\/threads\/([^/]+)\/messages$/.exec(url.pathname);
704
1004
  if (request.method === 'POST' && messageMatch) {
705
1005
  const threadId = decodeURIComponent(messageMatch[1]);
706
- await requireExecutionWritable(threadId);
707
- const body = await readJsonBody(request);
708
- if (!appServerTasks.owns(threadId) && usesSharedTask(body)) {
709
- sendJson(response, 200, { success: true, data: await sharedServer.send(appServerTurnArguments(threadId, body)) });
710
- } else if (appServerTasks.owns(threadId)) {
711
- const args = appServerTurnArguments(threadId, body);
712
- const current = await appServerTasks.readTask(threadId, { turnLimit: 1 });
713
- const active = current.turns?.at(-1);
714
- sendJson(response, 200, statusText(active?.status) === 'inProgress'
715
- ? await appServerTasks.steerTurn(threadId, active.id, args.input)
716
- : await appServerTasks.startTurn(threadId, args));
717
- } else {
718
- sendToolResult(response, await callTool('send_message_to_thread', await messageArguments(threadId, body)));
719
- }
720
- return;
721
- }
722
-
723
- if (request.method === 'POST' && url.pathname === '/api/wait') {
724
- const body = await readJsonBody(request);
725
- sendToolResult(response, await callTool('wait_threads', body, 130_000));
726
- return;
727
- }
728
-
729
- sendJson(response, 404, { error: '未找到请求的资源。' });
730
- } catch (error) {
731
- if (response.headersSent) {
732
- if (!response.writableEnded) response.end();
733
- return;
734
- }
735
- const status = error instanceof RequestError || error instanceof ThreadWriterConflictError ? (error.statusCode || 409) : 502;
1006
+ const body = await readJsonBody(request);
1007
+ if (body.hostId && body.hostId !== hostId) throw new RequestError(400, '请求中的对话主机不一致。');
1008
+ const result = await messageReceipts.submit([hostId, threadId], body.clientUserMessageId, async () => {
1009
+ let args;
1010
+ try { await requireExecutionWritable(threadId, execution); args = execution === dotsChat ? { ...body, threadId } : appServerTurnArguments(threadId, { ...body, hostId }); }
1011
+ catch (error) { error.deliveryUnknown = false; throw error; }
1012
+ messageSubmissionStarted = true;
1013
+ const responseFor = submitted => ({ success: true, data: submitted.result, usage: submitted.usage });
1014
+ try { return responseFor(await execution.send(args)); }
1015
+ catch (error) {
1016
+ if (error.deliveryResult) error.deliveryResult = error.deliveryResult.then(responseFor);
1017
+ throw error;
1018
+ }
1019
+ });
1020
+ sendJson(response, 200, result);
1021
+ return;
1022
+ }
1023
+
1024
+ sendJson(response, 404, { error: '未找到请求的资源。' });
1025
+ } catch (error) {
1026
+ if (response.headersSent) {
1027
+ if (!response.writableEnded) response.end();
1028
+ return;
1029
+ }
1030
+ if (!messageSubmissionStarted && error.deliveryUnknown === undefined && request.method === 'POST'
1031
+ && /^\/api\/threads\/[^/]+\/(?:messages|queue)(?:\?|$)/.test(request.url || '')) error.deliveryUnknown = false;
1032
+ const status = error instanceof RequestError || error instanceof ThreadWriterConflictError || error instanceof FileRangeError ? (error.statusCode || 409) : 502;
736
1033
  sendJson(response, status, {
737
1034
  error: error instanceof Error ? error.message : String(error),
738
1035
  code: error?.code,
739
1036
  threadId: error?.threadId,
740
1037
  readOnly: error?.readOnly,
741
- details: error?.details,
742
- });
743
- }
744
- }
745
-
746
- const server = http.createServer(handleRequest);
747
- const lanSocket = attachLanSocket(server, { token: options.token, dispatch: handleRequest,
748
- authorizeDevice: (clientId, credential) => authorizeLanDevice(stateRoot, clientId, credential),
749
- });
750
-
751
- if (options.appServer) {
752
- try {
753
- await appServer.start();
754
- } catch (error) {
755
- console.error(`app-server 启动失败:${error instanceof Error ? error.message : String(error)}`);
756
- process.exit(2);
757
- }
758
- }
759
-
1038
+ details: error?.deliveryUnknown === undefined ? error?.details : { ...error?.details, deliveryUnknown: error.deliveryUnknown },
1039
+ });
1040
+ }
1041
+ }
1042
+
1043
+ const server = http.createServer(handleRequest);
1044
+ const lanSocket = attachLanSocket(server, { token: options.token, dispatch: handleRequest,
1045
+ uploadsRoot,
1046
+ authorizeDevice: (clientId, credential) => authorizeLanDevice(stateRoot, clientId, credential),
1047
+ onDeviceAuthorizationsChanged: listener => onLanAuthorizationsChanged(stateRoot, listener),
1048
+ onPhoneConnected: (...args) => connectionReporting(stateRoot).connected(...args),
1049
+ onPhoneDisconnected: id => connectionReporting(stateRoot).disconnected(id),
1050
+ onDeviceConnected: (clientId, info) => {
1051
+ try { recordLanConnection(stateRoot, clientId, info); }
1052
+ catch (error) { console.error('记录局域网设备连接失败:', error.message); }
1053
+ },
1054
+ });
1055
+
760
1056
  let control;
761
- desktopMonitor.start();
1057
+ const powerManager = createPowerManager(stateRoot);
762
1058
  void managedAppServer.start();
763
1059
  executionHealth.start();
764
- server.on('error', error => {
765
- console.error('Bridge 监听失败:', error.message);
766
- process.exit(1);
767
- });
768
- server.listen(options.port, options.lan ? '0.0.0.0' : '127.0.0.1', async () => {
769
- try {
770
- if (process.env.CODEX_REMOTE_INSTANCE && process.env.CODEX_REMOTE_CONTROL_SECRET && process.env.CODEX_REMOTE_READY_PATH) {
1060
+ runtimeDiscovery.start();
1061
+ server.on('error', error => {
1062
+ console.error('桌面服务监听失败:', error.message);
1063
+ process.exit(1);
1064
+ });
1065
+ server.listen(options.port, options.lan ? '0.0.0.0' : '127.0.0.1', async () => {
1066
+ try {
1067
+ await powerManager.start();
1068
+ if (process.env.CODEX_REMOTE_INSTANCE && process.env.CODEX_REMOTE_CONTROL_SECRET && process.env.CODEX_REMOTE_READY_PATH) {
771
1069
  control = await openControl({ instanceId: process.env.CODEX_REMOTE_INSTANCE,
772
1070
  secret: process.env.CODEX_REMOTE_CONTROL_SECRET, shutdown: shutDown, actions: {
773
1071
  'refresh-execution': refreshExecution,
1072
+ 'keep-awake-status': () => powerManager.snapshot(),
1073
+ 'keep-awake-enable': () => powerManager.setEnabled(true),
1074
+ 'keep-awake-disable': () => powerManager.setEnabled(false),
1075
+ 'relay-start': () => relayManager.run('relay-start'),
1076
+ 'relay-status': () => relayManager.run('relay-status'),
1077
+ 'relay-stop': () => relayManager.run('relay-stop'),
1078
+ 'relay-pair': () => relayManager.run('relay-pair'),
1079
+ 'relay-revoke': () => relayManager.run('relay-revoke'),
1080
+ 'invite-create': details => relayManager.run('invite-create', details),
1081
+ 'invite-list': () => relayManager.run('invite-list'),
1082
+ 'invite-revoke': details => relayManager.run('invite-revoke', details),
1083
+ 'devices-list': () => relayManager.run('devices-list'),
1084
+ 'device-revoke': details => relayManager.run('device-revoke', details),
774
1085
  'restart-managed-app-server': () => {
775
1086
  const state = managedAppServer.snapshot();
776
1087
  return state.launcherRestartRequired ? managedAppServer.restart(crypto.randomUUID()) : state;
777
1088
  },
1089
+ 'restart-managed-app-server-confirmed': () => managedAppServer.restart(crypto.randomUUID()),
778
1090
  } });
779
- writeRecord(process.env.CODEX_REMOTE_READY_PATH, { instanceId: process.env.CODEX_REMOTE_INSTANCE,
780
- pid: process.pid, controlPort: control.port });
781
- }
782
- } catch (error) {
783
- console.error('Bridge 管理通道启动失败:', error.message);
784
- process.exit(1);
785
- }
786
- console.log(`Code Relax Bridge 已启动:http://127.0.0.1:${options.port}/`);
787
- if (options.lan) {
788
- console.log('LAN 模式使用鉴权 WebSocket,仅适合受信任局域网测试,不要暴露到公网。');
789
- }
790
- });
791
-
792
- let shuttingDown = false;
793
- async function shutDown() {
794
- if (shuttingDown) return;
795
- shuttingDown = true;
796
- lanSocket.close();
797
- server.close();
798
- desktopMonitor?.stop();
1091
+ writeRecord(process.env.CODEX_REMOTE_READY_PATH, { instanceId: process.env.CODEX_REMOTE_INSTANCE,
1092
+ pid: process.pid, controlPort: control.port });
1093
+ }
1094
+ } catch (error) {
1095
+ console.error('桌面服务管理通道启动失败:', error.message);
1096
+ process.exit(1);
1097
+ }
1098
+ console.log(`Code Relax 桌面服务已启动:http://127.0.0.1:${options.port}/`);
1099
+ if (options.lan) {
1100
+ console.log('LAN 模式使用鉴权 WebSocket,仅适合受信任局域网测试,不要暴露到公网。');
1101
+ }
1102
+ });
1103
+
1104
+ let shuttingDown = false;
1105
+ async function shutDown() {
1106
+ if (shuttingDown) return;
1107
+ shuttingDown = true;
1108
+ await powerManager.close();
1109
+ desktopHelper?.release();
1110
+ lanSocket.close();
1111
+ server.close();
799
1112
  executionHealth?.stop();
1113
+ runtimeDiscovery.stop();
1114
+ await relayManager.close();
1115
+ dotsServer.close();
1116
+ dotsChat.close();
800
1117
  managedAppServer.close();
801
- await appServer.close();
802
- await control?.close();
803
- process.exit(0);
804
- }
805
- process.on('SIGTERM', shutDown);
806
- process.on('SIGINT', shutDown);
807
-
808
- async function callTool(tool, argumentsValue, timeoutMs = 60_000, targetPipe) {
809
- if (!sourceThreadId) throw new RequestError(503, '基础控制尚未绑定调用上下文;请在 Codex 对话启动一次 Code Relax。电脑管理连接仍可使用。');
810
- if (!ALLOWED_TOOLS.has(tool)) {
811
- throw new RequestError(403, `工具 ${tool} 不在 Bridge 允许列表中。`);
812
- }
813
-
814
- const invocationId = crypto.randomUUID();
815
- const rpc = await callDesktop('tools/call', {
816
- arguments: argumentsValue,
817
- callId: `codex-remote-${invocationId}`,
818
- namespace: 'codex_app',
819
- threadId: sourceThreadId,
820
- tool,
821
- turnId: `codex-remote-turn-${invocationId}`,
822
- }, timeoutMs, targetPipe);
823
-
824
- const result = rpc.result;
825
- if (!result || typeof result.success !== 'boolean') {
826
- throw new BridgeError('Codex Desktop 没有返回有效的工具结果。', rpc);
827
- }
828
-
829
- const items = Array.isArray(result.contentItems) ? result.contentItems : [];
830
- let data = items;
831
- if (items.length === 1 && items[0]?.type === 'inputText' && typeof items[0].text === 'string') {
832
- try {
833
- data = JSON.parse(items[0].text);
834
- } catch {
835
- data = items[0].text;
836
- }
837
- }
838
-
839
- return { success: result.success, data };
840
- }
841
-
842
- function callDesktop(method, params, timeoutMs, targetPipe = desktopMonitor?.pipe || pipePath) {
843
- if (!targetPipe) return Promise.reject(new RequestError(503, 'Desktop 工具通道尚未就绪。'));
844
- return new Promise((resolve, reject) => {
845
- const socket = net.createConnection(targetPipe);
846
- const requestId = ++nextRequestId;
847
- const payload = Buffer.from(JSON.stringify({ jsonrpc: '2.0', id: requestId, method, params }), 'utf8');
848
- const header = Buffer.allocUnsafe(4);
849
- header.writeUInt32LE(payload.length, 0);
850
- let pending = Buffer.alloc(0);
851
- let expectedLength = null;
852
- let settled = false;
853
-
854
- const finish = (callback, value) => {
855
- if (settled) return;
856
- settled = true;
857
- clearTimeout(timer);
858
- socket.destroy();
859
- callback(value);
860
- };
861
-
862
- const timer = setTimeout(() => {
863
- finish(reject, new BridgeError('等待 Codex Desktop 响应超时。'));
864
- }, timeoutMs);
865
-
866
- socket.once('connect', () => socket.write(Buffer.concat([header, payload])));
867
- socket.on('data', chunk => {
868
- pending = Buffer.concat([pending, chunk]);
869
- if (expectedLength === null && pending.length >= 4) {
870
- expectedLength = pending.readUInt32LE(0);
871
- pending = pending.subarray(4);
872
- if (expectedLength <= 0 || expectedLength > MAX_FRAME_BYTES) {
873
- finish(reject, new BridgeError(`Codex Desktop 返回了无效帧长度:${expectedLength}。`));
874
- return;
875
- }
876
- }
877
-
878
- if (expectedLength !== null && pending.length >= expectedLength) {
879
- try {
880
- const responseValue = JSON.parse(pending.subarray(0, expectedLength).toString('utf8'));
881
- if (responseValue.error) {
882
- finish(reject, new BridgeError('Codex Desktop 工具调用失败。', responseValue.error));
883
- } else if (responseValue.id !== requestId) {
884
- finish(reject, new BridgeError('Codex Desktop 返回了不匹配的请求 ID。'));
885
- } else {
886
- finish(resolve, responseValue);
887
- }
888
- } catch (error) {
889
- finish(reject, new BridgeError('无法解析 Codex Desktop 响应。', undefined, error));
890
- }
891
- }
892
- });
893
- socket.once('error', error => finish(reject, new BridgeError(
894
- '无法连接 Codex Desktop Pipe。桌面版可能已重启,请重新启动 Bridge。',
895
- undefined,
896
- error,
897
- )));
898
- socket.once('close', () => {
899
- if (!settled) finish(reject, new BridgeError('Codex Desktop Pipe 在返回结果前关闭。'));
900
- });
901
- });
902
- }
903
-
904
- function sendToolResult(response, result) {
905
- if (result.success) {
906
- sendJson(response, 200, result.data);
907
- } else {
908
- sendJson(response, 502, { error: 'Codex Desktop 工具返回失败。', details: result.data });
909
- }
910
- }
911
-
912
- function appServerBridgeStatus() {
913
- return {
914
- ...appServer.snapshot(),
915
- platformFamily: appServer.initializeResult?.platformFamily || null,
916
- platformOs: appServer.initializeResult?.platformOs || null,
917
- taskCount: appServerTasks.store.list().length,
918
- ownershipError: appServerTasks.store.loadError?.message || null,
919
- };
920
- }
921
-
922
- function readThreadArguments(url, threadId, defaultTurnLimit, defaultOutputLimit = 4_000) {
923
- const argumentsValue = {
924
- threadId,
925
- turnLimit: boundedInteger(url.searchParams.get('turnLimit'), defaultTurnLimit, 1, 10),
926
- includeOutputs: booleanValue(url.searchParams.get('includeOutputs'), true),
927
- maxOutputCharsPerItem: boundedInteger(
928
- url.searchParams.get('maxOutputCharsPerItem'),
929
- defaultOutputLimit,
930
- 500,
931
- 100_000,
932
- ),
933
- };
934
- const hostId = url.searchParams.get('hostId')?.trim();
935
- if (hostId) argumentsValue.hostId = hostId;
936
- return argumentsValue;
937
- }
938
-
939
- function readContextUsage(threadId) {
940
- if (!/^[0-9a-f]{8}-[0-9a-f-]{20,55}$/i.test(threadId)) {
941
- return { available: false };
942
- }
943
- const sessionPath = findSessionPath(threadId);
944
- if (!sessionPath) return { available: false };
945
-
946
- let handle;
947
- try {
948
- const stat = fs.statSync(sessionPath);
949
- const readLength = Math.min(stat.size, MAX_CONTEXT_TAIL_BYTES);
950
- const buffer = Buffer.allocUnsafe(readLength);
951
- handle = fs.openSync(sessionPath, 'r');
952
- fs.readSync(handle, buffer, 0, readLength, stat.size - readLength);
953
- const lines = buffer.toString('utf8').split(/\r?\n/);
954
- let usage = null;
955
- let settings = null;
956
- for (let index = lines.length - 1; index >= 0; index -= 1) {
957
- let event;
958
- if (!usage && lines[index].includes('"token_count"')) {
959
- try { event = JSON.parse(lines[index]); } catch { event = null; }
960
- if (event?.type === 'event_msg' && event?.payload?.type === 'token_count') {
961
- const usedTokens = Number(event.payload.info?.last_token_usage?.total_tokens);
962
- const contextWindow = Number(event.payload.info?.model_context_window);
963
- if (Number.isFinite(usedTokens) && Number.isFinite(contextWindow) && contextWindow > 0) {
964
- usage = {
965
- available: true,
966
- usedTokens,
967
- contextWindow,
968
- remainingTokens: Math.max(0, contextWindow - usedTokens),
969
- usedPercent: Math.min(100, Math.max(0, usedTokens / contextWindow * 100)),
970
- updatedAt: event.timestamp || null,
971
- };
972
- }
973
- }
974
- }
975
- if (!settings && lines[index].includes('"type":"turn_context"')) {
976
- try { event = JSON.parse(lines[index]); } catch { event = null; }
977
- settings = turnSettings(event);
978
- }
979
- if (usage && settings) break;
980
- }
981
- settings ||= sessionSettingsCache.get(sessionPath) || readLatestTurnSettings(sessionPath);
982
- if (settings) sessionSettingsCache.set(sessionPath, settings);
983
- if (usage) return { ...usage, ...settings };
984
- if (settings) return { available: false, ...settings };
985
- } catch {
986
- sessionPathCache.delete(threadId);
987
- } finally {
988
- if (handle !== undefined) fs.closeSync(handle);
989
- }
990
- return { available: false };
991
- }
992
-
993
- function readLatestTurnSettings(sessionPath) {
994
- let handle;
995
- try {
996
- const stat = fs.statSync(sessionPath);
997
- const readLength = Math.min(stat.size, MAX_SETTINGS_SCAN_BYTES);
998
- const buffer = Buffer.allocUnsafe(readLength);
999
- handle = fs.openSync(sessionPath, 'r');
1000
- fs.readSync(handle, buffer, 0, readLength, stat.size - readLength);
1001
- const text = buffer.toString('utf8');
1002
- let searchFrom = text.length;
1003
- while (searchFrom > 0) {
1004
- const marker = text.lastIndexOf('"type":"turn_context"', searchFrom);
1005
- if (marker < 0) break;
1006
- const lineStart = text.lastIndexOf('\n', marker) + 1;
1007
- const nextLine = text.indexOf('\n', marker);
1008
- const lineEnd = nextLine < 0 ? text.length : nextLine;
1009
- let event;
1010
- try { event = JSON.parse(text.slice(lineStart, lineEnd)); } catch { event = null; }
1011
- const settings = turnSettings(event);
1012
- if (settings) return settings;
1013
- searchFrom = Math.max(0, lineStart - 1);
1014
- }
1015
- } catch {
1016
- return null;
1017
- } finally {
1018
- if (handle !== undefined) fs.closeSync(handle);
1019
- }
1020
- return null;
1021
- }
1022
-
1023
- function turnSettings(event) {
1024
- if (event?.type !== 'turn_context') return null;
1025
- const model = typeof event.payload?.model === 'string' && /^[a-z0-9.-]{1,80}$/i.test(event.payload.model)
1026
- ? event.payload.model
1027
- : '';
1028
- const thinking = typeof event.payload?.effort === 'string' && /^[a-z0-9_-]{1,32}$/i.test(event.payload.effort)
1029
- ? event.payload.effort
1030
- : '';
1031
- return model || thinking ? { model, thinking } : null;
1032
- }
1033
-
1034
- function findSessionPath(threadId) {
1035
- const cached = sessionPathCache.get(threadId);
1036
- if (cached && fs.existsSync(cached)) return cached;
1037
- if (!fs.existsSync(sessionsRoot)) return null;
1038
-
1039
- const suffix = `-${threadId}.jsonl`.toLowerCase();
1040
- const directories = [sessionsRoot];
1041
- while (directories.length) {
1042
- const directory = directories.pop();
1043
- let entries;
1044
- try {
1045
- entries = fs.readdirSync(directory, { withFileTypes: true });
1046
- } catch {
1047
- continue;
1048
- }
1049
- for (const entry of entries) {
1050
- const fullPath = path.join(directory, entry.name);
1051
- if (entry.isDirectory()) directories.push(fullPath);
1052
- if (entry.isFile() && entry.name.toLowerCase().endsWith(suffix)) {
1053
- sessionPathCache.set(threadId, fullPath);
1054
- return fullPath;
1055
- }
1056
- }
1057
- }
1058
- return null;
1059
- }
1060
-
1061
- function enrichTimelineWithLocalActivity(timeline, threadId) {
1062
- const turn = timeline.turns.at(-1);
1063
- if (!turn || turn.status !== 'inProgress') return;
1064
- if (turn.entries.some(entry => entry?.type === 'activity' && entry.status === 'inProgress')) return;
1065
- const pending = readPendingLocalActivity(threadId);
1066
- if (!pending || (pending.turnId && pending.turnId !== turn.id)) return;
1067
- turn.entries.push(pending.entry);
1068
- }
1069
-
1070
- function readPendingLocalActivity(threadId) {
1071
- const sessionPath = findSessionPath(threadId);
1072
- if (!sessionPath) return null;
1073
- let handle;
1074
- try {
1075
- const stat = fs.statSync(sessionPath);
1076
- const cacheKey = `${stat.size}:${stat.mtimeMs}`;
1077
- const cached = liveActivityCache.get(sessionPath);
1078
- if (cached?.key === cacheKey) return cached.value;
1079
- const readLength = Math.min(stat.size, MAX_LIVE_ACTIVITY_TAIL_BYTES);
1080
- const buffer = Buffer.allocUnsafe(readLength);
1081
- handle = fs.openSync(sessionPath, 'r');
1082
- fs.readSync(handle, buffer, 0, readLength, stat.size - readLength);
1083
- const lines = buffer.toString('utf8').split(/\r?\n/);
1084
- if (readLength < stat.size) lines.shift();
1085
- const pending = new Map();
1086
- for (const line of lines) {
1087
- if (!line.includes('"custom_tool_call')) continue;
1088
- let event;
1089
- try { event = JSON.parse(line); } catch { continue; }
1090
- if (event?.type !== 'response_item') continue;
1091
- const payload = event.payload;
1092
- const callId = payload?.call_id;
1093
- if (!callId) continue;
1094
- if (payload.type === 'custom_tool_call') pending.set(callId, payload);
1095
- if (payload.type === 'custom_tool_call_output') pending.delete(callId);
1096
- }
1097
- const payload = [...pending.values()].at(-1);
1098
- const value = payload ? pendingActivity(payload) : null;
1099
- liveActivityCache.set(sessionPath, { key: cacheKey, value });
1100
- return value;
1101
- } catch {
1102
- liveActivityCache.delete(sessionPath);
1103
- return null;
1104
- } finally {
1105
- if (handle !== undefined) fs.closeSync(handle);
1106
- }
1107
- }
1108
-
1109
- function pendingActivity(payload) {
1110
- const input = typeof payload.input === 'string' ? payload.input : '';
1111
- const nestedTool = input.match(/tools\.([A-Za-z0-9_]+)\s*\(/)?.[1] || payload.name || 'tool';
1112
- const id = `live:${payload.call_id}`;
1113
- const common = { type: 'activity', id, status: 'inProgress', transient: true };
1114
- const turnId = payload.internal_chat_message_metadata_passthrough?.turn_id || '';
1115
- if (nestedTool === 'exec_command') {
1116
- return {
1117
- turnId,
1118
- entry: {
1119
- ...common,
1120
- kind: 'command',
1121
- title: '命令',
1122
- command: jsonStringField(input, 'cmd'),
1123
- cwd: jsonStringField(input, 'workdir'),
1124
- },
1125
- };
1126
- }
1127
- if (nestedTool === 'apply_patch') {
1128
- return { turnId, entry: { ...common, kind: 'fileChange', title: '文件修改', changes: [] } };
1129
- }
1130
- if (nestedTool === 'web__run') {
1131
- return {
1132
- turnId,
1133
- entry: { ...common, kind: 'webSearch', title: '网页搜索', query: jsonStringField(input, 'q') },
1134
- };
1135
- }
1136
- if (nestedTool === 'view_image') {
1137
- return {
1138
- turnId,
1139
- entry: { ...common, kind: 'imageView', title: '查看图片', path: jsonStringField(input, 'path') },
1140
- };
1141
- }
1142
- const parts = nestedTool.replace(/^mcp__/, '').split('__');
1143
- return {
1144
- turnId,
1145
- entry: {
1146
- ...common,
1147
- kind: 'tool',
1148
- title: '工具',
1149
- server: parts.length > 1 ? parts.shift() : 'functions',
1150
- tool: parts.join('/') || nestedTool,
1151
- arguments: {},
1152
- },
1153
- };
1154
- }
1155
-
1156
- function jsonStringField(source, field) {
1157
- const pattern = new RegExp(`(?:["']${field}["']|${field})\\s*:\\s*("(?:\\\\.|[^"\\\\])*")`);
1158
- const match = pattern.exec(source);
1159
- if (!match) return '';
1160
- try { return JSON.parse(match[1]); } catch { return ''; }
1161
- }
1162
-
1163
- function messagePayload(body) {
1164
- const prompt = typeof body.prompt === 'string' ? body.prompt.trim() : '';
1165
- if (body.images !== undefined && (!Array.isArray(body.images) || body.images.length > 4)) {
1166
- throw new RequestError(400, '每条消息最多 4 张图片。');
1167
- }
1168
- const images = (body.images || []).map(id => {
1169
- if (typeof id === 'string' && id.startsWith('data:')) return validateInlineImage(id);
1170
- if (typeof id !== 'string' || !/^[a-f0-9]{64}$/.test(id)) throw new RequestError(400, '无效的图片标识。');
1171
- const webpFile = path.join(uploadsRoot, `${id}.webp`);
1172
- const file = fs.existsSync(webpFile) ? webpFile : path.join(uploadsRoot, `${id}.png`);
1173
- if (!fs.existsSync(file)) throw new RequestError(400, '图片不存在,请重新添加。');
1174
- return file;
1175
- });
1176
- if (images.filter(image => image.startsWith('data:')).reduce((size, image) => size + Buffer.byteLength(image.slice(image.indexOf(',') + 1), 'base64'), 0) > 8 * 1024 * 1024) throw new RequestError(413, '本条消息图片合计不能超过 8 MiB。');
1177
- if (!prompt && !images.length) throw new RequestError(400, 'prompt 不能为空。');
1178
- if (prompt.length > 32_000) throw new RequestError(400, 'prompt 不能超过 32000 个字符。');
1179
-
1180
- const payload = { prompt, images };
1181
- if (typeof body.hostId === 'string' && body.hostId.trim()) payload.hostId = body.hostId.trim();
1182
- if (typeof body.model === 'string' && body.model.trim()) {
1183
- const model = body.model.trim();
1184
- if (!ALLOWED_MODELS.has(model)) throw new RequestError(400, '不支持的模型。');
1185
- payload.model = model;
1186
- }
1187
- if (typeof body.thinking === 'string' && body.thinking.trim()) {
1188
- const thinking = body.thinking.trim();
1189
- if (!ALLOWED_THINKING.has(thinking)) throw new RequestError(400, '不支持的推理强度。');
1190
- if ((payload.model === 'gpt-5.5' || payload.model === 'gpt-5.3-codex-spark')
1191
- && (thinking === 'max' || thinking === 'ultra')) {
1192
- throw new RequestError(400, '所选模型不支持该推理强度。');
1193
- }
1194
- payload.thinking = thinking;
1195
- }
1196
- return payload;
1197
- }
1198
-
1199
- async function messageArguments(threadId, body) {
1200
- const payload = messagePayload(body);
1201
- payload.images = await Promise.all(payload.images.map(image => image.startsWith('data:') ? persistLegacyImage(image, uploadsRoot) : image));
1202
- let prompt = payload.prompt;
1203
- if (payload.images.length) {
1204
- prompt += (prompt ? '\n\n' : '') + payload.images
1205
- .map((file, index) => `![手机图片 ${index + 1}](${file.replaceAll('\\', '/')})`)
1206
- .join('\n\n');
1207
- }
1208
- const argumentsValue = { threadId, prompt };
1209
- if (payload.hostId) argumentsValue.hostId = payload.hostId;
1210
- if (payload.model) argumentsValue.model = payload.model;
1211
- if (payload.thinking) argumentsValue.thinking = payload.thinking;
1212
- return argumentsValue;
1213
- }
1214
-
1215
- function usesSharedTask(body) {
1216
- return Boolean(sharedServer && (!body.hostId || body.hostId === 'local'));
1217
- }
1218
-
1219
- function appServerTurnArguments(threadId, body) {
1220
- const payload = messagePayload(body);
1221
- const input = [];
1222
- if (payload.prompt) input.push({ type: 'text', text: payload.prompt });
1223
- for (const image of payload.images) input.push(image.startsWith('data:') ? { type: 'image', url: image } : { type: 'localImage', path: image });
1224
- const value = { threadId, input };
1225
- if (typeof body.clientUserMessageId === 'string' && /^[a-f0-9]{32}$/.test(body.clientUserMessageId)) value.clientUserMessageId = body.clientUserMessageId;
1226
- if (payload.model) value.model = payload.model;
1227
- if (payload.thinking) value.effort = payload.thinking;
1228
- return value;
1229
- }
1230
-
1231
- function messageDisplayText(body) {
1232
- const prompt = typeof body.prompt === 'string' ? body.prompt.trim() : '';
1233
- const imageCount = Array.isArray(body.images) ? body.images.length : 0;
1234
- return prompt || (imageCount ? `[${imageCount} 张图片]` : '');
1235
- }
1236
-
1237
- function createThreadBody(body) {
1238
- const payload = messagePayload(body);
1239
- return { ...body, prompt: payload.prompt, transport: body.transport === 'app-server' ? 'app-server' : 'desktop-tools' };
1240
- }
1241
-
1242
- function appServerProjectTarget(projectId, projects) {
1243
- if (projectId === '__unassigned__') return appServerEvents.projectlessThreadTarget(projectRoot);
1244
- const project = projects.find(item => item?.projectId === projectId);
1245
- if (!project) throw new RequestError(404, '没有找到对应项目。');
1246
- if (project.hostId && project.hostId !== 'local') {
1247
- throw new RequestError(400, '独立 app-server 当前只支持本机项目。');
1248
- }
1249
- if (typeof project.path !== 'string' || !path.isAbsolute(project.path)) {
1250
- throw new RequestError(400, '项目没有可用的本机路径。');
1251
- }
1252
- return { projectId, cwd: project.path };
1253
- }
1254
-
1255
- function mergeThreadCatalog(catalog, appThreads, limit, sharedSnapshot = Boolean(sharedServer)) {
1256
- const owned = new Map(appThreads.map(thread => [thread.id, thread]));
1257
- const pinnedThreads = (catalog.pinnedThreads || []).map(thread => owned.get(thread.id) || thread);
1258
- const pinnedIds = new Set(pinnedThreads.map(thread => thread.id));
1259
- if (sharedSnapshot) {
1260
- const listed = new Set([...pinnedIds, ...(catalog.threads || []).map(thread => thread.id)]);
1261
- const threads = (catalog.threads || []).map(thread => owned.get(thread.id) || thread);
1262
- threads.push(...appThreads.filter(thread => !listed.has(thread.id)));
1263
- threads.sort((left, right) => threadRecency(right) - threadRecency(left));
1264
- return { ...catalog, pinnedThreads: [], threads: threads.slice(0, limit) };
1118
+ await control?.close();
1119
+ process.exit(0);
1120
+ }
1121
+ process.on('SIGTERM', shutDown);
1122
+ process.on('SIGINT', shutDown);
1123
+
1124
+ function readContextUsage(threadId) {
1125
+ if (!/^[0-9a-f]{8}-[0-9a-f-]{20,55}$/i.test(threadId)) {
1126
+ return { available: false };
1127
+ }
1128
+ const sessionPath = findSessionPath(threadId);
1129
+ if (!sessionPath) return { available: false };
1130
+
1131
+ let handle;
1132
+ try {
1133
+ const stat = fs.statSync(sessionPath);
1134
+ const readLength = Math.min(stat.size, MAX_CONTEXT_TAIL_BYTES);
1135
+ const buffer = Buffer.allocUnsafe(readLength);
1136
+ handle = fs.openSync(sessionPath, 'r');
1137
+ fs.readSync(handle, buffer, 0, readLength, stat.size - readLength);
1138
+ const lines = buffer.toString('utf8').split(/\r?\n/);
1139
+ let usage = null;
1140
+ let settings = null;
1141
+ for (let index = lines.length - 1; index >= 0; index -= 1) {
1142
+ let event;
1143
+ if (!usage && lines[index].includes('"token_count"')) {
1144
+ try { event = JSON.parse(lines[index]); } catch { event = null; }
1145
+ if (event?.type === 'event_msg' && event?.payload?.type === 'token_count') {
1146
+ const usedTokens = Number(event.payload.info?.last_token_usage?.total_tokens);
1147
+ const contextWindow = Number(event.payload.info?.model_context_window);
1148
+ if (Number.isFinite(usedTokens) && Number.isFinite(contextWindow) && contextWindow > 0) {
1149
+ usage = {
1150
+ available: true,
1151
+ usedTokens,
1152
+ contextWindow,
1153
+ remainingTokens: Math.max(0, contextWindow - usedTokens),
1154
+ usedPercent: Math.min(100, Math.max(0, usedTokens / contextWindow * 100)),
1155
+ updatedAt: event.timestamp || null,
1156
+ };
1157
+ }
1158
+ }
1159
+ }
1160
+ if (!settings && lines[index].includes('"type":"turn_context"')) {
1161
+ try { event = JSON.parse(lines[index]); } catch { event = null; }
1162
+ settings = turnSettings(event);
1163
+ }
1164
+ if (usage && settings) break;
1165
+ }
1166
+ settings ||= sessionSettingsCache.get(sessionPath) || readLatestTurnSettings(sessionPath);
1167
+ if (settings) sessionSettingsCache.set(sessionPath, settings);
1168
+ if (usage) return { ...usage, ...settings };
1169
+ if (settings) return { available: false, ...settings };
1170
+ } catch {
1171
+ sessionPathCache.delete(threadId);
1172
+ } finally {
1173
+ if (handle !== undefined) fs.closeSync(handle);
1174
+ }
1175
+ return { available: false };
1176
+ }
1177
+
1178
+ function readLatestTurnSettings(sessionPath) {
1179
+ let handle;
1180
+ try {
1181
+ const stat = fs.statSync(sessionPath);
1182
+ const readLength = Math.min(stat.size, MAX_SETTINGS_SCAN_BYTES);
1183
+ const buffer = Buffer.allocUnsafe(readLength);
1184
+ handle = fs.openSync(sessionPath, 'r');
1185
+ fs.readSync(handle, buffer, 0, readLength, stat.size - readLength);
1186
+ const text = buffer.toString('utf8');
1187
+ let searchFrom = text.length;
1188
+ while (searchFrom > 0) {
1189
+ const marker = text.lastIndexOf('"type":"turn_context"', searchFrom);
1190
+ if (marker < 0) break;
1191
+ const lineStart = text.lastIndexOf('\n', marker) + 1;
1192
+ const nextLine = text.indexOf('\n', marker);
1193
+ const lineEnd = nextLine < 0 ? text.length : nextLine;
1194
+ let event;
1195
+ try { event = JSON.parse(text.slice(lineStart, lineEnd)); } catch { event = null; }
1196
+ const settings = turnSettings(event);
1197
+ if (settings) return settings;
1198
+ searchFrom = Math.max(0, lineStart - 1);
1199
+ }
1200
+ } catch {
1201
+ return null;
1202
+ } finally {
1203
+ if (handle !== undefined) fs.closeSync(handle);
1265
1204
  }
1266
- const desktopThreads = (catalog.threads || []).filter(thread => !owned.has(thread.id));
1267
- const merged = [...desktopThreads, ...appThreads.filter(thread => !pinnedIds.has(thread.id))]
1268
- .sort((left, right) => threadRecency(right) - threadRecency(left))
1269
- .slice(0, limit);
1270
- return { ...catalog, pinnedThreads, threads: merged };
1271
- }
1272
-
1273
- async function createThreadArguments(projectId, body, projects) {
1274
- const { threadId, hostId, ...message } = await messageArguments('', body);
1275
- if (projectId === '__unassigned__') return { ...message, target: { type:'projectless' } };
1276
- const project = projects.find(item => item?.projectId === projectId);
1277
- if (!project) throw new RequestError(404, '没有找到对应项目。');
1278
- return { ...message, target:{ type:'project', projectId, environment:{ type:'local' } } };
1279
- }
1280
-
1281
- function publicQueue(threadId) {
1282
- return (messageQueues.get(threadId) || []).map(item => ({
1283
- id: item.id,
1284
- prompt: item.displayPrompt || item.prompt,
1285
- text: item.text, attachments:item.attachments || [],
1286
- model: item.model || '',
1287
- thinking: item.thinking || item.effort || '',
1288
- createdAt: item.createdAt,
1289
- error: item.error || '',
1290
- }));
1291
- }
1292
-
1293
- function queueArguments(item) {
1294
- const value = { threadId: item.threadId, prompt: item.prompt };
1295
- if (item.hostId) value.hostId = item.hostId;
1296
- if (item.model) value.model = item.model;
1297
- if (item.thinking) value.thinking = item.thinking;
1298
- return value;
1299
- }
1300
-
1301
- async function sendQueuedItem(threadId, item) {
1302
- await requireExecutionWritable(threadId);
1303
- if (item.transport === 'app-server') {
1304
- try {
1305
- return { success: true, data: await appServerTasks.startTurn(threadId, item) };
1306
- } catch (error) {
1307
- return { success: false, data: error instanceof Error ? error.message : String(error) };
1308
- }
1309
- }
1310
- return callTool('send_message_to_thread', queueArguments(item));
1311
- }
1312
-
1313
- async function steerQueuedItem(threadId, item) {
1314
- if (item.transport !== 'app-server') return callTool('send_message_to_thread', queueArguments(item));
1315
- try {
1316
- const thread = await appServerTasks.readTask(threadId, { includeTurns: true, turnLimit: 1 });
1317
- const activeTurn = thread.turns?.at(-1);
1318
- if (!activeTurn || statusText(activeTurn.status) !== 'inProgress') {
1319
- throw new Error('当前没有可引导的 app-server 活动轮次。');
1320
- }
1321
- return {
1322
- success: true,
1323
- data: await appServerTasks.steerTurn(threadId, activeTurn.id, item.input),
1324
- };
1325
- } catch (error) {
1326
- return { success: false, data: error instanceof Error ? error.message : String(error) };
1327
- }
1328
- }
1329
-
1330
- function ensureQueueWorker(threadId) {
1331
- if (queueWorkers.has(threadId)) return;
1332
- const worker = runQueueWorker(threadId)
1333
- .catch(error => {
1334
- const item = messageQueues.get(threadId)?.[0];
1335
- if (item) item.error = error instanceof Error ? error.message : String(error);
1336
- })
1337
- .finally(() => queueWorkers.delete(threadId));
1338
- queueWorkers.set(threadId, worker);
1339
- }
1340
-
1341
- async function runQueueWorker(threadId) {
1342
- while (true) {
1343
- const queue = messageQueues.get(threadId) || [];
1344
- const item = queue[0];
1345
- if (!item || item.error) return;
1346
- let timeline;
1347
- if (item.transport === 'app-server') {
1348
- const thread = await appServerTasks.readTask(threadId, { includeTurns: true, turnLimit: 1 });
1349
- timeline = normalizeAppServerTimeline(thread);
1350
- } else {
1351
- const readArguments = {
1352
- threadId,
1353
- turnLimit: 1,
1354
- includeOutputs: false,
1355
- maxOutputCharsPerItem: 500,
1356
- };
1357
- if (item.hostId) readArguments.hostId = item.hostId;
1358
- const threadResult = await callTool('read_thread', readArguments, 15_000);
1359
- if (!threadResult.success) throw new BridgeError('读取排队任务状态失败。', threadResult.data);
1360
- timeline = normalizeTimeline(threadResult.data);
1361
- }
1362
- const active = timeline.thread.status === 'active' || timeline.turns.at(-1)?.status === 'inProgress';
1363
- if (active) {
1364
- await delay(2_000);
1365
- continue;
1366
- }
1367
- if (messageQueues.get(threadId) !== queue || queue[0]?.id !== item.id) continue;
1368
- const sendResult = await sendQueuedItem(threadId, item);
1369
- if (!sendResult.success) throw new BridgeError('发送排队消息失败。', sendResult.data);
1370
- if (queue[0]?.id === item.id) queue.shift();
1371
- }
1372
- }
1373
-
1374
- async function streamAppServerThreadEvents(request, response, threadId) {
1375
- response.startEvents();
1376
-
1377
- let closed = false;
1378
- let pushPending = false;
1379
- let pushing = false;
1380
- let previousHash = '';
1381
- let updateTimer = null;
1382
- let resolveClosed;
1383
- const closedPromise = new Promise(resolve => { resolveClosed = resolve; });
1384
- response.once('close', () => { closed = true; resolveClosed(); });
1385
-
1386
- const pushUpdate = async () => {
1387
- if (closed) return;
1388
- if (pushing) { pushPending = true; return; }
1389
- pushing = true;
1390
- try {
1391
- const thread = await appServerTasks.readTask(threadId, { includeTurns: true, turnLimit: 2 });
1392
- const timeline = normalizeAppServerTimeline(thread);
1393
- const update = {
1394
- thread: timeline.thread,
1395
- turns: timeline.turns.slice(-2),
1396
- turn: timeline.turns.at(-1) || null,
1397
- queue: publicQueue(threadId),
1398
- };
1399
- const serialized = JSON.stringify(update);
1400
- const hash = crypto.createHash('sha256').update(serialized).digest('base64url');
1401
- if (hash !== previousHash) {
1402
- sendEvent(response, 'update', update);
1403
- previousHash = hash;
1404
- }
1405
- } catch (error) {
1406
- sendEvent(response, 'bridge-error', { error: error instanceof Error ? error.message : String(error) });
1407
- } finally {
1408
- pushing = false;
1409
- if (pushPending && !closed) {
1410
- pushPending = false;
1411
- void pushUpdate();
1412
- }
1413
- }
1414
- };
1415
- const scheduleUpdate = event => {
1416
- if (closed || event.threadId !== threadId || updateTimer) return;
1417
- updateTimer = setTimeout(() => {
1418
- updateTimer = null;
1419
- void pushUpdate();
1420
- }, 60);
1421
- };
1422
- appServerTasks.on('thread-event', scheduleUpdate);
1423
- await pushUpdate();
1424
-
1425
- await closedPromise;
1426
- closed = true;
1427
- if (updateTimer) clearTimeout(updateTimer);
1428
- appServerTasks.off('thread-event', scheduleUpdate);
1429
- if (!response.writableEnded) response.end();
1430
- }
1431
-
1432
- async function streamThreadEvents(request, response, threadId, hostId) {
1433
- response.startEvents();
1434
-
1435
- let closed = false;
1436
- let previousHash = '';
1437
- response.once('close', () => { closed = true; });
1438
-
1439
- while (!closed) {
1440
- let intervalMs = 2_500;
1441
- try {
1442
- const argumentsValue = {
1443
- threadId,
1444
- turnLimit: 2,
1445
- includeOutputs: true,
1446
- maxOutputCharsPerItem: 4_000,
1447
- };
1448
- if (hostId) argumentsValue.hostId = hostId;
1449
- const result = await callTool('read_thread', argumentsValue);
1450
- if (!result.success) {
1451
- throw new BridgeError('Codex Desktop 工具返回失败。', result.data);
1452
- }
1453
-
1454
- const timeline = normalizeTimeline(result.data);
1455
- enrichTimelineWithLocalActivity(timeline, threadId);
1456
- const update = {
1457
- thread: timeline.thread,
1458
- turns: timeline.turns.slice(-2),
1459
- turn: timeline.turns.at(-1) || null,
1460
- queue: publicQueue(threadId),
1461
- };
1462
- const serialized = JSON.stringify(update);
1463
- const hash = crypto.createHash('sha256').update(serialized).digest('base64url');
1464
- if (hash !== previousHash) {
1465
- sendEvent(response, 'update', update);
1466
- previousHash = hash;
1467
- }
1468
- if (timeline.thread.status === 'active' || update.turn?.status === 'inProgress') {
1469
- intervalMs = 1_000;
1470
- }
1471
- } catch (error) {
1472
- sendEvent(response, 'bridge-error', {
1473
- error: error instanceof Error ? error.message : String(error),
1474
- });
1475
- intervalMs = 3_000;
1476
- }
1477
-
1478
- await delay(intervalMs);
1479
- }
1480
-
1481
- if (!response.writableEnded) response.end();
1482
- }
1483
-
1484
- function sendEvent(response, eventName, value) {
1485
- if (response.destroyed || response.writableEnded) return;
1486
- response.event(eventName, value);
1487
- }
1488
-
1489
- function normalizeTimeline(data, reverseTurns = true) {
1490
- const source = data && typeof data === 'object' ? data : {};
1491
- const sourceTurns = Array.isArray(source.turns) ? source.turns : source.thread?.turns;
1492
- const turns = Array.isArray(sourceTurns) ? sourceTurns.map(normalizeTurn) : [];
1493
- if (reverseTurns) turns.reverse();
1494
- if (reverseTurns) restoreEmptyTurnMessages(source.thread?.id, turns);
1495
- return {
1496
- schemaVersion: 1,
1497
- thread: normalizeThread(source.thread),
1498
- turns,
1499
- };
1500
- }
1501
-
1502
- function normalizeAppServerTimeline(thread) {
1503
- const record = appServerTasks.store.get(thread?.id);
1504
- return normalizeTimeline({
1505
- thread: {
1506
- ...thread,
1507
- title: thread?.name || thread?.title || record?.title || '',
1508
- projectId: record?.projectId || '',
1509
- hostId: 'app-server',
1510
- transport: 'app-server',
1511
- },
1512
- }, false);
1513
- }
1514
-
1515
- function normalizeSharedTimeline({ thread, nextCursor, sync }) {
1516
- return { ...normalizeTimeline({ thread: { ...thread, title: thread.name || thread.title || '', hostId: 'local', transport: 'shared-app-server' } }, false),
1517
- historyView: 'grouped-lazy-skeleton-v11',
1205
+ return null;
1206
+ }
1207
+
1208
+ function turnSettings(event) {
1209
+ if (event?.type !== 'turn_context') return null;
1210
+ const model = typeof event.payload?.model === 'string' && /^[a-z0-9.-]{1,80}$/i.test(event.payload.model)
1211
+ ? event.payload.model
1212
+ : '';
1213
+ const thinking = typeof event.payload?.effort === 'string' && /^[a-z0-9_-]{1,32}$/i.test(event.payload.effort)
1214
+ ? event.payload.effort
1215
+ : '';
1216
+ return model || thinking ? { model, thinking } : null;
1217
+ }
1218
+
1219
+ function findSessionPath(threadId) {
1220
+ const cached = sessionPathCache.get(threadId);
1221
+ if (cached && fs.existsSync(cached)) return cached;
1222
+ if (!fs.existsSync(sessionsRoot)) return null;
1223
+
1224
+ const suffix = `-${threadId}.jsonl`.toLowerCase();
1225
+ const directories = [sessionsRoot];
1226
+ while (directories.length) {
1227
+ const directory = directories.pop();
1228
+ let entries;
1229
+ try {
1230
+ entries = fs.readdirSync(directory, { withFileTypes: true });
1231
+ } catch {
1232
+ continue;
1233
+ }
1234
+ for (const entry of entries) {
1235
+ const fullPath = path.join(directory, entry.name);
1236
+ if (entry.isDirectory()) directories.push(fullPath);
1237
+ if (entry.isFile() && entry.name.toLowerCase().endsWith(suffix)) {
1238
+ sessionPathCache.set(threadId, fullPath);
1239
+ return fullPath;
1240
+ }
1241
+ }
1242
+ }
1243
+ return null;
1244
+ }
1245
+
1246
+ function messagePayload(body) {
1247
+ const prompt = typeof body.prompt === 'string' ? body.prompt.trim() : '';
1248
+ if (body.images !== undefined && (!Array.isArray(body.images) || body.images.length > 4)) {
1249
+ throw new RequestError(400, '每条消息最多 4 张图片。');
1250
+ }
1251
+ const images = (body.images || []).map(id => {
1252
+ if (typeof id === 'string' && id.startsWith('data:')) return validateInlineImage(id);
1253
+ if (typeof id !== 'string' || !/^[a-f0-9]{64}$/.test(id)) throw new RequestError(400, '无效的图片标识。');
1254
+ const webpFile = path.join(uploadsRoot, `${id}.webp`);
1255
+ const file = fs.existsSync(webpFile) ? webpFile : path.join(uploadsRoot, `${id}.png`);
1256
+ if (!fs.existsSync(file)) throw new RequestError(400, '图片不存在,请重新添加。');
1257
+ return file;
1258
+ });
1259
+ for (const input of fileMentions.originalInputs(body)) {
1260
+ const incoming = fs.realpathSync(path.join(uploadsRoot, 'incoming'));
1261
+ const imagePath = fs.realpathSync(input.path);
1262
+ if (path.dirname(imagePath).toLowerCase() !== incoming.toLowerCase()) throw new RequestError(400, '原图必须来自上传目录。');
1263
+ const stat = fs.statSync(imagePath);
1264
+ if (!stat.isFile() || stat.size <= 0 || stat.size > MAX_ATTACHMENT_BYTES) throw new RequestError(400, '原图为空或超过 20 MiB。');
1265
+ images.push(imagePath);
1266
+ }
1267
+ if (images.filter(image => image.startsWith('data:')).reduce((size, image) => size + Buffer.byteLength(image.slice(image.indexOf(',') + 1), 'base64'), 0) > 8 * 1024 * 1024) throw new RequestError(413, '本条消息图片合计不能超过 8 MiB。');
1268
+ if (!prompt && !images.length) throw new RequestError(400, 'prompt 不能为空。');
1269
+ if (prompt.length > 32_000) throw new RequestError(400, 'prompt 不能超过 32000 个字符。');
1270
+
1271
+ const payload = { prompt, images };
1272
+ if (typeof body.hostId === 'string' && body.hostId.trim()) payload.hostId = body.hostId.trim();
1273
+ if (typeof body.model === 'string' && body.model.trim()) {
1274
+ payload.model = body.model.trim();
1275
+ }
1276
+ if (typeof body.thinking === 'string' && body.thinking.trim()) {
1277
+ const thinking = body.thinking.trim();
1278
+ if (!ALLOWED_THINKING.has(thinking)) throw new RequestError(400, '不支持的推理强度。');
1279
+ if ((payload.model === 'gpt-5.5' || payload.model === 'gpt-5.3-codex-spark')
1280
+ && (thinking === 'max' || thinking === 'ultra')) {
1281
+ throw new RequestError(400, '所选模型不支持该推理强度。');
1282
+ }
1283
+ payload.thinking = thinking;
1284
+ }
1285
+ return payload;
1286
+ }
1287
+
1288
+ function appServerTurnArguments(threadId, body) {
1289
+ const payload = messagePayload(body);
1290
+ const mentioned = payload.hostId === 'durable' ? fileMentions.parse(payload.prompt) : null;
1291
+ if (mentioned?.files.length) payload.prompt = mentioned.text;
1292
+ const input = [];
1293
+ if (payload.prompt) input.push({ type: 'text', text: payload.prompt });
1294
+ for (const image of payload.images) {
1295
+ if (image.startsWith('data:')) input.push({ type: 'image', url: image });
1296
+ else if (payload.hostId === 'durable') {
1297
+ const mimeType = imageMimeType(image);
1298
+ if (!mimeType) throw new RequestError(400, '云端图片格式无效。');
1299
+ input.push({ type: 'image', url: `data:${mimeType};base64,${fs.readFileSync(image).toString('base64')}` });
1300
+ } else input.push({ type: 'localImage', path: image });
1301
+ }
1302
+ const value = { threadId, input, ...appServerEvents.mobileTurnSource(body.clientUserMessageId, body.containsVoiceInput) };
1303
+ if (mentioned?.files.length) value.uploadFiles = mentioned.files;
1304
+ if (typeof body.clientUserMessageId === 'string' && /^[a-f0-9]{32}$/.test(body.clientUserMessageId)) value.clientUserMessageId = body.clientUserMessageId;
1305
+ if (payload.model) value.model = payload.model;
1306
+ if (payload.thinking) value.effort = payload.thinking;
1307
+ return value;
1308
+ }
1309
+
1310
+ function normalizeTimeline(data) {
1311
+ const source = data && typeof data === 'object' ? data : {};
1312
+ const sourceTurns = Array.isArray(source.turns) ? source.turns : source.thread?.turns;
1313
+ const turns = Array.isArray(sourceTurns) ? sourceTurns.map(normalizeTurn) : [];
1314
+ return {
1315
+ schemaVersion: 1,
1316
+ thread: normalizeThread(source.thread),
1317
+ turns,
1318
+ };
1319
+ }
1320
+
1321
+ function normalizeSharedTimeline({ thread, nextCursor, sync, notModified, incremental }, hostId = thread?.hostId || 'local') {
1322
+ const timeline = normalizeTimeline({ thread: { ...thread, title: thread.name || thread.title || '', hostId,
1323
+ transport: 'shared-app-server' } });
1324
+ return { ...timeline,
1325
+ historyItemCount: (thread.turns || []).reduce((count, turn) => count + (turn.roomMessage ? 1 : turn.items?.length || 0), 0),
1326
+ historyView: thread?.threadSource === 'aeon' ? 'chat-items-v1' : 'item-pages-v1',
1327
+ ...(notModified ? { notModified: true } : {}),
1328
+ ...(incremental ? { incremental: true } : {}),
1518
1329
  ...(nextCursor !== undefined ? { nextCursor } : {}), ...(sync ? { sync } : {}) };
1519
1330
  }
1520
-
1521
- function restoreEmptyTurnMessages(threadId, turns) {
1522
- const missing = new Map(turns.filter(turn => turn.entries.length === 0).map(turn => [turn.id, turn]));
1523
- if (!missing.size || !threadId) return;
1524
- const sessionPath = findSessionPath(threadId);
1525
- if (!sessionPath) return;
1526
- const handle = fs.openSync(sessionPath, 'r');
1527
- try {
1528
- const size = fs.fstatSync(handle).size;
1529
- const length = Math.min(size, MAX_SETTINGS_SCAN_BYTES);
1530
- const buffer = Buffer.allocUnsafe(length);
1531
- fs.readSync(handle, buffer, 0, length, size - length);
1532
- const lines = buffer.toString('utf8').split(/\r?\n/);
1533
- if (length < size) lines.shift();
1534
- let turnId;
1535
- for (const line of lines) {
1536
- if (!line.trim()) continue;
1537
- let event;
1538
- try { event = JSON.parse(line); } catch { continue; }
1539
- const item = event.payload;
1540
- if (event.type === 'event_msg' && item?.type === 'task_started') turnId = item.turn_id;
1541
- if (event.type === 'turn_context') turnId = item?.turn_id;
1542
- const turn = missing.get(turnId);
1543
- if (!turn || event.type !== 'response_item') continue;
1544
- let entry;
1545
- if (item.type === 'function_call_output' && item.namespace === 'codex_app' && ['create_thread', 'send_message_to_thread'].includes(item.name)) {
1546
- const text = extractDelegatedInput(item.output);
1547
- if (text) entry = { type: 'message', id: item.id, role: 'user', source: 'remote', ...splitUploadedImages(text) };
1548
- } else if (item.type === 'message' && ['user', 'assistant'].includes(item.role) && item.channel !== 'analysis') {
1549
- const text = (item.content || []).filter(part => ['input_text', 'output_text'].includes(part.type)).map(part => part.text || '').join('\n');
1550
- if (text) entry = item.role === 'user'
1551
- ? normalizeItem({ type: 'userMessage', id: item.id, content: item.content.map(part => ({ ...part, type: part.type === 'input_text' ? 'text' : part.type })) })
1552
- : normalizeItem({ type: 'agentMessage', id: item.id, phase: item.phase || item.channel, text });
1553
- }
1554
- if (entry) turn.entries.push(entry);
1555
- }
1556
- } finally { fs.closeSync(handle); }
1557
- }
1558
-
1331
+
1332
+ async function prepareSharedTimeline(value, hostId) {
1333
+ if (value.thread?.threadSource !== 'aeon') return normalizeSharedTimeline(value, hostId);
1334
+ const epoch = dotsChat.epoch, threadId = value.thread.id;
1335
+ const current = () => {
1336
+ if (epoch !== dotsChat.epoch) throw new Error('点点连接已变化,请重新同步。');
1337
+ };
1338
+ if (value.notModified) {
1339
+ const record = dotsChat.threads.get(threadId);
1340
+ if (await dotsImages.prepareTimeline(record?.thread)) {
1341
+ current();
1342
+ value = dotsChat.snapshot(threadId, record);
1343
+ }
1344
+ }
1345
+ const snapshot = structuredClone(value);
1346
+ await dotsImages.prepareTimeline(snapshot.thread);
1347
+ current();
1348
+ return normalizeSharedTimeline(snapshot, hostId);
1349
+ }
1350
+
1559
1351
  function normalizeThread(thread) {
1560
- const source = thread && typeof thread === 'object' ? thread : {};
1561
- return {
1352
+ const source = thread && typeof thread === 'object' ? thread : {};
1353
+ return {
1562
1354
  id: source.id || '',
1563
1355
  title: source.name || source.title || source.preview || '',
1564
1356
  status: statusText(source.status),
1565
1357
  updatedAt: Number(source.updatedAt) || 0,
1566
1358
  recencyAt: Number(source.recencyAt ?? source.updatedAt) || 0,
1567
1359
  projectId: source.projectId || '',
1568
- cwd: source.cwd || '',
1569
- hostId: source.hostId || '',
1570
- transport: source.transport || (source.hostId === 'app-server' ? 'app-server' : 'desktop-tools'),
1360
+ cwd: source.cwd || '',
1361
+ hostId: source.hostId || '',
1362
+ threadSource: source.threadSource || '',
1363
+ ...(['aeon', 'aeon_child'].includes(source.threadSource) ? { chatRevision: source.chatRevision || '' } : {}),
1364
+ ...(source.threadSource === 'aeon' ? { avatar: source.avatar || '', color: source.color || '',
1365
+ chatMessageRevision: source.chatMessageRevision, messagingRoomId: source.messagingRoomId, lastMessageAt: source.lastMessageAt } : {}),
1366
+ transport: source.transport || 'shared-app-server',
1571
1367
  readOnly: source.readOnly === true,
1572
1368
  readOnlyReason: source.readOnlyReason || '',
1573
- };
1574
- }
1575
-
1369
+ };
1370
+ }
1371
+
1576
1372
  function normalizeTurn(turn) {
1577
1373
  const source = turn && typeof turn === 'object' ? turn : {};
1578
1374
  const turnId = source.id || '';
@@ -1584,10 +1380,16 @@ function normalizeTurn(turn) {
1584
1380
  return {
1585
1381
  id: turnId,
1586
1382
  status: statusText(source.status),
1587
- startedAt: source.startedAt || null,
1588
- completedAt: source.completedAt || null,
1589
- durationMs: source.durationMs ?? null,
1383
+ startedAt: source.startedAt || null,
1384
+ completedAt: source.completedAt || null,
1385
+ durationMs: source.durationMs ?? null,
1590
1386
  error: source.error ?? null,
1387
+ runtimeNotice: source.runtimeNotice ?? null,
1388
+ ...(source.itemPage ? { itemPage: source.itemPage } : {}),
1389
+ ...(source.roomMessage ? { roomMessage: true } : {}),
1390
+ ...(source.deleted ? { deleted: true } : {}),
1391
+ ...(source.historyOnly ? { historyOnly: true } : {}),
1392
+ ...(source.cacheStub ? { cacheStub: true } : {}),
1591
1393
  entries,
1592
1394
  };
1593
1395
  }
@@ -1595,70 +1397,61 @@ function normalizeTurn(turn) {
1595
1397
  function normalizeItem(item) {
1596
1398
  if (!item || typeof item !== 'object') return null;
1597
1399
 
1598
- if (item.type === 'lazyOperationGroup') {
1400
+ if (item.type === 'approval') {
1401
+ return { ...item, type: 'activity', kind: 'approval', title: item.params?.message || '需要你的批准' };
1402
+ }
1403
+
1404
+ if (item.type === 'contextCompaction') {
1599
1405
  return {
1600
1406
  type: 'activity',
1601
1407
  id: item.id || '',
1602
- kind: 'lazyOperationGroup',
1603
- turnId: item.turnId || '',
1604
- groupId: item.groupId || '',
1605
- title: item.title || '执行过程',
1606
- count: Number(item.count) || 0,
1408
+ kind: 'contextCompaction',
1409
+ title: '已自动压缩上下文',
1607
1410
  };
1608
1411
  }
1609
-
1610
- if (item.type === 'contextCompaction') {
1611
- return {
1612
- type: 'activity',
1613
- id: item.id || '',
1614
- kind: 'contextCompaction',
1615
- title: '已自动压缩上下文',
1616
- };
1617
- }
1618
-
1619
- if (item.type === 'functionCallOutput'
1620
- && item.namespace === 'codex_app'
1621
- && ['create_thread', 'send_message_to_thread'].includes(item.name)) {
1622
- const text = extractDelegatedInput(delegatedOutputText(item.output));
1623
- if (text) {
1624
- return {
1625
- type: 'message',
1626
- id: item.id || '',
1627
- role: 'user',
1628
- source: 'remote',
1629
- ...splitUploadedImages(text),
1630
- };
1631
- }
1632
- }
1633
-
1634
- if (item.type === 'userMessage') {
1635
- const text = extractUserText(item.content);
1636
- const attachments = normalizeAttachments(item.content);
1637
- if (!text && !attachments.length) return null;
1638
- return {
1639
- clientId: item.clientId,
1640
- type: 'message',
1641
- id: item.id || '',
1642
- role: 'user',
1643
- text,
1644
- attachments,
1645
- };
1646
- }
1647
-
1648
- if (item.type === 'agentMessage') {
1649
- return {
1650
- type: 'message',
1651
- id: item.id || '',
1652
- role: 'assistant',
1653
- phase: item.phase || '',
1654
- delivery: item.delivery,
1655
- questions: item.questions,
1656
- text: item.text || '',
1657
- imageAssets: markdownImageAssets(item.text || ''),
1658
- fileAssets: markdownFileAssets(item.text || ''),
1659
- };
1660
- }
1661
-
1412
+
1413
+ const delegated = appServerEvents.toolDelegation(item);
1414
+ if (delegated) {
1415
+ return {
1416
+ type: 'message', id: item.id || '', role: 'user', source: 'remote',
1417
+ delegation: delegationSource(delegated), ...splitUploadedImages(delegated.input),
1418
+ };
1419
+ }
1420
+
1421
+ if (item.type === 'userMessage') {
1422
+ const { text, files, delegation } = extractUserText(item.content);
1423
+ const attachments = [...normalizeAttachments(item.content), ...files.map(file => file.fileId ? normalizeAttachment(file) : ({
1424
+ ...file,
1425
+ remotePath: file.path,
1426
+ previewRemotePath: file.previewPath || '',
1427
+ }))];
1428
+ if (!text && !attachments.length) return null;
1429
+ return {
1430
+ clientId: item.clientId,
1431
+ type: 'message',
1432
+ id: item.id || '',
1433
+ role: 'user',
1434
+ text,
1435
+ attachments,
1436
+ ...(delegation ? { delegation: delegationSource(delegation) } : {}),
1437
+ };
1438
+ }
1439
+
1440
+ if (item.type === 'agentMessage') {
1441
+ return {
1442
+ type: 'message',
1443
+ id: item.id || '',
1444
+ role: 'assistant',
1445
+ phase: item.phase || '',
1446
+ delivery: item.delivery,
1447
+ questions: item.questions,
1448
+ text: item.text || '',
1449
+ attachments: normalizeAttachments(item.attachments),
1450
+ imageAssets: markdownImageAssets(item.text || ''),
1451
+ fileAssets: markdownFileAssets(item.text || ''),
1452
+ };
1453
+ }
1454
+
1662
1455
  if (item.type === 'reasoning') {
1663
1456
  const detail = Array.isArray(item.summary)
1664
1457
  ? item.summary.map(summaryText).filter(Boolean).join('\n')
@@ -1671,7 +1464,7 @@ function normalizeItem(item) {
1671
1464
  detail,
1672
1465
  };
1673
1466
  }
1674
-
1467
+
1675
1468
  if (item.type === 'commandExecution') {
1676
1469
  return {
1677
1470
  type: 'activity',
@@ -1727,10 +1520,10 @@ function normalizeItem(item) {
1727
1520
  title: '文件修改',
1728
1521
  titleOnly: true,
1729
1522
  status: statusText(item.status),
1730
- changes: Array.isArray(item.changes) ? item.changes : [],
1523
+ changes: fileChanges.summarize(item.changes),
1731
1524
  };
1732
1525
  }
1733
-
1526
+
1734
1527
  if (item.type === 'webSearch') {
1735
1528
  return {
1736
1529
  type: 'activity',
@@ -1744,6 +1537,9 @@ function normalizeItem(item) {
1744
1537
  }
1745
1538
 
1746
1539
  if (item.type === 'imageView') {
1540
+ const image = typeof item.path === 'string' && item.path
1541
+ ? normalizeAttachment({ type: 'localImage', path: item.path })
1542
+ : null;
1747
1543
  return {
1748
1544
  type: 'activity',
1749
1545
  id: item.id || '',
@@ -1751,16 +1547,16 @@ function normalizeItem(item) {
1751
1547
  title: '查看图片',
1752
1548
  titleOnly: true,
1753
1549
  path: item.path || '',
1754
- image: typeof item.path === 'string' && item.path
1755
- ? normalizeAttachment({ type: 'localImage', path: item.path })
1756
- : null,
1550
+ image,
1757
1551
  };
1758
1552
  }
1759
1553
 
1760
1554
  if (item.type === 'imageGeneration') {
1555
+ const result = typeof item.result === 'string' ? item.result.trim() : '';
1761
1556
  const image = typeof item.savedPath === 'string' && item.savedPath
1762
1557
  ? normalizeAttachment({ type: 'localImage', path: item.savedPath, name: '生成的图片' })
1763
- : null;
1558
+ : result ? normalizeAttachment({ type: 'image', name: '生成的图片',
1559
+ url: result.startsWith('data:image/') ? result : `data:image/png;base64,${result}` }) : null;
1764
1560
  return {
1765
1561
  type: 'activity',
1766
1562
  id: item.id || '',
@@ -1797,17 +1593,21 @@ function normalizeItem(item) {
1797
1593
  status: statusText(item.status),
1798
1594
  tool: item.tool || '',
1799
1595
  prompt: item.prompt || '',
1596
+ senderThreadId: item.senderThreadId || '',
1800
1597
  receiverThreadIds: Array.isArray(item.receiverThreadIds) ? item.receiverThreadIds : [],
1598
+ model: item.model || '',
1599
+ reasoningEffort: item.reasoningEffort || '',
1600
+ agentsStates: item.agentsStates && typeof item.agentsStates === 'object' ? item.agentsStates : {},
1801
1601
  };
1802
1602
  }
1803
-
1603
+
1804
1604
  return {
1805
- type: 'activity',
1806
- id: item.id || '',
1807
- kind: item.type || 'event',
1808
- title: item.type || '事件',
1809
- status: statusText(item.status),
1810
- detail: JSON.stringify(item, null, 2),
1605
+ type: 'activity',
1606
+ id: item.id || '',
1607
+ kind: item.type || 'event',
1608
+ title: item.type || '事件',
1609
+ status: statusText(item.status),
1610
+ detail: JSON.stringify(item, null, 2),
1811
1611
  };
1812
1612
  }
1813
1613
 
@@ -1819,142 +1619,148 @@ function sharedHistoryPageOptions() {
1819
1619
  };
1820
1620
  }
1821
1621
 
1822
- function threadRecency(thread) {
1823
- return Number(thread?.recencyAt ?? thread?.updatedAt) || 0;
1622
+ function normalizeAttachments(content) {
1623
+ if (!Array.isArray(content)) return [];
1624
+ const items = content.filter(item => item && !['text', 'input_text'].includes(item.type)).map(item => ({ ...item }));
1625
+ const text = content.filter(item => ['text', 'input_text'].includes(item?.type)).map(item => item.text || '').join('\n');
1626
+ const paths = [...new Set([...text.matchAll(/<image\b[^>]*\bpath="([^"]+)"[^>]*>/g)].map(match => match[1]))];
1627
+ const images = items.filter(item => ['image', 'input_image', 'localImage'].includes(item.type));
1628
+ for (const [index, imagePath] of paths.entries()) {
1629
+ if (images.some(item => item.path === imagePath)) continue;
1630
+ if (images[index] && !images[index].path) images[index].path = imagePath;
1631
+ else items.push({ type: 'localImage', path: imagePath });
1632
+ }
1633
+ const seen = new Set();
1634
+ return items.map(item => normalizeAttachment(item)).filter(item => {
1635
+ if (!item.url) return true;
1636
+ if (seen.has(item.url)) return false;
1637
+ seen.add(item.url); return true;
1638
+ });
1639
+ }
1640
+
1641
+ function markdownImageAssets(text) {
1642
+ const assets = Object.create(null);
1643
+ function visit(tokens) {
1644
+ for (const token of tokens) {
1645
+ const value = token.type === 'image' ? token.attrGet('src')
1646
+ : token.type === 'link_open' && !token.meta?.fileCitation ? token.attrGet('href') : null;
1647
+ if (value && !Object.hasOwn(assets, value)) {
1648
+ const localPath = fileCitations.localReference(value)?.path;
1649
+ // Only paths actually referenced by Desktop messages become accessible.
1650
+ // Do not expose a client-supplied filesystem path endpoint or UNC shares.
1651
+ if (localPath && /^[a-z]:[\\/]/i.test(localPath)) {
1652
+ const asset = normalizeAttachment({ type: 'localImage', path: localPath });
1653
+ if (asset.available) assets[value] = asset;
1654
+ }
1655
+ }
1656
+ if (token.children && token.type !== 'image') visit(token.children);
1657
+ }
1658
+ }
1659
+ visit(imageMarkdown.parse(text, { allowFileCitations: true }));
1660
+ return assets;
1824
1661
  }
1825
1662
 
1826
- function normalizeAttachments(content) {
1827
- if (!Array.isArray(content)) return [];
1828
- const items = content.filter(item => item && !['text', 'input_text'].includes(item.type)).map(item => ({ ...item }));
1829
- const text = content.filter(item => ['text', 'input_text'].includes(item?.type)).map(item => item.text || '').join('\n');
1830
- const paths = [...new Set([...text.matchAll(/<image\b[^>]*\bpath="([^"]+)"[^>]*>/g)].map(match => match[1]))];
1831
- const images = items.filter(item => ['image', 'input_image', 'localImage'].includes(item.type));
1832
- for (const [index, imagePath] of paths.entries()) {
1833
- if (images.some(item => item.path === imagePath)) continue;
1834
- if (images[index] && !images[index].path) images[index].path = imagePath;
1835
- else items.push({ type: 'localImage', path: imagePath });
1836
- }
1837
- const seen = new Set();
1838
- return items.map(normalizeAttachment).filter(item => {
1839
- if (!item.url) return true;
1840
- if (seen.has(item.url)) return false;
1841
- seen.add(item.url); return true;
1842
- });
1843
- }
1844
-
1845
- function markdownImageAssets(text) {
1846
- const assets = Object.create(null);
1847
- function visit(tokens) {
1848
- for (const token of tokens) {
1849
- const value = token.type === 'image' ? token.attrGet('src')
1850
- : token.type === 'link_open' ? token.attrGet('href') : null;
1851
- if (value && !Object.hasOwn(assets, value)) {
1852
- let localPath;
1853
- try { localPath = decodeURIComponent(value); } catch { continue; }
1854
- // Only paths actually referenced by Desktop messages become accessible.
1855
- // Do not expose a client-supplied filesystem path endpoint or UNC shares.
1856
- if (/^\/?[a-z]:[\\/]/i.test(localPath)) {
1857
- localPath = localPath.replace(/^\/(?=[a-z]:)/i, '');
1858
- const asset = normalizeAttachment({ type: 'localImage', path: localPath });
1859
- if (asset.available) assets[value] = asset;
1860
- }
1861
- }
1862
- if (token.children) visit(token.children);
1863
- }
1864
- }
1865
- visit(imageMarkdown.parse(text, {}));
1866
- return assets;
1867
- }
1868
-
1869
1663
  function markdownFileAssets(text) {
1870
- const assets = Object.create(null);
1871
- function visit(tokens) {
1872
- for (const token of tokens) {
1873
- if (token.type === 'link_open') {
1874
- const value = token.attrGet('href');
1875
- if (value && !Object.hasOwn(assets, value)) {
1876
- let localPath;
1877
- try { localPath = decodeURIComponent(value); } catch { localPath = ''; }
1878
- if (/^\/?[a-z]:[\\/]/i.test(localPath)) {
1879
- localPath = localPath.replace(/^\/(?=[a-z]:)/i, '');
1880
- localPath = localPath.replace(/:(\d+)(?::\d+)?$/, '');
1881
- const asset = rememberLocalFile(localPath);
1882
- if (asset) assets[value] = { remotePath: asset.id, name: asset.name, size: asset.size };
1883
- }
1884
- }
1885
- }
1886
- if (token.children) visit(token.children);
1887
- }
1888
- }
1889
- visit(imageMarkdown.parse(text, {}));
1890
- return assets;
1891
- }
1892
-
1893
- function rememberLocalFile(filePath) {
1894
- try {
1895
- const resolved = path.resolve(filePath);
1896
- const stat = fs.statSync(resolved);
1897
- if (!stat.isFile() || stat.size > MAX_ATTACHMENT_BYTES) return null;
1898
- const id = crypto.createHmac('sha256', fileCapabilitySecret)
1899
- .update(resolved).update('\0').update(String(stat.size)).update('\0').update(String(stat.mtimeMs)).digest('hex');
1900
- if (!fileCache.has(id)) {
1901
- while (fileCache.size >= MAX_CACHED_FILES) fileCache.delete(fileCache.keys().next().value);
1902
- fileCache.set(id, { id, path: resolved, name: path.basename(resolved), size: stat.size, mtimeMs: stat.mtimeMs });
1903
- }
1904
- return fileCache.get(id);
1905
- } catch {
1906
- return null;
1907
- }
1908
- }
1909
-
1910
- function splitUploadedImages(text) {
1911
- const tokens = imageMarkdown.parse(text, {});
1912
- const lines = text.split('\n');
1913
- const removed = new Set();
1914
- const attachments = [];
1915
- for (let index = 0; index < tokens.length - 1; index++) {
1916
- const block = tokens[index];
1917
- const inline = tokens[index + 1];
1918
- if (block.type !== 'paragraph_open' || !block.map || inline.type !== 'inline') continue;
1919
- const children = (inline.children || []).filter(token => token.type !== 'softbreak');
1920
- if (!children.length || children.some(token => token.type !== 'image')) continue;
1921
- const paths = children.map(token => {
1922
- try { return path.resolve(decodeURIComponent(token.attrGet('src') || '')); } catch { return ''; }
1923
- });
1924
- if (paths.some(file => !allowedUploadsRoots.some(root => path.dirname(file).toLowerCase() === root.toLowerCase())
1925
- || !/^[a-f0-9]{64}\.(png|webp)$/.test(path.basename(file)))) continue;
1926
- for (const file of paths) attachments.push(normalizeAttachment({ type: 'localImage', path: file, name: '手机图片' }));
1927
- for (let line = block.map[0]; line < block.map[1]; line++) removed.add(line);
1928
- }
1929
- const visibleText = lines.filter((line, index) => !removed.has(index)).join('\n').trim();
1930
- return { text: visibleText, attachments, imageAssets: markdownImageAssets(visibleText) };
1931
- }
1932
-
1933
- function normalizeAttachment(item) {
1934
- const name = item.name || item.fileName || (typeof item.path === 'string' ? path.basename(item.path) : '');
1935
- let cached = null;
1936
- const isImage = ['image', 'input_image', 'localImage'].includes(item.type);
1937
- if (isImage && typeof item.path === 'string') {
1938
- cached = cacheLocalImage(item.path, name);
1939
- }
1940
- if (!cached) {
1941
- const dataUrl = typeof item.url === 'string' && item.url.startsWith('data:image/')
1942
- ? item.url
1943
- : (typeof item.image_url === 'string' && item.image_url.startsWith('data:image/') ? item.image_url : '');
1944
- if (dataUrl) cached = cacheDataImage(dataUrl, name);
1945
- }
1946
- return {
1947
- type: isImage ? 'image' : item.type || 'attachment',
1948
- name,
1949
- mimeType: cached?.mimeType || item.mimeType || '',
1950
- available: Boolean(cached),
1951
- remotePath: cached ? cached.id : '',
1664
+ const assets = Object.create(null);
1665
+ function visit(tokens) {
1666
+ for (const token of tokens) {
1667
+ if (token.type === 'link_open') {
1668
+ const value = token.attrGet('href');
1669
+ if (value && !Object.hasOwn(assets, value)) {
1670
+ const localPath = fileCitations.localReference(value)?.path;
1671
+ if (localPath && /^[a-z]:[\\/]/i.test(localPath)) {
1672
+ assets[value] = { remotePath: localPath, name: path.basename(localPath) };
1673
+ // Link rendering must not depend on file existence or transient backend state.
1674
+ }
1675
+ }
1676
+ }
1677
+ if (token.children && token.type !== 'image') visit(token.children);
1678
+ }
1679
+ }
1680
+ visit(imageMarkdown.parse(text, { allowFileCitations: true }));
1681
+ return assets;
1682
+ }
1683
+
1684
+ function describeLocalFile(filePath) {
1685
+ const resolved = path.resolve(filePath);
1686
+ let stat;
1687
+ try { stat = fs.statSync(resolved); }
1688
+ catch (error) {
1689
+ if (error.code === 'ENOENT') throw new RequestError(404, '电脑上的文件已不存在。');
1690
+ if (error.code === 'EACCES' || error.code === 'EPERM') throw new RequestError(403, '电脑系统无法读取此文件:' + error.code);
1691
+ throw error;
1692
+ }
1693
+ if (!stat.isFile()) throw new RequestError(400, '此路径不是文件。');
1694
+ return { path: resolved, name: path.basename(resolved), size: stat.size, revision: fileRevision(stat) };
1695
+ }
1696
+
1697
+ function splitUploadedImages(text) {
1698
+ const tokens = imageMarkdown.parse(text, {});
1699
+ const lines = text.split('\n');
1700
+ const removed = new Set();
1701
+ const attachments = [];
1702
+ for (let index = 0; index < tokens.length - 1; index++) {
1703
+ const block = tokens[index];
1704
+ const inline = tokens[index + 1];
1705
+ if (block.type !== 'paragraph_open' || !block.map || inline.type !== 'inline') continue;
1706
+ const children = (inline.children || []).filter(token => token.type !== 'softbreak');
1707
+ if (!children.length || children.some(token => token.type !== 'image')) continue;
1708
+ const paths = children.map(token => {
1709
+ try { return path.resolve(decodeURIComponent(token.attrGet('src') || '')); } catch { return ''; }
1710
+ });
1711
+ if (paths.some(file => path.dirname(file).toLowerCase() !== uploadsRoot.toLowerCase()
1712
+ || !/^[a-f0-9]{64}\.(png|webp)$/.test(path.basename(file)))) continue;
1713
+ for (const file of paths) attachments.push(normalizeAttachment({ type: 'localImage', path: file, name: '移动端图片' }));
1714
+ for (let line = block.map[0]; line < block.map[1]; line++) removed.add(line);
1715
+ }
1716
+ const visibleText = lines.filter((line, index) => !removed.has(index)).join('\n').trim();
1717
+ return { text: visibleText, attachments, imageAssets: markdownImageAssets(visibleText) };
1718
+ }
1719
+
1720
+ async function externalDownloadAsset(value, recovery = '') {
1721
+ const match = /^([a-f0-9]{64})(?:\?recovery=([A-Za-z0-9_-]{1,4096}))?$/.exec(value || '');
1722
+ if (!match) throw new RequestError(400, '无效的外部文件标识。');
1723
+ const asset = attachmentCache.get(match[1]) || dotsImages.restore(match[1], match[2] || recovery || '');
1724
+ if (!asset?.dotsFile) throw new RequestError(404, '外部文件授权已失效,请重新读取对话。');
1725
+ await dotsImages.authorization(asset.identity);
1726
+ return asset;
1727
+ }
1728
+
1729
+ function workstationCopyReference(copy, asset) {
1730
+ const id = crypto.createHmac('sha256', fileCapabilitySecret).update(JSON.stringify([asset.id, copy.path, copy.revision])).digest('hex');
1731
+ fileCache.set(id, { ...copy, id, externalIdentity: asset.identity });
1732
+ while (fileCache.size > MAX_CACHED_FILES) fileCache.delete(fileCache.keys().next().value);
1733
+ return { remotePath: id, path: copy.path, name: copy.name, size: copy.size, mimeType: copy.mimeType };
1734
+ }
1735
+
1736
+ function normalizeAttachment(item, identity) {
1737
+ const name = item.name || item.fileName || (typeof item.path === 'string' ? path.basename(item.path) : '');
1738
+ let cached = null;
1739
+ const isImage = ['image', 'input_image', 'localImage'].includes(item.type);
1740
+ if (isImage || item.type === 'file') cached = dotsImages.remember(item, identity);
1741
+ if (isImage && typeof item.path === 'string') {
1742
+ cached = cacheLocalImage(item.path, name) || cached;
1743
+ }
1744
+ if (!cached) {
1745
+ const dataUrl = typeof item.url === 'string' && item.url.startsWith('data:image/')
1746
+ ? item.url
1747
+ : (typeof item.image_url === 'string' && item.image_url.startsWith('data:image/') ? item.image_url : '');
1748
+ if (dataUrl) cached = cacheDataImage(dataUrl, name);
1749
+ }
1750
+ return {
1751
+ type: isImage ? 'image' : item.type || 'attachment',
1752
+ name: cached?.name || name,
1753
+ mimeType: cached?.mimeType || item.mimeType || '',
1754
+ ...(cached?.size ? { size: cached.size } : {}),
1755
+ ...(cached?.dotsFile ? { networkFile: true } : {}),
1756
+ available: Boolean(cached),
1757
+ remotePath: cached ? (isImage ? cached.id : `${cached.id}?recovery=${cached.recoveryToken}`) : '',
1952
1758
  recoveryToken: cached?.recoveryToken || '',
1953
- url: '',
1954
- thumbnailUrl: '',
1955
- };
1956
- }
1957
-
1759
+ url: '',
1760
+ thumbnailUrl: '',
1761
+ };
1762
+ }
1763
+
1958
1764
  function cacheLocalImage(filePath, name) {
1959
1765
  try {
1960
1766
  const resolved = path.resolve(filePath);
@@ -1965,28 +1771,28 @@ function cacheLocalImage(filePath, name) {
1965
1771
  localImageRecoveryToken(resolved, stat));
1966
1772
  void attachmentThumbnail(cached).catch(() => {});
1967
1773
  return cached;
1968
- } catch {
1969
- return null;
1970
- }
1971
- }
1972
-
1973
- function cacheDataImage(dataUrl, name) {
1974
- const match = /^data:(image\/(?:png|jpeg|gif|webp|bmp));base64,([a-z0-9+/=\r\n]+)$/i.exec(dataUrl);
1975
- if (!match) return null;
1976
- try {
1977
- const buffer = Buffer.from(match[2], 'base64');
1978
- if (!buffer.length || buffer.length > MAX_ATTACHMENT_BYTES) return null;
1979
- return rememberAttachment(buffer, match[1].toLowerCase(), name);
1980
- } catch {
1981
- return null;
1982
- }
1983
- }
1984
-
1774
+ } catch {
1775
+ return null;
1776
+ }
1777
+ }
1778
+
1779
+ function cacheDataImage(dataUrl, name) {
1780
+ const match = /^data:(image\/(?:png|jpeg|gif|webp|bmp));base64,([a-z0-9+/=\r\n]+)$/i.exec(dataUrl);
1781
+ if (!match) return null;
1782
+ try {
1783
+ const buffer = Buffer.from(match[2], 'base64');
1784
+ if (!buffer.length || buffer.length > MAX_ATTACHMENT_BYTES) return null;
1785
+ return rememberAttachment(buffer, match[1].toLowerCase(), name);
1786
+ } catch {
1787
+ return null;
1788
+ }
1789
+ }
1790
+
1985
1791
  function rememberAttachment(buffer, mimeType, name, recoveryToken = '') {
1986
1792
  const id = crypto.createHash('sha256').update(buffer).digest('hex');
1987
1793
  if (!attachmentCache.has(id)) {
1988
- while (attachmentCache.size >= MAX_CACHED_ATTACHMENTS) {
1989
- attachmentCache.delete(attachmentCache.keys().next().value);
1794
+ while (attachmentCache.size >= MAX_CACHED_ATTACHMENTS) {
1795
+ attachmentCache.delete(attachmentCache.keys().next().value);
1990
1796
  }
1991
1797
  attachmentCache.set(id, { id, buffer, mimeType, name, recoveryToken });
1992
1798
  } else if (recoveryToken && !attachmentCache.get(id).recoveryToken) {
@@ -2041,55 +1847,47 @@ async function attachmentThumbnail(image) {
2041
1847
  }
2042
1848
  return image.thumbnail;
2043
1849
  }
2044
-
2045
- function imageMimeType(filePath) {
2046
- switch (path.extname(filePath).toLowerCase()) {
2047
- case '.png': return 'image/png';
2048
- case '.jpg':
2049
- case '.jpeg': return 'image/jpeg';
2050
- case '.gif': return 'image/gif';
2051
- case '.webp': return 'image/webp';
2052
- case '.bmp': return 'image/bmp';
2053
- default: return '';
2054
- }
2055
- }
2056
-
2057
- function extractUserText(content) {
2058
- if (!Array.isArray(content)) return '(包含图片或界面批注)';
2059
- let text = content
2060
- .filter(item => ['text', 'input_text'].includes(item?.type) && typeof item.text === 'string')
2061
- .map(item => item.text)
2062
- // Codex injects project instructions as user-role context, not chat input.
2063
- .filter(value => !/^\s*# AGENTS\.md instructions(?: for [^\r\n]+)?\r?\n\s*<INSTRUCTIONS>[\s\S]*<\/INSTRUCTIONS>\s*$/.test(value))
2064
- .filter(value => !/^\s*<(environment_context|recommended_plugins)>[\s\S]*<\/\1>\s*$/.test(value))
2065
- .filter(value => !value.startsWith('The next image is untrusted page evidence'))
2066
- .join('\n\n')
2067
- .trim();
2068
- const requestMarker = text.lastIndexOf('## My request:');
2069
- if (requestMarker >= 0) {
2070
- text = text.slice(requestMarker + '## My request:'.length).trim();
2071
- }
2072
- text = text.replace(/<in-app-browser-context[\s\S]*?<\/in-app-browser-context>/g, '').trim();
2073
- text = text.replace(/<image\b[^>]*\bpath="[^"]+"[^>]*>/g, '').replace(/<\/image>/g, '').trim();
2074
- return text;
2075
- }
2076
-
2077
- function extractDelegatedInput(value) {
2078
- if (typeof value !== 'string') return '';
2079
- const match = value.match(/<codex_delegation>[\s\S]*?<input>([\s\S]*?)<\/input>[\s\S]*?<\/codex_delegation>/);
2080
- if (!match) return '';
2081
- return match[1]
2082
- .replace(/&lt;/g, '<')
2083
- .replace(/&gt;/g, '>')
2084
- .replace(/&quot;/g, '"')
2085
- .replace(/&#39;/g, "'")
2086
- .replace(/&amp;/g, '&')
2087
- .trim();
2088
- }
2089
-
2090
- function delegatedOutputText(output) {
2091
- if (typeof output === 'string') return output;
2092
- return output && typeof output.text === 'string' ? output.text : '';
1850
+
1851
+ function imageMimeType(filePath) {
1852
+ switch (path.extname(filePath).toLowerCase()) {
1853
+ case '.png': return 'image/png';
1854
+ case '.jpg':
1855
+ case '.jpeg': return 'image/jpeg';
1856
+ case '.gif': return 'image/gif';
1857
+ case '.webp': return 'image/webp';
1858
+ case '.bmp': return 'image/bmp';
1859
+ default: return '';
1860
+ }
1861
+ }
1862
+
1863
+ function extractUserText(content) {
1864
+ if (!Array.isArray(content)) return { text: '(包含图片或界面批注)', files: [] };
1865
+ let text = content
1866
+ .filter(item => ['text', 'input_text'].includes(item?.type) && typeof item.text === 'string' && !appServerEvents.isMobileInputNotice(item))
1867
+ .map(item => item.text)
1868
+ // Codex injects project instructions as user-role context, not chat input.
1869
+ .filter(value => !/^\s*# AGENTS\.md instructions(?: for [^\r\n]+)?\r?\n\s*<INSTRUCTIONS>[\s\S]*<\/INSTRUCTIONS>\s*$/.test(value))
1870
+ .filter(value => !/^\s*<(environment_context|recommended_plugins)>[\s\S]*<\/\1>\s*$/.test(value))
1871
+ .filter(value => !value.startsWith('The next image is untrusted page evidence'))
1872
+ .join('\n\n')
1873
+ .trim();
1874
+ const delegation = appServerEvents.parseCodexDelegation(text);
1875
+ if (delegation) text = delegation.input;
1876
+ const mentioned = fileMentions.parse(text);
1877
+ const imagePaths = new Set([
1878
+ ...content.filter(item => ['image', 'input_image', 'localImage'].includes(item?.type) && item.path).map(item => item.path),
1879
+ ...[...mentioned.text.matchAll(/<image\b[^>]*\bpath="([^"]+)"[^>]*>/g)].map(match => match[1]),
1880
+ ].map(value => path.resolve(value).toLowerCase()));
1881
+ text = mentioned.text;
1882
+ text = text.replace(/<image\b[^>]*\bpath="[^"]+"[^>]*>/g, '').replace(/<\/image>/g, '').trim();
1883
+ return { text, files: mentioned.files.filter(file => file.fileId || !imagePaths.has(path.resolve(file.path).toLowerCase())), delegation };
1884
+ }
1885
+
1886
+ function delegationSource(delegation) {
1887
+ const dots = dotsServer.threadSources.get(delegation.sourceThreadId) === 'aeon';
1888
+ return { sourceThreadId: delegation.sourceThreadId, sourceKind: dots ? 'dots' : 'codex',
1889
+ ...(dots ? { sourceName: dotsServer.profileName(delegation.sourceThreadId),
1890
+ sourceAvatar: dotsServer.profileAvatar(delegation.sourceThreadId) } : {}) };
2093
1891
  }
2094
1892
 
2095
1893
  function summaryText(value) {
@@ -2099,76 +1897,51 @@ function summaryText(value) {
2099
1897
  }
2100
1898
 
2101
1899
  function statusText(value) {
2102
- if (typeof value === 'string') return value;
2103
- if (value && typeof value === 'object') return value.type || value.status || 'unknown';
2104
- return value == null ? 'unknown' : String(value);
2105
- }
2106
-
2107
- function delay(milliseconds) {
2108
- return new Promise(resolve => setTimeout(resolve, milliseconds));
2109
- }
2110
-
2111
- function sendJson(response, statusCode, value) {
2112
- if (response.json) { response.json(statusCode, value); return; }
2113
- sendBuffer(response, statusCode, 'application/json; charset=utf-8', Buffer.from(JSON.stringify(value)));
2114
- }
2115
-
2116
- function sendBuffer(response, statusCode, contentType, value) {
2117
- response.writeHead(statusCode, {
2118
- 'Content-Type': contentType,
2119
- 'Content-Length': value.length,
2120
- 'Cache-Control': 'no-store',
2121
- 'X-Content-Type-Options': 'nosniff',
2122
- 'Content-Security-Policy': "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; connect-src 'self'; img-src 'self' data:",
2123
- });
2124
- response.end(value);
2125
- }
2126
-
2127
- async function readJsonBody(request) {
2128
- return request.body;
2129
- }
2130
-
2131
- function parseOptions(args) {
2132
- let port = Number.parseInt(process.env.CODEX_REMOTE_BRIDGE_PORT || '45831', 10);
2133
- let lan = process.env.CODEX_REMOTE_BRIDGE_LAN === 'true';
2134
- let token = process.env.CODEX_REMOTE_BRIDGE_TOKEN?.trim() || '';
2135
- let appServer = process.env.CODEX_REMOTE_APP_SERVER === 'true';
2136
- for (let i = 0; i < args.length; i += 1) {
2137
- if (args[i] === '--lan') lan = true;
2138
- if (args[i] === '--app-server') appServer = true;
2139
- if (args[i] === '--port' && args[i + 1]) port = Number.parseInt(args[++i], 10);
2140
- if (args[i] === '--token' && args[i + 1]) token = args[++i];
2141
- }
2142
- if (!Number.isInteger(port) || port < 1024 || port > 65535) {
2143
- throw new Error('端口必须位于 1024 到 65535 之间。');
2144
- }
2145
- if (lan && !token) token = crypto.randomBytes(32).toString('base64url');
2146
- return { port, lan, token, appServer };
2147
- }
2148
-
2149
- function boundedInteger(value, fallback, minimum, maximum) {
2150
- const parsed = Number.parseInt(value || '', 10);
2151
- return Number.isFinite(parsed) ? Math.min(maximum, Math.max(minimum, parsed)) : fallback;
2152
- }
2153
-
2154
- function booleanValue(value, fallback) {
2155
- if (value === 'true') return true;
2156
- if (value === 'false') return false;
2157
- return fallback;
2158
- }
2159
-
2160
- class BridgeError extends Error {
2161
- constructor(message, details, options) {
2162
- super(message, options ? { cause: options } : undefined);
2163
- this.details = details;
2164
- }
2165
- }
2166
-
2167
- class RequestError extends Error {
2168
- constructor(statusCode, message, details) {
2169
- super(message);
2170
- this.statusCode = statusCode;
2171
- this.details = details;
2172
- this.code = details?.code;
1900
+ if (typeof value === 'string') return value;
1901
+ if (value && typeof value === 'object') return value.type || value.status || 'unknown';
1902
+ return value == null ? 'unknown' : String(value);
1903
+ }
1904
+
1905
+ function sendJson(response, statusCode, value) {
1906
+ if (response.json) { response.json(statusCode, value); return; }
1907
+ sendBuffer(response, statusCode, 'application/json; charset=utf-8', Buffer.from(JSON.stringify(value)));
1908
+ }
1909
+
1910
+ function sendBuffer(response, statusCode, contentType, value) {
1911
+ response.writeHead(statusCode, {
1912
+ 'Content-Type': contentType,
1913
+ 'Content-Length': value.length,
1914
+ 'Cache-Control': 'no-store',
1915
+ 'X-Content-Type-Options': 'nosniff',
1916
+ 'Content-Security-Policy': "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; connect-src 'self'; img-src 'self' data: blob:; media-src blob:",
1917
+ });
1918
+ response.end(value);
1919
+ }
1920
+
1921
+ async function readJsonBody(request) {
1922
+ return request.body;
1923
+ }
1924
+
1925
+ function parseOptions(args) {
1926
+ let port = Number.parseInt(process.env.CODEX_REMOTE_BRIDGE_PORT || '45831', 10);
1927
+ let lan = process.env.CODEX_REMOTE_BRIDGE_LAN === 'true';
1928
+ let token = process.env.CODEX_REMOTE_BRIDGE_TOKEN?.trim() || '';
1929
+ if (process.env.CODEX_REMOTE_APP_SERVER === 'true') throw new Error('此连接方式已停用。');
1930
+ for (let i = 0; i < args.length; i += 1) {
1931
+ if (args[i] === '--lan') lan = true;
1932
+ if (args[i] === '--app-server') throw new Error('此连接方式已停用。');
1933
+ if (args[i] === '--port' && args[i + 1]) port = Number.parseInt(args[++i], 10);
1934
+ if (args[i] === '--token' && args[i + 1]) token = args[++i];
1935
+ }
1936
+ if (!Number.isInteger(port) || port < 1024 || port > 65535) {
1937
+ throw new Error('端口必须位于 1024 到 65535 之间。');
2173
1938
  }
1939
+ if (lan && !token) token = crypto.randomBytes(32).toString('base64url');
1940
+ return { port, lan, token };
2174
1941
  }
1942
+
1943
+ function boundedInteger(value, fallback, minimum, maximum) {
1944
+ const parsed = Number.parseInt(value || '', 10);
1945
+ return Number.isFinite(parsed) ? Math.min(maximum, Math.max(minimum, parsed)) : fallback;
1946
+ }
1947
+