@walkhi/code-relax 0.1.0-beta.10 → 0.1.0-beta.101

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