@walkhi/code-relax 0.1.0-beta.7 → 0.1.0-beta.71

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 +364 -236
  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 +51 -0
  63. package/dist/src/platform/codex-runtime.mjs +4 -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 +121 -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 +225 -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 +453 -145
  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 +1037 -428
  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 +1317 -1522
  162. package/dist/src/service-control.mjs +17 -7
  163. package/dist/src/service-doctor.mjs +150 -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 -46
  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 -143
  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,155 +1,266 @@
1
1
  import crypto from 'node:crypto';
2
+ import { RustDeskVideoProbe } from './platform/windows/rustdesk-video-probe.mjs';
2
3
  import fs from 'node:fs';
3
4
  import http from 'node:http';
4
- import net from 'node:net';
5
+ import { createPowerManager } from './service-power.mjs';
5
6
  import path from 'node:path';
6
7
  import os from 'node:os';
7
8
  import { fileURLToPath } from 'node:url';
8
- import vm from 'node:vm';
9
- import sharp from 'sharp';
9
+ import vm from 'node:vm';
10
+ import sharp from 'sharp';
10
11
  import { attachLanSocket } from './lan-socket-server.mjs';
11
- import { validateInlineImage, persistLegacyImage } from './message-images.mjs';
12
- 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';
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';
16
+ import { ThreadWriterConflictError } from './shared-app-server.mjs';
17
+ import { ManagedAppServer } from './managed-app-server.mjs';
18
+ import { RuntimeDiscovery } from './runtime-discovery.mjs';
19
+ import { streamSharedThread, threadListNotificationMethods } from './shared-thread-stream.mjs';
18
20
  import appServerEvents from '../shared/app-server-events.cjs';
19
- import { runtimePaths } from './platform/windows/runtime-paths.mjs';
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';
20
25
  import { openControl } from './service-control.mjs';
21
26
  import { writeRecord, readRecord } from './service-lifecycle.mjs';
22
27
  import { ExecutionHealth } from './execution-health.mjs';
23
- import { activeDebugPort, serveDebugResource } from './debug-ui.mjs';
24
- import { authorizeLanDevice } from './self-relay/lan-authorizations.mjs';
28
+ import { authorizeLanDevice, onLanAuthorizationsChanged, recordLanConnection } from './self-relay/lan-authorizations.mjs';
29
+ import { createRelayManager } from './self-relay/lifecycle.mjs';
25
30
  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';
31
+ import { desktopHost } from './platform/desktop-host.mjs';
28
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';
29
41
  import { requestRecovery, recoveryStatus } from './shared-recovery.mjs';
42
+ import { startDesktop } from './desktop-launcher.mjs';
30
43
  import { requestServiceRestart } from './service-restart.mjs';
31
-
32
- const MAX_FRAME_BYTES = 32 * 1024 * 1024;
33
- const MAX_ATTACHMENT_BYTES = 16 * 1024 * 1024;
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;
34
65
  const MAX_CACHED_ATTACHMENTS = 32;
35
66
  const MAX_CACHED_FILES = 64;
36
67
  const MAX_CONTEXT_TAIL_BYTES = 4 * 1024 * 1024;
37
- const MAX_LIVE_ACTIVITY_TAIL_BYTES = 1024 * 1024;
38
- const SHARED_HISTORY_PAGE_BYTES = 500 * 1024;
39
- const SHARED_HISTORY_PAGE_TURNS = 8;
68
+ const MAX_LIVE_ACTIVITY_TAIL_BYTES = 1024 * 1024;
69
+ const SHARED_HISTORY_PAGE_BYTES = 500 * 1024;
70
+ const SHARED_HISTORY_PAGE_TURNS = 8;
40
71
  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
72
  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
- ]);
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
+ }
59
93
 
60
94
  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();
95
+ const runtimeVersion = readRuntimeVersion();
96
+ const serviceInstanceId = crypto.randomUUID();
63
97
 
64
98
  const webRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', 'web');
65
- const repositoryRoot = path.resolve(webRoot, '..', '..');
66
99
  const projectRoot = os.homedir();
67
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);
68
104
  const markdownItScript = fs.readFileSync(path.join(webRoot, 'vendor', 'markdown-it.umd.min.js'));
69
105
  const markdownSandbox = { atob };
70
106
  vm.runInNewContext(markdownItScript.toString(), markdownSandbox);
71
107
  const imageMarkdown = markdownSandbox.markdownit({ html: false });
72
- let nextRequestId = 0;
73
- const attachmentCache = new Map();
74
- const fileCache = new Map();
75
- const fileCapabilitySecret = crypto.randomBytes(32);
76
- const imageCapabilityKey = crypto.createHash('sha256')
77
- .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 || '[]')];
108
+ fileCitations.install(imageMarkdown);
109
+ const attachmentCache = new Map();
110
+ const fileCache = new Map();
111
+ const fileCapabilitySecret = crypto.randomBytes(32);
112
+ const imageCapabilityKey = crypto.createHash('sha256')
113
+ .update('code-relax-image-capability\0').update(options.token || fileCapabilitySecret).digest();
81
114
  const sessionPathCache = new Map();
82
115
  const sessionSettingsCache = new Map();
83
116
  const liveActivityCache = new Map();
84
- const messageQueues = new Map();
85
- let sharedServer = null;
86
- const desktopMonitor = new DesktopMonitor({
87
- read: (tool, args, pipe) => callTool(tool, args, 5000, pipe),
88
- });
89
- const managedAppServer = new ManagedAppServer({ stateRoot, host: desktopHost });
90
- let sharedCatalog = null, executionMode = 'ordinary';
91
- function selectSharedExecution(enabled) {
92
- const next = enabled ? managedAppServer.client : null;
93
- if (sharedServer === next) return;
94
- sharedServer = next;
95
- sharedCatalog = sharedServer ? new SharedCatalog(sharedServer) : null;
96
- }
97
- const executionHealth = new ExecutionHealth(async () => {
98
- await managedAppServer.ensure();
99
- const [desktop, attachedUrl] = await Promise.all([desktopHost.inspect(), desktopHost.sharedServiceUrl()]);
100
- const attached = Boolean(desktop.running && attachedUrl === managedAppServerUrl);
101
- const managed = managedAppServer.snapshot();
102
- // The managed app-server owns execution. Desktop is another client of the
103
- // same service, so closing Desktop must not disable phone writes.
104
- selectSharedExecution(managed.ready);
105
- executionMode = sharedServer ? 'shared' : 'ordinary';
106
- const basicReady = Boolean(desktop.running && sourceThreadId && desktopMonitor.pipe);
107
- const managedRestarting = managed.state === 'restarting';
108
- const ready = !managedRestarting && (sharedServer ? sharedServer.ready === true : basicReady);
109
- 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 } };
113
- });
114
- async function refreshExecution() {
115
- sourceThreadId = readRecord(path.join(stateRoot, 'bridge-startup-context.json'))?.sourceThreadId || sourceThreadId;
116
- await executionHealth.refresh();
117
- return { mode: executionMode };
118
- }
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
- }
124
- const execution = await executionHealth.refresh();
125
- 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 });
130
- }
131
- if (threadId && sharedServer) sharedServer.requireWritable(threadId);
132
- return execution;
133
- }
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})`);
117
+ let sharedServer = null;
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);
126
+ let sharedCatalog = null, executionMode = 'ordinary';
127
+ function selectSharedExecution(enabled) {
128
+ const next = enabled ? managedAppServer.client : null;
129
+ if (sharedServer === next) return;
130
+ sharedServer = next;
131
+ sharedCatalog = sharedServer ? new SharedCatalog(sharedServer, dotsServer, dotsChat) : null;
132
+ }
133
+ const executionHealth = new ExecutionHealth(async () => {
134
+ await managedAppServer.ensure();
135
+ const managed = managedAppServer.snapshot();
136
+ // The managed app-server owns execution. Desktop is another client of the
137
+ // same service, so closing Desktop must not disable phone writes.
138
+ selectSharedExecution(managed.ready);
139
+ executionMode = sharedServer ? 'shared' : 'ordinary';
140
+ const managedRestarting = managed.state === 'restarting';
141
+ const ready = !managedRestarting && sharedServer?.ready === true;
142
+ const state = managedRestarting ? 'restarting'
143
+ : ready ? 'full' : 'unavailable';
144
+ return { state, ready, canWrite: ready, mode: executionMode };
144
145
  });
146
+ function workstationHealth() {
147
+ const executionReady = executionHealth.state.ready && sharedServer?.ready === true;
148
+ return {
149
+ status: 'ok', resident: true, version: runtimeVersion, serviceInstanceId,
150
+ keepAwake: powerManager.snapshot(),
151
+ serviceUpdate: serviceUpdateStatus(stateRoot),
152
+ execution: { ...executionHealth.state, ready: executionReady, canWrite: executionReady,
153
+ mode: executionMode },
154
+ managedAppServer: managedAppServer.snapshot(),
155
+ ...runtimeDiscovery.state,
156
+ sharedAppServerConnected: sharedServer?.ready === true,
157
+ sharedRecoveryAvailable: runtimeDiscovery.state.desktop?.installed === true,
158
+ mode: options.lan ? 'lan' : 'loopback',
159
+ tokenRequired: Boolean(options.token),
160
+ nativeImageTransport: sharedServer ? 'shared-app-server' : null,
161
+ transportMode: sharedServer?.ready === true ? 'lan-shared' : 'unavailable',
162
+ };
163
+ }
164
+
165
+ async function refreshExecution() {
166
+ await executionHealth.refresh();
167
+ return { mode: executionMode };
168
+ }
169
+ async function requireExecutionWritable(threadId, client = sharedServer) {
170
+ const execution = await executionHealth.refresh();
171
+ if (!execution.canWrite) {
172
+ throw new RequestError(503, 'Codex 内核未就绪。请重启工作站服务;若仍失败,运行 relax doctor。',
173
+ { code: 'full_control_required', execution });
174
+ }
175
+ if (threadId && client) client.requireWritable(threadId);
176
+ return execution;
177
+ }
178
+ managedAppServer.client.on('subscription-error', error => console.error('Codex 内核任务取消订阅失败:', error.message));
179
+ dotsServer.on('subscription-error', error => console.error('Dots 任务取消订阅失败:', error.message));
180
+ dotsChat.on('subscription-error', error => console.error('Dots 对话取消订阅失败:', error.message));
181
+ dotsChat.on('activity-error', error => console.error('点点状态通道不可用:', error.message));
145
182
 
183
+ dotsServer.on('profile-error', error => console.error(error.message));
146
184
  async function handleRequest(request, response) {
185
+ let messageSubmissionStarted = false;
147
186
  try {
148
187
  const url = new URL(request.url || '/', `http://${request.headers.host || 'localhost'}`);
149
188
 
150
189
  if (!request.socketRequest) {
151
- if (serveDebugResource(request, response, stateRoot, options.port)) return;
152
- sendJson(response, 404, { error: 'Bridge 调试页面未开启,或请求的资源不存在。' });
190
+ sendJson(response, 404, { error: '此端口不提供网页。' });
191
+ return;
192
+ }
193
+
194
+ if (process.platform !== 'win32' && url.pathname.startsWith('/api/desktop/'))
195
+ throw new RequestError(501, 'Mac 版不提供远程桌面控制。', { code: 'desktop_control_unsupported' });
196
+
197
+ if (url.pathname === '/api/desktop/video-probe') {
198
+ if (request.method === 'POST') {
199
+ try { sendJson(response, 200, await desktopVideoProbe.begin()); }
200
+ catch (error) { throw new RequestError(error.statusCode || 502, error.message, { code: error.code || 'desktop_video_failed' }); }
201
+ }
202
+ else if (request.method === 'PUT' || request.method === 'DELETE') {
203
+ const body = await readJsonBody(request);
204
+ if (typeof body.leaseId !== 'string' || !/^[a-f0-9]{32}$/.test(body.leaseId)) throw new RequestError(400, '无效的视频会话');
205
+ sendJson(response, 200, request.method === 'PUT' ? desktopVideoProbe.renew(body.leaseId) : desktopVideoProbe.release(body.leaseId));
206
+ } else throw new RequestError(405, '不支持的操作');
207
+ return;
208
+ }
209
+ if (request.method === 'GET' && url.pathname === '/api/desktop/frame') {
210
+ const started = Date.now();
211
+ try {
212
+ const preview = url.searchParams.get('preview') === '1';
213
+ const frame = await desktopHelper.frame({ preview });
214
+ if (!preview) console.info(JSON.stringify({ time: new Date().toISOString(), event: 'desktop-frame', state: frame.state,
215
+ kind: frame.kind, width: frame.width, height: frame.height, bytes: frame.size, ms: Date.now() - started }));
216
+ if (response.writableEnded) { desktopHelper.release(frame.frameId); return; }
217
+ sendJson(response, 200, frame);
218
+ } catch (error) {
219
+ console.error(JSON.stringify({ time: new Date().toISOString(), event: 'desktop-frame', state: 'failed',
220
+ error: error.message.slice(0, 160), ms: Date.now() - started }));
221
+ throw error;
222
+ }
223
+ return;
224
+ }
225
+ const desktopImage = /^\/api\/desktop\/frames\/([a-f0-9]{32})$/.exec(url.pathname);
226
+ if (request.method === 'GET' && desktopImage) {
227
+ if (!request.fileTransfer) throw new RequestError(400, '请通过文件通道读取桌面画面。');
228
+ const image = desktopHelper.image(desktopImage[1]);
229
+ const { offset, length } = fileRange(url.searchParams, image.length, desktopImage[1]);
230
+ const mimeType = desktopHelper.active.frame.mimeType;
231
+ response.filePart({ name: mimeType === 'image/jpeg' ? 'desktop.jpg' : 'desktop.png', size: image.length, offset, revision: desktopImage[1], mimeType },
232
+ image.subarray(offset, offset + length));
233
+ return;
234
+ }
235
+ if (request.method === 'POST' && url.pathname === '/api/desktop/move') {
236
+ try { sendJson(response, 200, await desktopHelper.move(await readJsonBody(request))); }
237
+ catch (error) {
238
+ console.error(JSON.stringify({ time: new Date().toISOString(), event: 'desktop-move', state: 'failed',
239
+ code: error.code, error: error.message.slice(0, 160) }));
240
+ throw error;
241
+ }
242
+ return;
243
+ }
244
+ if (request.method === 'POST' && url.pathname === '/api/desktop/click') {
245
+ const started = Date.now();
246
+ console.info(JSON.stringify({ time: new Date().toISOString(), event: 'desktop-click', state: 'received' }));
247
+ try {
248
+ const result = await desktopHelper.click(await readJsonBody(request));
249
+ console.info(JSON.stringify({ time: new Date().toISOString(), event: 'desktop-click', state: result.state,
250
+ inputCount: result.inputCount, ms: Date.now() - started }));
251
+ sendJson(response, 200, result);
252
+ } catch (error) {
253
+ console.error(JSON.stringify({ time: new Date().toISOString(), event: 'desktop-click', state: 'failed',
254
+ code: error.code, error: error.message.slice(0, 160), ms: Date.now() - started }));
255
+ throw error;
256
+ }
257
+ return;
258
+ }
259
+ if (request.method === 'DELETE' && url.pathname === '/api/desktop/frame') {
260
+ const body = await readJsonBody(request);
261
+ if (typeof body.sessionId !== 'string' || !/^[a-f0-9]{32}$/.test(body.sessionId)) throw new RequestError(400, '无效的桌面会话');
262
+ desktopHelper.release(undefined, body.sessionId);
263
+ sendJson(response, 200, { released: true });
153
264
  return;
154
265
  }
