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