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