155
266
 
@@ -172,157 +283,401 @@ async function handleRequest(request, response) {
172
283
  if (request.method === 'GET' && url.pathname === '/api/shared-recovery') {
173
284
  sendJson(response, 200, await recoveryStatus(stateRoot)); return;
174
285
  }
286
+ if (request.method === 'POST' && ['/api/desktop/start', '/api/desktop/restart'].includes(url.pathname)) {
287
+ const body = await readJsonBody(request);
288
+ if (body.confirmed !== true) throw new RequestError(400, '请确认启动桌面端。');
289
+ const result = await startDesktop({ stateRoot, host: desktopHost, restart: url.pathname === '/api/desktop/restart' });
290
+ const observed = await runtimeDiscovery.sample();
291
+ sendJson(response, 202, { ...result, desktop: observed.desktop, checkedAt: observed.checkedAt }); return;
292
+ }
175
293
  if (request.method === 'POST' && url.pathname === '/api/shared-recovery') {
176
294
  const body = await readJsonBody(request);
177
- if (body.confirmed !== true || typeof body.operationId !== 'string') throw new RequestError(400, '请确认重新打开当前 Codex 并启用完整控制后再继续。');
295
+ if (body.confirmed !== true || typeof body.operationId !== 'string') throw new RequestError(400, '请确认重新打开当前 Codex 并接入 Codex 内核后再继续。');
178
296
  sendJson(response, 202, await requestRecovery(stateRoot, body)); return;
179
297
  }
180
- if (request.method === 'POST' && url.pathname === '/api/service/restart') {
181
- const body = await readJsonBody(request);
182
- if (body.confirmed !== true) throw new RequestError(400, '请确认重启工作站服务后再继续。');
183
- sendJson(response, 202, await requestServiceRestart(stateRoot)); return;
184
- }
185
- if (request.method === 'POST' && url.pathname === '/api/managed-app-server/restart') {
186
- const body = await readJsonBody(request);
187
- if (body.confirmed !== true || typeof body.operationId !== 'string' || !body.operationId.trim()) {
188
- throw new RequestError(400, '请先确认中断风险,再重启 Codex 内核。');
189
- }
190
- try {
191
- sendJson(response, 202, managedAppServer.restart(body.operationId.trim()));
192
- } catch (error) {
193
- if (error?.code === 'managed_app_server_restarting') throw new RequestError(409, error.message);
194
- throw error;
195
- }
196
- return;
197
- }
198
-
199
- if (request.method === 'GET' && url.pathname === '/api/health') {
200
- // Status readers consume the workstation's cached sample. A phone query
201
- // 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 },
208
- 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
- });
298
+ if (url.pathname === '/api/service/update' && request.method === 'GET') {
299
+ sendJson(response, 200, await checkServiceUpdate(stateRoot)); return;
300
+ }
301
+ if (url.pathname === '/api/service/update' && request.method === 'POST') {
302
+ sendJson(response, 202, await requestServiceUpdate(stateRoot, await readJsonBody(request))); return;
303
+ }
304
+ if (request.method === 'POST' && url.pathname === '/api/service/restart') {
305
+ const body = await readJsonBody(request);
306
+ if (body.confirmed !== true) throw new RequestError(400, '请确认重启工作站服务后再继续。');
307
+ sendJson(response, 202, { ...await requestServiceRestart(stateRoot), serviceInstanceId }); return;
308
+ }
309
+ if (request.method === 'POST' && url.pathname === '/api/managed-app-server/restart') {
310
+ const body = await readJsonBody(request);
311
+ if (body.confirmed !== true || typeof body.operationId !== 'string' || !body.operationId.trim()) {
312
+ throw new RequestError(400, '请先确认中断风险,再重启 Codex 内核。');
313
+ }
314
+ try {
315
+ sendJson(response, 202, managedAppServer.restart(body.operationId.trim()));
316
+ } catch (error) {
317
+ if (error?.code === 'managed_app_server_restarting') throw new RequestError(409, error.message);
318
+ throw error;
319
+ }
320
+ return;
321
+ }
322
+
323
+ if (request.method === 'GET' && url.pathname === '/api/official-remote/status') {
324
+ if (!managedAppServer.client.ready) throw new RequestError(503, 'Codex 内核未就绪,无法读取官方工作站状态。');
325
+ const result = await managedAppServer.client.request('remoteControl/status/read', {});
326
+ sendJson(response, 200, { status: result.status, environmentId: result.environmentId });
327
+ return;
328
+ }
329
+ if (request.method === 'POST' && url.pathname === '/api/official-remote/pairing') {
330
+ if (!managedAppServer.client.ready) throw new RequestError(503, 'Codex 内核未就绪,无法生成官方配对码。');
331
+ const status = await managedAppServer.client.request('remoteControl/status/read', {});
332
+ if (status.status !== 'connected') throw new RequestError(409, '当前 Codex 内核未连接 OpenAI Remote,无法配对。');
333
+ const result = await managedAppServer.client.request('remoteControl/pairing/start', { manualCode: true });
334
+ if (!result.manualPairingCode || !result.environmentId) throw new RequestError(502, 'Codex 内核未返回有效的官方配对码。');
335
+ // One-time code: return only to the authenticated station controller; never persist or log it.
336
+ sendJson(response, 200, { manualPairingCode: result.manualPairingCode,
337
+ environmentId: result.environmentId, expiresAt: result.expiresAt });
338
+ return;
339
+ }
340
+
341
+ if (request.method === 'GET' && url.pathname === '/api/health') {
342
+ sendJson(response, 200, workstationHealth()); return;
343
+ }
344
+ if (request.method === 'GET' && url.pathname === '/api/health/events') {
345
+ response.startEvents();
346
+ let previous = '', stopped = false, timer;
347
+ const publish = () => {
348
+ if (stopped) return;
349
+ try {
350
+ const health = workstationHealth();
351
+ // Sampling timestamps alone do not change a workstation's visible state.
352
+ const signature = JSON.stringify(health, (key, value) => key === 'checkedAt' ? undefined : value);
353
+ if (signature !== previous) { previous = signature; response.event('health', health); }
354
+ } catch (error) { previous = ''; response.event('health-error', { error: error.message }); }
355
+ };
356
+ const close = () => { stopped = true; clearInterval(timer); managedAppServer.off('state', publish); };
357
+ response.once('close', close);
358
+ managedAppServer.on('state', publish);
359
+ publish();
360
+ // Reads only existing local samples and operation records, never chat history
361
+ // or an extra Desktop/process probe. Idle snapshots are not sent over the network.
362
+ timer = setInterval(publish, 1000); timer.unref?.();
228
363
  return;
229
364
  }
230
365
 
366
+ const fileLinkMatch = /^\/api\/files\/([a-f0-9]{64})\/link$/.exec(url.pathname);
367
+ if (request.method === 'GET' && fileLinkMatch) {
368
+ const attachment = attachmentCache.get(fileLinkMatch[1])
369
+ || dotsImages.restore(fileLinkMatch[1], url.searchParams.get('recovery') || '');
370
+ if (!attachment?.dotsFile) throw new RequestError(404, '此附件没有网络链接,请重新读取对话。');
371
+ sendJson(response, 200, { url: await dotsImages.fileLink(attachment) });
372
+ return;
373
+ }
231
374
 
232
- const imageMatch = /^\/api\/images\/([a-f0-9]{64})$/.exec(url.pathname);
233
- if (request.method === 'GET' && imageMatch) {
234
- let image = attachmentCache.get(imageMatch[1])
235
- || recoverLocalImage(imageMatch[1], url.searchParams.get('recovery') || '');
236
- const contextThreadId = url.searchParams.get('threadId') || '';
237
- const contextTurnId = url.searchParams.get('turnId') || '';
238
- if (!image && sharedServer && contextThreadId && contextTurnId
239
- && contextThreadId.length <= 128 && contextTurnId.length <= 128) {
240
- try {
241
- normalizeTurn(await sharedServer.turnContext(contextThreadId, contextTurnId));
242
- image = attachmentCache.get(imageMatch[1]);
243
- } catch {}
244
- }
245
- 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')}` });
375
+ const imageMatch = /^\/api\/images\/([a-f0-9]{64})$/.exec(url.pathname);
376
+ if (request.method === 'GET' && imageMatch) {
377
+ let image = attachmentCache.get(imageMatch[1])
378
+ || dotsImages.restore(imageMatch[1], url.searchParams.get('recovery') || '')
379
+ || recoverLocalImage(imageMatch[1], url.searchParams.get('recovery') || '');
380
+ const contextThreadId = url.searchParams.get('threadId') || '';
381
+ const contextTurnId = url.searchParams.get('turnId') || '';
382
+ if (!image && sharedServer && contextThreadId && contextTurnId
383
+ && contextThreadId.length <= 128 && contextTurnId.length <= 128) {
384
+ try {
385
+ normalizeTurn(await sharedServer.turnContext(contextThreadId, contextTurnId));
386
+ image = attachmentCache.get(imageMatch[1]);
387
+ } catch {}
388
+ }
389
+ if (!image) throw new RequestError(404, '这张图片已失效,无法从工作站重新读取。');
390
+ if (image.dotsFile) await dotsImages.load(image);
391
+ if (url.searchParams.get('variant') === 'thumbnail') {
392
+ const asset = await attachmentThumbnail(image);
393
+ sendJson(response, 200, { url: `data:${asset.mimeType};base64,${asset.buffer.toString('base64')}` });
394
+ return;
395
+ }
396
+ const size = image.buffer.length;
397
+ if (size > MAX_ATTACHMENT_BYTES) throw new RequestError(413, '图片超过下载上限(20 MiB),无法下载。');
398
+ if (!size) throw new RequestError(400, '图片为空,无法下载。');
399
+ if (!request.fileTransfer) throw new RequestError(400, '请通过文件通道读取原图。');
400
+ const revision = imageMatch[1];
401
+ const { offset, length } = fileRange(url.searchParams, size, revision);
402
+ response.filePart({ name: image.name || '图片', size, offset, revision, mimeType: image.mimeType },
403
+ image.buffer.subarray(offset, offset + length));
249
404
  return;
250
405
  }
251
406
 
407
+ const localFileRequest = url.pathname === '/api/files/local';
252
408
  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, '电脑上的文件已发生变化,请重新读取对话。');
409
+ if (request.method === 'GET' && (fileMatch || localFileRequest)) {
410
+ if (!request.fileTransfer) throw new RequestError(400, '请通过文件通道下载附件。');
411
+ const hosted = !localFileRequest && (attachmentCache.get(fileMatch[1])
412
+ || dotsImages.restore(fileMatch[1], url.searchParams.get('recovery') || ''));
413
+ if (hosted?.dotsFile) {
414
+ const { bytes, ...metadata } = await dotsImages.readPart(hosted, url.searchParams);
415
+ response.filePart(metadata, bytes);
416
+ return;
261
417
  }
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') });
418
+ const localPath = url.searchParams.get('path') || '';
419
+ if (localFileRequest && (/[\x00-\x1f]/.test(localPath)
420
+ || !(process.platform === 'win32' ? /^[a-z]:[\\/]/i : /^\/(?!\/)/).test(localPath))) {
421
+ throw new RequestError(400, '无效的工作站文件路径。');
422
+ }
423
+ const file = localFileRequest ? describeLocalFile(localPath) : fileCache.get(fileMatch[1]);
424
+ if (!file) throw new RequestError(404, '电脑上的文件不存在或无法读取。');
425
+ if (file.externalIdentity) await dotsImages.authorization(file.externalIdentity);
426
+ const { offset, length } = fileRange(url.searchParams, file.size, file.revision);
427
+ let handle;
428
+ try { handle = fs.openSync(file.path, 'r'); }
429
+ catch (error) {
430
+ if (error.code === 'ENOENT') throw new RequestError(404, '电脑上的文件已不存在。');
431
+ throw error;
432
+ }
433
+ try {
434
+ const validate = () => {
435
+ const stat = fs.fstatSync(handle);
436
+ if (!stat.isFile() || fileRevision(stat) !== file.revision || fileRevision(fs.statSync(file.path)) !== file.revision) {
437
+ if (fileMatch) fileCache.delete(fileMatch[1]);
438
+ throw new RequestError(409, '电脑上的文件已发生变化,请重新读取对话。');
439
+ }
440
+ if (stat.size > MAX_FILE_DOWNLOAD_BYTES) throw new RequestError(413, '文件超过下载上限(20 MiB),无法下载。');
441
+ if (!stat.size) throw new RequestError(400, '文件为空,无法下载。');
442
+ };
443
+ validate();
444
+ const data = Buffer.alloc(length);
445
+ let bytesRead = 0;
446
+ while (bytesRead < data.length) {
447
+ const count = fs.readSync(handle, data, bytesRead, data.length - bytesRead, offset + bytesRead);
448
+ if (!count) throw new RequestError(409, '电脑上的文件读取不完整,请重新读取对话。');
449
+ bytesRead += count;
450
+ }
451
+ validate();
452
+ response.filePart({ name: file.name, size: file.size, offset, revision: file.revision }, data);
453
+ } finally { fs.closeSync(handle); }
454
+ return;
455
+ }
456
+
457
+ if (url.pathname.startsWith('/api/app-server/') || url.pathname === '/api/wait') {
458
+ throw new RequestError(410, '此连接方式已停用。请检查 Codex 内核状态。', { code: 'unsupported_control' });
459
+ }
460
+ const taskPath = /^\/api\/threads\/([^/]+)/.exec(url.pathname);
461
+ if (taskPath && (url.pathname.endsWith('/takeover') || url.pathname === `/api/threads/${taskPath[1]}`)) {
462
+ throw new RequestError(410, '此连接方式已停用。', { code: 'unsupported_control' });
463
+ }
464
+ const hostId = url.searchParams.get('hostId') || 'local';
465
+ if (!['local', 'durable'].includes(hostId)) {
466
+ throw new RequestError(410, '此对话的连接方式已停用,请检查 Codex 内核状态。', { code: 'unsupported_control' });
467
+ }
468
+ if (!executionHealth.state.ready || sharedServer?.ready !== true) {
469
+ throw new RequestError(503, 'Codex 内核未就绪。请重启工作站服务;若仍失败,运行 relax doctor。',
470
+ { code: 'full_control_required' });
471
+ }
472
+ const rootChat = hostId === 'durable' && taskPath
473
+ && await dotsServer.source(decodeURIComponent(taskPath[1])) === 'aeon';
474
+ const execution = rootChat ? dotsChat : hostId === 'durable' ? dotsServer : sharedServer;
475
+
476
+ if (url.pathname === '/api/permissions' && ['GET', 'POST'].includes(request.method)) {
477
+ if (hostId !== 'local') throw new RequestError(405, '当前来源不提供工作站全局审批方式。');
478
+ const body = request.method === 'POST' ? await readJsonBody(request) : {};
479
+ if (request.method === 'POST' && typeof body.mode !== 'string') throw new RequestError(400, '缺少审批方式。');
480
+ sendJson(response, 200, await sharedServer.globalPermissions(body.mode, {
481
+ threadId: url.searchParams.get('threadId') || '', cwd: url.searchParams.get('cwd') || '',
482
+ }));
483
+ return;
484
+ }
485
+
486
+ const dotsActivityMatch = /^\/api\/threads\/([^/]+)\/dots-activity-events$/.exec(url.pathname);
487
+ if (request.method === 'GET' && dotsActivityMatch) {
488
+ if (!rootChat) throw new RequestError(405, '只有点点主聊天提供此活动面板。');
489
+ response.startEvents();
490
+ const disconnected = () => response.end();
491
+ dotsChat.on('disconnected', disconnected);
492
+ const release = dotsChat.acquirePanelActivity(decodeURIComponent(dotsActivityMatch[1]), value => {
493
+ if (!response.destroyed) response.event('dots-activity', { threadId: value.threadId, activity: value.activity });
494
+ });
495
+ response.once('close', () => { release(); dotsChat.off('disconnected', disconnected); });
496
+ return;
497
+ }
498
+
499
+ const dotsTasksMatch = /^\/api\/threads\/([^/]+)\/dots-tasks$/.exec(url.pathname);
500
+ if (request.method === 'GET' && dotsTasksMatch) {
501
+ if (!rootChat) throw new RequestError(405, '只有点点主聊天提供此任务面板。');
502
+ sendJson(response, 200, await readDotsSummaryTasks(dotsServer, decodeURIComponent(dotsTasksMatch[1])));
503
+ return;
504
+ }
505
+
506
+ const dotsSchedulesMatch = /^\/api\/threads\/([^/]+)\/dots-schedules$/.exec(url.pathname);
507
+ if (request.method === 'GET' && dotsSchedulesMatch) {
508
+ if (!rootChat) throw new RequestError(405, '只有点点主聊天提供此定时任务列表。');
509
+ sendJson(response, 200, await readDotsSchedules(dotsServer, decodeURIComponent(dotsSchedulesMatch[1])));
510
+ return;
511
+ }
512
+
513
+ if (request.method === 'GET' && url.pathname.startsWith('/api/code/')) {
514
+ const operation = url.pathname.slice('/api/code/'.length);
515
+ if (hostId === 'durable' && operation !== 'change') throw new RequestError(405, 'Dots 委派任务尚未接入代码浏览。');
516
+ try {
517
+ if ((operation === 'image' || operation === 'binary') && !request.fileTransfer) throw new RequestError(400, '请通过文件通道读取预览文件。');
518
+ const result = await browseCode((method, params) => execution.request(method, params), operation, url.searchParams);
519
+ if (operation === 'image' || operation === 'binary') {
520
+ const { data, ...metadata } = result;
521
+ response.filePart(metadata, data);
522
+ } else sendJson(response, 200, result);
523
+ } catch (error) { throw new RequestError(error.statusCode || 502, error.message); }
264
524
  return;
265
525
  }
