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