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