266
526
 
267
527
  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());
528
+ sendJson(response, 200, await execution.models());
529
+ return;
530
+ }
531
+
532
+ if (request.method === 'GET' && url.pathname === '/api/archived-threads') {
533
+ sendJson(response, 200, await archivedThreadsPage(execution, sharedServer, hostId, url.searchParams.get('cursor')));
270
534
  return;
271
535
  }
272
536
 
273
537
  if (request.method === 'GET' && url.pathname === '/api/threads') {
274
538
  const limit = boundedInteger(url.searchParams.get('limit'), 20, 1, 200);
275
539
  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 };
540
+ const scope = url.searchParams.get('scope') || 'all';
541
+ if (!['all', 'local', 'durable'].includes(scope)) { sendJson(response, 400, { error: '无效的会话列表范围' }); return; }
542
+ const catalog = await sharedCatalog.threads(limit, archived, scope, url.searchParams.get('refresh') === '1');
543
+ const creations = await messageReceipts.creationBindings();
544
+ for (const thread of [...(catalog.threads || []), ...(catalog.pinnedThreads || [])]) {
545
+ const clientId = creations.get(thread.id);
546
+ if (clientId && (thread.hostId || 'local') === 'local') thread.creationClientId = clientId;
289
547
  }
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 {}
548
+ sendJson(response, 200, catalog);
549
+ return;
550
+ }
551
+
552
+ if (request.method === 'GET' && url.pathname === '/api/file-local-copy') {
553
+ const value = url.searchParams.get('source') || '';
554
+ const match = /^([a-f0-9]{64})(?:\?recovery=([A-Za-z0-9_-]{1,4096}))?$/.exec(value);
555
+ if (!match) throw new RequestError(400, '无效的文件标识。');
556
+ const asset = attachmentCache.get(match[1])
557
+ || dotsImages.restore(match[1], match[2] || url.searchParams.get('recovery') || '');
558
+ // A missing process cache does not make a local image an external file.
559
+ // The actual image endpoint owns recovery and download authorization.
560
+ if (!asset?.dotsFile) {
561
+ sendJson(response, 200, { external: false, localCopy: null }); return;
304
562
  }
305
- sendJson(response, 200, mergeThreadCatalog(desktopCatalog, appServerThreads, limit, sharedSnapshot));
563
+ await dotsImages.authorization(asset.identity);
564
+ const copy = await workstationDownloads.copy(asset.id);
565
+ sendJson(response, 200, { external: true, localCopy: copy ? workstationCopyReference(copy, asset) : null,
566
+ cachedContent: asset.buffer ? { remotePath: asset.id, name: asset.name, size: asset.buffer.length, mimeType: asset.mimeType } : null });
567
+ return;
568
+ }
569
+
570
+ if (request.method === 'POST' && url.pathname === '/api/workstation-file-attachments') {
571
+ await requireExecutionWritable();
572
+ const body = await readJsonBody(request);
573
+ const source = body.source;
574
+ const file = typeof source === 'string' && /^[a-z]:[\\/]/i.test(source) ? describeLocalFile(source)
575
+ : typeof source === 'string' && /^[a-f0-9]{64}$/.test(source) ? fileCache.get(source) : null;
576
+ if (!file) throw new RequestError(404, '电脑文件不可用,请从原文件入口重新添加。');
577
+ if (file.externalIdentity) await dotsImages.authorization(file.externalIdentity);
578
+ sendJson(response, 201, { file: await stageWorkstationMessageFile(file, uploadsRoot) });
579
+ return;
580
+ }
581
+
582
+ if (request.method === 'GET' && url.pathname === '/api/file-save-locations') {
583
+ await requireExecutionWritable();
584
+ const locations = await projectLocations();
585
+ const projects = (await sharedCatalog.projects()).projects.filter(project => project.hostId === 'local' && project.roots?.length);
586
+ sendJson(response, 200, { projects, downloads: locations.downloads });
587
+ return;
588
+ }
589
+
590
+ if (request.method === 'GET' && url.pathname === '/api/file-save-folders') {
591
+ await requireExecutionWritable();
592
+ sendJson(response, 200, await workstationDownloads.browse((method, params) => sharedServer.request(method, params),
593
+ await projectLocations(), Object.fromEntries(url.searchParams)));
594
+ return;
595
+ }
596
+
597
+ if (request.method === 'POST' && url.pathname === '/api/file-saves') {
598
+ await requireExecutionWritable();
599
+ const body = await readJsonBody(request);
600
+ const asset = await externalDownloadAsset(body.source, body.recovery);
601
+ const controller = new AbortController(), cancel = () => controller.abort();
602
+ response.once('close', cancel);
603
+ try {
604
+ const result = await workstationDownloads.save((method, params) => sharedServer.request(method, params),
605
+ await projectLocations(), body, asset, (file, query) => dotsImages.readPart(file, query), controller.signal);
606
+ sendJson(response, 201, { localCopy: workstationCopyReference(result, asset), warning: result.warning || '' });
607
+ } finally { response.off('close', cancel); }
306
608
  return;
307
609
  }
308
610
 
309
611
  if (request.method === 'GET' && url.pathname === '/api/projects') {
310
- if (sharedCatalog) {
311
- sendJson(response, 200, { projects: (await sharedCatalog.projects()).projects });
612
+ sendJson(response, 200, { projects: (await sharedCatalog.projects()).projects });
613
+ return;
614
+ }
615
+
616
+ if (url.pathname === '/api/project-folders' && ['GET', 'POST'].includes(request.method)) {
617
+ await requireExecutionWritable();
618
+ try {
619
+ const rpc = (method, params) => sharedServer.request(method, params);
620
+ const result = request.method === 'GET'
621
+ ? await browseProjectFolders(rpc, !url.searchParams.get('path') || url.searchParams.get('locations') === '1' ? await projectLocations() : null, url.searchParams.get('path'))
622
+ : await createProjectFolder(rpc, await readJsonBody(request));
623
+ sendJson(response, request.method === 'GET' ? 200 : 201, result);
624
+ } catch (error) { throw new RequestError(error.statusCode || 502, error.message); }
625
+ return;
626
+ }
627
+
628
+ const projectActionMatch = /^\/api\/projects\/([^/]+)\/(remove|move)$/.exec(url.pathname);
629
+ if (projectActionMatch) {
630
+ await requireExecutionWritable();
631
+ const projectId = decodeURIComponent(projectActionMatch[1]);
632
+ if (request.method === 'POST' && projectActionMatch[2] === 'move') {
633
+ const { beforeProjectId } = await readJsonBody(request);
634
+ if (beforeProjectId !== null && (typeof beforeProjectId !== 'string' || !beforeProjectId.trim()) || beforeProjectId === projectId) {
635
+ throw new RequestError(400, '排序目标无效。');
636
+ }
637
+ await sharedServer.request('project/move', { projectId, beforeProjectId });
638
+ sendJson(response, 200, await sharedCatalog.projects().then(({ projects }) => ({ projects })));
639
+ return;
640
+ }
641
+ if (request.method === 'POST' && projectActionMatch[2] === 'remove') {
642
+ const result = await removeWorkstationProject(sharedServer, stateRoot, codexDataRoot, projectId, await readJsonBody(request));
643
+ sendJson(response, 200, result);
312
644
  return;
313
645
  }
314
- const result = await callTool('list_projects', {});
315
- sendToolResult(response, result);
646
+ throw new RequestError(405, '不支持的项目操作。');
647
+ }
648
+
649
+ const updateProjectMatch = /^\/api\/projects\/([^/]+)$/.exec(url.pathname);
650
+ if (request.method === 'POST' && (url.pathname === '/api/projects' || updateProjectMatch)) {
651
+ await requireExecutionWritable();
652
+ const body = await readJsonBody(request);
653
+ try {
654
+ const result = !updateProjectMatch ? await createWorkstationProject(sharedServer, stateRoot, codexDataRoot, body)
655
+ : await updateWorkstationProject(sharedServer, stateRoot, codexDataRoot, decodeURIComponent(updateProjectMatch[1]), body);
656
+ sendJson(response, updateProjectMatch ? 200 : 201, result);
657
+ } catch (error) { throw new RequestError(error.statusCode || 502, error.message, error.details); }
658
+ return;
659
+ }
660
+
661
+ if (request.method === 'GET' && url.pathname === '/api/plan-pricing') {
662
+ sendJson(response, 200, await readPlanPricing(sharedServer));
316
663
  return;
317
664
  }
318
665
 
319
666
  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', {}));
667
+ sendJson(response, 200, await sharedServer.usage());
668
+ return;
669
+ }
670
+
671
+ if (request.method === 'POST' && url.pathname === '/api/usage-reset') {
672
+ await requireExecutionWritable();
673
+ const body = await readJsonBody(request);
674
+ try { sendJson(response, 200, await consumeUsageReset(sharedServer, body)); }
675
+ catch (error) { throw new RequestError(error.statusCode || 502, error.message,
676
+ error.code === 'usage_reset_not_submitted' ? { code: error.code, deliveryUnknown: false } : undefined); }
322
677
  return;
323
678
  }
324
679
 
