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

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 (849) 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 +49 -0
  105. package/dist/src/platform/windows/codex-runtime.mjs +215 -107
  106. package/dist/src/platform/windows/desktop-helper.mjs +272 -0
  107. package/dist/src/platform/windows/desktop-host.mjs +45 -33
  108. package/dist/src/platform/windows/desktop-info.ps1 +27 -3
  109. package/dist/src/platform/windows/desktop-project-registration.mjs +4 -0
  110. package/dist/src/platform/windows/desktop-runtime.ps1 +10 -0
  111. package/dist/src/platform/windows/desktop-shortcuts.ps1 +6 -13
  112. package/dist/src/platform/windows/dots-profile.mjs +49 -0
  113. package/dist/src/platform/windows/keep-awake.ps1 +20 -0
  114. package/dist/src/platform/windows/launch-worker.mjs +41 -42
  115. package/dist/src/platform/windows/project-locations.mjs +16 -0
  116. package/dist/src/platform/windows/resident-start.js +20 -0
  117. package/dist/src/platform/windows/resident-startup.ps1 +27 -11
  118. package/dist/src/platform/windows/rustdesk-video-probe.mjs +168 -0
  119. package/dist/src/platform/windows/service-host.mjs +17 -22
  120. package/dist/src/platform/windows/start-hidden-console.ps1 +11 -2
  121. package/dist/src/platform/windows/user-environment.mjs +52 -50
  122. package/dist/src/project-create.mjs +121 -0
  123. package/dist/src/project-management.mjs +59 -0
  124. package/dist/src/project-remove.mjs +55 -0
  125. package/dist/src/project-update.mjs +59 -0
  126. package/dist/src/relay-config-import.mjs +45 -0
  127. package/dist/src/remote-control.mjs +51 -0
  128. package/dist/src/runtime-discovery.mjs +65 -41
  129. package/dist/src/self-relay/account-cleanup.mjs +30 -0
  130. package/dist/src/self-relay/account-connections.mjs +63 -0
  131. package/dist/src/self-relay/admin-auth.mjs +59 -0
  132. package/dist/src/self-relay/admin-login-page.mjs +7 -0
  133. package/dist/src/self-relay/admin-page.mjs +87 -71
  134. package/dist/src/self-relay/admin-server.mjs +68 -24
  135. package/dist/src/self-relay/admin-state.mjs +11 -6
  136. package/dist/src/self-relay/admin-style.mjs +58 -0
  137. package/dist/src/self-relay/admin-workstations-page.mjs +95 -0
  138. package/dist/src/self-relay/admin-workstations.mjs +145 -0
  139. package/dist/src/self-relay/android-releases.mjs +61 -0
  140. package/dist/src/self-relay/client.mjs +45 -45
  141. package/dist/src/self-relay/connection-reporting.mjs +38 -0
  142. package/dist/src/self-relay/connector.mjs +456 -145
  143. package/dist/src/self-relay/demo.mjs +1 -1
  144. package/dist/src/self-relay/device-info.mjs +27 -0
  145. package/dist/src/self-relay/device-store.mjs +10 -2
  146. package/dist/src/self-relay/diagnostics.mjs +54 -0
  147. package/dist/src/self-relay/email-accounts.mjs +195 -0
  148. package/dist/src/self-relay/file-wire.mjs +76 -0
  149. package/dist/src/self-relay/lan-authorizations.mjs +44 -2
  150. package/dist/src/self-relay/lifecycle.mjs +252 -259
  151. package/dist/src/self-relay/long-pairing.mjs +40 -0
  152. package/dist/src/self-relay/mysql-state.mjs +118 -0
  153. package/dist/src/self-relay/p2p-probe.mjs +168 -123
  154. package/dist/src/self-relay/phone-connections.mjs +55 -0
  155. package/dist/src/self-relay/privacy-page.mjs +26 -0
  156. package/dist/src/self-relay/public-releases.mjs +67 -0
  157. package/dist/src/self-relay/rtc-file-bridge.mjs +46 -0
  158. package/dist/src/self-relay/server.mjs +1078 -441
  159. package/dist/src/self-relay/usage-daily.mjs +55 -0
  160. package/dist/src/self-relay/usage-meter.mjs +141 -0
  161. package/dist/src/server.mjs +1442 -716
  162. package/dist/src/service-control.mjs +17 -7
  163. package/dist/src/service-doctor.mjs +150 -51
  164. package/dist/src/service-health.mjs +2 -2
  165. package/dist/src/service-install.mjs +16 -10
  166. package/dist/src/service-lifecycle.mjs +52 -42
  167. package/dist/src/service-power.mjs +58 -0
  168. package/dist/src/service-restart.mjs +45 -12
  169. package/dist/src/service-update-worker.mjs +42 -0
  170. package/dist/src/service-update.mjs +80 -0
  171. package/dist/src/shared-app-server.mjs +511 -307
  172. package/dist/src/shared-catalog.mjs +132 -50
  173. package/dist/src/shared-recovery.mjs +21 -13
  174. package/dist/src/shared-thread-stream.mjs +143 -51
  175. package/dist/src/skill-install.mjs +0 -13
  176. package/dist/src/thread-goals.mjs +40 -0
  177. package/dist/src/turn-model-history.mjs +47 -0
  178. package/dist/src/usage-reset.mjs +17 -0
  179. package/dist/src/workstation-downloads.mjs +120 -0
  180. package/package.json +48 -48
  181. package/tools/postinstall.mjs +186 -146
  182. package/dist/src/debug-ui.mjs +0 -131
  183. package/dist/src/platform/windows/credential-store.mjs +0 -27
  184. package/dist/src/platform/windows/credential-store.ps1 +0 -49
  185. package/dist/src/platform/windows/desktop-shortcut-run.ps1 +0 -5
  186. package/dist/web/activity-view.js +0 -283
  187. package/dist/web/approval-ui.js +0 -188
  188. package/dist/web/capabilities.js +0 -50
  189. package/dist/web/chat-transport.js +0 -196
  190. package/dist/web/chat.css +0 -778
  191. package/dist/web/chat.js +0 -3815
  192. package/dist/web/client-platform.js +0 -11
  193. package/dist/web/community.css +0 -82
  194. package/dist/web/community.html +0 -36
  195. package/dist/web/composer-controller.js +0 -97
  196. package/dist/web/conversation-controller.js +0 -99
  197. package/dist/web/disclosure-state-controller.js +0 -95
  198. package/dist/web/draft-controller.js +0 -103
  199. package/dist/web/harmony-platform.js +0 -52
  200. package/dist/web/highlight-powershell.min.js +0 -39
  201. package/dist/web/highlight.min.js +0 -1262
  202. package/dist/web/history-cache.js +0 -240
  203. package/dist/web/history-controller.js +0 -167
  204. package/dist/web/index.html +0 -288
  205. package/dist/web/lan-session.js +0 -122
  206. package/dist/web/link-action-controller.js +0 -212
  207. package/dist/web/message-send-controller.js +0 -177
  208. package/dist/web/message-view.js +0 -98
  209. package/dist/web/p2p-data-channel.js +0 -50
  210. package/dist/web/p2p-diagnostics.js +0 -28
  211. package/dist/web/p2p-probe.js +0 -123
  212. package/dist/web/page-resume.js +0 -28
  213. package/dist/web/pending-message-store.js +0 -108
  214. package/dist/web/queue-controller.js +0 -82
  215. package/dist/web/resources.json +0 -1
  216. package/dist/web/self-relay-session.js +0 -129
  217. package/dist/web/server-requests.js +0 -67
  218. package/dist/web/station-connection-controller.js +0 -75
  219. package/dist/web/task-list-view.js +0 -289
  220. package/dist/web/thread-attention-controller.js +0 -124
  221. package/dist/web/thread-context-controller.js +0 -30
  222. package/dist/web/thread-list-controller.js +0 -61
  223. package/dist/web/thread-list-sync.js +0 -86
  224. package/dist/web/thread-title-controller.js +0 -57
  225. package/dist/web/timeline-formatters.js +0 -249
  226. package/dist/web/timeline-reducer.js +0 -77
  227. package/dist/web/timeline-renderer.js +0 -161
  228. package/dist/web/timeline-scroll-controller.js +0 -50
  229. package/dist/web/timeline-sync.js +0 -67
  230. package/dist/web/usage-controller.js +0 -258
  231. package/dist/web/vendor/README.md +0 -14
  232. package/dist/web/vendor/highlight-powershell.min.js +0 -39
  233. package/dist/web/vendor/highlight.LICENSE.txt +0 -29
  234. package/dist/web/vendor/highlight.min.js +0 -1262
  235. package/dist/web/vendor/lucide.LICENSE.txt +0 -17
  236. package/node_modules/@img/colour/LICENSE.md +0 -82
  237. package/node_modules/@img/colour/README.md +0 -15
  238. package/node_modules/@img/colour/color.cjs +0 -1596
  239. package/node_modules/@img/colour/index.cjs +0 -1
  240. package/node_modules/@img/colour/index.d.ts +0 -929
  241. package/node_modules/@img/colour/package.json +0 -58
  242. package/node_modules/@img/sharp-win32-x64/LICENSE +0 -191
  243. package/node_modules/@img/sharp-win32-x64/README.md +0 -59
  244. package/node_modules/@img/sharp-win32-x64/index.cjs +0 -1
  245. package/node_modules/@img/sharp-win32-x64/lib/libvips-42.dll +0 -0
  246. package/node_modules/@img/sharp-win32-x64/lib/libvips-cpp-8.18.6.dll +0 -0
  247. package/node_modules/@img/sharp-win32-x64/lib/sharp-win32-x64-0.35.4.node +0 -0
  248. package/node_modules/@img/sharp-win32-x64/package.json +0 -40
  249. package/node_modules/@img/sharp-win32-x64/versions.json +0 -30
  250. package/node_modules/@node-datachannel/win32-x64-msvc/node_datachannel.node +0 -0
  251. package/node_modules/@node-datachannel/win32-x64-msvc/package.json +0 -20
  252. package/node_modules/ansi-regex/index.d.ts +0 -37
  253. package/node_modules/ansi-regex/index.js +0 -10
  254. package/node_modules/ansi-regex/license +0 -9
  255. package/node_modules/ansi-regex/package.json +0 -55
  256. package/node_modules/ansi-regex/readme.md +0 -78
  257. package/node_modules/ansi-styles/index.d.ts +0 -345
  258. package/node_modules/ansi-styles/index.js +0 -163
  259. package/node_modules/ansi-styles/license +0 -9
  260. package/node_modules/ansi-styles/package.json +0 -56
  261. package/node_modules/ansi-styles/readme.md +0 -152
  262. package/node_modules/camelcase/index.d.ts +0 -63
  263. package/node_modules/camelcase/index.js +0 -76
  264. package/node_modules/camelcase/license +0 -9
  265. package/node_modules/camelcase/package.json +0 -43
  266. package/node_modules/camelcase/readme.md +0 -99
  267. package/node_modules/cliui/CHANGELOG.md +0 -76
  268. package/node_modules/cliui/LICENSE.txt +0 -14
  269. package/node_modules/cliui/README.md +0 -115
  270. package/node_modules/cliui/index.js +0 -354
  271. package/node_modules/cliui/package.json +0 -65
  272. package/node_modules/color-convert/CHANGELOG.md +0 -54
  273. package/node_modules/color-convert/LICENSE +0 -21
  274. package/node_modules/color-convert/README.md +0 -68
  275. package/node_modules/color-convert/conversions.js +0 -839
  276. package/node_modules/color-convert/index.js +0 -81
  277. package/node_modules/color-convert/package.json +0 -48
  278. package/node_modules/color-convert/route.js +0 -97
  279. package/node_modules/color-name/LICENSE +0 -8
  280. package/node_modules/color-name/README.md +0 -11
  281. package/node_modules/color-name/index.js +0 -152
  282. package/node_modules/color-name/package.json +0 -28
  283. package/node_modules/decamelize/index.js +0 -13
  284. package/node_modules/decamelize/license +0 -21
  285. package/node_modules/decamelize/package.json +0 -38
  286. package/node_modules/decamelize/readme.md +0 -48
  287. package/node_modules/detect-libc/LICENSE +0 -201
  288. package/node_modules/detect-libc/README.md +0 -163
  289. package/node_modules/detect-libc/index.d.ts +0 -14
  290. package/node_modules/detect-libc/lib/detect-libc.js +0 -313
  291. package/node_modules/detect-libc/lib/elf.js +0 -39
  292. package/node_modules/detect-libc/lib/filesystem.js +0 -51
  293. package/node_modules/detect-libc/lib/process.js +0 -24
  294. package/node_modules/detect-libc/package.json +0 -44
  295. package/node_modules/dijkstrajs/.travis.yml +0 -4
  296. package/node_modules/dijkstrajs/CONTRIBUTING.md +0 -8
  297. package/node_modules/dijkstrajs/LICENSE.md +0 -19
  298. package/node_modules/dijkstrajs/README.md +0 -18
  299. package/node_modules/dijkstrajs/dijkstra.js +0 -165
  300. package/node_modules/dijkstrajs/package.json +0 -60
  301. package/node_modules/dijkstrajs/test/dijkstra.test.js +0 -96
  302. package/node_modules/emoji-regex/LICENSE-MIT.txt +0 -20
  303. package/node_modules/emoji-regex/README.md +0 -73
  304. package/node_modules/emoji-regex/es2015/index.js +0 -6
  305. package/node_modules/emoji-regex/es2015/text.js +0 -6
  306. package/node_modules/emoji-regex/index.d.ts +0 -23
  307. package/node_modules/emoji-regex/index.js +0 -6
  308. package/node_modules/emoji-regex/package.json +0 -50
  309. package/node_modules/emoji-regex/text.js +0 -6
  310. package/node_modules/find-up/index.d.ts +0 -137
  311. package/node_modules/find-up/index.js +0 -89
  312. package/node_modules/find-up/license +0 -9
  313. package/node_modules/find-up/package.json +0 -53
  314. package/node_modules/find-up/readme.md +0 -156
  315. package/node_modules/get-caller-file/LICENSE.md +0 -6
  316. package/node_modules/get-caller-file/README.md +0 -41
  317. package/node_modules/get-caller-file/index.d.ts +0 -2
  318. package/node_modules/get-caller-file/index.js +0 -22
  319. package/node_modules/get-caller-file/index.js.map +0 -1
  320. package/node_modules/get-caller-file/package.json +0 -42
  321. package/node_modules/is-fullwidth-code-point/index.d.ts +0 -17
  322. package/node_modules/is-fullwidth-code-point/index.js +0 -50
  323. package/node_modules/is-fullwidth-code-point/license +0 -9
  324. package/node_modules/is-fullwidth-code-point/package.json +0 -42
  325. package/node_modules/is-fullwidth-code-point/readme.md +0 -39
  326. package/node_modules/locate-path/index.d.ts +0 -83
  327. package/node_modules/locate-path/index.js +0 -65
  328. package/node_modules/locate-path/license +0 -9
  329. package/node_modules/locate-path/package.json +0 -45
  330. package/node_modules/locate-path/readme.md +0 -122
  331. package/node_modules/node-datachannel/.clang-format +0 -17
  332. package/node_modules/node-datachannel/.editorconfig +0 -12
  333. package/node_modules/node-datachannel/.eslintignore +0 -8
  334. package/node_modules/node-datachannel/.eslintrc.json +0 -27
  335. package/node_modules/node-datachannel/.gitmodules +0 -3
  336. package/node_modules/node-datachannel/.prettierignore +0 -7
  337. package/node_modules/node-datachannel/.prettierrc +0 -13
  338. package/node_modules/node-datachannel/API.md +0 -247
  339. package/node_modules/node-datachannel/BULDING.md +0 -26
  340. package/node_modules/node-datachannel/CMakeLists.txt +0 -153
  341. package/node_modules/node-datachannel/LICENSE +0 -373
  342. package/node_modules/node-datachannel/README.md +0 -129
  343. package/node_modules/node-datachannel/dist/cjs/index.cjs +0 -11
  344. package/node_modules/node-datachannel/dist/cjs/index.cjs.map +0 -1
  345. package/node_modules/node-datachannel/dist/cjs/lib/datachannel-stream.cjs +0 -101
  346. package/node_modules/node-datachannel/dist/cjs/lib/datachannel-stream.cjs.map +0 -1
  347. package/node_modules/node-datachannel/dist/cjs/lib/index.cjs +0 -91
  348. package/node_modules/node-datachannel/dist/cjs/lib/index.cjs.map +0 -1
  349. package/node_modules/node-datachannel/dist/cjs/lib/node-datachannel.cjs +0 -94
  350. package/node_modules/node-datachannel/dist/cjs/lib/node-datachannel.cjs.map +0 -1
  351. package/node_modules/node-datachannel/dist/cjs/lib/websocket-server.cjs +0 -45
  352. package/node_modules/node-datachannel/dist/cjs/lib/websocket-server.cjs.map +0 -1
  353. package/node_modules/node-datachannel/dist/cjs/lib/websocket.cjs +0 -8
  354. package/node_modules/node-datachannel/dist/cjs/lib/websocket.cjs.map +0 -1
  355. package/node_modules/node-datachannel/dist/cjs/polyfill/Events.cjs +0 -86
  356. package/node_modules/node-datachannel/dist/cjs/polyfill/Events.cjs.map +0 -1
  357. package/node_modules/node-datachannel/dist/cjs/polyfill/Exception.cjs +0 -34
  358. package/node_modules/node-datachannel/dist/cjs/polyfill/Exception.cjs.map +0 -1
  359. package/node_modules/node-datachannel/dist/cjs/polyfill/RTCCertificate.cjs +0 -34
  360. package/node_modules/node-datachannel/dist/cjs/polyfill/RTCCertificate.cjs.map +0 -1
  361. package/node_modules/node-datachannel/dist/cjs/polyfill/RTCDataChannel.cjs +0 -214
  362. package/node_modules/node-datachannel/dist/cjs/polyfill/RTCDataChannel.cjs.map +0 -1
  363. package/node_modules/node-datachannel/dist/cjs/polyfill/RTCDtlsTransport.cjs +0 -53
  364. package/node_modules/node-datachannel/dist/cjs/polyfill/RTCDtlsTransport.cjs.map +0 -1
  365. package/node_modules/node-datachannel/dist/cjs/polyfill/RTCError.cjs +0 -83
  366. package/node_modules/node-datachannel/dist/cjs/polyfill/RTCError.cjs.map +0 -1
  367. package/node_modules/node-datachannel/dist/cjs/polyfill/RTCIceCandidate.cjs +0 -132
  368. package/node_modules/node-datachannel/dist/cjs/polyfill/RTCIceCandidate.cjs.map +0 -1
  369. package/node_modules/node-datachannel/dist/cjs/polyfill/RTCIceTransport.cjs +0 -94
  370. package/node_modules/node-datachannel/dist/cjs/polyfill/RTCIceTransport.cjs.map +0 -1
  371. package/node_modules/node-datachannel/dist/cjs/polyfill/RTCPeerConnection.cjs +0 -434
  372. package/node_modules/node-datachannel/dist/cjs/polyfill/RTCPeerConnection.cjs.map +0 -1
  373. package/node_modules/node-datachannel/dist/cjs/polyfill/RTCSctpTransport.cjs +0 -59
  374. package/node_modules/node-datachannel/dist/cjs/polyfill/RTCSctpTransport.cjs.map +0 -1
  375. package/node_modules/node-datachannel/dist/cjs/polyfill/RTCSessionDescription.cjs +0 -45
  376. package/node_modules/node-datachannel/dist/cjs/polyfill/RTCSessionDescription.cjs.map +0 -1
  377. package/node_modules/node-datachannel/dist/cjs/polyfill/index.cjs +0 -42
  378. package/node_modules/node-datachannel/dist/cjs/polyfill/index.cjs.map +0 -1
  379. package/node_modules/node-datachannel/dist/esm/index.mjs +0 -8
  380. package/node_modules/node-datachannel/dist/esm/index.mjs.map +0 -1
  381. package/node_modules/node-datachannel/dist/esm/lib/datachannel-stream.mjs +0 -78
  382. package/node_modules/node-datachannel/dist/esm/lib/datachannel-stream.mjs.map +0 -1
  383. package/node_modules/node-datachannel/dist/esm/lib/index.mjs +0 -64
  384. package/node_modules/node-datachannel/dist/esm/lib/index.mjs.map +0 -1
  385. package/node_modules/node-datachannel/dist/esm/lib/node-datachannel.mjs +0 -78
  386. package/node_modules/node-datachannel/dist/esm/lib/node-datachannel.mjs.map +0 -1
  387. package/node_modules/node-datachannel/dist/esm/lib/websocket-server.mjs +0 -43
  388. package/node_modules/node-datachannel/dist/esm/lib/websocket-server.mjs.map +0 -1
  389. package/node_modules/node-datachannel/dist/esm/lib/websocket.mjs +0 -6
  390. package/node_modules/node-datachannel/dist/esm/lib/websocket.mjs.map +0 -1
  391. package/node_modules/node-datachannel/dist/esm/polyfill/Events.mjs +0 -82
  392. package/node_modules/node-datachannel/dist/esm/polyfill/Events.mjs.map +0 -1
  393. package/node_modules/node-datachannel/dist/esm/polyfill/Exception.mjs +0 -28
  394. package/node_modules/node-datachannel/dist/esm/polyfill/Exception.mjs.map +0 -1
  395. package/node_modules/node-datachannel/dist/esm/polyfill/RTCCertificate.mjs +0 -30
  396. package/node_modules/node-datachannel/dist/esm/polyfill/RTCCertificate.mjs.map +0 -1
  397. package/node_modules/node-datachannel/dist/esm/polyfill/RTCDataChannel.mjs +0 -210
  398. package/node_modules/node-datachannel/dist/esm/polyfill/RTCDataChannel.mjs.map +0 -1
  399. package/node_modules/node-datachannel/dist/esm/polyfill/RTCDtlsTransport.mjs +0 -49
  400. package/node_modules/node-datachannel/dist/esm/polyfill/RTCDtlsTransport.mjs.map +0 -1
  401. package/node_modules/node-datachannel/dist/esm/polyfill/RTCError.mjs +0 -79
  402. package/node_modules/node-datachannel/dist/esm/polyfill/RTCError.mjs.map +0 -1
  403. package/node_modules/node-datachannel/dist/esm/polyfill/RTCIceCandidate.mjs +0 -128
  404. package/node_modules/node-datachannel/dist/esm/polyfill/RTCIceCandidate.mjs.map +0 -1
  405. package/node_modules/node-datachannel/dist/esm/polyfill/RTCIceTransport.mjs +0 -89
  406. package/node_modules/node-datachannel/dist/esm/polyfill/RTCIceTransport.mjs.map +0 -1
  407. package/node_modules/node-datachannel/dist/esm/polyfill/RTCPeerConnection.mjs +0 -430
  408. package/node_modules/node-datachannel/dist/esm/polyfill/RTCPeerConnection.mjs.map +0 -1
  409. package/node_modules/node-datachannel/dist/esm/polyfill/RTCSctpTransport.mjs +0 -55
  410. package/node_modules/node-datachannel/dist/esm/polyfill/RTCSctpTransport.mjs.map +0 -1
  411. package/node_modules/node-datachannel/dist/esm/polyfill/RTCSessionDescription.mjs +0 -41
  412. package/node_modules/node-datachannel/dist/esm/polyfill/RTCSessionDescription.mjs.map +0 -1
  413. package/node_modules/node-datachannel/dist/esm/polyfill/index.mjs +0 -27
  414. package/node_modules/node-datachannel/dist/esm/polyfill/index.mjs.map +0 -1
  415. package/node_modules/node-datachannel/dist/types/lib/datachannel-stream.d.ts +0 -24
  416. package/node_modules/node-datachannel/dist/types/lib/index.d.ts +0 -241
  417. package/node_modules/node-datachannel/dist/types/lib/types.d.ts +0 -119
  418. package/node_modules/node-datachannel/dist/types/lib/websocket-server.d.ts +0 -13
  419. package/node_modules/node-datachannel/dist/types/lib/websocket.d.ts +0 -25
  420. package/node_modules/node-datachannel/dist/types/polyfill/Events.d.ts +0 -15
  421. package/node_modules/node-datachannel/dist/types/polyfill/RTCCertificate.d.ts +0 -9
  422. package/node_modules/node-datachannel/dist/types/polyfill/RTCDataChannel.d.ts +0 -29
  423. package/node_modules/node-datachannel/dist/types/polyfill/RTCDtlsTransport.d.ts +0 -15
  424. package/node_modules/node-datachannel/dist/types/polyfill/RTCError.d.ts +0 -17
  425. package/node_modules/node-datachannel/dist/types/polyfill/RTCIceCandidate.d.ts +0 -21
  426. package/node_modules/node-datachannel/dist/types/polyfill/RTCIceTransport.d.ts +0 -30
  427. package/node_modules/node-datachannel/dist/types/polyfill/RTCPeerConnection.d.ts +0 -64
  428. package/node_modules/node-datachannel/dist/types/polyfill/RTCSctpTransport.d.ts +0 -15
  429. package/node_modules/node-datachannel/dist/types/polyfill/RTCSessionDescription.d.ts +0 -10
  430. package/node_modules/node-datachannel/dist/types/polyfill/index.d.ts +0 -26
  431. package/node_modules/node-datachannel/jest.config.ts +0 -14
  432. package/node_modules/node-datachannel/package.json +0 -133
  433. package/node_modules/node-datachannel/rollup.config.mjs +0 -67
  434. package/node_modules/node-datachannel/src/cpp/data-channel-wrapper.cpp +0 -530
  435. package/node_modules/node-datachannel/src/cpp/data-channel-wrapper.h +0 -63
  436. package/node_modules/node-datachannel/src/cpp/ice-udp-mux-listener-wrapper.cpp +0 -172
  437. package/node_modules/node-datachannel/src/cpp/ice-udp-mux-listener-wrapper.h +0 -43
  438. package/node_modules/node-datachannel/src/cpp/main.cpp +0 -58
  439. package/node_modules/node-datachannel/src/cpp/media-audio-wrapper.cpp +0 -457
  440. package/node_modules/node-datachannel/src/cpp/media-audio-wrapper.h +0 -52
  441. package/node_modules/node-datachannel/src/cpp/media-av1packetization.cpp +0 -24
  442. package/node_modules/node-datachannel/src/cpp/media-av1packetization.h +0 -11
  443. package/node_modules/node-datachannel/src/cpp/media-av1rtppacketizer-wrapper.cpp +0 -126
  444. package/node_modules/node-datachannel/src/cpp/media-av1rtppacketizer-wrapper.h +0 -29
  445. package/node_modules/node-datachannel/src/cpp/media-direction.cpp +0 -43
  446. package/node_modules/node-datachannel/src/cpp/media-direction.h +0 -10
  447. package/node_modules/node-datachannel/src/cpp/media-h264rtppacketizer-wrapper.cpp +0 -126
  448. package/node_modules/node-datachannel/src/cpp/media-h264rtppacketizer-wrapper.h +0 -30
  449. package/node_modules/node-datachannel/src/cpp/media-h265rtppacketizer-wrapper.cpp +0 -126
  450. package/node_modules/node-datachannel/src/cpp/media-h265rtppacketizer-wrapper.h +0 -30
  451. package/node_modules/node-datachannel/src/cpp/media-h26xseparator.cpp +0 -32
  452. package/node_modules/node-datachannel/src/cpp/media-h26xseparator.h +0 -11
  453. package/node_modules/node-datachannel/src/cpp/media-mediahandler-helper.cpp +0 -31
  454. package/node_modules/node-datachannel/src/cpp/media-mediahandler-helper.h +0 -11
  455. package/node_modules/node-datachannel/src/cpp/media-pacinghandler-wrapper.cpp +0 -79
  456. package/node_modules/node-datachannel/src/cpp/media-pacinghandler-wrapper.h +0 -28
  457. package/node_modules/node-datachannel/src/cpp/media-rtcpnackresponder-wrapper.cpp +0 -68
  458. package/node_modules/node-datachannel/src/cpp/media-rtcpnackresponder-wrapper.h +0 -28
  459. package/node_modules/node-datachannel/src/cpp/media-rtcpreceivingsession-wrapper.cpp +0 -57
  460. package/node_modules/node-datachannel/src/cpp/media-rtcpreceivingsession-wrapper.h +0 -28
  461. package/node_modules/node-datachannel/src/cpp/media-rtcpsrreporter-wrapper.cpp +0 -93
  462. package/node_modules/node-datachannel/src/cpp/media-rtcpsrreporter-wrapper.h +0 -30
  463. package/node_modules/node-datachannel/src/cpp/media-rtppacketizationconfig-wrapper.cpp +0 -167
  464. package/node_modules/node-datachannel/src/cpp/media-rtppacketizationconfig-wrapper.h +0 -35
  465. package/node_modules/node-datachannel/src/cpp/media-rtppacketizer-wrapper.cpp +0 -95
  466. package/node_modules/node-datachannel/src/cpp/media-rtppacketizer-wrapper.h +0 -30
  467. package/node_modules/node-datachannel/src/cpp/media-track-wrapper.cpp +0 -458
  468. package/node_modules/node-datachannel/src/cpp/media-track-wrapper.h +0 -61
  469. package/node_modules/node-datachannel/src/cpp/media-video-wrapper.cpp +0 -526
  470. package/node_modules/node-datachannel/src/cpp/media-video-wrapper.h +0 -56
  471. package/node_modules/node-datachannel/src/cpp/peer-connection-wrapper.cpp +0 -1298
  472. package/node_modules/node-datachannel/src/cpp/peer-connection-wrapper.h +0 -89
  473. package/node_modules/node-datachannel/src/cpp/rtc-wrapper.cpp +0 -205
  474. package/node_modules/node-datachannel/src/cpp/rtc-wrapper.h +0 -24
  475. package/node_modules/node-datachannel/src/cpp/thread-safe-callback.cpp +0 -57
  476. package/node_modules/node-datachannel/src/cpp/thread-safe-callback.h +0 -47
  477. package/node_modules/node-datachannel/src/cpp/web-socket-server-wrapper.cpp +0 -275
  478. package/node_modules/node-datachannel/src/cpp/web-socket-server-wrapper.h +0 -41
  479. package/node_modules/node-datachannel/src/cpp/web-socket-wrapper.cpp +0 -796
  480. package/node_modules/node-datachannel/src/cpp/web-socket-wrapper.h +0 -63
  481. package/node_modules/node-datachannel/src/index.ts +0 -9
  482. package/node_modules/node-datachannel/src/lib/datachannel-stream.ts +0 -100
  483. package/node_modules/node-datachannel/src/lib/index.ts +0 -290
  484. package/node_modules/node-datachannel/src/lib/node-datachannel.ts +0 -84
  485. package/node_modules/node-datachannel/src/lib/types.ts +0 -169
  486. package/node_modules/node-datachannel/src/lib/websocket-server.ts +0 -37
  487. package/node_modules/node-datachannel/src/lib/websocket.ts +0 -26
  488. package/node_modules/node-datachannel/src/polyfill/Events.ts +0 -82
  489. package/node_modules/node-datachannel/src/polyfill/Exception.ts +0 -37
  490. package/node_modules/node-datachannel/src/polyfill/README.md +0 -41
  491. package/node_modules/node-datachannel/src/polyfill/RTCCertificate.ts +0 -21
  492. package/node_modules/node-datachannel/src/polyfill/RTCDataChannel.ts +0 -225
  493. package/node_modules/node-datachannel/src/polyfill/RTCDtlsTransport.ts +0 -46
  494. package/node_modules/node-datachannel/src/polyfill/RTCError.ts +0 -78
  495. package/node_modules/node-datachannel/src/polyfill/RTCIceCandidate.ts +0 -128
  496. package/node_modules/node-datachannel/src/polyfill/RTCIceTransport.ts +0 -90
  497. package/node_modules/node-datachannel/src/polyfill/RTCPeerConnection.ts +0 -527
  498. package/node_modules/node-datachannel/src/polyfill/RTCSctpTransport.ts +0 -51
  499. package/node_modules/node-datachannel/src/polyfill/RTCSessionDescription.ts +0 -41
  500. package/node_modules/node-datachannel/src/polyfill/index.ts +0 -38
  501. package/node_modules/node-datachannel/tsconfig.json +0 -21
  502. package/node_modules/p-limit/index.d.ts +0 -38
  503. package/node_modules/p-limit/index.js +0 -57
  504. package/node_modules/p-limit/license +0 -9
  505. package/node_modules/p-limit/package.json +0 -52
  506. package/node_modules/p-limit/readme.md +0 -101
  507. package/node_modules/p-locate/index.d.ts +0 -64
  508. package/node_modules/p-locate/index.js +0 -52
  509. package/node_modules/p-locate/license +0 -9
  510. package/node_modules/p-locate/package.json +0 -53
  511. package/node_modules/p-locate/readme.md +0 -90
  512. package/node_modules/p-try/index.d.ts +0 -39
  513. package/node_modules/p-try/index.js +0 -9
  514. package/node_modules/p-try/license +0 -9
  515. package/node_modules/p-try/package.json +0 -42
  516. package/node_modules/p-try/readme.md +0 -58
  517. package/node_modules/path-exists/index.d.ts +0 -28
  518. package/node_modules/path-exists/index.js +0 -23
  519. package/node_modules/path-exists/license +0 -9
  520. package/node_modules/path-exists/package.json +0 -39
  521. package/node_modules/path-exists/readme.md +0 -52
  522. package/node_modules/pngjs/.eslintignore +0 -2
  523. package/node_modules/pngjs/.eslintrc.json +0 -19
  524. package/node_modules/pngjs/.prettierignore +0 -3
  525. package/node_modules/pngjs/LICENSE +0 -20
  526. package/node_modules/pngjs/README.md +0 -429
  527. package/node_modules/pngjs/browser.js +0 -16096
  528. package/node_modules/pngjs/coverage/lcov-report/base.css +0 -354
  529. package/node_modules/pngjs/coverage/lcov-report/bitmapper.js.html +0 -881
  530. package/node_modules/pngjs/coverage/lcov-report/bitpacker.js.html +0 -554
  531. package/node_modules/pngjs/coverage/lcov-report/block-navigation.js +0 -77
  532. package/node_modules/pngjs/coverage/lcov-report/chunkstream.js.html +0 -647
  533. package/node_modules/pngjs/coverage/lcov-report/constants.js.html +0 -176
  534. package/node_modules/pngjs/coverage/lcov-report/crc.js.html +0 -200
  535. package/node_modules/pngjs/coverage/lcov-report/favicon.png +0 -0
  536. package/node_modules/pngjs/coverage/lcov-report/filter-pack.js.html +0 -593
  537. package/node_modules/pngjs/coverage/lcov-report/filter-parse-async.js.html +0 -152
  538. package/node_modules/pngjs/coverage/lcov-report/filter-parse-sync.js.html +0 -143
  539. package/node_modules/pngjs/coverage/lcov-report/filter-parse.js.html +0 -611
  540. package/node_modules/pngjs/coverage/lcov-report/format-normaliser.js.html +0 -359
  541. package/node_modules/pngjs/coverage/lcov-report/index.html +0 -426
  542. package/node_modules/pngjs/coverage/lcov-report/interlace.js.html +0 -365
  543. package/node_modules/pngjs/coverage/lcov-report/packer-async.js.html +0 -230
  544. package/node_modules/pngjs/coverage/lcov-report/packer-sync.js.html +0 -248
  545. package/node_modules/pngjs/coverage/lcov-report/packer.js.html +0 -467
  546. package/node_modules/pngjs/coverage/lcov-report/paeth-predictor.js.html +0 -128
  547. package/node_modules/pngjs/coverage/lcov-report/parser-async.js.html +0 -575
  548. package/node_modules/pngjs/coverage/lcov-report/parser-sync.js.html +0 -404
  549. package/node_modules/pngjs/coverage/lcov-report/parser.js.html +0 -950
  550. package/node_modules/pngjs/coverage/lcov-report/png-sync.js.html +0 -116
  551. package/node_modules/pngjs/coverage/lcov-report/png.js.html +0 -662
  552. package/node_modules/pngjs/coverage/lcov-report/prettify.css +0 -101
  553. package/node_modules/pngjs/coverage/lcov-report/prettify.js +0 -1007
  554. package/node_modules/pngjs/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  555. package/node_modules/pngjs/coverage/lcov-report/sorter.js +0 -169
  556. package/node_modules/pngjs/coverage/lcov-report/sync-inflate.js.html +0 -584
  557. package/node_modules/pngjs/coverage/lcov-report/sync-reader.js.html +0 -215
  558. package/node_modules/pngjs/coverage/lcov.info +0 -2177
  559. package/node_modules/pngjs/lib/bitmapper.js +0 -267
  560. package/node_modules/pngjs/lib/bitpacker.js +0 -158
  561. package/node_modules/pngjs/lib/chunkstream.js +0 -189
  562. package/node_modules/pngjs/lib/constants.js +0 -32
  563. package/node_modules/pngjs/lib/crc.js +0 -40
  564. package/node_modules/pngjs/lib/filter-pack.js +0 -171
  565. package/node_modules/pngjs/lib/filter-parse-async.js +0 -24
  566. package/node_modules/pngjs/lib/filter-parse-sync.js +0 -21
  567. package/node_modules/pngjs/lib/filter-parse.js +0 -177
  568. package/node_modules/pngjs/lib/format-normaliser.js +0 -93
  569. package/node_modules/pngjs/lib/interlace.js +0 -95
  570. package/node_modules/pngjs/lib/packer-async.js +0 -50
  571. package/node_modules/pngjs/lib/packer-sync.js +0 -56
  572. package/node_modules/pngjs/lib/packer.js +0 -129
  573. package/node_modules/pngjs/lib/paeth-predictor.js +0 -16
  574. package/node_modules/pngjs/lib/parser-async.js +0 -165
  575. package/node_modules/pngjs/lib/parser-sync.js +0 -108
  576. package/node_modules/pngjs/lib/parser.js +0 -290
  577. package/node_modules/pngjs/lib/png-sync.js +0 -12
  578. package/node_modules/pngjs/lib/png.js +0 -194
  579. package/node_modules/pngjs/lib/sync-inflate.js +0 -168
  580. package/node_modules/pngjs/lib/sync-reader.js +0 -45
  581. package/node_modules/pngjs/package.json +0 -73
  582. package/node_modules/qrcode/README.md +0 -770
  583. package/node_modules/qrcode/bin/qrcode +0 -159
  584. package/node_modules/qrcode/helper/to-sjis-browser.js +0 -2
  585. package/node_modules/qrcode/helper/to-sjis.js +0 -105
  586. package/node_modules/qrcode/lib/browser.js +0 -76
  587. package/node_modules/qrcode/lib/can-promise.js +0 -7
  588. package/node_modules/qrcode/lib/core/alignment-pattern.js +0 -83
  589. package/node_modules/qrcode/lib/core/alphanumeric-data.js +0 -59
  590. package/node_modules/qrcode/lib/core/bit-buffer.js +0 -37
  591. package/node_modules/qrcode/lib/core/bit-matrix.js +0 -65
  592. package/node_modules/qrcode/lib/core/byte-data.js +0 -30
  593. package/node_modules/qrcode/lib/core/error-correction-code.js +0 -135
  594. package/node_modules/qrcode/lib/core/error-correction-level.js +0 -50
  595. package/node_modules/qrcode/lib/core/finder-pattern.js +0 -22
  596. package/node_modules/qrcode/lib/core/format-info.js +0 -29
  597. package/node_modules/qrcode/lib/core/galois-field.js +0 -69
  598. package/node_modules/qrcode/lib/core/kanji-data.js +0 -54
  599. package/node_modules/qrcode/lib/core/mask-pattern.js +0 -234
  600. package/node_modules/qrcode/lib/core/mode.js +0 -167
  601. package/node_modules/qrcode/lib/core/numeric-data.js +0 -43
  602. package/node_modules/qrcode/lib/core/polynomial.js +0 -62
  603. package/node_modules/qrcode/lib/core/qrcode.js +0 -495
  604. package/node_modules/qrcode/lib/core/reed-solomon-encoder.js +0 -56
  605. package/node_modules/qrcode/lib/core/regex.js +0 -31
  606. package/node_modules/qrcode/lib/core/segments.js +0 -330
  607. package/node_modules/qrcode/lib/core/utils.js +0 -63
  608. package/node_modules/qrcode/lib/core/version-check.js +0 -9
  609. package/node_modules/qrcode/lib/core/version.js +0 -163
  610. package/node_modules/qrcode/lib/index.js +0 -12
  611. package/node_modules/qrcode/lib/renderer/canvas.js +0 -63
  612. package/node_modules/qrcode/lib/renderer/png.js +0 -78
  613. package/node_modules/qrcode/lib/renderer/svg-tag.js +0 -81
  614. package/node_modules/qrcode/lib/renderer/svg.js +0 -19
  615. package/node_modules/qrcode/lib/renderer/terminal/terminal-small.js +0 -85
  616. package/node_modules/qrcode/lib/renderer/terminal/terminal.js +0 -49
  617. package/node_modules/qrcode/lib/renderer/terminal.js +0 -9
  618. package/node_modules/qrcode/lib/renderer/utf8.js +0 -71
  619. package/node_modules/qrcode/lib/renderer/utils.js +0 -99
  620. package/node_modules/qrcode/lib/server.js +0 -138
  621. package/node_modules/qrcode/license +0 -10
  622. package/node_modules/qrcode/package.json +0 -77
  623. package/node_modules/require-directory/.jshintrc +0 -67
  624. package/node_modules/require-directory/.npmignore +0 -1
  625. package/node_modules/require-directory/.travis.yml +0 -3
  626. package/node_modules/require-directory/LICENSE +0 -22
  627. package/node_modules/require-directory/README.markdown +0 -184
  628. package/node_modules/require-directory/index.js +0 -86
  629. package/node_modules/require-directory/package.json +0 -40
  630. package/node_modules/require-main-filename/CHANGELOG.md +0 -26
  631. package/node_modules/require-main-filename/LICENSE.txt +0 -14
  632. package/node_modules/require-main-filename/README.md +0 -26
  633. package/node_modules/require-main-filename/index.js +0 -18
  634. package/node_modules/require-main-filename/package.json +0 -35
  635. package/node_modules/semver/LICENSE +0 -15
  636. package/node_modules/semver/README.md +0 -680
  637. package/node_modules/semver/bin/semver.js +0 -195
  638. package/node_modules/semver/classes/comparator.js +0 -143
  639. package/node_modules/semver/classes/index.js +0 -7
  640. package/node_modules/semver/classes/range.js +0 -577
  641. package/node_modules/semver/classes/semver.js +0 -350
  642. package/node_modules/semver/functions/clean.js +0 -8
  643. package/node_modules/semver/functions/cmp.js +0 -54
  644. package/node_modules/semver/functions/coerce.js +0 -62
  645. package/node_modules/semver/functions/compare-build.js +0 -9
  646. package/node_modules/semver/functions/compare-loose.js +0 -5
  647. package/node_modules/semver/functions/compare.js +0 -7
  648. package/node_modules/semver/functions/diff.js +0 -60
  649. package/node_modules/semver/functions/eq.js +0 -5
  650. package/node_modules/semver/functions/gt.js +0 -5
  651. package/node_modules/semver/functions/gte.js +0 -5
  652. package/node_modules/semver/functions/inc.js +0 -21
  653. package/node_modules/semver/functions/lt.js +0 -5
  654. package/node_modules/semver/functions/lte.js +0 -5
  655. package/node_modules/semver/functions/major.js +0 -5
  656. package/node_modules/semver/functions/minor.js +0 -5
  657. package/node_modules/semver/functions/neq.js +0 -5
  658. package/node_modules/semver/functions/parse.js +0 -18
  659. package/node_modules/semver/functions/patch.js +0 -5
  660. package/node_modules/semver/functions/prerelease.js +0 -8
  661. package/node_modules/semver/functions/rcompare.js +0 -5
  662. package/node_modules/semver/functions/rsort.js +0 -5
  663. package/node_modules/semver/functions/satisfies.js +0 -12
  664. package/node_modules/semver/functions/sort.js +0 -5
  665. package/node_modules/semver/functions/truncate.js +0 -48
  666. package/node_modules/semver/functions/valid.js +0 -8
  667. package/node_modules/semver/index.js +0 -93
  668. package/node_modules/semver/internal/constants.js +0 -37
  669. package/node_modules/semver/internal/debug.js +0 -11
  670. package/node_modules/semver/internal/identifiers.js +0 -29
  671. package/node_modules/semver/internal/lrucache.js +0 -42
  672. package/node_modules/semver/internal/parse-options.js +0 -17
  673. package/node_modules/semver/internal/re.js +0 -223
  674. package/node_modules/semver/package.json +0 -78
  675. package/node_modules/semver/preload.js +0 -4
  676. package/node_modules/semver/range.bnf +0 -17
  677. package/node_modules/semver/ranges/gtr.js +0 -6
  678. package/node_modules/semver/ranges/intersects.js +0 -9
  679. package/node_modules/semver/ranges/ltr.js +0 -6
  680. package/node_modules/semver/ranges/max-satisfying.js +0 -27
  681. package/node_modules/semver/ranges/min-satisfying.js +0 -26
  682. package/node_modules/semver/ranges/min-version.js +0 -63
  683. package/node_modules/semver/ranges/outside.js +0 -82
  684. package/node_modules/semver/ranges/simplify.js +0 -49
  685. package/node_modules/semver/ranges/subset.js +0 -249
  686. package/node_modules/semver/ranges/to-comparators.js +0 -10
  687. package/node_modules/semver/ranges/valid.js +0 -13
  688. package/node_modules/set-blocking/CHANGELOG.md +0 -26
  689. package/node_modules/set-blocking/LICENSE.txt +0 -14
  690. package/node_modules/set-blocking/README.md +0 -31
  691. package/node_modules/set-blocking/index.js +0 -7
  692. package/node_modules/set-blocking/package.json +0 -42
  693. package/node_modules/sharp/LICENSE +0 -191
  694. package/node_modules/sharp/README.md +0 -112
  695. package/node_modules/sharp/dist/channel.cjs +0 -177
  696. package/node_modules/sharp/dist/channel.mjs +0 -177
  697. package/node_modules/sharp/dist/colour.cjs +0 -199
  698. package/node_modules/sharp/dist/colour.mjs +0 -199
  699. package/node_modules/sharp/dist/composite.cjs +0 -213
  700. package/node_modules/sharp/dist/composite.mjs +0 -213
  701. package/node_modules/sharp/dist/constructor.cjs +0 -511
  702. package/node_modules/sharp/dist/constructor.mjs +0 -511
  703. package/node_modules/sharp/dist/index.cjs +0 -25
  704. package/node_modules/sharp/dist/index.d.cts +0 -1999
  705. package/node_modules/sharp/dist/index.d.mts +0 -2046
  706. package/node_modules/sharp/dist/index.mjs +0 -25
  707. package/node_modules/sharp/dist/input.cjs +0 -819
  708. package/node_modules/sharp/dist/input.mjs +0 -819
  709. package/node_modules/sharp/dist/is.cjs +0 -143
  710. package/node_modules/sharp/dist/is.mjs +0 -143
  711. package/node_modules/sharp/dist/libvips.cjs +0 -212
  712. package/node_modules/sharp/dist/libvips.mjs +0 -212
  713. package/node_modules/sharp/dist/operation.cjs +0 -1002
  714. package/node_modules/sharp/dist/operation.mjs +0 -1002
  715. package/node_modules/sharp/dist/output.cjs +0 -1785
  716. package/node_modules/sharp/dist/output.mjs +0 -1785
  717. package/node_modules/sharp/dist/resize.cjs +0 -617
  718. package/node_modules/sharp/dist/resize.mjs +0 -617
  719. package/node_modules/sharp/dist/sharp.cjs +0 -174
  720. package/node_modules/sharp/dist/sharp.mjs +0 -174
  721. package/node_modules/sharp/dist/utility.cjs +0 -301
  722. package/node_modules/sharp/dist/utility.mjs +0 -301
  723. package/node_modules/sharp/install/build.js +0 -38
  724. package/node_modules/sharp/lib/index.d.ts +0 -1999
  725. package/node_modules/sharp/package.json +0 -227
  726. package/node_modules/sharp/src/binding.gyp +0 -303
  727. package/node_modules/sharp/src/common.cc +0 -1186
  728. package/node_modules/sharp/src/common.h +0 -421
  729. package/node_modules/sharp/src/metadata.cc +0 -404
  730. package/node_modules/sharp/src/metadata.h +0 -95
  731. package/node_modules/sharp/src/operations.cc +0 -516
  732. package/node_modules/sharp/src/operations.h +0 -137
  733. package/node_modules/sharp/src/pipeline.cc +0 -1948
  734. package/node_modules/sharp/src/pipeline.h +0 -422
  735. package/node_modules/sharp/src/sharp.cc +0 -43
  736. package/node_modules/sharp/src/stats.cc +0 -186
  737. package/node_modules/sharp/src/stats.h +0 -62
  738. package/node_modules/sharp/src/utilities.cc +0 -289
  739. package/node_modules/sharp/src/utilities.h +0 -22
  740. package/node_modules/string-width/index.d.ts +0 -29
  741. package/node_modules/string-width/index.js +0 -47
  742. package/node_modules/string-width/license +0 -9
  743. package/node_modules/string-width/package.json +0 -56
  744. package/node_modules/string-width/readme.md +0 -50
  745. package/node_modules/strip-ansi/index.d.ts +0 -17
  746. package/node_modules/strip-ansi/index.js +0 -4
  747. package/node_modules/strip-ansi/license +0 -9
  748. package/node_modules/strip-ansi/package.json +0 -54
  749. package/node_modules/strip-ansi/readme.md +0 -46
  750. package/node_modules/which-module/LICENSE +0 -13
  751. package/node_modules/which-module/README.md +0 -58
  752. package/node_modules/which-module/index.js +0 -9
  753. package/node_modules/which-module/package.json +0 -41
  754. package/node_modules/wrap-ansi/index.js +0 -186
  755. package/node_modules/wrap-ansi/license +0 -9
  756. package/node_modules/wrap-ansi/package.json +0 -61
  757. package/node_modules/wrap-ansi/readme.md +0 -97
  758. package/node_modules/ws/LICENSE +0 -20
  759. package/node_modules/ws/README.md +0 -548
  760. package/node_modules/ws/browser.js +0 -8
  761. package/node_modules/ws/index.js +0 -22
  762. package/node_modules/ws/lib/buffer-util.js +0 -131
  763. package/node_modules/ws/lib/constants.js +0 -19
  764. package/node_modules/ws/lib/event-target.js +0 -292
  765. package/node_modules/ws/lib/extension.js +0 -203
  766. package/node_modules/ws/lib/limiter.js +0 -55
  767. package/node_modules/ws/lib/permessage-deflate.js +0 -530
  768. package/node_modules/ws/lib/receiver.js +0 -743
  769. package/node_modules/ws/lib/sender.js +0 -607
  770. package/node_modules/ws/lib/stream.js +0 -161
  771. package/node_modules/ws/lib/subprotocol.js +0 -62
  772. package/node_modules/ws/lib/validation.js +0 -152
  773. package/node_modules/ws/lib/websocket-server.js +0 -562
  774. package/node_modules/ws/lib/websocket.js +0 -1407
  775. package/node_modules/ws/package.json +0 -74
  776. package/node_modules/ws/wrapper.mjs +0 -21
  777. package/node_modules/y18n/CHANGELOG.md +0 -35
  778. package/node_modules/y18n/LICENSE +0 -13
  779. package/node_modules/y18n/README.md +0 -109
  780. package/node_modules/y18n/index.js +0 -188
  781. package/node_modules/y18n/package.json +0 -39
  782. package/node_modules/yargs/CHANGELOG.md +0 -420
  783. package/node_modules/yargs/LICENSE +0 -21
  784. package/node_modules/yargs/README.md +0 -140
  785. package/node_modules/yargs/build/lib/apply-extends.d.ts +0 -2
  786. package/node_modules/yargs/build/lib/apply-extends.js +0 -65
  787. package/node_modules/yargs/build/lib/argsert.d.ts +0 -2
  788. package/node_modules/yargs/build/lib/argsert.js +0 -65
  789. package/node_modules/yargs/build/lib/command.d.ts +0 -64
  790. package/node_modules/yargs/build/lib/command.js +0 -416
  791. package/node_modules/yargs/build/lib/common-types.d.ts +0 -36
  792. package/node_modules/yargs/build/lib/common-types.js +0 -25
  793. package/node_modules/yargs/build/lib/completion-templates.d.ts +0 -2
  794. package/node_modules/yargs/build/lib/completion-templates.js +0 -50
  795. package/node_modules/yargs/build/lib/completion.d.ts +0 -21
  796. package/node_modules/yargs/build/lib/completion.js +0 -135
  797. package/node_modules/yargs/build/lib/is-promise.d.ts +0 -1
  798. package/node_modules/yargs/build/lib/is-promise.js +0 -9
  799. package/node_modules/yargs/build/lib/levenshtein.d.ts +0 -1
  800. package/node_modules/yargs/build/lib/levenshtein.js +0 -58
  801. package/node_modules/yargs/build/lib/middleware.d.ts +0 -10
  802. package/node_modules/yargs/build/lib/middleware.js +0 -57
  803. package/node_modules/yargs/build/lib/obj-filter.d.ts +0 -1
  804. package/node_modules/yargs/build/lib/obj-filter.js +0 -14
  805. package/node_modules/yargs/build/lib/parse-command.d.ts +0 -11
  806. package/node_modules/yargs/build/lib/parse-command.js +0 -36
  807. package/node_modules/yargs/build/lib/process-argv.d.ts +0 -2
  808. package/node_modules/yargs/build/lib/process-argv.js +0 -31
  809. package/node_modules/yargs/build/lib/usage.d.ts +0 -49
  810. package/node_modules/yargs/build/lib/usage.js +0 -540
  811. package/node_modules/yargs/build/lib/validation.d.ts +0 -34
  812. package/node_modules/yargs/build/lib/validation.js +0 -330
  813. package/node_modules/yargs/build/lib/yargs.d.ts +0 -274
  814. package/node_modules/yargs/build/lib/yargs.js +0 -1190
  815. package/node_modules/yargs/build/lib/yerror.d.ts +0 -4
  816. package/node_modules/yargs/build/lib/yerror.js +0 -11
  817. package/node_modules/yargs/index.js +0 -40
  818. package/node_modules/yargs/locales/be.json +0 -46
  819. package/node_modules/yargs/locales/de.json +0 -46
  820. package/node_modules/yargs/locales/en.json +0 -51
  821. package/node_modules/yargs/locales/es.json +0 -46
  822. package/node_modules/yargs/locales/fi.json +0 -49
  823. package/node_modules/yargs/locales/fr.json +0 -53
  824. package/node_modules/yargs/locales/hi.json +0 -49
  825. package/node_modules/yargs/locales/hu.json +0 -46
  826. package/node_modules/yargs/locales/id.json +0 -50
  827. package/node_modules/yargs/locales/it.json +0 -46
  828. package/node_modules/yargs/locales/ja.json +0 -51
  829. package/node_modules/yargs/locales/ko.json +0 -49
  830. package/node_modules/yargs/locales/nb.json +0 -44
  831. package/node_modules/yargs/locales/nl.json +0 -49
  832. package/node_modules/yargs/locales/nn.json +0 -44
  833. package/node_modules/yargs/locales/pirate.json +0 -13
  834. package/node_modules/yargs/locales/pl.json +0 -49
  835. package/node_modules/yargs/locales/pt.json +0 -45
  836. package/node_modules/yargs/locales/pt_BR.json +0 -48
  837. package/node_modules/yargs/locales/ru.json +0 -46
  838. package/node_modules/yargs/locales/th.json +0 -46
  839. package/node_modules/yargs/locales/tr.json +0 -48
  840. package/node_modules/yargs/locales/zh_CN.json +0 -48
  841. package/node_modules/yargs/locales/zh_TW.json +0 -47
  842. package/node_modules/yargs/package.json +0 -92
  843. package/node_modules/yargs/yargs.js +0 -14
  844. package/node_modules/yargs-parser/CHANGELOG.md +0 -601
  845. package/node_modules/yargs-parser/LICENSE.txt +0 -14
  846. package/node_modules/yargs-parser/README.md +0 -449
  847. package/node_modules/yargs-parser/index.js +0 -1032
  848. package/node_modules/yargs-parser/lib/tokenize-arg-string.js +0 -40
  849. 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
- }