@walkhi/code-relax 0.1.0-beta.9 → 0.1.0-beta.91

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