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