325
- if (request.method === 'GET' && url.pathname === '/api/usage-events' && sharedServer) {
680
+ if (request.method === 'GET' && url.pathname === '/api/usage-events') {
326
681
  const execution = sharedServer;
327
682
  response.startEvents();
328
683
  const emit = value => { if (!response.destroyed) response.event('usage', value); };
@@ -334,395 +689,357 @@ async function handleRequest(request, response) {
334
689
  return;
335
690
  }
336
691
 
337
- if (request.method === 'GET' && url.pathname === '/api/app-server/status') {
338
- sendJson(response, 200, appServerBridgeStatus());
692
+ if (request.method === 'GET' && url.pathname === '/api/thread-events') {
693
+ await sharedServer.connect();
694
+ const clients = [sharedServer, dotsServer, dotsChat];
695
+ response.startEvents();
696
+ const listener = ({ method, params }) => {
697
+ const questionChange = questionAttention.fromNotification(method, params);
698
+ if ((!threadListNotificationMethods.has(method) && !questionChange) || response.destroyed) return;
699
+ response.event('thread-list-changed', { method, threadId: params.threadId || params.thread?.id || '',
700
+ ...(questionChange ? { questionChange } : {}),
701
+ ...(params.hostId ? { hostId: params.hostId } : {}),
702
+ ...(params.chatRevision !== undefined ? { chatRevision: params.chatRevision } : {}),
703
+ ...(params.updatedAt !== undefined ? { updatedAt: params.updatedAt } : {}),
704
+ ...(params.chatMessageRevision !== undefined ? { chatMessageRevision: params.chatMessageRevision } : {}),
705
+ ...(params.status ? { status: typeof params.status === 'string' ? params.status : params.status.type } : {}) });
706
+ };
707
+ const disconnected = () => response.end();
708
+ for (const client of clients) {
709
+ client.on('notification', listener);
710
+ client.on('disconnected', disconnected);
711
+ }
712
+ response.once('close', () => {
713
+ for (const client of clients) {
714
+ client.off('notification', listener);
715
+ client.off('disconnected', disconnected);
716
+ }
717
+ });
718
+ response.event('ready', {});
339
719
  return;
340
720
  }
341
721
 
342
- if (request.method === 'POST' && url.pathname === '/api/app-server/start') {
343
- await requireExecutionWritable();
344
- await appServer.start();
345
- sendJson(response, 200, appServerBridgeStatus());
722
+ if (request.method === 'GET' && url.pathname === '/api/turn-events') {
723
+ const clients = [sharedServer, dotsServer, dotsChat];
724
+ response.startEvents();
725
+ const listener = ({ method, params }) => {
726
+ if (!['turn/started', 'turn/completed'].includes(method)) return;
727
+ const threadId = params?.threadId, turnId = params?.turn?.id;
728
+ if (typeof threadId === 'string' && typeof turnId === 'string' && !response.destroyed) {
729
+ response.event('turn', { method, threadId, turnId });
730
+ }
731
+ };
732
+ const disconnected = () => response.end();
733
+ for (const client of clients) {
734
+ client.on('notification', listener);
735
+ client.on('disconnected', disconnected);
736
+ }
737
+ response.once('close', () => {
738
+ for (const client of clients) {
739
+ client.off('notification', listener);
740
+ client.off('disconnected', disconnected);
741
+ }
742
+ });
346
743
  return;
347
744
  }
348
745
 
349
- const createThreadMatch = /^\/api\/projects\/([^/]+)\/threads$/.exec(url.pathname);
350
- if (request.method === 'POST' && createThreadMatch) {
351
- 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,
746
+ const activeTurnMatch = /^\/api\/threads\/([^/]+)\/active-turn$/.exec(url.pathname);
747
+ if (request.method === 'GET' && activeTurnMatch) {
748
+ if (rootChat) { sendJson(response, 200, { turnId: null }); return; }
749
+ const threadId = decodeURIComponent(activeTurnMatch[1]);
750
+ await execution.connect();
751
+ const { thread } = await execution.request('thread/read', { threadId, includeTurns: false });
752
+ const status = typeof thread.status === 'string' ? thread.status : thread.status?.type;
753
+ let turnId = null;
754
+ if (status === 'active') {
755
+ const page = await execution.request('thread/turns/list', {
756
+ threadId, limit: 1, sortDirection: 'desc', itemsView: 'summary',
371
757
  });
372
- sendJson(response, 201, {
373
- threadId: result.record.id,
374
- hostId: 'app-server',
375
- transport: 'app-server',
376
- turnId: result.turn.id,
758
+ const latest = page.data?.[0];
759
+ if (latest?.status === 'inProgress') turnId = latest.id;
760
+ }
761
+ sendJson(response, 200, { turnId });
762
+ return;
763
+ }
764
+
765
+ const createStatusMatch = /^\/api\/projects\/([^/]+)\/threads\/([a-f0-9]{32})\/status$/.exec(url.pathname);
766
+ if (request.method === 'GET' && createStatusMatch) {
767
+ const creationScope = ['project', decodeURIComponent(createStatusMatch[1])], clientId = createStatusMatch[2];
768
+ let status = messageReceipts.status(creationScope, clientId);
769
+ if (status.state === 'unknown' && status.createdThreadId) {
770
+ const threadId = status.createdThreadId;
771
+ status = await messageReceipts.reconcile(creationScope, clientId, async () => {
772
+ const found = await sharedServer.findSentMessage(threadId, clientId);
773
+ return found ? { threadId, hostId: 'local', turnId: found.data?.turnId } : null;
377
774
  });
378
- return;
379
775
  }
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 };
776
+ sendJson(response, 200, status); return;
777
+ }
778
+ const createThreadMatch = /^\/api\/projects\/([^/]+)\/threads$/.exec(url.pathname);
779
+ if (request.method === 'POST' && createThreadMatch) {
780
+ const projectId = decodeURIComponent(createThreadMatch[1]);
781
+ const body = await readJsonBody(request);
782
+ const result = await messageReceipts.submit(['project', projectId], body.clientUserMessageId, async () => {
783
+ const bindCreation = messageReceipts.beginCreation(['project', projectId], body.clientUserMessageId);
784
+ try {
785
+ await requireExecutionWritable();
786
+ if (body.transport === 'app-server' || (body.hostId && body.hostId !== 'local')) {
787
+ throw new RequestError(410, '此连接方式已停用,请检查 Codex 内核状态。', { code: 'unsupported_control' });
788
+ }
789
+ const projects = (await sharedCatalog.projects()).projects;
790
+ const project = projects.find(item => item.projectId === projectId);
791
+ if (projectId !== '__unassigned__' && !project) throw new RequestError(404, '没有找到对应项目。');
792
+ const execution = sharedServer;
793
+ await execution.connect();
794
+ const rpc = (method, params) => execution.request(method, params);
795
+ let target = appServerEvents.projectlessThreadTarget(projectRoot);
796
+ if (project) {
797
+ const nativeProjects = await appServerEvents.readProjects(rpc);
798
+ const nativeProject = nativeProjects.find(item => item.id === project.projectId);
799
+ if (!nativeProject) throw new RequestError(404, 'Codex 内核中没有找到对应项目,请刷新项目列表。');
800
+ target = appServerEvents.projectThreadTarget(nativeProject);
801
+ }
802
+ const { input } = appServerTurnArguments('', body);
803
+ const preference = await execution.globalPermissions(undefined, { cwd: target.cwd });
804
+ messageSubmissionStarted = true;
805
+ const createRpc = (method, params) => method === 'thread/start'
806
+ ? execution.startThread(params) : rpc(method, params);
807
+ const created = await appServerEvents.createThread(createRpc, target, input,
808
+ { ...body, desktopPermissionMode: preference.globalPermissionMode }, undefined, bindCreation);
809
+ const prompt = input.filter(part => part.type === 'text').map(part => part.text).join('\n');
810
+ void generateAndSetThreadTitle({
811
+ request: rpc,
812
+ subscribe: listener => {
813
+ execution.on('notification', listener);
814
+ return () => execution.off('notification', listener);
815
+ },
816
+ threadId: created.threadId,
817
+ prompt,
818
+ cwd: target.cwd || null,
819
+ }).catch(error => console.error(`生成对话标题失败:${error.message}`));
820
+ return { ...created, hostId:'local' };
821
+ } catch (error) {
822
+ error.deliveryUnknown = Boolean(messageReceipts.status(['project', projectId], body.clientUserMessageId).createdThreadId)
823
+ || messageSubmissionStarted && !messageDelivery.rejectedBeforeSubmission(error);
824
+ if (error.deliveryResult) error.deliveryResult = error.deliveryResult.then(created => ({ ...created, hostId: 'local' })).finally(() => bindCreation());
825
+ else bindCreation();
826
+ throw error;
392
827
  }
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));
828
+ });
829
+ sendJson(response, 201, result);
411
830
  return;
412
831
  }
413
832
 
414
833
  const timelineMatch = /^\/api\/threads\/([^/]+)\/timeline$/.exec(url.pathname);
415
- if (request.method === 'GET' && timelineMatch) {
416
- 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;
436
- }
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;
834
+ if (request.method === 'GET' && timelineMatch) {
835
+ const threadId = decodeURIComponent(timelineMatch[1]);
836
+ const knownSeq = url.searchParams.get('knownSeq');
837
+ const knownSync = { id: url.searchParams.get('knownSyncId'),
838
+ epoch: url.searchParams.get('knownEpoch'), seq: knownSeq === null ? undefined : Number(knownSeq),
839
+ updatedAt: Number(url.searchParams.get('knownUpdatedAt')), chatRevision: url.searchParams.get('knownChatRevision') };
840
+ const pageOptions = sharedHistoryPageOptions();
841
+ pageOptions.reconcileObserved = url.searchParams.get('refresh') === '1';
842
+ if (request.socketRequest) {
843
+ pageOptions.onWindow = async window => {
844
+ if (response.writableEnded) return;
845
+ await response.historyPage(await prepareSharedTimeline(window));
846
+ };
444
847
  }
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 });
848
+ const timeline = await prepareSharedTimeline(await execution.timeline(threadId, pageOptions, knownSync));
849
+ if (request.socketRequest) {
850
+ response.on('progress', () => execution.keepSnapshotAlive(threadId, timeline.sync));
851
+ response.once('close', () => execution.keepSnapshotAlive(threadId, timeline.sync));
453
852
  }
853
+ sendJson(response, 200, timeline);
454
854
  return;
455
855
  }
456
856
 
457
857
  const approvalMatch = /^\/api\/threads\/([^/]+)\/approvals$/.exec(url.pathname);
458
- if (request.method === 'POST' && approvalMatch) {
459
- 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
- }
858
+ if (request.method === 'GET' && approvalMatch && rootChat) {
859
+ const threadId = decodeURIComponent(approvalMatch[1]);
860
+ sendJson(response, 200, await execution.inspectApproval(threadId, {
861
+ id: url.searchParams.get('requestId'), roomMessageId: url.searchParams.get('roomMessageId'),
862
+ })); return;
863
+ }
864
+ if (request.method === 'POST' && approvalMatch) {
865
+ const threadId = decodeURIComponent(approvalMatch[1]);
866
+ await requireExecutionWritable(threadId, execution);
464
867
  const body = await readJsonBody(request);
465
- sendJson(response, 200, await sharedServer.respondApproval(threadId, body)); return;
868
+ sendJson(response, 200, await execution.respondApproval(threadId, body)); return;
466
869
  }
467
870
 
468
871
  const eventsMatch = /^\/api\/threads\/([^/]+)\/events$/.exec(url.pathname);
469
872
  if (request.method === 'GET' && eventsMatch) {
470
873
  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
- );
874
+ const after = Number(url.searchParams.get('afterSeq'));
875
+ const requestedSync = { id: url.searchParams.get('syncId'), epoch: url.searchParams.get('syncEpoch'),
876
+ after: Number.isSafeInteger(after) && after >= 0 ? after : undefined,
877
+ updatedAt: Number(url.searchParams.get('knownUpdatedAt')), chatRevision: url.searchParams.get('knownChatRevision') };
878
+ await streamSharedThread(response, threadId, execution,
879
+ prepareSharedTimeline, requestedSync);
494
880
  return;
495
881
  }
496
882
 
883
+ const modelChangesMatch = /^\/api\/threads\/([^/]+)\/model-changes$/.exec(url.pathname);
884
+ if (request.method === 'GET' && modelChangesMatch) {
885
+ const threadId = decodeURIComponent(modelChangesMatch[1]);
886
+ if (!/^[0-9a-f]{8}-[0-9a-f-]{20,55}$/i.test(threadId)) throw new RequestError(400, '对话 ID 无效。');
887
+ sendJson(response, 200, hostId === 'durable' ? [] : await turnModelHistory.read(findSessionPath(threadId), (url.searchParams.get('turnIds') || '').split(',').filter(Boolean)));
888
+ return;
889
+ }
497
890
  const contextMatch = /^\/api\/threads\/([^/]+)\/context$/.exec(url.pathname);
498
891
  if (request.method === 'GET' && contextMatch) {
499
892
  const threadId = decodeURIComponent(contextMatch[1]);
500
- const current = readContextUsage(threadId);
501
- const native = usesSharedTask({ hostId: url.searchParams.get('hostId') }) ? await sharedServer.readContext(threadId) : {};
893
+ const current = hostId === 'durable' ? { available: false } : readContextUsage(threadId);
894
+ const native = await execution.readContext(threadId, url.searchParams.get('refreshSettings') === 'true');
502
895
  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 : {}) });
896
+ permissionMode: native.permissionMode, permissionModes: native.permissionModes, globalPermissionMode: native.globalPermissionMode } : {}), ...(native.available ? native : {}) });
504
897
  return;
505
898
  }
506
899
 
507
900
  const sharedAction = /^\/api\/threads\/([^/]+)\/(history|settings|permissions)$/.exec(url.pathname);
508
- if (sharedAction) {
901
+ if (sharedAction) {
509
902
  const id = decodeURIComponent(sharedAction[1]);
510
- if (!usesSharedTask({ hostId: url.searchParams.get('hostId') }) || appServerTasks.owns(id)) throw new RequestError(400, '此连接不支持该操作。');
511
903
  if (request.method === 'GET' && sharedAction[2] === 'history') {
512
- sendJson(response, 200, normalizeSharedTimeline(await sharedServer.history(id, url.searchParams.get('cursor'), sharedHistoryPageOptions())));
904
+ const pageOptions = sharedHistoryPageOptions();
905
+ if (url.searchParams.has('itemLimit')) {
906
+ const itemLimit = Number(url.searchParams.get('itemLimit'));
907
+ if (!Number.isInteger(itemLimit) || itemLimit < 1 || itemLimit > 100) throw new RequestError(400, '历史条目数量无效。');
908
+ pageOptions.itemLimit = itemLimit;
909
+ }
910
+ const history = await execution.history(id, url.searchParams.get('cursor'), pageOptions);
911
+ sendJson(response, 200, await prepareSharedTimeline(history, hostId));
513
912
  return;
514
913
  }
515
- if (request.method === 'POST' && sharedAction[2] === 'settings') {
516
- await requireExecutionWritable(id);
914
+ if (request.method === 'POST' && sharedAction[2] === 'settings') {
915
+ if (rootChat) throw new RequestError(405, 'Dots 主对话不提供 Codex 模型设置。');
916
+ await requireExecutionWritable(id, execution);
517
917
  const body = await readJsonBody(request);
518
- sendJson(response, 200, await sharedServer.settings(id, body.model, body.thinking, body.serviceTier));
918
+ sendJson(response, 200, await execution.settings(id, body.model, body.thinking, body.serviceTier));
519
919
  return;
520
920
  }
521
- if (request.method === 'POST' && sharedAction[2] === 'permissions') {
522
- await requireExecutionWritable(id);
921
+ if (request.method === 'POST' && sharedAction[2] === 'permissions') {
922
+ if (rootChat) throw new RequestError(405, 'Dots 主对话不提供 Codex 权限设置。');
923
+ await requireExecutionWritable(id, execution);
523
924
  const body = await readJsonBody(request);
524
- sendJson(response, 200, await sharedServer.permissions(id, body.mode));
925
+ sendJson(response, 200, await execution.permissions(id, body.mode));
525
926
  return;
526
- }
527
- }
528
-
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 });
540
- return;
541
- }
542
-
543
- const archiveMatch = /^\/api\/threads\/([^/]+)\/(archive|unarchive)$/.exec(url.pathname);
544
- if (request.method === 'POST' && archiveMatch) {
545
- 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
927
  }
550
- await sharedServer.request(`thread/${archiveMatch[2]}`, { threadId });
551
- sendJson(response, 200, { threadId, archived: archiveMatch[2] === 'archive' });
552
- return;
553
928
  }
554
929
 
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' });
930
+ const renameMatch = /^\/api\/threads\/([^/]+)\/name$/.exec(url.pathname);
931
+ if (request.method === 'POST' && renameMatch) {
932
+ if (rootChat) throw new RequestError(405, '点点主聊天名称由点点资料管理。');
933
+ const threadId = decodeURIComponent(renameMatch[1]);
934
+ await requireExecutionWritable(threadId, execution);
935
+ const body = await readJsonBody(request);
936
+ const name = typeof body.name === 'string' ? body.name.trim() : '';
937
+ if (!name || /[\r\n]/.test(name)) throw new RequestError(400, '请输入单行对话名称。');
938
+ await execution.request('thread/name/set', { threadId, name });
939
+ sendJson(response, 200, { threadId, name });
565
940
  return;
566
941
  }
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;
942
+
943
+ const archiveMatch = /^\/api\/threads\/([^/]+)\/(archive|unarchive)$/.exec(url.pathname);
944
+ if (request.method === 'POST' && archiveMatch) {
945
+ if (rootChat) throw new RequestError(405, 'Dots 主对话不提供任务归档。');
946
+ const threadId = decodeURIComponent(archiveMatch[1]);
947
+ await requireExecutionWritable(threadId, execution);
948
+ if (archiveMatch[2] === 'archive') {
949
+ const { thread } = await execution.request('thread/read', { threadId, includeTurns: false });
950
+ const status = typeof thread.status === 'string' ? thread.status : thread.status?.type;
951
+ if (['active', 'inProgress'].includes(status)) throw new RequestError(409, '对话正在运行,请先停止任务。');
575
952
  }
576
- const argumentsValue = readThreadArguments(url, threadId, 10, 12_000);
577
- sendToolResult(response, await callTool('read_thread', argumentsValue));
953
+ await execution.request(`thread/${archiveMatch[2]}`, { threadId });
954
+ sendJson(response, 200, { threadId, archived: archiveMatch[2] === 'archive' });
578
955
  return;
579
956
  }
580
957
 
581
958
  const queueMatch = /^\/api\/threads\/([^/]+)\/queue$/.exec(url.pathname);
582
959
  if (queueMatch) {
960
+ if (hostId === 'durable') throw new RequestError(405, '当前托管连接不提供排队消息。');
583
961
  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
- }
598
- if (request.method === 'POST') {
599
- await requireExecutionWritable(threadId);
962
+ await sharedServer.connect();
963
+ const rpc = (method, params) => sharedServer.request(method, params);
964
+ if (request.method === 'GET') { sendJson(response, 200, await appServerEvents.readQueue(rpc, threadId)); return; }
965
+ if (request.method === 'POST') {
600
966
  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;
967
+ const result = await messageReceipts.submit([hostId, threadId], body.clientUserMessageId, async () => {
968
+ let args;
969
+ try { await requireExecutionWritable(threadId); args = appServerTurnArguments(threadId, body); }
970
+ catch (error) { error.deliveryUnknown = false; throw error; }
971
+ messageSubmissionStarted = true;
972
+ try { return await appServerEvents.addQueue(rpc, threadId, args.input, body.clientUserMessageId, body.containsVoiceInput); }
973
+ catch (error) { if (messageDelivery.rejectedBeforeSubmission(error)) error.deliveryUnknown = false; throw error; }
974
+ });
975
+ sendJson(response, 202, result); return;
620
976
  }
621
977
  }
622
978
 
623
979
  const queueItemMatch = /^\/api\/threads\/([^/]+)\/queue\/([a-f0-9-]+)(?:\/(steer))?$/.exec(url.pathname);
624
980
  if (queueItemMatch) {
981
+ if (hostId === 'durable') throw new RequestError(405, '当前托管连接不提供排队消息。');
625
982
  const threadId = decodeURIComponent(queueItemMatch[1]);
626
983
  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]);
663
- 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;
984
+ await sharedServer.connect();
985
+ const action = request.method === 'DELETE' ? 'delete' : queueItemMatch[3] === 'steer' ? 'steer' : null;
986
+ if (!action) throw new RequestError(405, '不支持的队列操作');
987
+ await requireExecutionWritable(threadId);
988
+ sendJson(response, 200, await appServerEvents.changeQueue((method, params) => sharedServer.request(method, params), threadId, itemId, action)); return;
678
989
  }
679
990
 
680
991
  const interruptMatch = /^\/api\/threads\/([^/]+)\/interrupt$/.exec(url.pathname);
681
- if (request.method === 'POST' && interruptMatch) {
682
- 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, '请先接管空闲聊天。');
992
+ if (request.method === 'POST' && interruptMatch) {
993
+ if (rootChat) throw new RequestError(405, 'Dots 主对话不提供停止 Codex 任务的操作。');
994
+ const threadId = decodeURIComponent(interruptMatch[1]);
995
+ await requireExecutionWritable(threadId, execution);
692
996
  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);
997
+ if (!body.turnId) throw new RequestError(400, '缺少活动轮次 ID。');
998
+ await execution.interrupt(threadId, body.turnId);
699
999
  sendJson(response, 200, { requested: true });
700
1000
  return;
701
1001
  }
702
1002
 
703
- const messageMatch = /^\/api\/threads\/([^/]+)\/messages$/.exec(url.pathname);
704
- if (request.method === 'POST' && messageMatch) {
705
- 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
- }
1003
+ const roomReadMatch = /^\/api\/threads\/([^/]+)\/read$/.exec(url.pathname);
1004
+ if (request.method === 'POST' && roomReadMatch) {
1005
+ if (!rootChat) throw new RequestError(405, '此对话不提供消息房间已读操作。');
1006
+ await dotsChat.markRead(decodeURIComponent(roomReadMatch[1]), await readJsonBody(request));
1007
+ sendJson(response, 200, { success: true });
720
1008
  return;
721
1009
  }
722
1010
 
723
- if (request.method === 'POST' && url.pathname === '/api/wait') {
1011
+ const messageStatusMatch = /^\/api\/threads\/([^/]+)\/messages\/([a-f0-9]{32})\/status$/.exec(url.pathname);
1012
+ if (request.method === 'GET' && messageStatusMatch) {
1013
+ const threadId = decodeURIComponent(messageStatusMatch[1]), clientId = messageStatusMatch[2];
1014
+ const result = await messageReceipts.reconcile([hostId, threadId], clientId, async () => {
1015
+ if (hostId !== 'durable') {
1016
+ await sharedServer.connect();
1017
+ const queue = await appServerEvents.readQueue((method, params) => sharedServer.request(method, params), threadId);
1018
+ if (queue.items.some(item => messageDelivery.normalizeId(item.clientUserMessageId) === clientId)) return queue;
1019
+ }
1020
+ return execution.findSentMessage(threadId, clientId, url.searchParams.get('anchorItemId') || '');
1021
+ });
1022
+ sendJson(response, 200, result);
1023
+ return;
1024
+ }
1025
+ const messageMatch = /^\/api\/threads\/([^/]+)\/messages$/.exec(url.pathname);
1026
+ if (request.method === 'POST' && messageMatch) {
1027
+ const threadId = decodeURIComponent(messageMatch[1]);
724
1028
  const body = await readJsonBody(request);
725
- sendToolResult(response, await callTool('wait_threads', body, 130_000));
1029
+ if (body.hostId && body.hostId !== hostId) throw new RequestError(400, '请求中的对话主机不一致。');
1030
+ const result = await messageReceipts.submit([hostId, threadId], body.clientUserMessageId, async () => {
1031
+ let args;
1032
+ try { await requireExecutionWritable(threadId, execution); args = execution === dotsChat ? { ...body, threadId } : appServerTurnArguments(threadId, { ...body, hostId }); }
1033
+ catch (error) { error.deliveryUnknown = false; throw error; }
1034
+ messageSubmissionStarted = true;
1035
+ const responseFor = submitted => ({ success: true, data: submitted.result, usage: submitted.usage });
1036
+ try { return responseFor(await execution.send(args)); }
1037
+ catch (error) {
1038
+ if (error.deliveryResult) error.deliveryResult = error.deliveryResult.then(responseFor);
1039
+ throw error;
1040
+ }
1041
+ });
1042
+ sendJson(response, 200, result);
726
1043
  return;
727
1044
  }
728
1045
 
@@ -732,58 +1049,75 @@ async function handleRequest(request, response) {
732
1049
  if (!response.writableEnded) response.end();
733
1050
  return;
734
1051
  }
735
- const status = error instanceof RequestError || error instanceof ThreadWriterConflictError ? (error.statusCode || 409) : 502;
736
- sendJson(response, status, {
737
- error: error instanceof Error ? error.message : String(error),
738
- code: error?.code,
739
- threadId: error?.threadId,
740
- readOnly: error?.readOnly,
741
- details: error?.details,
1052
+ if (!messageSubmissionStarted && error.deliveryUnknown === undefined && request.method === 'POST'
1053
+ && /^\/api\/threads\/[^/]+\/(?:messages|queue)(?:\?|$)/.test(request.url || '')) error.deliveryUnknown = false;
1054
+ const status = error instanceof RequestError || error instanceof ThreadWriterConflictError || error instanceof FileRangeError ? (error.statusCode || 409) : 502;
1055
+ sendJson(response, status, {
1056
+ error: error instanceof Error ? error.message : String(error),
1057
+ code: error?.code,
1058
+ threadId: error?.threadId,
1059
+ readOnly: error?.readOnly,
1060
+ details: error?.deliveryUnknown === undefined ? error?.details : { ...error?.details, deliveryUnknown: error.deliveryUnknown },
742
1061
  });
743
1062
  }
744
1063
  }
745
1064
 
746
1065
  const server = http.createServer(handleRequest);
747
1066
  const lanSocket = attachLanSocket(server, { token: options.token, dispatch: handleRequest,
1067
+ uploadsRoot,
748
1068
  authorizeDevice: (clientId, credential) => authorizeLanDevice(stateRoot, clientId, credential),
1069
+ onDeviceAuthorizationsChanged: listener => onLanAuthorizationsChanged(stateRoot, listener),
1070
+ onPhoneConnected: (...args) => connectionReporting(stateRoot).connected(...args),
1071
+ onPhoneDisconnected: id => connectionReporting(stateRoot).disconnected(id),
1072
+ onDeviceConnected: (clientId, info) => {
1073
+ try { recordLanConnection(stateRoot, clientId, info); }
1074
+ catch (error) { console.error('记录局域网设备连接失败:', error.message); }
1075
+ },
749
1076
  });
750
1077
 
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
-
760
- let control;
761
- desktopMonitor.start();
762
- void managedAppServer.start();
763
- executionHealth.start();
1078
+ let control;
1079
+ const powerManager = createPowerManager(stateRoot);
1080
+ void managedAppServer.start();
1081
+ executionHealth.start();
1082
+ runtimeDiscovery.start();
764
1083
  server.on('error', error => {
765
- console.error('Bridge 监听失败:', error.message);
1084
+ console.error('桌面服务监听失败:', error.message);
766
1085
  process.exit(1);
767
1086
  });
768
1087
  server.listen(options.port, options.lan ? '0.0.0.0' : '127.0.0.1', async () => {
769
1088
  try {
1089
+ await powerManager.start();
770
1090
  if (process.env.CODEX_REMOTE_INSTANCE && process.env.CODEX_REMOTE_CONTROL_SECRET && process.env.CODEX_REMOTE_READY_PATH) {
771
- control = await openControl({ instanceId: process.env.CODEX_REMOTE_INSTANCE,
772
- secret: process.env.CODEX_REMOTE_CONTROL_SECRET, shutdown: shutDown, actions: {
773
- 'refresh-execution': refreshExecution,
774
- 'restart-managed-app-server': () => {
775
- const state = managedAppServer.snapshot();
776
- return state.launcherRestartRequired ? managedAppServer.restart(crypto.randomUUID()) : state;
777
- },
778
- } });
1091
+ control = await openControl({ instanceId: process.env.CODEX_REMOTE_INSTANCE,
1092
+ secret: process.env.CODEX_REMOTE_CONTROL_SECRET, shutdown: shutDown, actions: {
1093
+ 'refresh-execution': refreshExecution,
1094
+ 'keep-awake-status': () => powerManager.snapshot(),
1095
+ 'keep-awake-enable': () => powerManager.setEnabled(true),
1096
+ 'keep-awake-disable': () => powerManager.setEnabled(false),
1097
+ 'relay-start': () => relayManager.run('relay-start'),
1098
+ 'relay-status': () => relayManager.run('relay-status'),
1099
+ 'relay-stop': () => relayManager.run('relay-stop'),
1100
+ 'relay-pair': () => relayManager.run('relay-pair'),
1101
+ 'relay-revoke': () => relayManager.run('relay-revoke'),
1102
+ 'invite-create': details => relayManager.run('invite-create', details),
1103
+ 'invite-list': () => relayManager.run('invite-list'),
1104
+ 'invite-revoke': details => relayManager.run('invite-revoke', details),
1105
+ 'devices-list': () => relayManager.run('devices-list'),
1106
+ 'device-revoke': details => relayManager.run('device-revoke', details),
1107
+ 'restart-managed-app-server': () => {
1108
+ const state = managedAppServer.snapshot();
1109
+ return state.launcherRestartRequired ? managedAppServer.restart(crypto.randomUUID()) : state;
1110
+ },
1111
+ 'restart-managed-app-server-confirmed': () => managedAppServer.restart(crypto.randomUUID()),
1112
+ } });
779
1113
  writeRecord(process.env.CODEX_REMOTE_READY_PATH, { instanceId: process.env.CODEX_REMOTE_INSTANCE,
780
1114
  pid: process.pid, controlPort: control.port });
781
1115
  }
782
1116
  } catch (error) {
783
- console.error('Bridge 管理通道启动失败:', error.message);
1117
+ console.error('桌面服务管理通道启动失败:', error.message);
784
1118
  process.exit(1);
785
1119
  }
786
- console.log(`Code Relax Bridge 已启动:http://127.0.0.1:${options.port}/`);
1120
+ console.log(`Code Relax 桌面服务已启动:http://127.0.0.1:${options.port}/`);
787
1121
  if (options.lan) {
788
1122
  console.log('LAN 模式使用鉴权 WebSocket,仅适合受信任局域网测试,不要暴露到公网。');
789
1123
  }
@@ -793,149 +1127,22 @@ let shuttingDown = false;
793
1127
  async function shutDown() {
794
1128
  if (shuttingDown) return;
795
1129
  shuttingDown = true;
1130
+ await powerManager.close();
1131
+ desktopHelper?.release();
796
1132
  lanSocket.close();
797
1133
  server.close();
798
- desktopMonitor?.stop();
799
- executionHealth?.stop();
800
- managedAppServer.close();
801
- await appServer.close();
1134
+ executionHealth?.stop();
1135
+ runtimeDiscovery.stop();
1136
+ await relayManager.close();
1137
+ dotsServer.close();
1138
+ dotsChat.close();
1139
+ managedAppServer.close();
802
1140
  await control?.close();
803
1141
  process.exit(0);
804
1142
  }
805
1143
  process.on('SIGTERM', shutDown);
806
1144
  process.on('SIGINT', shutDown);
807
1145
 
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
1146
  function readContextUsage(threadId) {
940
1147
  if (!/^[0-9a-f]{8}-[0-9a-f-]{20,55}$/i.test(threadId)) {
941
1148
  return { available: false };
@@ -1058,108 +1265,6 @@ function findSessionPath(threadId) {
1058
1265
  return null;
1059
1266
  }
1060
1267
 
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
1268
  function messagePayload(body) {
1164
1269
  const prompt = typeof body.prompt === 'string' ? body.prompt.trim() : '';
1165
1270
  if (body.images !== undefined && (!Array.isArray(body.images) || body.images.length > 4)) {
@@ -1173,6 +1278,14 @@ function messagePayload(body) {
1173
1278
  if (!fs.existsSync(file)) throw new RequestError(400, '图片不存在,请重新添加。');
1174
1279
  return file;
1175
1280
  });
1281
+ for (const input of fileMentions.originalInputs(body)) {
1282
+ const incoming = fs.realpathSync(path.join(uploadsRoot, 'incoming'));
1283
+ const imagePath = fs.realpathSync(input.path);
1284
+ if (path.dirname(imagePath).toLowerCase() !== incoming.toLowerCase()) throw new RequestError(400, '原图必须来自上传目录。');
1285
+ const stat = fs.statSync(imagePath);
1286
+ if (!stat.isFile() || stat.size <= 0 || stat.size > MAX_ATTACHMENT_BYTES) throw new RequestError(400, '原图为空或超过 20 MiB。');
1287
+ images.push(imagePath);
1288
+ }
1176
1289
  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
1290
  if (!prompt && !images.length) throw new RequestError(400, 'prompt 不能为空。');
1178
1291
  if (prompt.length > 32_000) throw new RequestError(400, 'prompt 不能超过 32000 个字符。');
@@ -1180,9 +1293,7 @@ function messagePayload(body) {
1180
1293
  const payload = { prompt, images };
1181
1294
  if (typeof body.hostId === 'string' && body.hostId.trim()) payload.hostId = body.hostId.trim();
1182
1295
  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;
1296
+ payload.model = body.model.trim();
1186
1297
  }
1187
1298
  if (typeof body.thinking === 'string' && body.thinking.trim()) {
1188
1299
  const thinking = body.thinking.trim();
@@ -1196,302 +1307,32 @@ function messagePayload(body) {
1196
1307
  return payload;
1197
1308
  }
1198
1309
 
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
1310
  function appServerTurnArguments(threadId, body) {
1220
1311
  const payload = messagePayload(body);
1312
+ const mentioned = payload.hostId === 'durable' ? fileMentions.parse(payload.prompt) : null;
1313
+ if (mentioned?.files.length) payload.prompt = mentioned.text;
1221
1314
  const input = [];
1222
1315
  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 };
1316
+ for (const image of payload.images) {
1317
+ if (image.startsWith('data:')) input.push({ type: 'image', url: image });
1318
+ else if (payload.hostId === 'durable') {
1319
+ const mimeType = imageMimeType(image);
1320
+ if (!mimeType) throw new RequestError(400, '云端图片格式无效。');
1321
+ input.push({ type: 'image', url: `data:${mimeType};base64,${fs.readFileSync(image).toString('base64')}` });
1322
+ } else input.push({ type: 'localImage', path: image });
1323
+ }
1324
+ const value = { threadId, input, ...appServerEvents.mobileTurnSource(body.clientUserMessageId, body.containsVoiceInput) };
1325
+ if (mentioned?.files.length) value.uploadFiles = mentioned.files;
1225
1326
  if (typeof body.clientUserMessageId === 'string' && /^[a-f0-9]{32}$/.test(body.clientUserMessageId)) value.clientUserMessageId = body.clientUserMessageId;
1226
1327
  if (payload.model) value.model = payload.model;
1227
1328
  if (payload.thinking) value.effort = payload.thinking;
1228
1329
  return value;
1229
1330
  }
1230
1331
 
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) };
1265
- }
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) {
1332
+ function normalizeTimeline(data) {
1490
1333
  const source = data && typeof data === 'object' ? data : {};
1491
1334
  const sourceTurns = Array.isArray(source.turns) ? source.turns : source.thread?.turns;
1492
1335
  const turns = Array.isArray(sourceTurns) ? sourceTurns.map(normalizeTurn) : [];
1493
- if (reverseTurns) turns.reverse();
1494
- if (reverseTurns) restoreEmptyTurnMessages(source.thread?.id, turns);
1495
1336
  return {
1496
1337
  schemaVersion: 1,
1497
1338
  thread: normalizeThread(source.thread),
@@ -1499,113 +1340,88 @@ function normalizeTimeline(data, reverseTurns = true) {
1499
1340
  };
1500
1341
  }
1501
1342
 
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);
1343
+ function normalizeSharedTimeline({ thread, nextCursor, sync, notModified, incremental }, hostId = thread?.hostId || 'local') {
1344
+ const timeline = normalizeTimeline({ thread: { ...thread, title: thread.name || thread.title || '', hostId,
1345
+ transport: 'shared-app-server' } });
1346
+ return { ...timeline,
1347
+ historyItemCount: (thread.turns || []).reduce((count, turn) => count + (turn.roomMessage ? 1 : turn.items?.length || 0), 0),
1348
+ historyView: thread?.threadSource === 'aeon' ? 'chat-items-v1' : 'item-pages-v1',
1349
+ ...(notModified ? { notModified: true } : {}),
1350
+ ...(incremental ? { incremental: true } : {}),
1351
+ ...(nextCursor !== undefined ? { nextCursor } : {}), ...(sync ? { sync } : {}) };
1513
1352
  }
1514
1353
 
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',
1518
- ...(nextCursor !== undefined ? { nextCursor } : {}), ...(sync ? { sync } : {}) };
1519
- }
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);
1354
+ async function prepareSharedTimeline(value, hostId) {
1355
+ if (value.thread?.threadSource !== 'aeon') return normalizeSharedTimeline(value, hostId);
1356
+ const epoch = dotsChat.epoch, threadId = value.thread.id;
1357
+ const current = () => {
1358
+ if (epoch !== dotsChat.epoch) throw new Error('点点连接已变化,请重新同步。');
1359
+ };
1360
+ if (value.notModified) {
1361
+ const record = dotsChat.threads.get(threadId);
1362
+ if (await dotsImages.prepareTimeline(record?.thread)) {
1363
+ current();
1364
+ value = dotsChat.snapshot(threadId, record);
1555
1365
  }
1556
- } finally { fs.closeSync(handle); }
1366
+ }
1367
+ const snapshot = structuredClone(value);
1368
+ await dotsImages.prepareTimeline(snapshot.thread);
1369
+ current();
1370
+ return normalizeSharedTimeline(snapshot, hostId);
1557
1371
  }
1558
1372
 
1559
- function normalizeThread(thread) {
1373
+ function normalizeThread(thread) {
1560
1374
  const source = thread && typeof thread === 'object' ? thread : {};
1561
1375
  return {
1562
- id: source.id || '',
1563
- title: source.name || source.title || source.preview || '',
1564
- status: statusText(source.status),
1565
- updatedAt: Number(source.updatedAt) || 0,
1566
- recencyAt: Number(source.recencyAt ?? source.updatedAt) || 0,
1567
- projectId: source.projectId || '',
1376
+ id: source.id || '',
1377
+ title: source.name || source.title || source.preview || '',
1378
+ status: statusText(source.status),
1379
+ updatedAt: Number(source.updatedAt) || 0,
1380
+ recencyAt: Number(source.recencyAt ?? source.updatedAt) || 0,
1381
+ projectId: source.projectId || '',
1568
1382
  cwd: source.cwd || '',
1569
1383
  hostId: source.hostId || '',
1570
- transport: source.transport || (source.hostId === 'app-server' ? 'app-server' : 'desktop-tools'),
1571
- readOnly: source.readOnly === true,
1572
- readOnlyReason: source.readOnlyReason || '',
1384
+ threadSource: source.threadSource || '',
1385
+ ...(['aeon', 'aeon_child'].includes(source.threadSource) ? { chatRevision: source.chatRevision || '' } : {}),
1386
+ ...(source.threadSource === 'aeon' ? { avatar: source.avatar || '', color: source.color || '',
1387
+ chatMessageRevision: source.chatMessageRevision, messagingRoomId: source.messagingRoomId, lastMessageAt: source.lastMessageAt } : {}),
1388
+ transport: source.transport || 'shared-app-server',
1389
+ readOnly: source.readOnly === true,
1390
+ readOnlyReason: source.readOnlyReason || '',
1573
1391
  };
1574
1392
  }
1575
1393
 
1576
- function normalizeTurn(turn) {
1577
- const source = turn && typeof turn === 'object' ? turn : {};
1578
- const turnId = source.id || '';
1579
- const entries = Array.isArray(source.items) ? source.items.map(normalizeItem).filter(Boolean) : [];
1580
- for (const entry of entries) {
1581
- const images = [...(entry.attachments || []), entry.image, ...Object.values(entry.imageAssets || {})].filter(Boolean);
1582
- for (const image of images) if (image.remotePath && !image.recoveryTurnId) image.recoveryTurnId = turnId;
1583
- }
1584
- return {
1585
- id: turnId,
1586
- status: statusText(source.status),
1394
+ function normalizeTurn(turn) {
1395
+ const source = turn && typeof turn === 'object' ? turn : {};
1396
+ const turnId = source.id || '';
1397
+ const entries = Array.isArray(source.items) ? source.items.map(normalizeItem).filter(Boolean) : [];
1398
+ for (const entry of entries) {
1399
+ const images = [...(entry.attachments || []), entry.image, ...Object.values(entry.imageAssets || {})].filter(Boolean);
1400
+ for (const image of images) if (image.remotePath && !image.recoveryTurnId) image.recoveryTurnId = turnId;
1401
+ }
1402
+ return {
1403
+ id: turnId,
1404
+ status: statusText(source.status),
1587
1405
  startedAt: source.startedAt || null,
1588
1406
  completedAt: source.completedAt || null,
1589
1407
  durationMs: source.durationMs ?? null,
1590
- error: source.error ?? null,
1591
- entries,
1592
- };
1593
- }
1594
-
1595
- function normalizeItem(item) {
1596
- if (!item || typeof item !== 'object') return null;
1597
-
1598
- if (item.type === 'lazyOperationGroup') {
1599
- return {
1600
- type: 'activity',
1601
- id: item.id || '',
1602
- kind: 'lazyOperationGroup',
1603
- turnId: item.turnId || '',
1604
- groupId: item.groupId || '',
1605
- title: item.title || '执行过程',
1606
- count: Number(item.count) || 0,
1607
- };
1608
- }
1408
+ error: source.error ?? null,
1409
+ runtimeNotice: source.runtimeNotice ?? null,
1410
+ ...(source.itemPage ? { itemPage: source.itemPage } : {}),
1411
+ ...(source.roomMessage ? { roomMessage: true } : {}),
1412
+ ...(source.deleted ? { deleted: true } : {}),
1413
+ ...(source.historyOnly ? { historyOnly: true } : {}),
1414
+ ...(source.cacheStub ? { cacheStub: true } : {}),
1415
+ entries,
1416
+ };
1417
+ }
1418
+
1419
+ function normalizeItem(item) {
1420
+ if (!item || typeof item !== 'object') return null;
1421
+
1422
+ if (item.type === 'approval') {
1423
+ return { ...item, type: 'activity', kind: 'approval', title: item.params?.message || '需要你的批准' };
1424
+ }
1609
1425
 
1610
1426
  if (item.type === 'contextCompaction') {
1611
1427
  return {
@@ -1616,24 +1432,21 @@ function normalizeItem(item) {
1616
1432
  };
1617
1433
  }
1618
1434
 
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
- }
1435
+ const delegated = appServerEvents.toolDelegation(item);
1436
+ if (delegated) {
1437
+ return {
1438
+ type: 'message', id: item.id || '', role: 'user', source: 'remote',
1439
+ delegation: delegationSource(delegated), ...splitUploadedImages(delegated.input),
1440
+ };
1632
1441
  }
1633
1442
 
1634
1443
  if (item.type === 'userMessage') {
1635
- const text = extractUserText(item.content);
1636
- const attachments = normalizeAttachments(item.content);
1444
+ const { text, files, delegation } = extractUserText(item.content);
1445
+ const attachments = [...normalizeAttachments(item.content), ...files.map(file => file.fileId ? normalizeAttachment(file) : ({
1446
+ ...file,
1447
+ remotePath: file.path,
1448
+ previewRemotePath: file.previewPath || '',
1449
+ }))];
1637
1450
  if (!text && !attachments.length) return null;
1638
1451
  return {
1639
1452
  clientId: item.clientId,
@@ -1642,6 +1455,7 @@ function normalizeItem(item) {
1642
1455
  role: 'user',
1643
1456
  text,
1644
1457
  attachments,
1458
+ ...(delegation ? { delegation: delegationSource(delegation) } : {}),
1645
1459
  };
1646
1460
  }
1647
1461
 
@@ -1654,176 +1468,180 @@ function normalizeItem(item) {
1654
1468
  delivery: item.delivery,
1655
1469
  questions: item.questions,
1656
1470
  text: item.text || '',
1471
+ attachments: normalizeAttachments(item.attachments),
1657
1472
  imageAssets: markdownImageAssets(item.text || ''),
1658
1473
  fileAssets: markdownFileAssets(item.text || ''),
1659
1474
  };
1660
1475
  }
1661
1476
 
1662
- if (item.type === 'reasoning') {
1663
- const detail = Array.isArray(item.summary)
1664
- ? item.summary.map(summaryText).filter(Boolean).join('\n')
1665
- : summaryText(item.summary);
1666
- return {
1667
- type: 'activity',
1668
- id: item.id || '',
1669
- kind: 'reasoning',
1670
- title: '思考摘要',
1671
- detail,
1672
- };
1673
- }
1674
-
1675
- if (item.type === 'commandExecution') {
1676
- return {
1677
- type: 'activity',
1678
- id: item.id || '',
1679
- kind: 'command',
1680
- title: '命令',
1681
- titleOnly: true,
1682
- status: statusText(item.status),
1683
- command: item.command,
1684
- cwd: item.cwd,
1685
- exitCode: item.exitCode,
1686
- durationMs: item.durationMs,
1687
- };
1688
- }
1689
-
1690
- if (item.type === 'mcpToolCall') {
1691
- return {
1692
- type: 'activity',
1693
- id: item.id || '',
1694
- kind: 'tool',
1695
- title: '工具',
1696
- titleOnly: true,
1697
- status: statusText(item.status),
1698
- server: item.server,
1699
- tool: item.tool,
1700
- arguments: item.arguments,
1701
- durationMs: item.durationMs,
1702
- error: item.error,
1703
- };
1704
- }
1705
-
1706
- if (item.type === 'dynamicToolCall') {
1707
- return {
1708
- type: 'activity',
1709
- id: item.id || '',
1710
- kind: 'tool',
1711
- title: '工具',
1712
- titleOnly: true,
1713
- status: statusText(item.status),
1714
- namespace: item.namespace,
1715
- tool: item.tool,
1716
- arguments: item.arguments,
1717
- durationMs: item.durationMs,
1718
- success: item.success,
1719
- };
1720
- }
1721
-
1722
- if (item.type === 'fileChange') {
1723
- return {
1724
- type: 'activity',
1725
- id: item.id || '',
1726
- kind: 'fileChange',
1727
- title: '文件修改',
1728
- titleOnly: true,
1729
- status: statusText(item.status),
1730
- changes: Array.isArray(item.changes) ? item.changes : [],
1731
- };
1732
- }
1733
-
1734
- if (item.type === 'webSearch') {
1735
- return {
1736
- type: 'activity',
1737
- id: item.id || '',
1738
- kind: 'webSearch',
1739
- title: '网页搜索',
1740
- titleOnly: true,
1741
- query: item.query || item.action?.query || '',
1742
- ...(item.action && typeof item.action === 'object' ? { action: item.action } : {}),
1743
- };
1744
- }
1745
-
1746
- if (item.type === 'imageView') {
1747
- return {
1748
- type: 'activity',
1749
- id: item.id || '',
1750
- kind: 'imageView',
1751
- title: '查看图片',
1752
- titleOnly: true,
1753
- path: item.path || '',
1754
- image: typeof item.path === 'string' && item.path
1755
- ? normalizeAttachment({ type: 'localImage', path: item.path })
1756
- : null,
1757
- };
1758
- }
1759
-
1760
- if (item.type === 'imageGeneration') {
1761
- const image = typeof item.savedPath === 'string' && item.savedPath
1762
- ? normalizeAttachment({ type: 'localImage', path: item.savedPath, name: '生成的图片' })
1763
- : null;
1764
- return {
1765
- type: 'activity',
1766
- id: item.id || '',
1767
- kind: 'imageGeneration',
1768
- title: '生成图片',
1769
- titleOnly: true,
1770
- status: statusText(item.status),
1771
- image,
1772
- failure: item.failure || null,
1773
- transparentBackground: item.transparentBackground === true,
1774
- };
1775
- }
1776
-
1777
- if (item.type === 'subAgentActivity') {
1778
- return {
1779
- type: 'activity',
1780
- id: item.id || '',
1781
- kind: 'subAgent',
1782
- title: '子任务',
1783
- titleOnly: true,
1784
- status: statusText(item.kind),
1785
- agentPath: item.agentPath || '',
1786
- agentThreadId: item.agentThreadId || '',
1787
- };
1788
- }
1789
-
1790
- if (item.type === 'collabAgentToolCall') {
1791
- return {
1792
- type: 'activity',
1793
- id: item.id || '',
1794
- kind: 'collaboration',
1795
- title: '任务协作',
1796
- titleOnly: true,
1797
- status: statusText(item.status),
1798
- tool: item.tool || '',
1799
- prompt: item.prompt || '',
1800
- receiverThreadIds: Array.isArray(item.receiverThreadIds) ? item.receiverThreadIds : [],
1801
- };
1802
- }
1803
-
1804
- return {
1477
+ if (item.type === 'reasoning') {
1478
+ const detail = Array.isArray(item.summary)
1479
+ ? item.summary.map(summaryText).filter(Boolean).join('\n')
1480
+ : summaryText(item.summary);
1481
+ return {
1482
+ type: 'activity',
1483
+ id: item.id || '',
1484
+ kind: 'reasoning',
1485
+ title: '思考摘要',
1486
+ detail,
1487
+ };
1488
+ }
1489
+
1490
+ if (item.type === 'commandExecution') {
1491
+ return {
1492
+ type: 'activity',
1493
+ id: item.id || '',
1494
+ kind: 'command',
1495
+ title: '命令',
1496
+ titleOnly: true,
1497
+ status: statusText(item.status),
1498
+ command: item.command,
1499
+ cwd: item.cwd,
1500
+ exitCode: item.exitCode,
1501
+ durationMs: item.durationMs,
1502
+ };
1503
+ }
1504
+
1505
+ if (item.type === 'mcpToolCall') {
1506
+ return {
1507
+ type: 'activity',
1508
+ id: item.id || '',
1509
+ kind: 'tool',
1510
+ title: '工具',
1511
+ titleOnly: true,
1512
+ status: statusText(item.status),
1513
+ server: item.server,
1514
+ tool: item.tool,
1515
+ arguments: item.arguments,
1516
+ durationMs: item.durationMs,
1517
+ error: item.error,
1518
+ };
1519
+ }
1520
+
1521
+ if (item.type === 'dynamicToolCall') {
1522
+ return {
1523
+ type: 'activity',
1524
+ id: item.id || '',
1525
+ kind: 'tool',
1526
+ title: '工具',
1527
+ titleOnly: true,
1528
+ status: statusText(item.status),
1529
+ namespace: item.namespace,
1530
+ tool: item.tool,
1531
+ arguments: item.arguments,
1532
+ durationMs: item.durationMs,
1533
+ success: item.success,
1534
+ };
1535
+ }
1536
+
1537
+ if (item.type === 'fileChange') {
1538
+ return {
1539
+ type: 'activity',
1540
+ id: item.id || '',
1541
+ kind: 'fileChange',
1542
+ title: '文件修改',
1543
+ titleOnly: true,
1544
+ status: statusText(item.status),
1545
+ changes: fileChanges.summarize(item.changes),
1546
+ };
1547
+ }
1548
+
1549
+ if (item.type === 'webSearch') {
1550
+ return {
1551
+ type: 'activity',
1552
+ id: item.id || '',
1553
+ kind: 'webSearch',
1554
+ title: '网页搜索',
1555
+ titleOnly: true,
1556
+ query: item.query || item.action?.query || '',
1557
+ ...(item.action && typeof item.action === 'object' ? { action: item.action } : {}),
1558
+ };
1559
+ }
1560
+
1561
+ if (item.type === 'imageView') {
1562
+ const image = typeof item.path === 'string' && item.path
1563
+ ? normalizeAttachment({ type: 'localImage', path: item.path })
1564
+ : null;
1565
+ return {
1566
+ type: 'activity',
1567
+ id: item.id || '',
1568
+ kind: 'imageView',
1569
+ title: '查看图片',
1570
+ titleOnly: true,
1571
+ path: item.path || '',
1572
+ image,
1573
+ };
1574
+ }
1575
+
1576
+ if (item.type === 'imageGeneration') {
1577
+ const result = typeof item.result === 'string' ? item.result.trim() : '';
1578
+ const image = typeof item.savedPath === 'string' && item.savedPath
1579
+ ? normalizeAttachment({ type: 'localImage', path: item.savedPath, name: '生成的图片' })
1580
+ : result ? normalizeAttachment({ type: 'image', name: '生成的图片',
1581
+ url: result.startsWith('data:image/') ? result : `data:image/png;base64,${result}` }) : null;
1582
+ return {
1583
+ type: 'activity',
1584
+ id: item.id || '',
1585
+ kind: 'imageGeneration',
1586
+ title: '生成图片',
1587
+ titleOnly: true,
1588
+ status: statusText(item.status),
1589
+ image,
1590
+ failure: item.failure || null,
1591
+ transparentBackground: item.transparentBackground === true,
1592
+ };
1593
+ }
1594
+
1595
+ if (item.type === 'subAgentActivity') {
1596
+ return {
1597
+ type: 'activity',
1598
+ id: item.id || '',
1599
+ kind: 'subAgent',
1600
+ title: '子任务',
1601
+ titleOnly: true,
1602
+ status: statusText(item.kind),
1603
+ agentPath: item.agentPath || '',
1604
+ agentThreadId: item.agentThreadId || '',
1605
+ };
1606
+ }
1607
+
1608
+ if (item.type === 'collabAgentToolCall') {
1609
+ return {
1610
+ type: 'activity',
1611
+ id: item.id || '',
1612
+ kind: 'collaboration',
1613
+ title: '任务协作',
1614
+ titleOnly: true,
1615
+ status: statusText(item.status),
1616
+ tool: item.tool || '',
1617
+ prompt: item.prompt || '',
1618
+ senderThreadId: item.senderThreadId || '',
1619
+ receiverThreadIds: Array.isArray(item.receiverThreadIds) ? item.receiverThreadIds : [],
1620
+ model: item.model || '',
1621
+ reasoningEffort: item.reasoningEffort || '',
1622
+ agentsStates: item.agentsStates && typeof item.agentsStates === 'object' ? item.agentsStates : {},
1623
+ };
1624
+ }
1625
+
1626
+ return {
1805
1627
  type: 'activity',
1806
1628
  id: item.id || '',
1807
1629
  kind: item.type || 'event',
1808
1630
  title: item.type || '事件',
1809
1631
  status: statusText(item.status),
1810
1632
  detail: JSON.stringify(item, null, 2),
1811
- };
1812
- }
1813
-
1814
- function sharedHistoryPageOptions() {
1815
- return {
1816
- maxTurns: SHARED_HISTORY_PAGE_TURNS,
1817
- maxBytes: SHARED_HISTORY_PAGE_BYTES,
1818
- measure: value => Buffer.byteLength(JSON.stringify(normalizeSharedTimeline(value))),
1819
- };
1820
- }
1821
-
1822
- function threadRecency(thread) {
1823
- return Number(thread?.recencyAt ?? thread?.updatedAt) || 0;
1824
- }
1825
-
1826
- function normalizeAttachments(content) {
1633
+ };
1634
+ }
1635
+
1636
+ function sharedHistoryPageOptions() {
1637
+ return {
1638
+ maxTurns: SHARED_HISTORY_PAGE_TURNS,
1639
+ maxBytes: SHARED_HISTORY_PAGE_BYTES,
1640
+ measure: value => Buffer.byteLength(JSON.stringify(normalizeSharedTimeline(value))),
1641
+ };
1642
+ }
1643
+
1644
+ function normalizeAttachments(content) {
1827
1645
  if (!Array.isArray(content)) return [];
1828
1646
  const items = content.filter(item => item && !['text', 'input_text'].includes(item.type)).map(item => ({ ...item }));
1829
1647
  const text = content.filter(item => ['text', 'input_text'].includes(item?.type)).map(item => item.text || '').join('\n');
@@ -1835,7 +1653,7 @@ function normalizeAttachments(content) {
1835
1653
  else items.push({ type: 'localImage', path: imagePath });
1836
1654
  }
1837
1655
  const seen = new Set();
1838
- return items.map(normalizeAttachment).filter(item => {
1656
+ return items.map(item => normalizeAttachment(item)).filter(item => {
1839
1657
  if (!item.url) return true;
1840
1658
  if (seen.has(item.url)) return false;
1841
1659
  seen.add(item.url); return true;
@@ -1847,64 +1665,55 @@ function markdownImageAssets(text) {
1847
1665
  function visit(tokens) {
1848
1666
  for (const token of tokens) {
1849
1667
  const value = token.type === 'image' ? token.attrGet('src')
1850
- : token.type === 'link_open' ? token.attrGet('href') : null;
1668
+ : token.type === 'link_open' && !token.meta?.fileCitation ? token.attrGet('href') : null;
1851
1669
  if (value && !Object.hasOwn(assets, value)) {
1852
- let localPath;
1853
- try { localPath = decodeURIComponent(value); } catch { continue; }
1670
+ const localPath = fileCitations.localReference(value)?.path;
1854
1671
  // Only paths actually referenced by Desktop messages become accessible.
1855
1672
  // 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, '');
1673
+ if (localPath && /^[a-z]:[\\/]/i.test(localPath)) {
1858
1674
  const asset = normalizeAttachment({ type: 'localImage', path: localPath });
1859
1675
  if (asset.available) assets[value] = asset;
1860
1676
  }
1861
1677
  }
1862
- if (token.children) visit(token.children);
1678
+ if (token.children && token.type !== 'image') visit(token.children);
1863
1679
  }
1864
1680
  }
1865
- visit(imageMarkdown.parse(text, {}));
1681
+ visit(imageMarkdown.parse(text, { allowFileCitations: true }));
1866
1682
  return assets;
1867
1683
  }
1868
1684
 
1869
- function markdownFileAssets(text) {
1685
+ function markdownFileAssets(text) {
1870
1686
  const assets = Object.create(null);
1871
1687
  function visit(tokens) {
1872
1688
  for (const token of tokens) {
1873
1689
  if (token.type === 'link_open') {
1874
1690
  const value = token.attrGet('href');
1875
1691
  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 };
1692
+ const localPath = fileCitations.localReference(value)?.path;
1693
+ if (localPath && /^[a-z]:[\\/]/i.test(localPath)) {
1694
+ assets[value] = { remotePath: localPath, name: path.basename(localPath) };
1695
+ // Link rendering must not depend on file existence or transient backend state.
1883
1696
  }
1884
1697
  }
1885
1698
  }
1886
- if (token.children) visit(token.children);
1699
+ if (token.children && token.type !== 'image') visit(token.children);
1887
1700
  }
1888
1701
  }
1889
- visit(imageMarkdown.parse(text, {}));
1702
+ visit(imageMarkdown.parse(text, { allowFileCitations: true }));
1890
1703
  return assets;
1891
1704
  }
1892
1705
 
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;
1706
+ function describeLocalFile(filePath) {
1707
+ const resolved = path.resolve(filePath);
1708
+ let stat;
1709
+ try { stat = fs.statSync(resolved); }
1710
+ catch (error) {
1711
+ if (error.code === 'ENOENT') throw new RequestError(404, '电脑上的文件已不存在。');
1712
+ if (error.code === 'EACCES' || error.code === 'EPERM') throw new RequestError(403, '电脑系统无法读取此文件:' + error.code);
1713
+ throw error;
1907
1714
  }
1715
+ if (!stat.isFile()) throw new RequestError(400, '此路径不是文件。');
1716
+ return { path: resolved, name: path.basename(resolved), size: stat.size, revision: fileRevision(stat) };
1908
1717
  }
1909
1718
 
1910
1719
  function splitUploadedImages(text) {
@@ -1921,21 +1730,38 @@ function splitUploadedImages(text) {
1921
1730
  const paths = children.map(token => {
1922
1731
  try { return path.resolve(decodeURIComponent(token.attrGet('src') || '')); } catch { return ''; }
1923
1732
  });
1924
- if (paths.some(file => !allowedUploadsRoots.some(root => path.dirname(file).toLowerCase() === root.toLowerCase())
1733
+ if (paths.some(file => path.dirname(file).toLowerCase() !== uploadsRoot.toLowerCase()
1925
1734
  || !/^[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: '手机图片' }));
1735
+ for (const file of paths) attachments.push(normalizeAttachment({ type: 'localImage', path: file, name: '移动端图片' }));
1927
1736
  for (let line = block.map[0]; line < block.map[1]; line++) removed.add(line);
1928
1737
  }
1929
1738
  const visibleText = lines.filter((line, index) => !removed.has(index)).join('\n').trim();
1930
1739
  return { text: visibleText, attachments, imageAssets: markdownImageAssets(visibleText) };
1931
1740
  }
1932
1741
 
1933
- function normalizeAttachment(item) {
1742
+ async function externalDownloadAsset(value, recovery = '') {
1743
+ const match = /^([a-f0-9]{64})(?:\?recovery=([A-Za-z0-9_-]{1,4096}))?$/.exec(value || '');
1744
+ if (!match) throw new RequestError(400, '无效的外部文件标识。');
1745
+ const asset = attachmentCache.get(match[1]) || dotsImages.restore(match[1], match[2] || recovery || '');
1746
+ if (!asset?.dotsFile) throw new RequestError(404, '外部文件授权已失效,请重新读取对话。');
1747
+ await dotsImages.authorization(asset.identity);
1748
+ return asset;
1749
+ }
1750
+
1751
+ function workstationCopyReference(copy, asset) {
1752
+ const id = crypto.createHmac('sha256', fileCapabilitySecret).update(JSON.stringify([asset.id, copy.path, copy.revision])).digest('hex');
1753
+ fileCache.set(id, { ...copy, id, externalIdentity: asset.identity });
1754
+ while (fileCache.size > MAX_CACHED_FILES) fileCache.delete(fileCache.keys().next().value);
1755
+ return { remotePath: id, path: copy.path, name: copy.name, size: copy.size, mimeType: copy.mimeType };
1756
+ }
1757
+
1758
+ function normalizeAttachment(item, identity) {
1934
1759
  const name = item.name || item.fileName || (typeof item.path === 'string' ? path.basename(item.path) : '');
1935
1760
  let cached = null;
1936
1761
  const isImage = ['image', 'input_image', 'localImage'].includes(item.type);
1762
+ if (isImage || item.type === 'file') cached = dotsImages.remember(item, identity);
1937
1763
  if (isImage && typeof item.path === 'string') {
1938
- cached = cacheLocalImage(item.path, name);
1764
+ cached = cacheLocalImage(item.path, name) || cached;
1939
1765
  }
1940
1766
  if (!cached) {
1941
1767
  const dataUrl = typeof item.url === 'string' && item.url.startsWith('data:image/')
@@ -1945,26 +1771,28 @@ function normalizeAttachment(item) {
1945
1771
  }
1946
1772
  return {
1947
1773
  type: isImage ? 'image' : item.type || 'attachment',
1948
- name,
1774
+ name: cached?.name || name,
1949
1775
  mimeType: cached?.mimeType || item.mimeType || '',
1776
+ ...(cached?.size ? { size: cached.size } : {}),
1777
+ ...(cached?.dotsFile ? { networkFile: true } : {}),
1950
1778
  available: Boolean(cached),
1951
- remotePath: cached ? cached.id : '',
1952
- recoveryToken: cached?.recoveryToken || '',
1779
+ remotePath: cached ? (isImage ? cached.id : `${cached.id}?recovery=${cached.recoveryToken}`) : '',
1780
+ recoveryToken: cached?.recoveryToken || '',
1953
1781
  url: '',
1954
1782
  thumbnailUrl: '',
1955
1783
  };
1956
1784
  }
1957
1785
 
1958
- function cacheLocalImage(filePath, name) {
1959
- try {
1960
- const resolved = path.resolve(filePath);
1961
- const stat = fs.statSync(resolved);
1962
- const mimeType = imageMimeType(resolved);
1963
- if (!stat.isFile() || !mimeType || stat.size > MAX_ATTACHMENT_BYTES) return null;
1964
- const cached = rememberAttachment(fs.readFileSync(resolved), mimeType, name,
1965
- localImageRecoveryToken(resolved, stat));
1966
- void attachmentThumbnail(cached).catch(() => {});
1967
- return cached;
1786
+ function cacheLocalImage(filePath, name) {
1787
+ try {
1788
+ const resolved = path.resolve(filePath);
1789
+ const stat = fs.statSync(resolved);
1790
+ const mimeType = imageMimeType(resolved);
1791
+ if (!stat.isFile() || !mimeType || stat.size > MAX_ATTACHMENT_BYTES) return null;
1792
+ const cached = rememberAttachment(fs.readFileSync(resolved), mimeType, name,
1793
+ localImageRecoveryToken(resolved, stat));
1794
+ void attachmentThumbnail(cached).catch(() => {});
1795
+ return cached;
1968
1796
  } catch {
1969
1797
  return null;
1970
1798
  }
@@ -1982,65 +1810,65 @@ function cacheDataImage(dataUrl, name) {
1982
1810
  }
1983
1811
  }
1984
1812
 
1985
- function rememberAttachment(buffer, mimeType, name, recoveryToken = '') {
1986
- const id = crypto.createHash('sha256').update(buffer).digest('hex');
1987
- if (!attachmentCache.has(id)) {
1813
+ function rememberAttachment(buffer, mimeType, name, recoveryToken = '') {
1814
+ const id = crypto.createHash('sha256').update(buffer).digest('hex');
1815
+ if (!attachmentCache.has(id)) {
1988
1816
  while (attachmentCache.size >= MAX_CACHED_ATTACHMENTS) {
1989
1817
  attachmentCache.delete(attachmentCache.keys().next().value);
1990
- }
1991
- attachmentCache.set(id, { id, buffer, mimeType, name, recoveryToken });
1992
- } else if (recoveryToken && !attachmentCache.get(id).recoveryToken) {
1993
- attachmentCache.get(id).recoveryToken = recoveryToken;
1994
- }
1995
- return attachmentCache.get(id);
1996
- }
1997
-
1998
- function localImageRecoveryToken(filePath, stat) {
1999
- const iv = crypto.randomBytes(12);
2000
- const cipher = crypto.createCipheriv('aes-256-gcm', imageCapabilityKey, iv);
2001
- const payload = Buffer.from(JSON.stringify([filePath, stat.size, stat.mtimeMs]));
2002
- const encrypted = Buffer.concat([cipher.update(payload), cipher.final()]);
2003
- return Buffer.concat([iv, cipher.getAuthTag(), encrypted]).toString('base64url');
2004
- }
2005
-
2006
- function recoverLocalImage(id, token) {
2007
- if (!token || token.length > 2048) return null;
2008
- try {
2009
- const value = Buffer.from(token, 'base64url');
2010
- if (value.length < 29) return null;
2011
- const decipher = crypto.createDecipheriv('aes-256-gcm', imageCapabilityKey, value.subarray(0, 12));
2012
- decipher.setAuthTag(value.subarray(12, 28));
2013
- const [filePath, size, mtimeMs] = JSON.parse(Buffer.concat([
2014
- decipher.update(value.subarray(28)), decipher.final(),
2015
- ]).toString('utf8'));
2016
- if (typeof filePath !== 'string' || !path.isAbsolute(filePath)
2017
- || !Number.isSafeInteger(size) || size <= 0 || size > MAX_ATTACHMENT_BYTES
2018
- || typeof mtimeMs !== 'number' || !Number.isFinite(mtimeMs)) return null;
2019
- const stat = fs.statSync(filePath);
2020
- const mimeType = imageMimeType(filePath);
2021
- if (!stat.isFile() || stat.size !== size || stat.mtimeMs !== mtimeMs || !mimeType) return null;
2022
- const buffer = fs.readFileSync(filePath);
2023
- if (crypto.createHash('sha256').update(buffer).digest('hex') !== id) return null;
2024
- return rememberAttachment(buffer, mimeType, path.basename(filePath), token);
2025
- } catch {
2026
- return null;
2027
- }
2028
- }
2029
-
2030
- async function attachmentThumbnail(image) {
2031
- if (!image.thumbnail) {
2032
- image.thumbnail = sharp(image.buffer, { limitInputPixels: 40_000_000 }).rotate()
2033
- .resize({ width: 640, height: 640, fit: 'inside', withoutEnlargement: true })
2034
- .webp({ quality: 78, effort: 4 })
2035
- .toBuffer()
2036
- .then(buffer => ({ buffer, mimeType: 'image/webp' }))
2037
- .catch(error => {
2038
- image.thumbnail = null;
2039
- throw error;
2040
- });
2041
- }
2042
- return image.thumbnail;
2043
- }
1818
+ }
1819
+ attachmentCache.set(id, { id, buffer, mimeType, name, recoveryToken });
1820
+ } else if (recoveryToken && !attachmentCache.get(id).recoveryToken) {
1821
+ attachmentCache.get(id).recoveryToken = recoveryToken;
1822
+ }
1823
+ return attachmentCache.get(id);
1824
+ }
1825
+
1826
+ function localImageRecoveryToken(filePath, stat) {
1827
+ const iv = crypto.randomBytes(12);
1828
+ const cipher = crypto.createCipheriv('aes-256-gcm', imageCapabilityKey, iv);
1829
+ const payload = Buffer.from(JSON.stringify([filePath, stat.size, stat.mtimeMs]));
1830
+ const encrypted = Buffer.concat([cipher.update(payload), cipher.final()]);
1831
+ return Buffer.concat([iv, cipher.getAuthTag(), encrypted]).toString('base64url');
1832
+ }
1833
+
1834
+ function recoverLocalImage(id, token) {
1835
+ if (!token || token.length > 2048) return null;
1836
+ try {
1837
+ const value = Buffer.from(token, 'base64url');
1838
+ if (value.length < 29) return null;
1839
+ const decipher = crypto.createDecipheriv('aes-256-gcm', imageCapabilityKey, value.subarray(0, 12));
1840
+ decipher.setAuthTag(value.subarray(12, 28));
1841
+ const [filePath, size, mtimeMs] = JSON.parse(Buffer.concat([
1842
+ decipher.update(value.subarray(28)), decipher.final(),
1843
+ ]).toString('utf8'));
1844
+ if (typeof filePath !== 'string' || !path.isAbsolute(filePath)
1845
+ || !Number.isSafeInteger(size) || size <= 0 || size > MAX_ATTACHMENT_BYTES
1846
+ || typeof mtimeMs !== 'number' || !Number.isFinite(mtimeMs)) return null;
1847
+ const stat = fs.statSync(filePath);
1848
+ const mimeType = imageMimeType(filePath);
1849
+ if (!stat.isFile() || stat.size !== size || stat.mtimeMs !== mtimeMs || !mimeType) return null;
1850
+ const buffer = fs.readFileSync(filePath);
1851
+ if (crypto.createHash('sha256').update(buffer).digest('hex') !== id) return null;
1852
+ return rememberAttachment(buffer, mimeType, path.basename(filePath), token);
1853
+ } catch {
1854
+ return null;
1855
+ }
1856
+ }
1857
+
1858
+ async function attachmentThumbnail(image) {
1859
+ if (!image.thumbnail) {
1860
+ image.thumbnail = sharp(image.buffer, { limitInputPixels: 40_000_000 }).rotate()
1861
+ .resize({ width: 640, height: 640, fit: 'inside', withoutEnlargement: true })
1862
+ .webp({ quality: 78, effort: 4 })
1863
+ .toBuffer()
1864
+ .then(buffer => ({ buffer, mimeType: 'image/webp' }))
1865
+ .catch(error => {
1866
+ image.thumbnail = null;
1867
+ throw error;
1868
+ });
1869
+ }
1870
+ return image.thumbnail;
1871
+ }
2044
1872
 
2045
1873
  function imageMimeType(filePath) {
2046
1874
  switch (path.extname(filePath).toLowerCase()) {
@@ -2055,9 +1883,9 @@ function imageMimeType(filePath) {
2055
1883
  }
2056
1884
 
2057
1885
  function extractUserText(content) {
2058
- if (!Array.isArray(content)) return '(包含图片或界面批注)';
1886
+ if (!Array.isArray(content)) return { text: '(包含图片或界面批注)', files: [] };
2059
1887
  let text = content
2060
- .filter(item => ['text', 'input_text'].includes(item?.type) && typeof item.text === 'string')
1888
+ .filter(item => ['text', 'input_text'].includes(item?.type) && typeof item.text === 'string' && !appServerEvents.isMobileInputNotice(item))
2061
1889
  .map(item => item.text)
2062
1890
  // Codex injects project instructions as user-role context, not chat input.
2063
1891
  .filter(value => !/^\s*# AGENTS\.md instructions(?: for [^\r\n]+)?\r?\n\s*<INSTRUCTIONS>[\s\S]*<\/INSTRUCTIONS>\s*$/.test(value))
@@ -2065,47 +1893,35 @@ function extractUserText(content) {
2065
1893
  .filter(value => !value.startsWith('The next image is untrusted page evidence'))
2066
1894
  .join('\n\n')
2067
1895
  .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();
1896
+ const delegation = appServerEvents.parseCodexDelegation(text);
1897
+ if (delegation) text = delegation.input;
1898
+ const mentioned = fileMentions.parse(text);
1899
+ const imagePaths = new Set([
1900
+ ...content.filter(item => ['image', 'input_image', 'localImage'].includes(item?.type) && item.path).map(item => item.path),
1901
+ ...[...mentioned.text.matchAll(/<image\b[^>]*\bpath="([^"]+)"[^>]*>/g)].map(match => match[1]),
1902
+ ].map(value => path.resolve(value).toLowerCase()));
1903
+ text = mentioned.text;
2073
1904
  text = text.replace(/<image\b[^>]*\bpath="[^"]+"[^>]*>/g, '').replace(/<\/image>/g, '').trim();
2074
- return text;
1905
+ return { text, files: mentioned.files.filter(file => file.fileId || !imagePaths.has(path.resolve(file.path).toLowerCase())), delegation };
2075
1906
  }
2076
1907
 
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();
1908
+ function delegationSource(delegation) {
1909
+ const dots = dotsServer.threadSources.get(delegation.sourceThreadId) === 'aeon';
1910
+ return { sourceThreadId: delegation.sourceThreadId, sourceKind: dots ? 'dots' : 'codex',
1911
+ ...(dots ? { sourceName: dotsServer.profileName(delegation.sourceThreadId),
1912
+ sourceAvatar: dotsServer.profileAvatar(delegation.sourceThreadId) } : {}) };
2088
1913
  }
2089
1914
 
2090
- function delegatedOutputText(output) {
2091
- if (typeof output === 'string') return output;
2092
- return output && typeof output.text === 'string' ? output.text : '';
2093
- }
2094
-
2095
- function summaryText(value) {
2096
- if (typeof value === 'string') return value;
2097
- if (value && typeof value === 'object') return value.text || value.summary || JSON.stringify(value);
2098
- return '';
2099
- }
2100
-
2101
- function statusText(value) {
1915
+ function summaryText(value) {
2102
1916
  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);
1917
+ if (value && typeof value === 'object') return value.text || value.summary || JSON.stringify(value);
1918
+ return '';
2105
1919
  }
2106
1920
 
2107
- function delay(milliseconds) {
2108
- return new Promise(resolve => setTimeout(resolve, milliseconds));
1921
+ function statusText(value) {
1922
+ if (typeof value === 'string') return value;
1923
+ if (value && typeof value === 'object') return value.type || value.status || 'unknown';
1924
+ return value == null ? 'unknown' : String(value);
2109
1925
  }
2110
1926
 
2111
1927
  function sendJson(response, statusCode, value) {
@@ -2119,7 +1935,7 @@ function sendBuffer(response, statusCode, contentType, value) {
2119
1935
  'Content-Length': value.length,
2120
1936
  'Cache-Control': 'no-store',
2121
1937
  '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:",
1938
+ '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:",
2123
1939
  });
2124
1940
  response.end(value);
2125
1941
  }
@@ -2132,10 +1948,10 @@ function parseOptions(args) {
2132
1948
  let port = Number.parseInt(process.env.CODEX_REMOTE_BRIDGE_PORT || '45831', 10);
2133
1949
  let lan = process.env.CODEX_REMOTE_BRIDGE_LAN === 'true';
2134
1950
  let token = process.env.CODEX_REMOTE_BRIDGE_TOKEN?.trim() || '';
2135
- let appServer = process.env.CODEX_REMOTE_APP_SERVER === 'true';
1951
+ if (process.env.CODEX_REMOTE_APP_SERVER === 'true') throw new Error('此连接方式已停用。');
2136
1952
  for (let i = 0; i < args.length; i += 1) {
2137
1953
  if (args[i] === '--lan') lan = true;
2138
- if (args[i] === '--app-server') appServer = true;
1954
+ if (args[i] === '--app-server') throw new Error('此连接方式已停用。');
2139
1955
  if (args[i] === '--port' && args[i + 1]) port = Number.parseInt(args[++i], 10);
2140
1956
  if (args[i] === '--token' && args[i + 1]) token = args[++i];
2141
1957
  }
@@ -2143,7 +1959,7 @@ function parseOptions(args) {
2143
1959
  throw new Error('端口必须位于 1024 到 65535 之间。');
2144
1960
  }
2145
1961
  if (lan && !token) token = crypto.randomBytes(32).toString('base64url');
2146
- return { port, lan, token, appServer };
1962
+ return { port, lan, token };
2147
1963
  }
2148
1964
 
2149
1965
  function boundedInteger(value, fallback, minimum, maximum) {
@@ -2151,24 +1967,3 @@ function boundedInteger(value, fallback, minimum, maximum) {
2151
1967
  return Number.isFinite(parsed) ? Math.min(maximum, Math.max(minimum, parsed)) : fallback;
2152
1968
  }
2153
1969
 
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;
2173
- }
2174
- }