@supen-ai/cli 0.1.6
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 +235 -0
- package/daemon/dist/acp-client.d.ts +42 -0
- package/daemon/dist/acp-client.d.ts.map +1 -0
- package/daemon/dist/acp-client.js +149 -0
- package/daemon/dist/acp-client.js.map +1 -0
- package/daemon/dist/acp-types.d.ts +98 -0
- package/daemon/dist/acp-types.d.ts.map +1 -0
- package/daemon/dist/acp-types.js +2 -0
- package/daemon/dist/acp-types.js.map +1 -0
- package/daemon/dist/agent-sdk/app-server-approvals.d.ts +24 -0
- package/daemon/dist/agent-sdk/app-server-approvals.d.ts.map +1 -0
- package/daemon/dist/agent-sdk/app-server-approvals.js +99 -0
- package/daemon/dist/agent-sdk/app-server-approvals.js.map +1 -0
- package/daemon/dist/agent-sdk/app-server-stream.d.ts +8 -0
- package/daemon/dist/agent-sdk/app-server-stream.d.ts.map +1 -0
- package/daemon/dist/agent-sdk/app-server-stream.js +328 -0
- package/daemon/dist/agent-sdk/app-server-stream.js.map +1 -0
- package/daemon/dist/agent-sdk/driver-output-ui.d.ts +9 -0
- package/daemon/dist/agent-sdk/driver-output-ui.d.ts.map +1 -0
- package/daemon/dist/agent-sdk/driver-output-ui.js +290 -0
- package/daemon/dist/agent-sdk/driver-output-ui.js.map +1 -0
- package/daemon/dist/agent-sdk/drivers/acpx-driver.d.ts +21 -0
- package/daemon/dist/agent-sdk/drivers/acpx-driver.d.ts.map +1 -0
- package/daemon/dist/agent-sdk/drivers/acpx-driver.js +488 -0
- package/daemon/dist/agent-sdk/drivers/acpx-driver.js.map +1 -0
- package/daemon/dist/agent-sdk/drivers/claude-acpx-driver.d.ts +5 -0
- package/daemon/dist/agent-sdk/drivers/claude-acpx-driver.d.ts.map +1 -0
- package/daemon/dist/agent-sdk/drivers/claude-acpx-driver.js +7 -0
- package/daemon/dist/agent-sdk/drivers/claude-acpx-driver.js.map +1 -0
- package/daemon/dist/agent-sdk/drivers/claude-cli-direct-driver.d.ts +20 -0
- package/daemon/dist/agent-sdk/drivers/claude-cli-direct-driver.d.ts.map +1 -0
- package/daemon/dist/agent-sdk/drivers/claude-cli-direct-driver.js +264 -0
- package/daemon/dist/agent-sdk/drivers/claude-cli-direct-driver.js.map +1 -0
- package/daemon/dist/agent-sdk/drivers/claude-code-driver.d.ts +29 -0
- package/daemon/dist/agent-sdk/drivers/claude-code-driver.d.ts.map +1 -0
- package/daemon/dist/agent-sdk/drivers/claude-code-driver.js +24 -0
- package/daemon/dist/agent-sdk/drivers/claude-code-driver.js.map +1 -0
- package/daemon/dist/agent-sdk/drivers/claude-code-events.d.ts +25 -0
- package/daemon/dist/agent-sdk/drivers/claude-code-events.d.ts.map +1 -0
- package/daemon/dist/agent-sdk/drivers/claude-code-events.js +58 -0
- package/daemon/dist/agent-sdk/drivers/claude-code-events.js.map +1 -0
- package/daemon/dist/agent-sdk/drivers/claude-code-items.d.ts +41 -0
- package/daemon/dist/agent-sdk/drivers/claude-code-items.d.ts.map +1 -0
- package/daemon/dist/agent-sdk/drivers/claude-code-items.js +77 -0
- package/daemon/dist/agent-sdk/drivers/claude-code-items.js.map +1 -0
- package/daemon/dist/agent-sdk/drivers/codex-acpx-driver.d.ts +5 -0
- package/daemon/dist/agent-sdk/drivers/codex-acpx-driver.d.ts.map +1 -0
- package/daemon/dist/agent-sdk/drivers/codex-acpx-driver.js +7 -0
- package/daemon/dist/agent-sdk/drivers/codex-acpx-driver.js.map +1 -0
- package/daemon/dist/agent-sdk/drivers/codex-app-server-driver.d.ts +12 -0
- package/daemon/dist/agent-sdk/drivers/codex-app-server-driver.d.ts.map +1 -0
- package/daemon/dist/agent-sdk/drivers/codex-app-server-driver.js +484 -0
- package/daemon/dist/agent-sdk/drivers/codex-app-server-driver.js.map +1 -0
- package/daemon/dist/agent-sdk/drivers/codex-exec-driver.d.ts +28 -0
- package/daemon/dist/agent-sdk/drivers/codex-exec-driver.d.ts.map +1 -0
- package/daemon/dist/agent-sdk/drivers/codex-exec-driver.js +219 -0
- package/daemon/dist/agent-sdk/drivers/codex-exec-driver.js.map +1 -0
- package/daemon/dist/agent-sdk/drivers/codex-exec-events.d.ts +9 -0
- package/daemon/dist/agent-sdk/drivers/codex-exec-events.d.ts.map +1 -0
- package/daemon/dist/agent-sdk/drivers/codex-exec-events.js +82 -0
- package/daemon/dist/agent-sdk/drivers/codex-exec-events.js.map +1 -0
- package/daemon/dist/agent-sdk/drivers/codex-exec-items.d.ts +9 -0
- package/daemon/dist/agent-sdk/drivers/codex-exec-items.d.ts.map +1 -0
- package/daemon/dist/agent-sdk/drivers/codex-exec-items.js +86 -0
- package/daemon/dist/agent-sdk/drivers/codex-exec-items.js.map +1 -0
- package/daemon/dist/agent-sdk/drivers/driver.d.ts +60 -0
- package/daemon/dist/agent-sdk/drivers/driver.d.ts.map +1 -0
- package/daemon/dist/agent-sdk/drivers/driver.js +2 -0
- package/daemon/dist/agent-sdk/drivers/driver.js.map +1 -0
- package/daemon/dist/agent-sdk/drivers/gemini-cli-driver.d.ts +17 -0
- package/daemon/dist/agent-sdk/drivers/gemini-cli-driver.d.ts.map +1 -0
- package/daemon/dist/agent-sdk/drivers/gemini-cli-driver.js +255 -0
- package/daemon/dist/agent-sdk/drivers/gemini-cli-driver.js.map +1 -0
- package/daemon/dist/agent-sdk/drivers/registry.d.ts +21 -0
- package/daemon/dist/agent-sdk/drivers/registry.d.ts.map +1 -0
- package/daemon/dist/agent-sdk/drivers/registry.js +167 -0
- package/daemon/dist/agent-sdk/drivers/registry.js.map +1 -0
- package/daemon/dist/agent-sdk/index.d.ts +24 -0
- package/daemon/dist/agent-sdk/index.d.ts.map +1 -0
- package/daemon/dist/agent-sdk/index.js +19 -0
- package/daemon/dist/agent-sdk/index.js.map +1 -0
- package/daemon/dist/agent-sdk/intelligence/contracts.d.ts +17 -0
- package/daemon/dist/agent-sdk/intelligence/contracts.d.ts.map +1 -0
- package/daemon/dist/agent-sdk/intelligence/contracts.js +2 -0
- package/daemon/dist/agent-sdk/intelligence/contracts.js.map +1 -0
- package/daemon/dist/agent-sdk/memory/filesystem.d.ts +7 -0
- package/daemon/dist/agent-sdk/memory/filesystem.d.ts.map +1 -0
- package/daemon/dist/agent-sdk/memory/filesystem.js +133 -0
- package/daemon/dist/agent-sdk/memory/filesystem.js.map +1 -0
- package/daemon/dist/agent-sdk/memory/subsystem.d.ts +54 -0
- package/daemon/dist/agent-sdk/memory/subsystem.d.ts.map +1 -0
- package/daemon/dist/agent-sdk/memory/subsystem.js +106 -0
- package/daemon/dist/agent-sdk/memory/subsystem.js.map +1 -0
- package/daemon/dist/agent-sdk/session-events.d.ts +59 -0
- package/daemon/dist/agent-sdk/session-events.d.ts.map +1 -0
- package/daemon/dist/agent-sdk/session-events.js +104 -0
- package/daemon/dist/agent-sdk/session-events.js.map +1 -0
- package/daemon/dist/agent-sdk/session-manager.d.ts +28 -0
- package/daemon/dist/agent-sdk/session-manager.d.ts.map +1 -0
- package/daemon/dist/agent-sdk/session-manager.js +54 -0
- package/daemon/dist/agent-sdk/session-manager.js.map +1 -0
- package/daemon/dist/agent-sdk/types.d.ts +110 -0
- package/daemon/dist/agent-sdk/types.d.ts.map +1 -0
- package/daemon/dist/agent-sdk/types.js +2 -0
- package/daemon/dist/agent-sdk/types.js.map +1 -0
- package/daemon/dist/automation-event-listener.d.ts +27 -0
- package/daemon/dist/automation-event-listener.d.ts.map +1 -0
- package/daemon/dist/automation-event-listener.js +342 -0
- package/daemon/dist/automation-event-listener.js.map +1 -0
- package/daemon/dist/automation-runner.d.ts +17 -0
- package/daemon/dist/automation-runner.d.ts.map +1 -0
- package/daemon/dist/automation-runner.js +592 -0
- package/daemon/dist/automation-runner.js.map +1 -0
- package/daemon/dist/autonomy/feedback-memory.d.ts +8 -0
- package/daemon/dist/autonomy/feedback-memory.d.ts.map +1 -0
- package/daemon/dist/autonomy/feedback-memory.js +23 -0
- package/daemon/dist/autonomy/feedback-memory.js.map +1 -0
- package/daemon/dist/autonomy/intent-router.d.ts +10 -0
- package/daemon/dist/autonomy/intent-router.d.ts.map +1 -0
- package/daemon/dist/autonomy/intent-router.js +72 -0
- package/daemon/dist/autonomy/intent-router.js.map +1 -0
- package/daemon/dist/autonomy/memory-rules.d.ts +35 -0
- package/daemon/dist/autonomy/memory-rules.d.ts.map +1 -0
- package/daemon/dist/autonomy/memory-rules.js +117 -0
- package/daemon/dist/autonomy/memory-rules.js.map +1 -0
- package/daemon/dist/autonomy/proof-packet.d.ts +17 -0
- package/daemon/dist/autonomy/proof-packet.d.ts.map +1 -0
- package/daemon/dist/autonomy/proof-packet.js +28 -0
- package/daemon/dist/autonomy/proof-packet.js.map +1 -0
- package/daemon/dist/autonomy/session-autonomy.d.ts +18 -0
- package/daemon/dist/autonomy/session-autonomy.d.ts.map +1 -0
- package/daemon/dist/autonomy/session-autonomy.js +71 -0
- package/daemon/dist/autonomy/session-autonomy.js.map +1 -0
- package/daemon/dist/autonomy/source-inventory.d.ts +3 -0
- package/daemon/dist/autonomy/source-inventory.d.ts.map +1 -0
- package/daemon/dist/autonomy/source-inventory.js +84 -0
- package/daemon/dist/autonomy/source-inventory.js.map +1 -0
- package/daemon/dist/autonomy/types.d.ts +20 -0
- package/daemon/dist/autonomy/types.d.ts.map +1 -0
- package/daemon/dist/autonomy/types.js +2 -0
- package/daemon/dist/autonomy/types.js.map +1 -0
- package/daemon/dist/bin/mcp-os.d.ts +3 -0
- package/daemon/dist/bin/mcp-os.d.ts.map +1 -0
- package/daemon/dist/bin/mcp-os.js +63 -0
- package/daemon/dist/bin/mcp-os.js.map +1 -0
- package/daemon/dist/bin/mcp-scheduler.d.ts +3 -0
- package/daemon/dist/bin/mcp-scheduler.d.ts.map +1 -0
- package/daemon/dist/bin/mcp-scheduler.js +90 -0
- package/daemon/dist/bin/mcp-scheduler.js.map +1 -0
- package/daemon/dist/bin/supen-sys.d.ts +3 -0
- package/daemon/dist/bin/supen-sys.d.ts.map +1 -0
- package/daemon/dist/bin/supen-sys.js +38 -0
- package/daemon/dist/bin/supen-sys.js.map +1 -0
- package/daemon/dist/bootstrap/hub-bootstrap.d.ts +2 -0
- package/daemon/dist/bootstrap/hub-bootstrap.d.ts.map +1 -0
- package/daemon/dist/bootstrap/hub-bootstrap.js +409 -0
- package/daemon/dist/bootstrap/hub-bootstrap.js.map +1 -0
- package/daemon/dist/bootstrap/skill-bootstrap.d.ts +2 -0
- package/daemon/dist/bootstrap/skill-bootstrap.d.ts.map +1 -0
- package/daemon/dist/bootstrap/skill-bootstrap.js +92 -0
- package/daemon/dist/bootstrap/skill-bootstrap.js.map +1 -0
- package/daemon/dist/channels/acp.d.ts +23 -0
- package/daemon/dist/channels/acp.d.ts.map +1 -0
- package/daemon/dist/channels/acp.js +916 -0
- package/daemon/dist/channels/acp.js.map +1 -0
- package/daemon/dist/channels/base.d.ts +38 -0
- package/daemon/dist/channels/base.d.ts.map +1 -0
- package/daemon/dist/channels/base.js +56 -0
- package/daemon/dist/channels/base.js.map +1 -0
- package/daemon/dist/channels/http-routes.d.ts +44 -0
- package/daemon/dist/channels/http-routes.d.ts.map +1 -0
- package/daemon/dist/channels/http-routes.js +688 -0
- package/daemon/dist/channels/http-routes.js.map +1 -0
- package/daemon/dist/channels/http.d.ts +16 -0
- package/daemon/dist/channels/http.d.ts.map +1 -0
- package/daemon/dist/channels/http.js +84 -0
- package/daemon/dist/channels/http.js.map +1 -0
- package/daemon/dist/channels/index.d.ts +3 -0
- package/daemon/dist/channels/index.d.ts.map +1 -0
- package/daemon/dist/channels/index.js +7 -0
- package/daemon/dist/channels/index.js.map +1 -0
- package/daemon/dist/channels/registry.d.ts +18 -0
- package/daemon/dist/channels/registry.d.ts.map +1 -0
- package/daemon/dist/channels/registry.js +11 -0
- package/daemon/dist/channels/registry.js.map +1 -0
- package/daemon/dist/commands/builtin.d.ts +7 -0
- package/daemon/dist/commands/builtin.d.ts.map +1 -0
- package/daemon/dist/commands/builtin.js +111 -0
- package/daemon/dist/commands/builtin.js.map +1 -0
- package/daemon/dist/commands/catalog.d.ts +47 -0
- package/daemon/dist/commands/catalog.d.ts.map +1 -0
- package/daemon/dist/commands/catalog.js +487 -0
- package/daemon/dist/commands/catalog.js.map +1 -0
- package/daemon/dist/core/app.d.ts +18 -0
- package/daemon/dist/core/app.d.ts.map +1 -0
- package/daemon/dist/core/app.js +49 -0
- package/daemon/dist/core/app.js.map +1 -0
- package/daemon/dist/core/automation-timing.d.ts +10 -0
- package/daemon/dist/core/automation-timing.d.ts.map +1 -0
- package/daemon/dist/core/automation-timing.js +211 -0
- package/daemon/dist/core/automation-timing.js.map +1 -0
- package/daemon/dist/core/codex-subscription.d.ts +8 -0
- package/daemon/dist/core/codex-subscription.d.ts.map +1 -0
- package/daemon/dist/core/codex-subscription.js +150 -0
- package/daemon/dist/core/codex-subscription.js.map +1 -0
- package/daemon/dist/core/command-hub.d.ts +22 -0
- package/daemon/dist/core/command-hub.d.ts.map +1 -0
- package/daemon/dist/core/command-hub.js +59 -0
- package/daemon/dist/core/command-hub.js.map +1 -0
- package/daemon/dist/core/config.d.ts +146 -0
- package/daemon/dist/core/config.d.ts.map +1 -0
- package/daemon/dist/core/config.js +663 -0
- package/daemon/dist/core/config.js.map +1 -0
- package/daemon/dist/core/control-commands.d.ts +17 -0
- package/daemon/dist/core/control-commands.d.ts.map +1 -0
- package/daemon/dist/core/control-commands.js +35 -0
- package/daemon/dist/core/control-commands.js.map +1 -0
- package/daemon/dist/core/control-log.d.ts +17 -0
- package/daemon/dist/core/control-log.d.ts.map +1 -0
- package/daemon/dist/core/control-log.js +67 -0
- package/daemon/dist/core/control-log.js.map +1 -0
- package/daemon/dist/core/cortex.d.ts +53 -0
- package/daemon/dist/core/cortex.d.ts.map +1 -0
- package/daemon/dist/core/cortex.js +1690 -0
- package/daemon/dist/core/cortex.js.map +1 -0
- package/daemon/dist/core/daemon-lock.d.ts +16 -0
- package/daemon/dist/core/daemon-lock.d.ts.map +1 -0
- package/daemon/dist/core/daemon-lock.js +285 -0
- package/daemon/dist/core/daemon-lock.js.map +1 -0
- package/daemon/dist/core/dispatcher.d.ts +42 -0
- package/daemon/dist/core/dispatcher.d.ts.map +1 -0
- package/daemon/dist/core/dispatcher.js +173 -0
- package/daemon/dist/core/dispatcher.js.map +1 -0
- package/daemon/dist/core/enrollment.d.ts +41 -0
- package/daemon/dist/core/enrollment.d.ts.map +1 -0
- package/daemon/dist/core/enrollment.js +195 -0
- package/daemon/dist/core/enrollment.js.map +1 -0
- package/daemon/dist/core/env.d.ts +109 -0
- package/daemon/dist/core/env.d.ts.map +1 -0
- package/daemon/dist/core/env.js +329 -0
- package/daemon/dist/core/env.js.map +1 -0
- package/daemon/dist/core/gateway-config.d.ts +16 -0
- package/daemon/dist/core/gateway-config.d.ts.map +1 -0
- package/daemon/dist/core/gateway-config.js +103 -0
- package/daemon/dist/core/gateway-config.js.map +1 -0
- package/daemon/dist/core/gateway-protocol.d.ts +80 -0
- package/daemon/dist/core/gateway-protocol.d.ts.map +1 -0
- package/daemon/dist/core/gateway-protocol.js +2 -0
- package/daemon/dist/core/gateway-protocol.js.map +1 -0
- package/daemon/dist/core/gateway-routing-config.d.ts +21 -0
- package/daemon/dist/core/gateway-routing-config.d.ts.map +1 -0
- package/daemon/dist/core/gateway-routing-config.js +56 -0
- package/daemon/dist/core/gateway-routing-config.js.map +1 -0
- package/daemon/dist/core/gateway.d.ts +124 -0
- package/daemon/dist/core/gateway.d.ts.map +1 -0
- package/daemon/dist/core/gateway.js +887 -0
- package/daemon/dist/core/gateway.js.map +1 -0
- package/daemon/dist/core/hub-snapshot.d.ts +42 -0
- package/daemon/dist/core/hub-snapshot.d.ts.map +1 -0
- package/daemon/dist/core/hub-snapshot.js +125 -0
- package/daemon/dist/core/hub-snapshot.js.map +1 -0
- package/daemon/dist/core/interrupts.d.ts +7 -0
- package/daemon/dist/core/interrupts.d.ts.map +1 -0
- package/daemon/dist/core/interrupts.js +28 -0
- package/daemon/dist/core/interrupts.js.map +1 -0
- package/daemon/dist/core/logger.d.ts +3 -0
- package/daemon/dist/core/logger.d.ts.map +1 -0
- package/daemon/dist/core/logger.js +91 -0
- package/daemon/dist/core/logger.js.map +1 -0
- package/daemon/dist/core/loop-guard.d.ts +27 -0
- package/daemon/dist/core/loop-guard.d.ts.map +1 -0
- package/daemon/dist/core/loop-guard.js +47 -0
- package/daemon/dist/core/loop-guard.js.map +1 -0
- package/daemon/dist/core/observable-logging.d.ts +43 -0
- package/daemon/dist/core/observable-logging.d.ts.map +1 -0
- package/daemon/dist/core/observable-logging.js +77 -0
- package/daemon/dist/core/observable-logging.js.map +1 -0
- package/daemon/dist/core/pairing.d.ts +51 -0
- package/daemon/dist/core/pairing.d.ts.map +1 -0
- package/daemon/dist/core/pairing.js +207 -0
- package/daemon/dist/core/pairing.js.map +1 -0
- package/daemon/dist/core/progress.d.ts +32 -0
- package/daemon/dist/core/progress.d.ts.map +1 -0
- package/daemon/dist/core/progress.js +145 -0
- package/daemon/dist/core/progress.js.map +1 -0
- package/daemon/dist/core/protocol-adapter.d.ts +14 -0
- package/daemon/dist/core/protocol-adapter.d.ts.map +1 -0
- package/daemon/dist/core/protocol-adapter.js +472 -0
- package/daemon/dist/core/protocol-adapter.js.map +1 -0
- package/daemon/dist/core/sdk-wrapper.d.ts +36 -0
- package/daemon/dist/core/sdk-wrapper.d.ts.map +1 -0
- package/daemon/dist/core/sdk-wrapper.js +533 -0
- package/daemon/dist/core/sdk-wrapper.js.map +1 -0
- package/daemon/dist/core/security.d.ts +10 -0
- package/daemon/dist/core/security.d.ts.map +1 -0
- package/daemon/dist/core/security.js +95 -0
- package/daemon/dist/core/security.js.map +1 -0
- package/daemon/dist/core/space-env.d.ts +15 -0
- package/daemon/dist/core/space-env.d.ts.map +1 -0
- package/daemon/dist/core/space-env.js +182 -0
- package/daemon/dist/core/space-env.js.map +1 -0
- package/daemon/dist/core/status-inspector.d.ts +31 -0
- package/daemon/dist/core/status-inspector.d.ts.map +1 -0
- package/daemon/dist/core/status-inspector.js +35 -0
- package/daemon/dist/core/status-inspector.js.map +1 -0
- package/daemon/dist/core/storage-paths.d.ts +30 -0
- package/daemon/dist/core/storage-paths.d.ts.map +1 -0
- package/daemon/dist/core/storage-paths.js +84 -0
- package/daemon/dist/core/storage-paths.js.map +1 -0
- package/daemon/dist/core/store.d.ts +256 -0
- package/daemon/dist/core/store.d.ts.map +1 -0
- package/daemon/dist/core/store.js +2956 -0
- package/daemon/dist/core/store.js.map +1 -0
- package/daemon/dist/core/streaming.d.ts +24 -0
- package/daemon/dist/core/streaming.d.ts.map +1 -0
- package/daemon/dist/core/streaming.js +57 -0
- package/daemon/dist/core/streaming.js.map +1 -0
- package/daemon/dist/core/task-artifacts.d.ts +17 -0
- package/daemon/dist/core/task-artifacts.d.ts.map +1 -0
- package/daemon/dist/core/task-artifacts.js +104 -0
- package/daemon/dist/core/task-artifacts.js.map +1 -0
- package/daemon/dist/core/thread-event-log.d.ts +54 -0
- package/daemon/dist/core/thread-event-log.d.ts.map +1 -0
- package/daemon/dist/core/thread-event-log.js +218 -0
- package/daemon/dist/core/thread-event-log.js.map +1 -0
- package/daemon/dist/core/thread-runtime-state.d.ts +53 -0
- package/daemon/dist/core/thread-runtime-state.d.ts.map +1 -0
- package/daemon/dist/core/thread-runtime-state.js +271 -0
- package/daemon/dist/core/thread-runtime-state.js.map +1 -0
- package/daemon/dist/core/types.d.ts +552 -0
- package/daemon/dist/core/types.d.ts.map +1 -0
- package/daemon/dist/core/types.js +2 -0
- package/daemon/dist/core/types.js.map +1 -0
- package/daemon/dist/core/utils.d.ts +5 -0
- package/daemon/dist/core/utils.d.ts.map +1 -0
- package/daemon/dist/core/utils.js +35 -0
- package/daemon/dist/core/utils.js.map +1 -0
- package/daemon/dist/http/command-catalog.d.ts +3 -0
- package/daemon/dist/http/command-catalog.d.ts.map +1 -0
- package/daemon/dist/http/command-catalog.js +2 -0
- package/daemon/dist/http/command-catalog.js.map +1 -0
- package/daemon/dist/http/context.d.ts +18 -0
- package/daemon/dist/http/context.d.ts.map +1 -0
- package/daemon/dist/http/context.js +121 -0
- package/daemon/dist/http/context.js.map +1 -0
- package/daemon/dist/http/office-preview.d.ts +10 -0
- package/daemon/dist/http/office-preview.d.ts.map +1 -0
- package/daemon/dist/http/office-preview.js +234 -0
- package/daemon/dist/http/office-preview.js.map +1 -0
- package/daemon/dist/http/response.d.ts +5 -0
- package/daemon/dist/http/response.d.ts.map +1 -0
- package/daemon/dist/http/response.js +37 -0
- package/daemon/dist/http/response.js.map +1 -0
- package/daemon/dist/http/router.d.ts +4 -0
- package/daemon/dist/http/router.d.ts.map +1 -0
- package/daemon/dist/http/router.js +57 -0
- package/daemon/dist/http/router.js.map +1 -0
- package/daemon/dist/http/routes/agents.d.ts +4 -0
- package/daemon/dist/http/routes/agents.d.ts.map +1 -0
- package/daemon/dist/http/routes/agents.js +747 -0
- package/daemon/dist/http/routes/agents.js.map +1 -0
- package/daemon/dist/http/routes/automations.d.ts +6 -0
- package/daemon/dist/http/routes/automations.d.ts.map +1 -0
- package/daemon/dist/http/routes/automations.js +530 -0
- package/daemon/dist/http/routes/automations.js.map +1 -0
- package/daemon/dist/http/routes/autonomy.d.ts +4 -0
- package/daemon/dist/http/routes/autonomy.d.ts.map +1 -0
- package/daemon/dist/http/routes/autonomy.js +78 -0
- package/daemon/dist/http/routes/autonomy.js.map +1 -0
- package/daemon/dist/http/routes/chat-input.d.ts +18 -0
- package/daemon/dist/http/routes/chat-input.d.ts.map +1 -0
- package/daemon/dist/http/routes/chat-input.js +122 -0
- package/daemon/dist/http/routes/chat-input.js.map +1 -0
- package/daemon/dist/http/routes/plugins.d.ts +5 -0
- package/daemon/dist/http/routes/plugins.d.ts.map +1 -0
- package/daemon/dist/http/routes/plugins.js +221 -0
- package/daemon/dist/http/routes/plugins.js.map +1 -0
- package/daemon/dist/http/routes/rpc.d.ts +28 -0
- package/daemon/dist/http/routes/rpc.d.ts.map +1 -0
- package/daemon/dist/http/routes/rpc.js +790 -0
- package/daemon/dist/http/routes/rpc.js.map +1 -0
- package/daemon/dist/http/routes/sessions.d.ts +11 -0
- package/daemon/dist/http/routes/sessions.d.ts.map +1 -0
- package/daemon/dist/http/routes/sessions.js +963 -0
- package/daemon/dist/http/routes/sessions.js.map +1 -0
- package/daemon/dist/http/routes/skills.d.ts +5 -0
- package/daemon/dist/http/routes/skills.d.ts.map +1 -0
- package/daemon/dist/http/routes/skills.js +420 -0
- package/daemon/dist/http/routes/skills.js.map +1 -0
- package/daemon/dist/http/routes/system.d.ts +64 -0
- package/daemon/dist/http/routes/system.d.ts.map +1 -0
- package/daemon/dist/http/routes/system.js +2676 -0
- package/daemon/dist/http/routes/system.js.map +1 -0
- package/daemon/dist/http/stream.d.ts +11 -0
- package/daemon/dist/http/stream.d.ts.map +1 -0
- package/daemon/dist/http/stream.js +100 -0
- package/daemon/dist/http/stream.js.map +1 -0
- package/daemon/dist/http/thread-stream.d.ts +10 -0
- package/daemon/dist/http/thread-stream.d.ts.map +1 -0
- package/daemon/dist/http/thread-stream.js +50 -0
- package/daemon/dist/http/thread-stream.js.map +1 -0
- package/daemon/dist/http/thread-title.d.ts +12 -0
- package/daemon/dist/http/thread-title.d.ts.map +1 -0
- package/daemon/dist/http/thread-title.js +122 -0
- package/daemon/dist/http/thread-title.js.map +1 -0
- package/daemon/dist/http/utils.d.ts +2 -0
- package/daemon/dist/http/utils.d.ts.map +1 -0
- package/daemon/dist/http/utils.js +18 -0
- package/daemon/dist/http/utils.js.map +1 -0
- package/daemon/dist/http/websocket.d.ts +5 -0
- package/daemon/dist/http/websocket.d.ts.map +1 -0
- package/daemon/dist/http/websocket.js +100 -0
- package/daemon/dist/http/websocket.js.map +1 -0
- package/daemon/dist/index.d.ts +35 -0
- package/daemon/dist/index.d.ts.map +1 -0
- package/daemon/dist/index.js +1582 -0
- package/daemon/dist/index.js.map +1 -0
- package/daemon/dist/mcp/aggregate-config.d.ts +16 -0
- package/daemon/dist/mcp/aggregate-config.d.ts.map +1 -0
- package/daemon/dist/mcp/aggregate-config.js +97 -0
- package/daemon/dist/mcp/aggregate-config.js.map +1 -0
- package/daemon/dist/mcp/client.d.ts +94 -0
- package/daemon/dist/mcp/client.d.ts.map +1 -0
- package/daemon/dist/mcp/client.js +207 -0
- package/daemon/dist/mcp/client.js.map +1 -0
- package/daemon/dist/mcp/default-servers.d.ts +35 -0
- package/daemon/dist/mcp/default-servers.d.ts.map +1 -0
- package/daemon/dist/mcp/default-servers.js +209 -0
- package/daemon/dist/mcp/default-servers.js.map +1 -0
- package/daemon/dist/mcp/gateway-client.d.ts +58 -0
- package/daemon/dist/mcp/gateway-client.d.ts.map +1 -0
- package/daemon/dist/mcp/gateway-client.js +181 -0
- package/daemon/dist/mcp/gateway-client.js.map +1 -0
- package/daemon/dist/mcp/index.d.ts +26 -0
- package/daemon/dist/mcp/index.d.ts.map +1 -0
- package/daemon/dist/mcp/index.js +50 -0
- package/daemon/dist/mcp/index.js.map +1 -0
- package/daemon/dist/mcp/settings.d.ts +3 -0
- package/daemon/dist/mcp/settings.d.ts.map +1 -0
- package/daemon/dist/mcp/settings.js +60 -0
- package/daemon/dist/mcp/settings.js.map +1 -0
- package/daemon/dist/mcp/tools.d.ts +21 -0
- package/daemon/dist/mcp/tools.d.ts.map +1 -0
- package/daemon/dist/mcp/tools.js +136 -0
- package/daemon/dist/mcp/tools.js.map +1 -0
- package/daemon/dist/plugins/catalog.d.ts +10 -0
- package/daemon/dist/plugins/catalog.d.ts.map +1 -0
- package/daemon/dist/plugins/catalog.js +304 -0
- package/daemon/dist/plugins/catalog.js.map +1 -0
- package/daemon/dist/plugins/hub.d.ts +42 -0
- package/daemon/dist/plugins/hub.d.ts.map +1 -0
- package/daemon/dist/plugins/hub.js +812 -0
- package/daemon/dist/plugins/hub.js.map +1 -0
- package/daemon/dist/plugins/types.d.ts +144 -0
- package/daemon/dist/plugins/types.d.ts.map +1 -0
- package/daemon/dist/plugins/types.js +2 -0
- package/daemon/dist/plugins/types.js.map +1 -0
- package/daemon/dist/router.d.ts +13 -0
- package/daemon/dist/router.d.ts.map +1 -0
- package/daemon/dist/router.js +43 -0
- package/daemon/dist/router.js.map +1 -0
- package/daemon/dist/skills/adapter.d.ts +4 -0
- package/daemon/dist/skills/adapter.d.ts.map +1 -0
- package/daemon/dist/skills/adapter.js +141 -0
- package/daemon/dist/skills/adapter.js.map +1 -0
- package/daemon/dist/skills/allowlist.d.ts +20 -0
- package/daemon/dist/skills/allowlist.d.ts.map +1 -0
- package/daemon/dist/skills/allowlist.js +52 -0
- package/daemon/dist/skills/allowlist.js.map +1 -0
- package/daemon/dist/skills/catalog.d.ts +26 -0
- package/daemon/dist/skills/catalog.d.ts.map +1 -0
- package/daemon/dist/skills/catalog.js +274 -0
- package/daemon/dist/skills/catalog.js.map +1 -0
- package/daemon/dist/skills/claude_code.d.ts +25 -0
- package/daemon/dist/skills/claude_code.d.ts.map +1 -0
- package/daemon/dist/skills/claude_code.js +49 -0
- package/daemon/dist/skills/claude_code.js.map +1 -0
- package/daemon/dist/skills/commands.d.ts +3 -0
- package/daemon/dist/skills/commands.d.ts.map +1 -0
- package/daemon/dist/skills/commands.js +689 -0
- package/daemon/dist/skills/commands.js.map +1 -0
- package/daemon/dist/skills/enabled.d.ts +7 -0
- package/daemon/dist/skills/enabled.d.ts.map +1 -0
- package/daemon/dist/skills/enabled.js +37 -0
- package/daemon/dist/skills/enabled.js.map +1 -0
- package/daemon/dist/skills/hub.d.ts +35 -0
- package/daemon/dist/skills/hub.d.ts.map +1 -0
- package/daemon/dist/skills/hub.js +574 -0
- package/daemon/dist/skills/hub.js.map +1 -0
- package/daemon/dist/skills/loader.d.ts +24 -0
- package/daemon/dist/skills/loader.d.ts.map +1 -0
- package/daemon/dist/skills/loader.js +693 -0
- package/daemon/dist/skills/loader.js.map +1 -0
- package/daemon/dist/skills/mcp-config.d.ts +8 -0
- package/daemon/dist/skills/mcp-config.d.ts.map +1 -0
- package/daemon/dist/skills/mcp-config.js +110 -0
- package/daemon/dist/skills/mcp-config.js.map +1 -0
- package/daemon/dist/skills/parser.d.ts +3 -0
- package/daemon/dist/skills/parser.d.ts.map +1 -0
- package/daemon/dist/skills/parser.js +279 -0
- package/daemon/dist/skills/parser.js.map +1 -0
- package/daemon/dist/skills/registry.d.ts +2 -0
- package/daemon/dist/skills/registry.d.ts.map +1 -0
- package/daemon/dist/skills/registry.js +2 -0
- package/daemon/dist/skills/registry.js.map +1 -0
- package/daemon/dist/skills/runtime-contract.d.ts +3 -0
- package/daemon/dist/skills/runtime-contract.d.ts.map +1 -0
- package/daemon/dist/skills/runtime-contract.js +92 -0
- package/daemon/dist/skills/runtime-contract.js.map +1 -0
- package/daemon/dist/skills/runtime.d.ts +28 -0
- package/daemon/dist/skills/runtime.d.ts.map +1 -0
- package/daemon/dist/skills/runtime.js +286 -0
- package/daemon/dist/skills/runtime.js.map +1 -0
- package/daemon/dist/skills/types.d.ts +228 -0
- package/daemon/dist/skills/types.d.ts.map +1 -0
- package/daemon/dist/skills/types.js +2 -0
- package/daemon/dist/skills/types.js.map +1 -0
- package/daemon/dist/start.d.ts +6 -0
- package/daemon/dist/start.d.ts.map +1 -0
- package/daemon/dist/start.js +19 -0
- package/daemon/dist/start.js.map +1 -0
- package/daemon/dist/sub-agent.d.ts +46 -0
- package/daemon/dist/sub-agent.d.ts.map +1 -0
- package/daemon/dist/sub-agent.js +120 -0
- package/daemon/dist/sub-agent.js.map +1 -0
- package/daemon/dist/sync/supabase-sync.d.ts +16 -0
- package/daemon/dist/sync/supabase-sync.d.ts.map +1 -0
- package/daemon/dist/sync/supabase-sync.js +240 -0
- package/daemon/dist/sync/supabase-sync.js.map +1 -0
- package/daemon/dist/task-executor.d.ts +6 -0
- package/daemon/dist/task-executor.d.ts.map +1 -0
- package/daemon/dist/task-executor.js +31 -0
- package/daemon/dist/task-executor.js.map +1 -0
- package/daemon/dist/tools/automations.d.ts +57 -0
- package/daemon/dist/tools/automations.d.ts.map +1 -0
- package/daemon/dist/tools/automations.js +94 -0
- package/daemon/dist/tools/automations.js.map +1 -0
- package/daemon/dist/tools/built-ins.d.ts +112 -0
- package/daemon/dist/tools/built-ins.d.ts.map +1 -0
- package/daemon/dist/tools/built-ins.js +251 -0
- package/daemon/dist/tools/built-ins.js.map +1 -0
- package/daemon/dist/tools/index.d.ts +287 -0
- package/daemon/dist/tools/index.d.ts.map +1 -0
- package/daemon/dist/tools/index.js +86 -0
- package/daemon/dist/tools/index.js.map +1 -0
- package/daemon/dist/tools/shell.d.ts +15 -0
- package/daemon/dist/tools/shell.d.ts.map +1 -0
- package/daemon/dist/tools/shell.js +46 -0
- package/daemon/dist/tools/shell.js.map +1 -0
- package/daemon/dist/tools/skill-tools.d.ts +23 -0
- package/daemon/dist/tools/skill-tools.d.ts.map +1 -0
- package/daemon/dist/tools/skill-tools.js +64 -0
- package/daemon/dist/tools/skill-tools.js.map +1 -0
- package/daemon/dist/tools/system.d.ts +36 -0
- package/daemon/dist/tools/system.d.ts.map +1 -0
- package/daemon/dist/tools/system.js +54 -0
- package/daemon/dist/tools/system.js.map +1 -0
- package/daemon/dist/tools/types.d.ts +11 -0
- package/daemon/dist/tools/types.d.ts.map +1 -0
- package/daemon/dist/tools/types.js +2 -0
- package/daemon/dist/tools/types.js.map +1 -0
- package/daemon/scripts/browser-smoke.mjs +125 -0
- package/daemon/scripts/supen-daemon.js +15 -0
- package/dist/agent.d.ts +11 -0
- package/dist/agent.js +159 -0
- package/dist/agent.js.map +1 -0
- package/dist/auth/login.d.ts +20 -0
- package/dist/auth/login.js +151 -0
- package/dist/auth/login.js.map +1 -0
- package/dist/auth/logout.d.ts +5 -0
- package/dist/auth/logout.js +19 -0
- package/dist/auth/logout.js.map +1 -0
- package/dist/auth/store.d.ts +37 -0
- package/dist/auth/store.js +80 -0
- package/dist/auth/store.js.map +1 -0
- package/dist/auth/whoami.d.ts +5 -0
- package/dist/auth/whoami.js +24 -0
- package/dist/auth/whoami.js.map +1 -0
- package/dist/backend.d.ts +8 -0
- package/dist/backend.js +148 -0
- package/dist/backend.js.map +1 -0
- package/dist/bootstrap.d.ts +13 -0
- package/dist/bootstrap.js +230 -0
- package/dist/bootstrap.js.map +1 -0
- package/dist/chat.d.ts +13 -0
- package/dist/chat.js +255 -0
- package/dist/chat.js.map +1 -0
- package/dist/commands.d.ts +48 -0
- package/dist/commands.js +273 -0
- package/dist/commands.js.map +1 -0
- package/dist/computer.d.ts +2 -0
- package/dist/computer.js +510 -0
- package/dist/computer.js.map +1 -0
- package/dist/config.d.ts +11 -0
- package/dist/config.js +149 -0
- package/dist/config.js.map +1 -0
- package/dist/daemon-manage.d.ts +29 -0
- package/dist/daemon-manage.js +255 -0
- package/dist/daemon-manage.js.map +1 -0
- package/dist/daemon.d.ts +15 -0
- package/dist/daemon.js +205 -0
- package/dist/daemon.js.map +1 -0
- package/dist/doctor.d.ts +13 -0
- package/dist/doctor.js +323 -0
- package/dist/doctor.js.map +1 -0
- package/dist/enroll.d.ts +7 -0
- package/dist/enroll.js +154 -0
- package/dist/enroll.js.map +1 -0
- package/dist/env.d.ts +10 -0
- package/dist/env.js +363 -0
- package/dist/env.js.map +1 -0
- package/dist/index.d.ts +54 -0
- package/dist/index.js +141 -0
- package/dist/index.js.map +1 -0
- package/dist/knowledge.d.ts +3 -0
- package/dist/knowledge.js +1581 -0
- package/dist/knowledge.js.map +1 -0
- package/dist/mcp.d.ts +11 -0
- package/dist/mcp.js +137 -0
- package/dist/mcp.js.map +1 -0
- package/dist/model.d.ts +8 -0
- package/dist/model.js +192 -0
- package/dist/model.js.map +1 -0
- package/dist/pairing.d.ts +21 -0
- package/dist/pairing.js +376 -0
- package/dist/pairing.js.map +1 -0
- package/dist/repl-events.d.ts +60 -0
- package/dist/repl-events.js +89 -0
- package/dist/repl-events.js.map +1 -0
- package/dist/repl-renderer.d.ts +37 -0
- package/dist/repl-renderer.js +140 -0
- package/dist/repl-renderer.js.map +1 -0
- package/dist/repl.d.ts +52 -0
- package/dist/repl.js +624 -0
- package/dist/repl.js.map +1 -0
- package/dist/service.d.ts +8 -0
- package/dist/service.js +238 -0
- package/dist/service.js.map +1 -0
- package/dist/skills.d.ts +14 -0
- package/dist/skills.js +423 -0
- package/dist/skills.js.map +1 -0
- package/dist/sse.d.ts +15 -0
- package/dist/sse.js +166 -0
- package/dist/sse.js.map +1 -0
- package/dist/thread.d.ts +9 -0
- package/dist/thread.js +152 -0
- package/dist/thread.js.map +1 -0
- package/dist/transport/computer-api.d.ts +1 -0
- package/dist/transport/computer-api.js +20 -0
- package/dist/transport/computer-api.js.map +1 -0
- package/dist/transport/gateway.d.ts +23 -0
- package/dist/transport/gateway.js +161 -0
- package/dist/transport/gateway.js.map +1 -0
- package/dist/transport/index.d.ts +27 -0
- package/dist/transport/index.js +77 -0
- package/dist/transport/index.js.map +1 -0
- package/dist/transport/local.d.ts +20 -0
- package/dist/transport/local.js +138 -0
- package/dist/transport/local.js.map +1 -0
- package/dist/transport/types.d.ts +42 -0
- package/dist/transport/types.js +11 -0
- package/dist/transport/types.js.map +1 -0
- package/dist/ui/app.d.ts +7 -0
- package/dist/ui/app.js +192 -0
- package/dist/ui/app.js.map +1 -0
- package/dist/ui/history-item.d.ts +9 -0
- package/dist/ui/history-item.js +35 -0
- package/dist/ui/history-item.js.map +1 -0
- package/dist/ui/input-bar.d.ts +17 -0
- package/dist/ui/input-bar.js +67 -0
- package/dist/ui/input-bar.js.map +1 -0
- package/dist/ui/streaming-view.d.ts +6 -0
- package/dist/ui/streaming-view.js +6 -0
- package/dist/ui/streaming-view.js.map +1 -0
- package/dist/ui/thread-input-history.d.ts +18 -0
- package/dist/ui/thread-input-history.js +67 -0
- package/dist/ui/thread-input-history.js.map +1 -0
- package/dist/utils.d.ts +17 -0
- package/dist/utils.js +80 -0
- package/dist/utils.js.map +1 -0
- package/package.json +55 -0
|
@@ -0,0 +1,2676 @@
|
|
|
1
|
+
import os from 'os';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import yaml from 'yaml';
|
|
5
|
+
import { createRequire } from 'module';
|
|
6
|
+
import { spawn, spawnSync } from 'child_process';
|
|
7
|
+
import { DAEMON_HOSTNAME, DEFAULT_MODEL, MODELS_REGISTRY, SUPEN_HOME, reloadModelsRegistry, } from '../../core/config.js';
|
|
8
|
+
import { readConfigSummary, readConfigYamlFile, writeConfigYamlFile, } from '../../core/env.js';
|
|
9
|
+
import { readSpaceEnvMap, updateSpaceEnvMap, withSupenLlmEnv } from '../../core/space-env.js';
|
|
10
|
+
import { readCodexSubscription } from '../../core/codex-subscription.js';
|
|
11
|
+
import { buildHubSnapshotForSpace } from '../../core/hub-snapshot.js';
|
|
12
|
+
import { readEnrollmentState, createEnrollmentToken, verifyEnrollmentToken, setTrustState } from '../../core/enrollment.js';
|
|
13
|
+
import { getGatewayInstance, getLlmToken } from '../../core/gateway.js';
|
|
14
|
+
import { normalizeGatewayUplinkUrl, readGatewayConfig, writeGatewayConfig, } from '../../core/gateway-config.js';
|
|
15
|
+
import { ensureSession, getAllAgents, getDailyUsage, getGlobalUsage, getSessionsForAgent, getSessionUiEvents, updateSessionBackendDriverId, updateSessionSdkId, } from '../../core/store.js';
|
|
16
|
+
import { listMcpEnvKeys, listMcpServers } from '../../mcp/default-servers.js';
|
|
17
|
+
import { getMcpEnvOverrides, updateMcpEnvOverrides } from '../../mcp/settings.js';
|
|
18
|
+
import { getMcpManager } from '../../mcp/index.js';
|
|
19
|
+
import { buildDaemonOpenApiSpec } from '../../channels/http-routes.js';
|
|
20
|
+
import { writeJson, writeProtocolError, readJsonBody } from '../response.js';
|
|
21
|
+
import { listRecentThreadEventsAfter, readThreadEventLogHead, } from '../../core/thread-event-log.js';
|
|
22
|
+
import { addThreadStreamClient, removeThreadStreamClient, writeThreadStreamEvent } from '../thread-stream.js';
|
|
23
|
+
const DEFAULT_TOKEN_TTL_MINUTES = 20;
|
|
24
|
+
const SPACE_LOG_EVENT_LIMIT = 200;
|
|
25
|
+
const SPACE_LOG_STREAM_POLL_MS = 1000;
|
|
26
|
+
const SPACE_LOG_STREAM_PING_MS = 15000;
|
|
27
|
+
const MIRRORED_THREAD_LIMIT = 80;
|
|
28
|
+
const MIRRORED_THREAD_HISTORY_LIMIT = 100;
|
|
29
|
+
const MIRRORED_THREAD_HISTORY_MAX_BYTES = 64 * 1024 * 1024;
|
|
30
|
+
const MIRRORED_THREAD_TEXT_LIMIT = 48_000;
|
|
31
|
+
const MIRRORED_THREAD_TOOL_STRING_LIMIT = 24_000;
|
|
32
|
+
const MIRRORED_THREAD_OBJECT_DEPTH_LIMIT = 4;
|
|
33
|
+
const MIRRORED_THREAD_OBJECT_KEY_LIMIT = 80;
|
|
34
|
+
const MIRRORED_THREAD_OBJECT_ARRAY_LIMIT = 80;
|
|
35
|
+
const MIRRORED_THREAD_INLINE_DATA_URL_LIMIT = 120_000;
|
|
36
|
+
const MIRRORED_THREAD_STREAM_REPLAY_LIMIT = 500;
|
|
37
|
+
const MIRRORED_THREAD_STREAM_REPLAY_MAX_BYTES = 8 * 1024 * 1024;
|
|
38
|
+
const MIRRORED_THREAD_RUNNING_LEASE_MS = 30 * 60 * 1000;
|
|
39
|
+
const MIRRORED_THREAD_HISTORY_CHUNK_BYTES = 1024 * 1024;
|
|
40
|
+
const require = createRequire(import.meta.url);
|
|
41
|
+
function readSqliteRows(dbPath, query) {
|
|
42
|
+
try {
|
|
43
|
+
const { DatabaseSync } = require('node:sqlite');
|
|
44
|
+
const db = new DatabaseSync(dbPath, { readOnly: true });
|
|
45
|
+
try {
|
|
46
|
+
return db.prepare(query).all();
|
|
47
|
+
}
|
|
48
|
+
finally {
|
|
49
|
+
db.close();
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function gatewayUplinkStatus(config = readGatewayConfig()) {
|
|
57
|
+
const gatewayUrl = (config.gateway_url || '').trim();
|
|
58
|
+
return {
|
|
59
|
+
configured: Boolean(gatewayUrl),
|
|
60
|
+
gateway_url: gatewayUrl || null,
|
|
61
|
+
connected: Boolean(getGatewayInstance()?.isConnected()),
|
|
62
|
+
env_override: Boolean(process.env.SUPEN_GATEWAY_URL),
|
|
63
|
+
disabled_by_env: process.env.SUPEN_DISABLE_GATEWAY_UPLINK === 'true' ||
|
|
64
|
+
process.env.SUPEN_DISABLE_GATEWAY_UPLINK === '1',
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
function resolveHostTimeZone() {
|
|
68
|
+
return Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC';
|
|
69
|
+
}
|
|
70
|
+
function truncateText(value, max = 160) {
|
|
71
|
+
const trimmed = value.trim();
|
|
72
|
+
return trimmed.length <= max ? trimmed : `${trimmed.slice(0, max - 1)}…`;
|
|
73
|
+
}
|
|
74
|
+
function truncateStreamDelta(value, max = 160) {
|
|
75
|
+
return value.length <= max ? value : `${value.slice(0, max - 1)}…`;
|
|
76
|
+
}
|
|
77
|
+
function currentSpaceId() {
|
|
78
|
+
return (process.env.SUPEN_SPACE_ID || '').trim() || 'local';
|
|
79
|
+
}
|
|
80
|
+
function summarizeStreamEvent(raw) {
|
|
81
|
+
const inner = raw.event && typeof raw.event === 'object'
|
|
82
|
+
? raw.event
|
|
83
|
+
: null;
|
|
84
|
+
if (!inner)
|
|
85
|
+
return null;
|
|
86
|
+
if (inner.type === 'content_block_delta' && inner.delta?.type === 'text_delta') {
|
|
87
|
+
const text = typeof inner.delta.text === 'string' ? inner.delta.text : '';
|
|
88
|
+
return text ? { category: 'assistant', summary: truncateStreamDelta(text) } : null;
|
|
89
|
+
}
|
|
90
|
+
if (inner.type === 'content_block_delta' && inner.delta?.type === 'thinking_delta') {
|
|
91
|
+
const text = typeof inner.delta.thinking === 'string' ? inner.delta.thinking : '';
|
|
92
|
+
return text ? { category: 'reasoning', summary: truncateStreamDelta(text) } : null;
|
|
93
|
+
}
|
|
94
|
+
if (inner.type === 'content_block_start' && inner.content_block?.type === 'tool_use') {
|
|
95
|
+
const toolName = typeof inner.content_block.name === 'string' ? inner.content_block.name : 'tool';
|
|
96
|
+
return { category: 'tool', summary: `Requested tool: ${toolName}` };
|
|
97
|
+
}
|
|
98
|
+
if (inner.type === 'content_block_delta' && inner.delta?.type === 'input_json_delta') {
|
|
99
|
+
const partialJson = typeof inner.delta.partial_json === 'string' ? inner.delta.partial_json : '';
|
|
100
|
+
return partialJson ? { category: 'tool input', summary: truncateStreamDelta(partialJson) } : null;
|
|
101
|
+
}
|
|
102
|
+
if (inner.type === 'message_delta' && inner.delta?.stop_reason === 'tool_use') {
|
|
103
|
+
return { category: 'tool', summary: 'Waiting for tool result' };
|
|
104
|
+
}
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
function summarizeToolResultContent(content) {
|
|
108
|
+
if (typeof content === 'string')
|
|
109
|
+
return truncateText(content);
|
|
110
|
+
if (!Array.isArray(content))
|
|
111
|
+
return null;
|
|
112
|
+
const text = content
|
|
113
|
+
.map((item) => {
|
|
114
|
+
if (typeof item === 'string')
|
|
115
|
+
return item;
|
|
116
|
+
if (item && typeof item === 'object' && typeof item.text === 'string') {
|
|
117
|
+
return item.text;
|
|
118
|
+
}
|
|
119
|
+
return '';
|
|
120
|
+
})
|
|
121
|
+
.filter(Boolean)
|
|
122
|
+
.join('\n');
|
|
123
|
+
return text.trim() ? truncateText(text) : null;
|
|
124
|
+
}
|
|
125
|
+
function summarizeAppServerEvent(raw) {
|
|
126
|
+
const method = typeof raw.method === 'string' ? raw.method : '';
|
|
127
|
+
if (!method)
|
|
128
|
+
return null;
|
|
129
|
+
const params = raw.params && typeof raw.params === 'object'
|
|
130
|
+
? raw.params
|
|
131
|
+
: {};
|
|
132
|
+
if (method === 'warning') {
|
|
133
|
+
const message = typeof params.message === 'string' ? params.message : 'Warning';
|
|
134
|
+
return { category: 'warning', summary: truncateText(message) };
|
|
135
|
+
}
|
|
136
|
+
if (method === 'thread/started') {
|
|
137
|
+
const thread = params.thread && typeof params.thread === 'object'
|
|
138
|
+
? params.thread
|
|
139
|
+
: {};
|
|
140
|
+
const cwd = typeof thread.cwd === 'string' ? thread.cwd : '';
|
|
141
|
+
return { category: 'thread.started', summary: cwd ? `started in ${cwd}` : 'started' };
|
|
142
|
+
}
|
|
143
|
+
if (method === 'thread/status/changed') {
|
|
144
|
+
const status = params.status && typeof params.status === 'object'
|
|
145
|
+
? params.status
|
|
146
|
+
: {};
|
|
147
|
+
const type = typeof status.type === 'string' ? status.type : 'changed';
|
|
148
|
+
return { category: 'thread.status', summary: type };
|
|
149
|
+
}
|
|
150
|
+
if (method === 'turn/started') {
|
|
151
|
+
return { category: 'turn.started', summary: 'started' };
|
|
152
|
+
}
|
|
153
|
+
if (method === 'turn/completed' || method === 'turn/failed' || method === 'turn/cancelled') {
|
|
154
|
+
const turn = params.turn && typeof params.turn === 'object'
|
|
155
|
+
? params.turn
|
|
156
|
+
: {};
|
|
157
|
+
const status = typeof turn.status === 'string' ? turn.status : method.slice('turn/'.length);
|
|
158
|
+
const durationMs = typeof turn.durationMs === 'number' ? turn.durationMs : null;
|
|
159
|
+
const duration = durationMs !== null ? ` in ${(durationMs / 1000).toFixed(1)}s` : '';
|
|
160
|
+
return { category: method.replace('/', '.'), summary: `${status}${duration}` };
|
|
161
|
+
}
|
|
162
|
+
if (method === 'mcpServer/startupStatus/updated') {
|
|
163
|
+
const name = typeof params.name === 'string' ? params.name : 'mcp server';
|
|
164
|
+
const status = typeof params.status === 'string' ? params.status : 'updated';
|
|
165
|
+
const error = typeof params.error === 'string' && params.error.trim() ? `: ${params.error.trim()}` : '';
|
|
166
|
+
return { category: 'mcp.status', summary: truncateText(`${name} ${status}${error}`) };
|
|
167
|
+
}
|
|
168
|
+
if (method === 'hook/started' || method === 'hook/completed') {
|
|
169
|
+
const run = params.run && typeof params.run === 'object'
|
|
170
|
+
? params.run
|
|
171
|
+
: {};
|
|
172
|
+
const eventName = typeof run.eventName === 'string' ? run.eventName : 'hook';
|
|
173
|
+
const status = typeof run.status === 'string' ? run.status : method.slice('hook/'.length);
|
|
174
|
+
const durationMs = typeof run.durationMs === 'number' ? run.durationMs : null;
|
|
175
|
+
const duration = durationMs !== null ? ` in ${durationMs}ms` : '';
|
|
176
|
+
return { category: method.replace('/', '.'), summary: `${eventName} ${status}${duration}` };
|
|
177
|
+
}
|
|
178
|
+
if (method === 'item/started' || method === 'item/completed') {
|
|
179
|
+
const item = params.item && typeof params.item === 'object'
|
|
180
|
+
? params.item
|
|
181
|
+
: {};
|
|
182
|
+
const itemType = typeof item.type === 'string' ? item.type : 'item';
|
|
183
|
+
if (itemType === 'agentMessage') {
|
|
184
|
+
return { category: method.replace('/', '.'), summary: method === 'item/started' ? 'assistant response started' : 'assistant response completed' };
|
|
185
|
+
}
|
|
186
|
+
if (itemType === 'userMessage') {
|
|
187
|
+
return { category: method.replace('/', '.'), summary: method === 'item/started' ? 'user message received' : 'user message accepted' };
|
|
188
|
+
}
|
|
189
|
+
return { category: method.replace('/', '.'), summary: `${itemType} ${method.slice('item/'.length)}` };
|
|
190
|
+
}
|
|
191
|
+
if (method === 'item/commandExecution/outputDelta') {
|
|
192
|
+
const delta = typeof params.delta === 'string' ? params.delta.trim() : '';
|
|
193
|
+
return {
|
|
194
|
+
category: 'command.output',
|
|
195
|
+
summary: delta ? truncateText(delta) : 'output received',
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
if (method === 'account/rateLimits/updated') {
|
|
199
|
+
return { category: 'rate_limits', summary: 'updated' };
|
|
200
|
+
}
|
|
201
|
+
return { category: method.replace('/', '.'), summary: 'received' };
|
|
202
|
+
}
|
|
203
|
+
function summarizeUiChunk(chunk) {
|
|
204
|
+
const type = typeof chunk.type === 'string' ? chunk.type : '';
|
|
205
|
+
if (type === 'text-delta') {
|
|
206
|
+
const delta = typeof chunk.delta === 'string' ? chunk.delta : '';
|
|
207
|
+
return delta ? { category: 'assistant', summary: truncateStreamDelta(delta) } : null;
|
|
208
|
+
}
|
|
209
|
+
if (type === 'reasoning-delta') {
|
|
210
|
+
const delta = typeof chunk.delta === 'string' ? chunk.delta : '';
|
|
211
|
+
return delta ? { category: 'reasoning', summary: truncateStreamDelta(delta) } : null;
|
|
212
|
+
}
|
|
213
|
+
if (type === 'error') {
|
|
214
|
+
const errorText = typeof chunk.errorText === 'string'
|
|
215
|
+
? chunk.errorText
|
|
216
|
+
: typeof chunk.message === 'string'
|
|
217
|
+
? chunk.message
|
|
218
|
+
: 'Unknown error';
|
|
219
|
+
return { category: 'error', summary: truncateText(errorText) };
|
|
220
|
+
}
|
|
221
|
+
if (type === 'data-supen-event') {
|
|
222
|
+
const data = chunk.data && typeof chunk.data === 'object'
|
|
223
|
+
? chunk.data
|
|
224
|
+
: {};
|
|
225
|
+
const eventType = typeof data.eventType === 'string'
|
|
226
|
+
? data.eventType
|
|
227
|
+
: typeof data.type === 'string'
|
|
228
|
+
? data.type
|
|
229
|
+
: 'event';
|
|
230
|
+
const subtype = typeof data.subtype === 'string' ? data.subtype : '';
|
|
231
|
+
const raw = data.raw && typeof data.raw === 'object'
|
|
232
|
+
? data.raw
|
|
233
|
+
: {};
|
|
234
|
+
if (raw.type === 'stream_event') {
|
|
235
|
+
return summarizeStreamEvent(raw);
|
|
236
|
+
}
|
|
237
|
+
if (typeof raw.method === 'string') {
|
|
238
|
+
return summarizeAppServerEvent(raw);
|
|
239
|
+
}
|
|
240
|
+
if (raw.type === 'user' && Array.isArray(raw.message?.content)) {
|
|
241
|
+
for (const block of raw.message.content) {
|
|
242
|
+
if (block?.type !== 'tool_result')
|
|
243
|
+
continue;
|
|
244
|
+
const summary = summarizeToolResultContent(block.content);
|
|
245
|
+
if (summary)
|
|
246
|
+
return { category: 'tool result', summary };
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
const detail = typeof raw.status === 'string'
|
|
250
|
+
? raw.status
|
|
251
|
+
: typeof raw.message === 'string'
|
|
252
|
+
? raw.message
|
|
253
|
+
: typeof raw.error === 'string'
|
|
254
|
+
? raw.error
|
|
255
|
+
: '';
|
|
256
|
+
return detail
|
|
257
|
+
? { category: [eventType, subtype].filter(Boolean).join('.'), summary: truncateText(detail) }
|
|
258
|
+
: null;
|
|
259
|
+
}
|
|
260
|
+
return null;
|
|
261
|
+
}
|
|
262
|
+
function recordValue(record, keys) {
|
|
263
|
+
for (const key of keys) {
|
|
264
|
+
if (record[key] !== undefined && record[key] !== null)
|
|
265
|
+
return record[key];
|
|
266
|
+
}
|
|
267
|
+
return null;
|
|
268
|
+
}
|
|
269
|
+
function numberValue(value) {
|
|
270
|
+
if (value === null || value === undefined || value === '')
|
|
271
|
+
return null;
|
|
272
|
+
const parsed = Number(value);
|
|
273
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
274
|
+
}
|
|
275
|
+
function labelForQuotaWindow(key, record) {
|
|
276
|
+
const explicit = recordValue(record, ['label', 'name', 'window', 'period', 'type']);
|
|
277
|
+
if (typeof explicit === 'string' && explicit.trim())
|
|
278
|
+
return explicit.trim();
|
|
279
|
+
return key
|
|
280
|
+
.replace(/[_-]+/g, ' ')
|
|
281
|
+
.replace(/\b\w/g, (match) => match.toUpperCase());
|
|
282
|
+
}
|
|
283
|
+
function quotaWindowFromRecord(key, record) {
|
|
284
|
+
const used = numberValue(recordValue(record, ['used', 'current', 'consumed', 'usage', 'used_tokens', 'tokens_used']));
|
|
285
|
+
const limit = numberValue(recordValue(record, ['limit', 'quota', 'total', 'max', 'cap']));
|
|
286
|
+
const remaining = numberValue(recordValue(record, ['remaining', 'left', 'available']));
|
|
287
|
+
const percent = numberValue(recordValue(record, ['percent', 'percentage', 'used_percent', 'usage_percent']));
|
|
288
|
+
const resetRaw = recordValue(record, ['reset_at', 'resets_at', 'resetAt', 'resetsAt', 'reset']);
|
|
289
|
+
const reset_at = typeof resetRaw === 'string' && resetRaw.trim() ? resetRaw.trim() : null;
|
|
290
|
+
if (used === null && limit === null && remaining === null && percent === null && !reset_at)
|
|
291
|
+
return null;
|
|
292
|
+
return {
|
|
293
|
+
label: labelForQuotaWindow(key, record),
|
|
294
|
+
used,
|
|
295
|
+
limit,
|
|
296
|
+
remaining,
|
|
297
|
+
percent: percent !== null
|
|
298
|
+
? percent
|
|
299
|
+
: (used !== null && limit && limit > 0 ? Math.round((used / limit) * 1000) / 10 : null),
|
|
300
|
+
reset_at,
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
function normalizeQuotaWindows(value) {
|
|
304
|
+
if (!value || typeof value !== 'object')
|
|
305
|
+
return [];
|
|
306
|
+
if (Array.isArray(value)) {
|
|
307
|
+
return value
|
|
308
|
+
.map((entry, index) => entry && typeof entry === 'object'
|
|
309
|
+
? quotaWindowFromRecord(`Quota ${index + 1}`, entry)
|
|
310
|
+
: null)
|
|
311
|
+
.filter((entry) => Boolean(entry));
|
|
312
|
+
}
|
|
313
|
+
const record = value;
|
|
314
|
+
const direct = quotaWindowFromRecord('Quota', record);
|
|
315
|
+
const nested = Object.entries(record)
|
|
316
|
+
.map(([key, nestedValue]) => nestedValue && typeof nestedValue === 'object'
|
|
317
|
+
? quotaWindowFromRecord(key, nestedValue)
|
|
318
|
+
: null)
|
|
319
|
+
.filter((entry) => Boolean(entry));
|
|
320
|
+
return nested.length > 0 ? nested : (direct ? [direct] : []);
|
|
321
|
+
}
|
|
322
|
+
function quotaPayloadFromEvent(raw) {
|
|
323
|
+
const params = raw.params && typeof raw.params === 'object'
|
|
324
|
+
? raw.params
|
|
325
|
+
: {};
|
|
326
|
+
return (params.rateLimits ??
|
|
327
|
+
params.rate_limits ??
|
|
328
|
+
params.limits ??
|
|
329
|
+
params.quota ??
|
|
330
|
+
params.quotas ??
|
|
331
|
+
params);
|
|
332
|
+
}
|
|
333
|
+
function extractQuotaEvent(event) {
|
|
334
|
+
const chunk = event.chunk && typeof event.chunk === 'object' ? event.chunk : {};
|
|
335
|
+
const data = chunk.data && typeof chunk.data === 'object'
|
|
336
|
+
? chunk.data
|
|
337
|
+
: {};
|
|
338
|
+
const raw = data.raw && typeof data.raw === 'object'
|
|
339
|
+
? data.raw
|
|
340
|
+
: {};
|
|
341
|
+
if (raw.method !== 'account/rateLimits/updated')
|
|
342
|
+
return null;
|
|
343
|
+
const windows = normalizeQuotaWindows(quotaPayloadFromEvent(raw));
|
|
344
|
+
return {
|
|
345
|
+
updated_at: event.timestamp || new Date().toISOString(),
|
|
346
|
+
windows,
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
function readLatestSpaceQuotaStatus() {
|
|
350
|
+
const sessions = getAllAgents()
|
|
351
|
+
.flatMap((agent) => getSessionsForAgent(agent.agent_id).map((session) => ({
|
|
352
|
+
agent_id: agent.agent_id,
|
|
353
|
+
session_id: session.session_id,
|
|
354
|
+
})));
|
|
355
|
+
const latest = sessions
|
|
356
|
+
.flatMap((session) => getSessionUiEvents(session.agent_id, session.session_id, 200)
|
|
357
|
+
.map((event) => extractQuotaEvent(event)))
|
|
358
|
+
.filter((entry) => Boolean(entry))
|
|
359
|
+
.sort((a, b) => a.updated_at.localeCompare(b.updated_at))
|
|
360
|
+
.at(-1);
|
|
361
|
+
return {
|
|
362
|
+
source: latest ? 'codex' : null,
|
|
363
|
+
updated_at: latest?.updated_at ?? null,
|
|
364
|
+
windows: latest?.windows ?? [],
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
function localAgentHome() {
|
|
368
|
+
const configured = (process.env.CODEX_HOME || '').trim();
|
|
369
|
+
return configured || path.join(os.homedir(), '.codex');
|
|
370
|
+
}
|
|
371
|
+
function parseJsonLine(line) {
|
|
372
|
+
try {
|
|
373
|
+
const parsed = JSON.parse(line);
|
|
374
|
+
return parsed && typeof parsed === 'object' ? parsed : null;
|
|
375
|
+
}
|
|
376
|
+
catch {
|
|
377
|
+
return null;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
export function readRecentJsonlLines(filePath, maxBytes = MIRRORED_THREAD_HISTORY_MAX_BYTES) {
|
|
381
|
+
const safeMaxBytes = Math.max(1, Math.min(Math.floor(maxBytes), MIRRORED_THREAD_HISTORY_MAX_BYTES));
|
|
382
|
+
const size = fs.statSync(filePath).size;
|
|
383
|
+
if (size <= safeMaxBytes) {
|
|
384
|
+
return fs.readFileSync(filePath, 'utf-8').split(/\r?\n/).filter(Boolean);
|
|
385
|
+
}
|
|
386
|
+
const readLength = Math.min(size, safeMaxBytes);
|
|
387
|
+
const start = size - readLength;
|
|
388
|
+
const fd = fs.openSync(filePath, 'r');
|
|
389
|
+
try {
|
|
390
|
+
const buffer = Buffer.alloc(readLength);
|
|
391
|
+
const bytesRead = fs.readSync(fd, buffer, 0, readLength, start);
|
|
392
|
+
let text = buffer.subarray(0, bytesRead).toString('utf-8');
|
|
393
|
+
if (start > 0) {
|
|
394
|
+
const firstLineEnd = text.indexOf('\n');
|
|
395
|
+
if (firstLineEnd < 0)
|
|
396
|
+
return [];
|
|
397
|
+
text = text.slice(firstLineEnd + 1);
|
|
398
|
+
}
|
|
399
|
+
return text.split(/\r?\n/).filter(Boolean);
|
|
400
|
+
}
|
|
401
|
+
finally {
|
|
402
|
+
fs.closeSync(fd);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
function isCodexHistoryMessageLine(line) {
|
|
406
|
+
const parsed = parseJsonLine(line);
|
|
407
|
+
const payload = parsed?.payload && typeof parsed.payload === 'object'
|
|
408
|
+
? parsed.payload
|
|
409
|
+
: {};
|
|
410
|
+
return ((parsed?.type === 'event_msg' && payload.type === 'user_message') ||
|
|
411
|
+
(parsed?.type === 'response_item' && payload.type === 'message'));
|
|
412
|
+
}
|
|
413
|
+
function readRecentCodexHistoryLines(filePath, targetMessages, maxBytes = MIRRORED_THREAD_HISTORY_MAX_BYTES) {
|
|
414
|
+
const safeTargetMessages = Math.max(1, Math.min(Math.floor(targetMessages), 501));
|
|
415
|
+
const safeMaxBytes = Math.max(1, Math.min(Math.floor(maxBytes), MIRRORED_THREAD_HISTORY_MAX_BYTES));
|
|
416
|
+
const size = fs.statSync(filePath).size;
|
|
417
|
+
const fd = fs.openSync(filePath, 'r');
|
|
418
|
+
try {
|
|
419
|
+
let position = size;
|
|
420
|
+
let scannedBytes = 0;
|
|
421
|
+
let leadingPartial = '';
|
|
422
|
+
let messageCount = 0;
|
|
423
|
+
let collected = [];
|
|
424
|
+
while (position > 0 && scannedBytes < safeMaxBytes && messageCount < safeTargetMessages) {
|
|
425
|
+
const readLength = Math.min(MIRRORED_THREAD_HISTORY_CHUNK_BYTES, position, safeMaxBytes - scannedBytes);
|
|
426
|
+
position -= readLength;
|
|
427
|
+
scannedBytes += readLength;
|
|
428
|
+
const buffer = Buffer.alloc(readLength);
|
|
429
|
+
const bytesRead = fs.readSync(fd, buffer, 0, readLength, position);
|
|
430
|
+
const text = `${buffer.subarray(0, bytesRead).toString('utf-8')}${leadingPartial}`;
|
|
431
|
+
const lines = text.split(/\r?\n/);
|
|
432
|
+
leadingPartial = lines.shift() || '';
|
|
433
|
+
if (position === 0 && leadingPartial) {
|
|
434
|
+
lines.unshift(leadingPartial);
|
|
435
|
+
leadingPartial = '';
|
|
436
|
+
}
|
|
437
|
+
const completeLines = lines.filter(Boolean);
|
|
438
|
+
for (const line of completeLines) {
|
|
439
|
+
if (isCodexHistoryMessageLine(line))
|
|
440
|
+
messageCount += 1;
|
|
441
|
+
}
|
|
442
|
+
collected = [...completeLines, ...collected];
|
|
443
|
+
}
|
|
444
|
+
return collected.filter(Boolean);
|
|
445
|
+
}
|
|
446
|
+
finally {
|
|
447
|
+
fs.closeSync(fd);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
function readThreadIndex(limit) {
|
|
451
|
+
const indexPath = path.join(localAgentHome(), 'session_index.jsonl');
|
|
452
|
+
if (!fs.existsSync(indexPath))
|
|
453
|
+
return [];
|
|
454
|
+
const lines = fs.readFileSync(indexPath, 'utf-8').split(/\r?\n/).filter(Boolean);
|
|
455
|
+
const byId = new Map();
|
|
456
|
+
for (const line of lines) {
|
|
457
|
+
const parsed = parseJsonLine(line);
|
|
458
|
+
const id = typeof parsed?.id === 'string' ? parsed.id.trim() : '';
|
|
459
|
+
const title = typeof parsed?.thread_name === 'string' ? parsed.thread_name.trim() : '';
|
|
460
|
+
const updatedAt = typeof parsed?.updated_at === 'string' ? parsed.updated_at.trim() : '';
|
|
461
|
+
if (!id || !updatedAt)
|
|
462
|
+
continue;
|
|
463
|
+
byId.set(id, {
|
|
464
|
+
id,
|
|
465
|
+
thread_name: title || 'New Task',
|
|
466
|
+
updated_at: updatedAt,
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
return Array.from(byId.values())
|
|
470
|
+
.sort((a, b) => b.updated_at.localeCompare(a.updated_at))
|
|
471
|
+
.slice(0, limit);
|
|
472
|
+
}
|
|
473
|
+
function readThreadIndexEntry(threadId) {
|
|
474
|
+
return readThreadIndex(1000).find((entry) => entry.id === threadId) || null;
|
|
475
|
+
}
|
|
476
|
+
function readThreadStateEntry(threadId) {
|
|
477
|
+
return readThreadStateEntries(1000).find((entry) => entry.id === threadId) || null;
|
|
478
|
+
}
|
|
479
|
+
function isoFromMillis(value) {
|
|
480
|
+
const ms = typeof value === 'number' ? value : Number(value);
|
|
481
|
+
if (!Number.isFinite(ms) || ms <= 0)
|
|
482
|
+
return null;
|
|
483
|
+
return new Date(ms).toISOString();
|
|
484
|
+
}
|
|
485
|
+
function readThreadStateEntries(limit) {
|
|
486
|
+
const dbPath = path.join(localAgentHome(), 'state_5.sqlite');
|
|
487
|
+
if (!fs.existsSync(dbPath))
|
|
488
|
+
return [];
|
|
489
|
+
const query = `
|
|
490
|
+
select id, title, cwd, updated_at, updated_at_ms
|
|
491
|
+
from threads
|
|
492
|
+
where archived = 0
|
|
493
|
+
and coalesce(thread_source, 'user') = 'user'
|
|
494
|
+
order by coalesce(updated_at_ms, updated_at * 1000) desc, id desc
|
|
495
|
+
limit ${Math.max(1, Math.min(limit, 500))}
|
|
496
|
+
`;
|
|
497
|
+
let rows = readSqliteRows(dbPath, query);
|
|
498
|
+
if (!rows) {
|
|
499
|
+
const result = spawnSync('sqlite3', ['-json', dbPath, query], {
|
|
500
|
+
encoding: 'utf-8',
|
|
501
|
+
maxBuffer: 4 * 1024 * 1024,
|
|
502
|
+
});
|
|
503
|
+
if (result.status !== 0 || !result.stdout.trim())
|
|
504
|
+
return [];
|
|
505
|
+
try {
|
|
506
|
+
rows = JSON.parse(result.stdout);
|
|
507
|
+
}
|
|
508
|
+
catch {
|
|
509
|
+
return [];
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
try {
|
|
513
|
+
return rows
|
|
514
|
+
.map((row) => {
|
|
515
|
+
const id = typeof row.id === 'string' ? row.id.trim() : '';
|
|
516
|
+
if (!id)
|
|
517
|
+
return null;
|
|
518
|
+
const updatedAtMs = typeof row.updated_at_ms === 'number'
|
|
519
|
+
? row.updated_at_ms
|
|
520
|
+
: Number(row.updated_at) * 1000;
|
|
521
|
+
const updatedAt = isoFromMillis(updatedAtMs);
|
|
522
|
+
if (!updatedAt)
|
|
523
|
+
return null;
|
|
524
|
+
return {
|
|
525
|
+
id,
|
|
526
|
+
title: typeof row.title === 'string' && row.title.trim() ? row.title.trim() : 'New Task',
|
|
527
|
+
cwd: typeof row.cwd === 'string' && row.cwd.trim() ? row.cwd.trim() : null,
|
|
528
|
+
updated_at: updatedAt,
|
|
529
|
+
updated_at_ms: updatedAtMs,
|
|
530
|
+
};
|
|
531
|
+
})
|
|
532
|
+
.filter((entry) => Boolean(entry));
|
|
533
|
+
}
|
|
534
|
+
catch {
|
|
535
|
+
return [];
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
function walkSessionFiles(dir, out = new Map()) {
|
|
539
|
+
if (!fs.existsSync(dir))
|
|
540
|
+
return out;
|
|
541
|
+
let entries;
|
|
542
|
+
try {
|
|
543
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
544
|
+
}
|
|
545
|
+
catch {
|
|
546
|
+
return out;
|
|
547
|
+
}
|
|
548
|
+
for (const entry of entries) {
|
|
549
|
+
const fullPath = path.join(dir, entry.name);
|
|
550
|
+
if (entry.isDirectory()) {
|
|
551
|
+
walkSessionFiles(fullPath, out);
|
|
552
|
+
continue;
|
|
553
|
+
}
|
|
554
|
+
if (!entry.isFile() || !entry.name.endsWith('.jsonl'))
|
|
555
|
+
continue;
|
|
556
|
+
const match = entry.name.match(/([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/i);
|
|
557
|
+
if (match?.[1])
|
|
558
|
+
out.set(match[1], fullPath);
|
|
559
|
+
}
|
|
560
|
+
return out;
|
|
561
|
+
}
|
|
562
|
+
function readFileHead(filePath, maxBytes = 64 * 1024) {
|
|
563
|
+
const fd = fs.openSync(filePath, 'r');
|
|
564
|
+
try {
|
|
565
|
+
const buffer = Buffer.alloc(maxBytes);
|
|
566
|
+
const bytesRead = fs.readSync(fd, buffer, 0, maxBytes, 0);
|
|
567
|
+
return buffer.subarray(0, bytesRead).toString('utf-8');
|
|
568
|
+
}
|
|
569
|
+
finally {
|
|
570
|
+
fs.closeSync(fd);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
function readFileTail(filePath, maxBytes = 256 * 1024) {
|
|
574
|
+
const fd = fs.openSync(filePath, 'r');
|
|
575
|
+
try {
|
|
576
|
+
const stat = fs.fstatSync(fd);
|
|
577
|
+
const length = Math.min(maxBytes, stat.size);
|
|
578
|
+
const buffer = Buffer.alloc(length);
|
|
579
|
+
fs.readSync(fd, buffer, 0, length, Math.max(0, stat.size - length));
|
|
580
|
+
return buffer.toString('utf-8');
|
|
581
|
+
}
|
|
582
|
+
finally {
|
|
583
|
+
fs.closeSync(fd);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
function readThreadWorkspacePath(filePath) {
|
|
587
|
+
if (!filePath)
|
|
588
|
+
return null;
|
|
589
|
+
try {
|
|
590
|
+
const head = readFileHead(filePath);
|
|
591
|
+
for (const line of head.split(/\r?\n/)) {
|
|
592
|
+
if (!line.trim())
|
|
593
|
+
continue;
|
|
594
|
+
const parsed = parseJsonLine(line);
|
|
595
|
+
if (parsed?.type !== 'session_meta')
|
|
596
|
+
continue;
|
|
597
|
+
const payload = parsed.payload && typeof parsed.payload === 'object'
|
|
598
|
+
? parsed.payload
|
|
599
|
+
: {};
|
|
600
|
+
const cwd = typeof payload.cwd === 'string' ? payload.cwd.trim() : '';
|
|
601
|
+
return cwd || null;
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
catch {
|
|
605
|
+
return null;
|
|
606
|
+
}
|
|
607
|
+
return null;
|
|
608
|
+
}
|
|
609
|
+
function normalizeThreadStatusToken(value) {
|
|
610
|
+
return typeof value === 'string'
|
|
611
|
+
? value.trim().toLowerCase().replace(/[-_\s]/g, '')
|
|
612
|
+
: '';
|
|
613
|
+
}
|
|
614
|
+
const RUNNING_THREAD_STATUS_TOKENS = new Set([
|
|
615
|
+
'running',
|
|
616
|
+
'inprogress',
|
|
617
|
+
'processing',
|
|
618
|
+
'active',
|
|
619
|
+
'started',
|
|
620
|
+
'streaming',
|
|
621
|
+
'busy',
|
|
622
|
+
'claimed',
|
|
623
|
+
]);
|
|
624
|
+
const TERMINAL_THREAD_STATUS_TOKENS = new Set([
|
|
625
|
+
'completed',
|
|
626
|
+
'complete',
|
|
627
|
+
'succeeded',
|
|
628
|
+
'success',
|
|
629
|
+
'failed',
|
|
630
|
+
'error',
|
|
631
|
+
'cancelled',
|
|
632
|
+
'canceled',
|
|
633
|
+
'aborted',
|
|
634
|
+
'idle',
|
|
635
|
+
]);
|
|
636
|
+
function isRunningThreadStatusToken(value) {
|
|
637
|
+
return RUNNING_THREAD_STATUS_TOKENS.has(normalizeThreadStatusToken(value));
|
|
638
|
+
}
|
|
639
|
+
function isTerminalThreadStatusToken(value) {
|
|
640
|
+
return TERMINAL_THREAD_STATUS_TOKENS.has(normalizeThreadStatusToken(value));
|
|
641
|
+
}
|
|
642
|
+
function isFreshRunningActivity(timestampMs) {
|
|
643
|
+
if (!timestampMs)
|
|
644
|
+
return false;
|
|
645
|
+
return Date.now() - timestampMs <= MIRRORED_THREAD_RUNNING_LEASE_MS;
|
|
646
|
+
}
|
|
647
|
+
function timestampMsFromParsedLine(parsed) {
|
|
648
|
+
const timestamp = typeof parsed?.timestamp === 'string' ? parsed.timestamp : '';
|
|
649
|
+
if (!timestamp)
|
|
650
|
+
return null;
|
|
651
|
+
const timestampMs = Date.parse(timestamp);
|
|
652
|
+
return Number.isFinite(timestampMs) ? timestampMs : null;
|
|
653
|
+
}
|
|
654
|
+
function readThreadStatus(filePath) {
|
|
655
|
+
if (!filePath)
|
|
656
|
+
return 'idle';
|
|
657
|
+
try {
|
|
658
|
+
let sawUserTurn = false;
|
|
659
|
+
let completedAfterLastUser = false;
|
|
660
|
+
let lastRunningActivityMs = null;
|
|
661
|
+
const tail = readFileTail(filePath, MIRRORED_THREAD_STREAM_REPLAY_MAX_BYTES);
|
|
662
|
+
for (const line of tail.split(/\r?\n/)) {
|
|
663
|
+
if (!line.trim())
|
|
664
|
+
continue;
|
|
665
|
+
const parsed = parseJsonLine(line);
|
|
666
|
+
const timestampMs = timestampMsFromParsedLine(parsed);
|
|
667
|
+
const payload = parsed?.payload && typeof parsed.payload === 'object'
|
|
668
|
+
? parsed.payload
|
|
669
|
+
: {};
|
|
670
|
+
if (parsed?.type === 'event_msg') {
|
|
671
|
+
if (payload.type === 'task_started' || payload.type === 'user_message') {
|
|
672
|
+
sawUserTurn = true;
|
|
673
|
+
completedAfterLastUser = false;
|
|
674
|
+
lastRunningActivityMs = timestampMs;
|
|
675
|
+
continue;
|
|
676
|
+
}
|
|
677
|
+
if (payload.type === 'task_complete' ||
|
|
678
|
+
payload.type === 'task_failed' ||
|
|
679
|
+
payload.type === 'task_cancelled' ||
|
|
680
|
+
payload.type === 'task_canceled' ||
|
|
681
|
+
payload.type === 'turn_aborted') {
|
|
682
|
+
completedAfterLastUser = true;
|
|
683
|
+
continue;
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
if (parsed?.type === 'response_item' &&
|
|
687
|
+
payload.type === 'message' &&
|
|
688
|
+
payload.role === 'user') {
|
|
689
|
+
sawUserTurn = true;
|
|
690
|
+
completedAfterLastUser = false;
|
|
691
|
+
lastRunningActivityMs = timestampMs;
|
|
692
|
+
continue;
|
|
693
|
+
}
|
|
694
|
+
const raw = payload.raw && typeof payload.raw === 'object'
|
|
695
|
+
? payload.raw
|
|
696
|
+
: null;
|
|
697
|
+
const method = typeof raw?.method === 'string' ? raw.method : '';
|
|
698
|
+
const params = raw?.params && typeof raw.params === 'object'
|
|
699
|
+
? raw.params
|
|
700
|
+
: {};
|
|
701
|
+
if (method === 'turn/started') {
|
|
702
|
+
sawUserTurn = true;
|
|
703
|
+
completedAfterLastUser = false;
|
|
704
|
+
lastRunningActivityMs = timestampMs;
|
|
705
|
+
continue;
|
|
706
|
+
}
|
|
707
|
+
if (method === 'turn/completed' ||
|
|
708
|
+
method === 'turn/failed' ||
|
|
709
|
+
method === 'turn/cancelled' ||
|
|
710
|
+
method === 'turn/canceled' ||
|
|
711
|
+
method === 'turn/aborted') {
|
|
712
|
+
completedAfterLastUser = true;
|
|
713
|
+
continue;
|
|
714
|
+
}
|
|
715
|
+
if (method === 'thread/status/changed') {
|
|
716
|
+
const status = params.status && typeof params.status === 'object'
|
|
717
|
+
? params.status.type
|
|
718
|
+
: params.status;
|
|
719
|
+
if (isRunningThreadStatusToken(status)) {
|
|
720
|
+
sawUserTurn = true;
|
|
721
|
+
completedAfterLastUser = false;
|
|
722
|
+
lastRunningActivityMs = timestampMs;
|
|
723
|
+
continue;
|
|
724
|
+
}
|
|
725
|
+
if (isTerminalThreadStatusToken(status)) {
|
|
726
|
+
completedAfterLastUser = true;
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
return sawUserTurn && !completedAfterLastUser && isFreshRunningActivity(lastRunningActivityMs)
|
|
731
|
+
? 'running'
|
|
732
|
+
: 'idle';
|
|
733
|
+
}
|
|
734
|
+
catch {
|
|
735
|
+
return 'idle';
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
function projectNameFromPath(projectPath) {
|
|
739
|
+
if (!projectPath)
|
|
740
|
+
return 'No workspace';
|
|
741
|
+
const normalized = projectPath.replace(/\\/g, '/').replace(/\/+$/, '');
|
|
742
|
+
return normalized.split('/').filter(Boolean).at(-1) || normalized || 'No workspace';
|
|
743
|
+
}
|
|
744
|
+
function isPathWithin(parent, candidate) {
|
|
745
|
+
const relative = path.relative(path.resolve(parent), path.resolve(candidate));
|
|
746
|
+
return relative === '' || (!!relative && !relative.startsWith('..') && !path.isAbsolute(relative));
|
|
747
|
+
}
|
|
748
|
+
function isMirrorableCodexWorkspace(projectPath) {
|
|
749
|
+
if (!projectPath || !path.isAbsolute(projectPath))
|
|
750
|
+
return false;
|
|
751
|
+
try {
|
|
752
|
+
if (!fs.existsSync(projectPath) || !fs.statSync(projectPath).isDirectory())
|
|
753
|
+
return false;
|
|
754
|
+
}
|
|
755
|
+
catch {
|
|
756
|
+
return false;
|
|
757
|
+
}
|
|
758
|
+
return !isPathWithin(path.join(SUPEN_HOME, 'tasks'), projectPath);
|
|
759
|
+
}
|
|
760
|
+
function isAutomationRunThreadTitle(title) {
|
|
761
|
+
if (!title)
|
|
762
|
+
return false;
|
|
763
|
+
return (/^Automation context file:\s*.+\/automations\/[^/\s]+\/runs\/[^/\s]+\.context\.json\s*$/m.test(title) &&
|
|
764
|
+
/^Automation id:\s*\S+\s*$/m.test(title) &&
|
|
765
|
+
/^Run id:\s*\S+\s*$/m.test(title));
|
|
766
|
+
}
|
|
767
|
+
function readMirroredTaskProjects(limit = MIRRORED_THREAD_LIMIT) {
|
|
768
|
+
const safeLimit = Math.max(1, Math.min(limit, 200));
|
|
769
|
+
const stateEntries = readThreadStateEntries(safeLimit);
|
|
770
|
+
const indexEntries = readThreadIndex(safeLimit);
|
|
771
|
+
const sessionFiles = walkSessionFiles(path.join(localAgentHome(), 'sessions'));
|
|
772
|
+
const projects = new Map();
|
|
773
|
+
if (stateEntries.length > 0) {
|
|
774
|
+
for (const entry of stateEntries) {
|
|
775
|
+
const sessionPath = sessionFiles.get(entry.id) || null;
|
|
776
|
+
if (!sessionPath)
|
|
777
|
+
continue;
|
|
778
|
+
const workspacePath = entry.cwd || readThreadWorkspacePath(sessionPath);
|
|
779
|
+
if (!isMirrorableCodexWorkspace(workspacePath))
|
|
780
|
+
continue;
|
|
781
|
+
const projectId = workspacePath || 'no-workspace';
|
|
782
|
+
const indexEntry = readThreadIndexEntry(entry.id);
|
|
783
|
+
const title = indexEntry?.thread_name || entry.title;
|
|
784
|
+
if (isAutomationRunThreadTitle(title))
|
|
785
|
+
continue;
|
|
786
|
+
const project = projects.get(projectId) || {
|
|
787
|
+
id: projectId,
|
|
788
|
+
name: projectNameFromPath(workspacePath),
|
|
789
|
+
path: workspacePath,
|
|
790
|
+
threads: [],
|
|
791
|
+
};
|
|
792
|
+
project.threads.push({
|
|
793
|
+
id: entry.id,
|
|
794
|
+
title,
|
|
795
|
+
updated_at: entry.updated_at,
|
|
796
|
+
status: readThreadStatus(sessionPath),
|
|
797
|
+
session_path: sessionPath,
|
|
798
|
+
});
|
|
799
|
+
projects.set(projectId, project);
|
|
800
|
+
}
|
|
801
|
+
return {
|
|
802
|
+
generated_at: new Date().toISOString(),
|
|
803
|
+
projects: Array.from(projects.values()).map((project) => ({
|
|
804
|
+
...project,
|
|
805
|
+
threads: project.threads.sort((a, b) => b.updated_at.localeCompare(a.updated_at)),
|
|
806
|
+
})),
|
|
807
|
+
};
|
|
808
|
+
}
|
|
809
|
+
for (const entry of indexEntries) {
|
|
810
|
+
const sessionPath = sessionFiles.get(entry.id) || null;
|
|
811
|
+
const workspacePath = readThreadWorkspacePath(sessionPath);
|
|
812
|
+
if (!isMirrorableCodexWorkspace(workspacePath))
|
|
813
|
+
continue;
|
|
814
|
+
if (isAutomationRunThreadTitle(entry.thread_name))
|
|
815
|
+
continue;
|
|
816
|
+
const projectId = workspacePath || 'no-workspace';
|
|
817
|
+
const project = projects.get(projectId) || {
|
|
818
|
+
id: projectId,
|
|
819
|
+
name: projectNameFromPath(workspacePath),
|
|
820
|
+
path: workspacePath,
|
|
821
|
+
threads: [],
|
|
822
|
+
};
|
|
823
|
+
project.threads.push({
|
|
824
|
+
id: entry.id,
|
|
825
|
+
title: entry.thread_name,
|
|
826
|
+
updated_at: entry.updated_at,
|
|
827
|
+
status: readThreadStatus(sessionPath),
|
|
828
|
+
session_path: sessionPath,
|
|
829
|
+
});
|
|
830
|
+
projects.set(projectId, project);
|
|
831
|
+
}
|
|
832
|
+
return {
|
|
833
|
+
generated_at: new Date().toISOString(),
|
|
834
|
+
projects: Array.from(projects.values()).map((project) => ({
|
|
835
|
+
...project,
|
|
836
|
+
threads: project.threads.sort((a, b) => b.updated_at.localeCompare(a.updated_at)),
|
|
837
|
+
})),
|
|
838
|
+
};
|
|
839
|
+
}
|
|
840
|
+
function sanitizeMirroredUserText(text) {
|
|
841
|
+
return text
|
|
842
|
+
.replace(/\[Image blocked: No description\]/g, '')
|
|
843
|
+
.replace(/(?:^|\n)\s*<\/?image>\s*(?=\n|$)/gi, '\n')
|
|
844
|
+
.replace(/\n{3,}/g, '\n\n')
|
|
845
|
+
.trim();
|
|
846
|
+
}
|
|
847
|
+
function truncateMirroredHistoryString(value, limit, label) {
|
|
848
|
+
if (value.length <= limit)
|
|
849
|
+
return value;
|
|
850
|
+
const omitted = value.length - limit;
|
|
851
|
+
return `${value.slice(0, limit)}\n\n[${label} truncated: ${omitted.toLocaleString()} more characters omitted]`;
|
|
852
|
+
}
|
|
853
|
+
function sanitizeMirroredHistoryPayload(value, depth = 0) {
|
|
854
|
+
if (typeof value === 'string') {
|
|
855
|
+
return truncateMirroredHistoryString(value, MIRRORED_THREAD_TOOL_STRING_LIMIT, 'Tool output');
|
|
856
|
+
}
|
|
857
|
+
if (value === null || typeof value !== 'object')
|
|
858
|
+
return value;
|
|
859
|
+
if (depth >= MIRRORED_THREAD_OBJECT_DEPTH_LIMIT) {
|
|
860
|
+
return Array.isArray(value) ? '[Nested array omitted]' : '[Nested object omitted]';
|
|
861
|
+
}
|
|
862
|
+
if (Array.isArray(value)) {
|
|
863
|
+
const visibleItems = value
|
|
864
|
+
.slice(0, MIRRORED_THREAD_OBJECT_ARRAY_LIMIT)
|
|
865
|
+
.map((item) => sanitizeMirroredHistoryPayload(item, depth + 1));
|
|
866
|
+
if (value.length > MIRRORED_THREAD_OBJECT_ARRAY_LIMIT) {
|
|
867
|
+
visibleItems.push(`[${value.length - MIRRORED_THREAD_OBJECT_ARRAY_LIMIT} more items omitted]`);
|
|
868
|
+
}
|
|
869
|
+
return visibleItems;
|
|
870
|
+
}
|
|
871
|
+
const result = {};
|
|
872
|
+
const entries = Object.entries(value);
|
|
873
|
+
for (const [key, entryValue] of entries.slice(0, MIRRORED_THREAD_OBJECT_KEY_LIMIT)) {
|
|
874
|
+
result[key] = sanitizeMirroredHistoryPayload(entryValue, depth + 1);
|
|
875
|
+
}
|
|
876
|
+
if (entries.length > MIRRORED_THREAD_OBJECT_KEY_LIMIT) {
|
|
877
|
+
result.__truncated_keys = `${entries.length - MIRRORED_THREAD_OBJECT_KEY_LIMIT} more keys omitted`;
|
|
878
|
+
}
|
|
879
|
+
return result;
|
|
880
|
+
}
|
|
881
|
+
function safeMirroredHistoryAttachmentUrl(url) {
|
|
882
|
+
const trimmed = url.trim();
|
|
883
|
+
if (trimmed.startsWith('data:') && trimmed.length > MIRRORED_THREAD_INLINE_DATA_URL_LIMIT) {
|
|
884
|
+
return '';
|
|
885
|
+
}
|
|
886
|
+
return trimmed;
|
|
887
|
+
}
|
|
888
|
+
function mimeTypeFromImageUrl(url) {
|
|
889
|
+
const match = url.match(/^data:([^;,]+)?/i);
|
|
890
|
+
return match?.[1] || 'image/png';
|
|
891
|
+
}
|
|
892
|
+
function imageExtensionFromMimeType(mimeType) {
|
|
893
|
+
if (mimeType === 'image/jpeg')
|
|
894
|
+
return 'jpg';
|
|
895
|
+
if (mimeType === 'image/webp')
|
|
896
|
+
return 'webp';
|
|
897
|
+
if (mimeType === 'image/gif')
|
|
898
|
+
return 'gif';
|
|
899
|
+
return 'png';
|
|
900
|
+
}
|
|
901
|
+
function imageAttachmentFromUrl(url, index) {
|
|
902
|
+
const trimmed = url.trim();
|
|
903
|
+
if (!trimmed)
|
|
904
|
+
return null;
|
|
905
|
+
const mimeType = mimeTypeFromImageUrl(trimmed);
|
|
906
|
+
const filename = `pasted-image-${index + 1}.${imageExtensionFromMimeType(mimeType)}`;
|
|
907
|
+
return {
|
|
908
|
+
name: filename,
|
|
909
|
+
filename,
|
|
910
|
+
url: safeMirroredHistoryAttachmentUrl(trimmed),
|
|
911
|
+
mimeType,
|
|
912
|
+
mime_type: mimeType,
|
|
913
|
+
};
|
|
914
|
+
}
|
|
915
|
+
function imageAttachmentsFromUnknown(value) {
|
|
916
|
+
if (!Array.isArray(value))
|
|
917
|
+
return [];
|
|
918
|
+
return value
|
|
919
|
+
.map((image, index) => (typeof image === 'string' ? imageAttachmentFromUrl(image, index) : null))
|
|
920
|
+
.filter((attachment) => Boolean(attachment));
|
|
921
|
+
}
|
|
922
|
+
function mergeMirroredAttachments(left = [], right = []) {
|
|
923
|
+
const seen = new Set();
|
|
924
|
+
const merged = [];
|
|
925
|
+
for (const attachment of [...left, ...right]) {
|
|
926
|
+
const key = attachment.url || attachment.filename;
|
|
927
|
+
if (!key || seen.has(key))
|
|
928
|
+
continue;
|
|
929
|
+
seen.add(key);
|
|
930
|
+
merged.push(attachment);
|
|
931
|
+
}
|
|
932
|
+
return merged;
|
|
933
|
+
}
|
|
934
|
+
function mirroredMessageContentParts(content) {
|
|
935
|
+
if (typeof content === 'string') {
|
|
936
|
+
return { text: sanitizeMirroredUserText(content), attachments: [] };
|
|
937
|
+
}
|
|
938
|
+
if (!Array.isArray(content))
|
|
939
|
+
return { text: '', attachments: [] };
|
|
940
|
+
const textParts = [];
|
|
941
|
+
const attachments = [];
|
|
942
|
+
for (const part of content) {
|
|
943
|
+
if (typeof part === 'string') {
|
|
944
|
+
textParts.push(part);
|
|
945
|
+
continue;
|
|
946
|
+
}
|
|
947
|
+
if (!part || typeof part !== 'object')
|
|
948
|
+
continue;
|
|
949
|
+
const record = part;
|
|
950
|
+
if (record.type === 'input_text' ||
|
|
951
|
+
record.type === 'output_text' ||
|
|
952
|
+
record.type === 'text') {
|
|
953
|
+
const text = typeof record.text === 'string' ? record.text : '';
|
|
954
|
+
if (text.trim().toLowerCase() !== '<image>' && text.trim().toLowerCase() !== '</image>') {
|
|
955
|
+
textParts.push(text);
|
|
956
|
+
}
|
|
957
|
+
continue;
|
|
958
|
+
}
|
|
959
|
+
if (record.type === 'input_image' && typeof record.image_url === 'string') {
|
|
960
|
+
const attachment = imageAttachmentFromUrl(record.image_url, attachments.length);
|
|
961
|
+
if (attachment)
|
|
962
|
+
attachments.push(attachment);
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
return {
|
|
966
|
+
text: sanitizeMirroredUserText(textParts.filter(Boolean).join('\n')),
|
|
967
|
+
attachments,
|
|
968
|
+
};
|
|
969
|
+
}
|
|
970
|
+
function textFromMirroredMessageContent(content) {
|
|
971
|
+
return mirroredMessageContentParts(content).text;
|
|
972
|
+
}
|
|
973
|
+
function isInternalMirroredUserMessage(text) {
|
|
974
|
+
const trimmed = text.trimStart();
|
|
975
|
+
return (trimmed.startsWith('<environment_context>') ||
|
|
976
|
+
trimmed.startsWith('<developer_context>') ||
|
|
977
|
+
trimmed.startsWith('[plugin:vite:'));
|
|
978
|
+
}
|
|
979
|
+
function isMirroredGoalContextMessage(text) {
|
|
980
|
+
const trimmed = text.trim();
|
|
981
|
+
return trimmed.startsWith('<goal_context>') && trimmed.endsWith('</goal_context>');
|
|
982
|
+
}
|
|
983
|
+
function parseJsonObject(value) {
|
|
984
|
+
if (typeof value !== 'string')
|
|
985
|
+
return value;
|
|
986
|
+
try {
|
|
987
|
+
return JSON.parse(value);
|
|
988
|
+
}
|
|
989
|
+
catch {
|
|
990
|
+
return value;
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
function readableOutputFromMcpResult(result) {
|
|
994
|
+
if (!result || typeof result !== 'object')
|
|
995
|
+
return result;
|
|
996
|
+
const ok = result.Ok;
|
|
997
|
+
const error = result.Err;
|
|
998
|
+
const payload = ok && typeof ok === 'object' ? ok : error;
|
|
999
|
+
if (!payload || typeof payload !== 'object')
|
|
1000
|
+
return result;
|
|
1001
|
+
const content = payload.content;
|
|
1002
|
+
if (!Array.isArray(content))
|
|
1003
|
+
return payload;
|
|
1004
|
+
const text = content
|
|
1005
|
+
.map((entry) => {
|
|
1006
|
+
if (typeof entry === 'string')
|
|
1007
|
+
return entry;
|
|
1008
|
+
if (entry && typeof entry === 'object' && typeof entry.text === 'string') {
|
|
1009
|
+
return entry.text;
|
|
1010
|
+
}
|
|
1011
|
+
return '';
|
|
1012
|
+
})
|
|
1013
|
+
.filter(Boolean)
|
|
1014
|
+
.join('\n');
|
|
1015
|
+
return text || payload;
|
|
1016
|
+
}
|
|
1017
|
+
function fileNameFromPath(value) {
|
|
1018
|
+
const trimmed = value.trim();
|
|
1019
|
+
if (!trimmed)
|
|
1020
|
+
return '';
|
|
1021
|
+
return path.basename(trimmed);
|
|
1022
|
+
}
|
|
1023
|
+
function displayPatchPath(filePath, workspacePath) {
|
|
1024
|
+
const trimmed = filePath.trim();
|
|
1025
|
+
if (!trimmed || !workspacePath || !path.isAbsolute(trimmed))
|
|
1026
|
+
return trimmed;
|
|
1027
|
+
const relativePath = path.relative(workspacePath, trimmed);
|
|
1028
|
+
if (!relativePath || relativePath.startsWith('..') || path.isAbsolute(relativePath))
|
|
1029
|
+
return trimmed;
|
|
1030
|
+
return relativePath;
|
|
1031
|
+
}
|
|
1032
|
+
function diffStatsFromText(value) {
|
|
1033
|
+
let additions = 0;
|
|
1034
|
+
let deletions = 0;
|
|
1035
|
+
for (const line of value.split(/\r?\n/)) {
|
|
1036
|
+
if (line.startsWith('+++') || line.startsWith('---'))
|
|
1037
|
+
continue;
|
|
1038
|
+
if (line.startsWith('+'))
|
|
1039
|
+
additions += 1;
|
|
1040
|
+
if (line.startsWith('-'))
|
|
1041
|
+
deletions += 1;
|
|
1042
|
+
}
|
|
1043
|
+
return { additions, deletions };
|
|
1044
|
+
}
|
|
1045
|
+
function extractPatchFiles(input) {
|
|
1046
|
+
if (typeof input !== 'string')
|
|
1047
|
+
return [];
|
|
1048
|
+
const files = [];
|
|
1049
|
+
const seen = new Set();
|
|
1050
|
+
for (const line of input.split(/\r?\n/)) {
|
|
1051
|
+
const match = line.match(/^\*\*\* (?:Update|Add|Delete) File:\s+(.+?)\s*$/);
|
|
1052
|
+
if (!match)
|
|
1053
|
+
continue;
|
|
1054
|
+
const filePath = match[1]?.trim();
|
|
1055
|
+
if (!filePath || seen.has(filePath))
|
|
1056
|
+
continue;
|
|
1057
|
+
seen.add(filePath);
|
|
1058
|
+
files.push({ path: filePath, name: fileNameFromPath(filePath) || filePath });
|
|
1059
|
+
}
|
|
1060
|
+
return files;
|
|
1061
|
+
}
|
|
1062
|
+
function extractPatchEndFiles(changes, workspacePath) {
|
|
1063
|
+
if (!changes || typeof changes !== 'object')
|
|
1064
|
+
return [];
|
|
1065
|
+
return Object.entries(changes).map(([filePath, change]) => {
|
|
1066
|
+
const diff = typeof change?.unified_diff === 'string' ? change.unified_diff : '';
|
|
1067
|
+
const displayPath = displayPatchPath(filePath, workspacePath);
|
|
1068
|
+
return {
|
|
1069
|
+
path: displayPath,
|
|
1070
|
+
name: fileNameFromPath(displayPath) || displayPath,
|
|
1071
|
+
...(diff ? { diff } : {}),
|
|
1072
|
+
...diffStatsFromText(diff),
|
|
1073
|
+
};
|
|
1074
|
+
});
|
|
1075
|
+
}
|
|
1076
|
+
function codexReplayEvent(eventId, timestamp, taskId, raw) {
|
|
1077
|
+
return {
|
|
1078
|
+
id: eventId,
|
|
1079
|
+
timestamp,
|
|
1080
|
+
task_id: taskId,
|
|
1081
|
+
chunk: {
|
|
1082
|
+
type: 'data-supen-event',
|
|
1083
|
+
id: eventId,
|
|
1084
|
+
data: { raw },
|
|
1085
|
+
},
|
|
1086
|
+
};
|
|
1087
|
+
}
|
|
1088
|
+
function mirroredThreadWorkspacePath(sessionPath, stateEntry) {
|
|
1089
|
+
const stateCwd = typeof stateEntry?.cwd === 'string' ? stateEntry.cwd : null;
|
|
1090
|
+
const sessionWorkspace = readThreadWorkspacePath(sessionPath);
|
|
1091
|
+
for (const candidate of [stateCwd, sessionWorkspace]) {
|
|
1092
|
+
if (candidate && isMirrorableCodexWorkspace(candidate))
|
|
1093
|
+
return candidate;
|
|
1094
|
+
}
|
|
1095
|
+
return null;
|
|
1096
|
+
}
|
|
1097
|
+
export function readCodexThreadHistory(threadId, limit = MIRRORED_THREAD_HISTORY_LIMIT, options = {}) {
|
|
1098
|
+
const safeLimit = Math.max(1, Math.min(limit, 500));
|
|
1099
|
+
const sessionPath = walkSessionFiles(path.join(localAgentHome(), 'sessions')).get(threadId);
|
|
1100
|
+
if (!sessionPath)
|
|
1101
|
+
return null;
|
|
1102
|
+
const eventLogThreadId = options.eventLogThreadId || threadId;
|
|
1103
|
+
const stateEntry = readThreadStateEntry(threadId);
|
|
1104
|
+
const workspacePath = mirroredThreadWorkspacePath(sessionPath, stateEntry);
|
|
1105
|
+
const indexEntry = readThreadIndexEntry(threadId);
|
|
1106
|
+
const eventLogHead = readThreadEventLogHead(eventLogThreadId);
|
|
1107
|
+
const lines = readRecentCodexHistoryLines(sessionPath, safeLimit + 1);
|
|
1108
|
+
const messages = [];
|
|
1109
|
+
const events = [];
|
|
1110
|
+
let messageIndex = 0;
|
|
1111
|
+
let eventIndex = 0;
|
|
1112
|
+
let latestTimestamp = indexEntry?.updated_at || '';
|
|
1113
|
+
let recentGoalContext = null;
|
|
1114
|
+
const toolCalls = new Map();
|
|
1115
|
+
const mirroredUserMessageKey = (text) => sanitizeMirroredUserText(text)
|
|
1116
|
+
.replace(/\s+/g, ' ')
|
|
1117
|
+
.trim();
|
|
1118
|
+
const pushUserMessage = (text, timestamp, messageId, attachments = []) => {
|
|
1119
|
+
text = sanitizeMirroredUserText(text);
|
|
1120
|
+
if (isMirroredGoalContextMessage(text)) {
|
|
1121
|
+
recentGoalContext = { text, timestamp };
|
|
1122
|
+
}
|
|
1123
|
+
text = truncateMirroredHistoryString(text, MIRRORED_THREAD_TEXT_LIMIT, 'Message');
|
|
1124
|
+
if ((!text && attachments.length === 0) || isInternalMirroredUserMessage(text))
|
|
1125
|
+
return;
|
|
1126
|
+
const key = mirroredUserMessageKey(text);
|
|
1127
|
+
const timestampMs = Date.parse(timestamp);
|
|
1128
|
+
const duplicateIndex = messages.findIndex((message) => {
|
|
1129
|
+
if (message.role !== 'user')
|
|
1130
|
+
return false;
|
|
1131
|
+
if (mirroredUserMessageKey(message.content) !== key)
|
|
1132
|
+
return false;
|
|
1133
|
+
const existingTimestampMs = Date.parse(message.timestamp);
|
|
1134
|
+
if (!Number.isFinite(timestampMs) || !Number.isFinite(existingTimestampMs)) {
|
|
1135
|
+
return message.timestamp === timestamp;
|
|
1136
|
+
}
|
|
1137
|
+
return Math.abs(existingTimestampMs - timestampMs) <= 15_000;
|
|
1138
|
+
});
|
|
1139
|
+
if (duplicateIndex >= 0) {
|
|
1140
|
+
if (text.length > messages[duplicateIndex].content.length) {
|
|
1141
|
+
messages[duplicateIndex].content = text;
|
|
1142
|
+
}
|
|
1143
|
+
const mergedAttachments = mergeMirroredAttachments(messages[duplicateIndex].attachments, attachments);
|
|
1144
|
+
if (mergedAttachments.length > 0) {
|
|
1145
|
+
messages[duplicateIndex].attachments = mergedAttachments;
|
|
1146
|
+
}
|
|
1147
|
+
return;
|
|
1148
|
+
}
|
|
1149
|
+
const id = messageId || `${threadId}-message-${messageIndex + 1}`;
|
|
1150
|
+
if (!messageId)
|
|
1151
|
+
messageIndex += 1;
|
|
1152
|
+
messages.push({
|
|
1153
|
+
id,
|
|
1154
|
+
role: 'user',
|
|
1155
|
+
content: text,
|
|
1156
|
+
timestamp,
|
|
1157
|
+
task_id: threadId,
|
|
1158
|
+
...(attachments.length > 0 ? { attachments } : {}),
|
|
1159
|
+
});
|
|
1160
|
+
};
|
|
1161
|
+
const pushEvent = (timestamp, chunk) => {
|
|
1162
|
+
eventIndex += 1;
|
|
1163
|
+
events.push({
|
|
1164
|
+
id: `${threadId}-event-${eventIndex}`,
|
|
1165
|
+
timestamp,
|
|
1166
|
+
task_id: threadId,
|
|
1167
|
+
chunk,
|
|
1168
|
+
});
|
|
1169
|
+
};
|
|
1170
|
+
const pushToolInput = (timestamp, toolCallId, toolName, input) => {
|
|
1171
|
+
const sanitizedInput = sanitizeMirroredHistoryPayload(input);
|
|
1172
|
+
toolCalls.set(toolCallId, { toolName, input: sanitizedInput });
|
|
1173
|
+
pushEvent(timestamp, {
|
|
1174
|
+
type: 'tool-input-available',
|
|
1175
|
+
toolCallId,
|
|
1176
|
+
toolName,
|
|
1177
|
+
input: sanitizedInput,
|
|
1178
|
+
});
|
|
1179
|
+
};
|
|
1180
|
+
const pushToolOutput = (timestamp, toolCallId, output, fallbackToolName, fallbackInput, failed = false) => {
|
|
1181
|
+
const toolCall = toolCalls.get(toolCallId);
|
|
1182
|
+
const toolName = toolCall?.toolName || fallbackToolName;
|
|
1183
|
+
const input = toolCall?.input ?? (fallbackInput === undefined ? undefined : sanitizeMirroredHistoryPayload(fallbackInput));
|
|
1184
|
+
pushEvent(timestamp, {
|
|
1185
|
+
type: failed ? 'tool-output-error' : 'tool-output-available',
|
|
1186
|
+
toolCallId,
|
|
1187
|
+
...(toolName ? { toolName } : {}),
|
|
1188
|
+
...(input !== undefined ? { input } : {}),
|
|
1189
|
+
...(failed
|
|
1190
|
+
? { errorText: typeof output === 'string' ? output : 'Tool failed' }
|
|
1191
|
+
: { output: sanitizeMirroredHistoryPayload(parseJsonObject(output)) }),
|
|
1192
|
+
});
|
|
1193
|
+
};
|
|
1194
|
+
for (const line of lines) {
|
|
1195
|
+
const parsed = parseJsonLine(line);
|
|
1196
|
+
if (typeof parsed?.timestamp === 'string')
|
|
1197
|
+
latestTimestamp = parsed.timestamp;
|
|
1198
|
+
const timestamp = typeof parsed?.timestamp === 'string'
|
|
1199
|
+
? parsed.timestamp
|
|
1200
|
+
: latestTimestamp || new Date().toISOString();
|
|
1201
|
+
const payload = parsed?.payload && typeof parsed.payload === 'object'
|
|
1202
|
+
? parsed.payload
|
|
1203
|
+
: {};
|
|
1204
|
+
if (parsed?.type === 'event_msg') {
|
|
1205
|
+
if (payload.type === 'user_message') {
|
|
1206
|
+
const text = typeof payload.message === 'string' ? payload.message.trim() : '';
|
|
1207
|
+
pushUserMessage(text, timestamp, undefined, imageAttachmentsFromUnknown(payload.images));
|
|
1208
|
+
continue;
|
|
1209
|
+
}
|
|
1210
|
+
if (payload.type === 'turn_aborted') {
|
|
1211
|
+
if (eventLogThreadId !== threadId) {
|
|
1212
|
+
events.push(codexReplayEvent(`${threadId}-turn-aborted-${eventIndex + 1}`, timestamp, threadId, {
|
|
1213
|
+
method: 'turn/completed',
|
|
1214
|
+
params: {
|
|
1215
|
+
turn: {
|
|
1216
|
+
id: payload.turn_id || payload.turnId || 'turn',
|
|
1217
|
+
status: 'interrupted',
|
|
1218
|
+
},
|
|
1219
|
+
},
|
|
1220
|
+
}));
|
|
1221
|
+
eventIndex += 1;
|
|
1222
|
+
}
|
|
1223
|
+
continue;
|
|
1224
|
+
}
|
|
1225
|
+
if (payload.type === 'context_compacted') {
|
|
1226
|
+
events.push(codexReplayEvent(`${threadId}-context-compacted-${eventIndex + 1}`, timestamp, threadId, {
|
|
1227
|
+
method: 'contextCompaction/completed',
|
|
1228
|
+
params: {
|
|
1229
|
+
threadId,
|
|
1230
|
+
messageCount: payload.message_count || payload.messageCount,
|
|
1231
|
+
tokensSaved: payload.tokens_saved || payload.tokensSaved,
|
|
1232
|
+
},
|
|
1233
|
+
}));
|
|
1234
|
+
eventIndex += 1;
|
|
1235
|
+
continue;
|
|
1236
|
+
}
|
|
1237
|
+
if (payload.type === 'web_search_end') {
|
|
1238
|
+
const toolCallId = typeof payload.call_id === 'string' ? payload.call_id : `${threadId}-web-search-${eventIndex + 1}`;
|
|
1239
|
+
const input = {
|
|
1240
|
+
query: payload.query,
|
|
1241
|
+
action: payload.action,
|
|
1242
|
+
};
|
|
1243
|
+
pushToolInput(timestamp, toolCallId, 'web_search', input);
|
|
1244
|
+
pushToolOutput(timestamp, toolCallId, payload.action || payload.query || 'completed');
|
|
1245
|
+
continue;
|
|
1246
|
+
}
|
|
1247
|
+
if (payload.type === 'mcp_tool_call_end') {
|
|
1248
|
+
const toolCallId = typeof payload.call_id === 'string' ? payload.call_id : `${threadId}-mcp-tool-${eventIndex + 1}`;
|
|
1249
|
+
const invocation = payload.invocation && typeof payload.invocation === 'object'
|
|
1250
|
+
? payload.invocation
|
|
1251
|
+
: {};
|
|
1252
|
+
const server = typeof invocation.server === 'string' ? invocation.server : '';
|
|
1253
|
+
const tool = typeof invocation.tool === 'string' ? invocation.tool : '';
|
|
1254
|
+
const toolName = [server, tool].filter(Boolean).join('.') || 'mcpToolCall';
|
|
1255
|
+
const input = invocation.arguments;
|
|
1256
|
+
const result = payload.result && typeof payload.result === 'object'
|
|
1257
|
+
? payload.result
|
|
1258
|
+
: payload.result;
|
|
1259
|
+
const failed = Boolean(result && typeof result === 'object' && 'Err' in result);
|
|
1260
|
+
pushToolInput(timestamp, toolCallId, toolName, input);
|
|
1261
|
+
pushToolOutput(timestamp, toolCallId, readableOutputFromMcpResult(result), toolName, input, failed);
|
|
1262
|
+
continue;
|
|
1263
|
+
}
|
|
1264
|
+
if (payload.type === 'patch_apply_end') {
|
|
1265
|
+
const files = extractPatchEndFiles(payload.changes, workspacePath);
|
|
1266
|
+
const additions = files.reduce((sum, file) => sum + file.additions, 0);
|
|
1267
|
+
const deletions = files.reduce((sum, file) => sum + file.deletions, 0);
|
|
1268
|
+
events.push(codexReplayEvent(`${threadId}-patch-end-${eventIndex + 1}`, timestamp, threadId, {
|
|
1269
|
+
method: 'codex/patchApply/completed',
|
|
1270
|
+
params: {
|
|
1271
|
+
callId: payload.call_id,
|
|
1272
|
+
success: payload.success !== false,
|
|
1273
|
+
files,
|
|
1274
|
+
fileCount: files.length,
|
|
1275
|
+
additions,
|
|
1276
|
+
deletions,
|
|
1277
|
+
status: payload.status,
|
|
1278
|
+
},
|
|
1279
|
+
}));
|
|
1280
|
+
eventIndex += 1;
|
|
1281
|
+
continue;
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
if (parsed?.type === 'compacted') {
|
|
1285
|
+
events.push(codexReplayEvent(`${threadId}-context-compacted-${eventIndex + 1}`, timestamp, threadId, {
|
|
1286
|
+
method: 'contextCompaction/completed',
|
|
1287
|
+
params: { threadId },
|
|
1288
|
+
}));
|
|
1289
|
+
eventIndex += 1;
|
|
1290
|
+
continue;
|
|
1291
|
+
}
|
|
1292
|
+
if (parsed?.type !== 'response_item')
|
|
1293
|
+
continue;
|
|
1294
|
+
if (payload.type === 'function_call') {
|
|
1295
|
+
const toolCallId = typeof payload.call_id === 'string' ? payload.call_id : `${threadId}-tool-${eventIndex + 1}`;
|
|
1296
|
+
const toolName = typeof payload.name === 'string' ? payload.name : 'tool';
|
|
1297
|
+
const input = sanitizeMirroredHistoryPayload(parseJsonObject(payload.arguments));
|
|
1298
|
+
pushToolInput(timestamp, toolCallId, toolName, input);
|
|
1299
|
+
continue;
|
|
1300
|
+
}
|
|
1301
|
+
if (payload.type === 'function_call_output') {
|
|
1302
|
+
const toolCallId = typeof payload.call_id === 'string' ? payload.call_id : `${threadId}-tool-${eventIndex + 1}`;
|
|
1303
|
+
pushToolOutput(timestamp, toolCallId, payload.output);
|
|
1304
|
+
continue;
|
|
1305
|
+
}
|
|
1306
|
+
if (payload.type === 'tool_search_call') {
|
|
1307
|
+
const toolCallId = typeof payload.call_id === 'string' ? payload.call_id : `${threadId}-tool-search-${eventIndex + 1}`;
|
|
1308
|
+
pushToolInput(timestamp, toolCallId, 'tool_search', payload.arguments);
|
|
1309
|
+
continue;
|
|
1310
|
+
}
|
|
1311
|
+
if (payload.type === 'tool_search_output') {
|
|
1312
|
+
const toolCallId = typeof payload.call_id === 'string' ? payload.call_id : `${threadId}-tool-search-${eventIndex + 1}`;
|
|
1313
|
+
pushToolOutput(timestamp, toolCallId, {
|
|
1314
|
+
status: payload.status,
|
|
1315
|
+
tools: payload.tools,
|
|
1316
|
+
}, 'tool_search');
|
|
1317
|
+
continue;
|
|
1318
|
+
}
|
|
1319
|
+
if (payload.type === 'custom_tool_call') {
|
|
1320
|
+
const toolName = typeof payload.name === 'string' ? payload.name : 'custom_tool';
|
|
1321
|
+
const toolCallId = typeof payload.call_id === 'string' ? payload.call_id : `${threadId}-tool-${eventIndex + 1}`;
|
|
1322
|
+
if (toolName === 'apply_patch') {
|
|
1323
|
+
const input = typeof payload.input === 'string' ? payload.input : '';
|
|
1324
|
+
const files = extractPatchFiles(input);
|
|
1325
|
+
events.push(codexReplayEvent(`${threadId}-patch-start-${eventIndex + 1}`, timestamp, threadId, {
|
|
1326
|
+
method: 'codex/patchApply/started',
|
|
1327
|
+
params: {
|
|
1328
|
+
callId: payload.call_id,
|
|
1329
|
+
files,
|
|
1330
|
+
fileCount: files.length,
|
|
1331
|
+
...diffStatsFromText(input),
|
|
1332
|
+
},
|
|
1333
|
+
}));
|
|
1334
|
+
eventIndex += 1;
|
|
1335
|
+
}
|
|
1336
|
+
else {
|
|
1337
|
+
pushToolInput(timestamp, toolCallId, toolName, parseJsonObject(payload.input));
|
|
1338
|
+
}
|
|
1339
|
+
continue;
|
|
1340
|
+
}
|
|
1341
|
+
if (payload.type === 'custom_tool_call_output') {
|
|
1342
|
+
const toolCallId = typeof payload.call_id === 'string' ? payload.call_id : `${threadId}-tool-${eventIndex + 1}`;
|
|
1343
|
+
if (toolCalls.has(toolCallId)) {
|
|
1344
|
+
pushToolOutput(timestamp, toolCallId, payload.output);
|
|
1345
|
+
}
|
|
1346
|
+
continue;
|
|
1347
|
+
}
|
|
1348
|
+
if (payload.type !== 'message')
|
|
1349
|
+
continue;
|
|
1350
|
+
messageIndex += 1;
|
|
1351
|
+
const messageId = `${threadId}-message-${messageIndex}`;
|
|
1352
|
+
const role = payload.role;
|
|
1353
|
+
if (role !== 'user' && role !== 'assistant' && role !== 'system')
|
|
1354
|
+
continue;
|
|
1355
|
+
const contentParts = mirroredMessageContentParts(payload.content);
|
|
1356
|
+
const text = contentParts.text;
|
|
1357
|
+
if (!text && contentParts.attachments.length === 0)
|
|
1358
|
+
continue;
|
|
1359
|
+
if (role === 'user') {
|
|
1360
|
+
pushUserMessage(text, timestamp, messageId, contentParts.attachments);
|
|
1361
|
+
continue;
|
|
1362
|
+
}
|
|
1363
|
+
messages.push({
|
|
1364
|
+
id: messageId,
|
|
1365
|
+
role,
|
|
1366
|
+
content: truncateMirroredHistoryString(text, MIRRORED_THREAD_TEXT_LIMIT, 'Message'),
|
|
1367
|
+
timestamp,
|
|
1368
|
+
task_id: threadId,
|
|
1369
|
+
});
|
|
1370
|
+
}
|
|
1371
|
+
for (const event of listRecentThreadEventsAfter(eventLogThreadId, 0, {
|
|
1372
|
+
limit: MIRRORED_THREAD_STREAM_REPLAY_LIMIT,
|
|
1373
|
+
maxBytes: MIRRORED_THREAD_STREAM_REPLAY_MAX_BYTES,
|
|
1374
|
+
})) {
|
|
1375
|
+
const chunk = threadStreamChunkForEvent(event);
|
|
1376
|
+
if (!chunk)
|
|
1377
|
+
continue;
|
|
1378
|
+
events.push({
|
|
1379
|
+
id: `${threadId}-event-log-${event.sequence}`,
|
|
1380
|
+
timestamp: event.received_at,
|
|
1381
|
+
task_id: threadId,
|
|
1382
|
+
chunk,
|
|
1383
|
+
});
|
|
1384
|
+
}
|
|
1385
|
+
const slicedMessages = messages.slice(-safeLimit);
|
|
1386
|
+
const slicedEvents = events.slice(-safeLimit * 8);
|
|
1387
|
+
const status = readThreadStatus(sessionPath);
|
|
1388
|
+
const latestGoalContext = recentGoalContext;
|
|
1389
|
+
return {
|
|
1390
|
+
session: {
|
|
1391
|
+
task_id: threadId,
|
|
1392
|
+
agent_id: 'local-agent-mirror',
|
|
1393
|
+
channel: 'local-agent',
|
|
1394
|
+
source_ref: `local-agent:${threadId}`,
|
|
1395
|
+
title: indexEntry?.thread_name || stateEntry?.title || slicedMessages.find((message) => message.role === 'user')?.content || 'New Task',
|
|
1396
|
+
status,
|
|
1397
|
+
created_at: slicedMessages[0]?.timestamp || latestTimestamp || '',
|
|
1398
|
+
updated_at: latestTimestamp || slicedMessages.at(-1)?.timestamp || '',
|
|
1399
|
+
task_workspace_folder: workspacePath,
|
|
1400
|
+
},
|
|
1401
|
+
messages: slicedMessages,
|
|
1402
|
+
events: slicedEvents,
|
|
1403
|
+
interaction_events: [],
|
|
1404
|
+
control_log: [],
|
|
1405
|
+
items: [],
|
|
1406
|
+
runtimeState: {
|
|
1407
|
+
event_log_head: eventLogHead,
|
|
1408
|
+
projection_version: 1,
|
|
1409
|
+
event_log: { status: 'current' },
|
|
1410
|
+
runtime: {
|
|
1411
|
+
status: status === 'running' ? 'running' : 'idle',
|
|
1412
|
+
updated_at: latestTimestamp || undefined,
|
|
1413
|
+
},
|
|
1414
|
+
},
|
|
1415
|
+
has_more: messages.length > safeLimit,
|
|
1416
|
+
...(latestGoalContext ? { goal_context: latestGoalContext } : {}),
|
|
1417
|
+
};
|
|
1418
|
+
}
|
|
1419
|
+
function serializeAdoptedMirroredThread(session) {
|
|
1420
|
+
return {
|
|
1421
|
+
id: session.session_id,
|
|
1422
|
+
taskId: session.session_id,
|
|
1423
|
+
agentId: session.agent_id,
|
|
1424
|
+
status: session.status || 'idle',
|
|
1425
|
+
title: session.title,
|
|
1426
|
+
createdAt: session.created_at,
|
|
1427
|
+
updatedAt: session.updated_at,
|
|
1428
|
+
supen: {
|
|
1429
|
+
channel: session.channel,
|
|
1430
|
+
sourceRef: session.source_ref,
|
|
1431
|
+
taskWorkspaceFolder: session.task_workspace_folder,
|
|
1432
|
+
},
|
|
1433
|
+
};
|
|
1434
|
+
}
|
|
1435
|
+
function adoptMirroredThread(threadId, agentId) {
|
|
1436
|
+
const history = readCodexThreadHistory(threadId, 1);
|
|
1437
|
+
if (!history)
|
|
1438
|
+
return null;
|
|
1439
|
+
const historySession = history.session;
|
|
1440
|
+
const session = ensureSession({
|
|
1441
|
+
agent_id: agentId,
|
|
1442
|
+
session_id: threadId,
|
|
1443
|
+
channel: 'http',
|
|
1444
|
+
agent_name: agentId,
|
|
1445
|
+
source_ref: `local-agent:${threadId}`,
|
|
1446
|
+
title: typeof historySession.title === 'string' ? historySession.title : undefined,
|
|
1447
|
+
status: historySession.status === 'running' ? 'running' : 'idle',
|
|
1448
|
+
backend_driver_id: 'codex-app-server',
|
|
1449
|
+
task_workspace_folder: typeof historySession.task_workspace_folder === 'string'
|
|
1450
|
+
? historySession.task_workspace_folder
|
|
1451
|
+
: null,
|
|
1452
|
+
});
|
|
1453
|
+
updateSessionSdkId(agentId, threadId, threadId);
|
|
1454
|
+
updateSessionBackendDriverId(agentId, threadId, 'codex-app-server');
|
|
1455
|
+
return session;
|
|
1456
|
+
}
|
|
1457
|
+
function coerceSingleQueryParam(value) {
|
|
1458
|
+
if (Array.isArray(value))
|
|
1459
|
+
return value[0] || null;
|
|
1460
|
+
return value || null;
|
|
1461
|
+
}
|
|
1462
|
+
function collectSpaceLogEntries(filters = {}) {
|
|
1463
|
+
const spaceId = currentSpaceId();
|
|
1464
|
+
const limit = Math.max(1, Math.min(filters.limit || SPACE_LOG_EVENT_LIMIT, 500));
|
|
1465
|
+
const agents = getAllAgents()
|
|
1466
|
+
// Single-Computer runtime: include all agents in this mounted volume.
|
|
1467
|
+
.sort((a, b) => (a.name || a.agent_id).localeCompare(b.name || b.agent_id));
|
|
1468
|
+
const sessions = agents.flatMap((agent) => getSessionsForAgent(agent.agent_id)
|
|
1469
|
+
.map((session) => ({
|
|
1470
|
+
agent_id: agent.agent_id,
|
|
1471
|
+
agent_name: agent.name || null,
|
|
1472
|
+
session_id: session.session_id,
|
|
1473
|
+
status: session.status,
|
|
1474
|
+
updated_at: session.updated_at,
|
|
1475
|
+
title: session.title || null,
|
|
1476
|
+
})));
|
|
1477
|
+
const filteredSessions = sessions.filter((session) => {
|
|
1478
|
+
if (filters.agentId && session.agent_id !== filters.agentId)
|
|
1479
|
+
return false;
|
|
1480
|
+
if (filters.sessionId && session.session_id !== filters.sessionId)
|
|
1481
|
+
return false;
|
|
1482
|
+
return true;
|
|
1483
|
+
});
|
|
1484
|
+
const entries = filteredSessions
|
|
1485
|
+
.flatMap((session) => getSessionUiEvents(session.agent_id, session.session_id, limit).map((event) => {
|
|
1486
|
+
const logSummary = event.chunk && typeof event.chunk === 'object'
|
|
1487
|
+
? summarizeUiChunk(event.chunk)
|
|
1488
|
+
: null;
|
|
1489
|
+
return logSummary
|
|
1490
|
+
? {
|
|
1491
|
+
id: event.id,
|
|
1492
|
+
timestamp: event.timestamp,
|
|
1493
|
+
agent_id: session.agent_id,
|
|
1494
|
+
agent_name: session.agent_name,
|
|
1495
|
+
session_id: session.session_id,
|
|
1496
|
+
session_title: session.title,
|
|
1497
|
+
task_id: event.task_id || null,
|
|
1498
|
+
category: logSummary.category,
|
|
1499
|
+
summary: logSummary.summary,
|
|
1500
|
+
}
|
|
1501
|
+
: null;
|
|
1502
|
+
}))
|
|
1503
|
+
.filter((entry) => Boolean(entry))
|
|
1504
|
+
.sort((a, b) => a.timestamp.localeCompare(b.timestamp))
|
|
1505
|
+
.slice(-limit);
|
|
1506
|
+
return {
|
|
1507
|
+
space_id: spaceId,
|
|
1508
|
+
generated_at: new Date().toISOString(),
|
|
1509
|
+
timezone: resolveHostTimeZone(),
|
|
1510
|
+
agents,
|
|
1511
|
+
sessions,
|
|
1512
|
+
entries,
|
|
1513
|
+
filters: {
|
|
1514
|
+
agent_id: filters.agentId || null,
|
|
1515
|
+
session_id: filters.sessionId || null,
|
|
1516
|
+
},
|
|
1517
|
+
};
|
|
1518
|
+
}
|
|
1519
|
+
function writeSse(res, payload) {
|
|
1520
|
+
if (!payload) {
|
|
1521
|
+
res.write(': ping\n\n');
|
|
1522
|
+
return;
|
|
1523
|
+
}
|
|
1524
|
+
res.write(`data: ${JSON.stringify(payload)}\n\n`);
|
|
1525
|
+
}
|
|
1526
|
+
function numericQueryParam(value) {
|
|
1527
|
+
if (!value)
|
|
1528
|
+
return null;
|
|
1529
|
+
const parsed = Number.parseInt(value, 10);
|
|
1530
|
+
return Number.isFinite(parsed) && parsed >= 0 ? parsed : null;
|
|
1531
|
+
}
|
|
1532
|
+
function lastEventIdFromRequest(req) {
|
|
1533
|
+
const raw = req.headers?.['last-event-id'];
|
|
1534
|
+
const value = Array.isArray(raw) ? raw[0] : raw;
|
|
1535
|
+
return typeof value === 'string' ? numericQueryParam(value) : null;
|
|
1536
|
+
}
|
|
1537
|
+
function threadStreamChunkForEvent(event) {
|
|
1538
|
+
if (event.source !== 'codex-app-server')
|
|
1539
|
+
return null;
|
|
1540
|
+
if (event.raw_payload &&
|
|
1541
|
+
typeof event.raw_payload === 'object' &&
|
|
1542
|
+
!Array.isArray(event.raw_payload)) {
|
|
1543
|
+
return event.raw_payload;
|
|
1544
|
+
}
|
|
1545
|
+
return {
|
|
1546
|
+
type: 'data-supen-event',
|
|
1547
|
+
data: {
|
|
1548
|
+
eventType: event.event_type,
|
|
1549
|
+
raw: event.raw_payload,
|
|
1550
|
+
},
|
|
1551
|
+
};
|
|
1552
|
+
}
|
|
1553
|
+
function buildMcpSettingsResponse() {
|
|
1554
|
+
const overrides = getMcpEnvOverrides();
|
|
1555
|
+
const runtimeEnv = { ...process.env };
|
|
1556
|
+
const spaceEnv = readSpaceEnvMap();
|
|
1557
|
+
for (const [key, entry] of Object.entries(spaceEnv)) {
|
|
1558
|
+
if (entry.scope !== 'runtime')
|
|
1559
|
+
continue;
|
|
1560
|
+
runtimeEnv[key] = entry.value;
|
|
1561
|
+
}
|
|
1562
|
+
const servers = listMcpServers(runtimeEnv, overrides);
|
|
1563
|
+
const envStatus = listMcpEnvKeys().map((key) => {
|
|
1564
|
+
const overrideValue = overrides[key];
|
|
1565
|
+
const processValue = runtimeEnv[key];
|
|
1566
|
+
const effective = (overrideValue && overrideValue.trim().length > 0)
|
|
1567
|
+
? overrideValue
|
|
1568
|
+
: processValue;
|
|
1569
|
+
return {
|
|
1570
|
+
key,
|
|
1571
|
+
hasValue: !!effective && effective.trim().length > 0,
|
|
1572
|
+
source: overrideValue && overrideValue.trim().length > 0
|
|
1573
|
+
? 'settings'
|
|
1574
|
+
: (processValue && processValue.trim().length > 0 ? 'process' : null),
|
|
1575
|
+
};
|
|
1576
|
+
});
|
|
1577
|
+
return { servers, env: envStatus };
|
|
1578
|
+
}
|
|
1579
|
+
const CODING_CLI_INSTALL_COMMANDS = {
|
|
1580
|
+
codex: { command: 'npm', args: ['install', '-g', '@openai/codex'] },
|
|
1581
|
+
gemini: { command: 'npm', args: ['install', '-g', '@google/gemini-cli'] },
|
|
1582
|
+
};
|
|
1583
|
+
const MANAGED_CODING_CLI_INSTALL_COMMANDS = {
|
|
1584
|
+
codex: CODING_CLI_INSTALL_COMMANDS.codex,
|
|
1585
|
+
gemini: CODING_CLI_INSTALL_COMMANDS.gemini,
|
|
1586
|
+
};
|
|
1587
|
+
const CODING_CLI_NAMES = ['codex', 'gemini', 'claude'];
|
|
1588
|
+
const DETECTABLE_SPACE_APPS = [
|
|
1589
|
+
{ id: 'codex', command: 'codex', managed: true },
|
|
1590
|
+
{ id: 'gemini', command: 'gemini', managed: true },
|
|
1591
|
+
{ id: 'claude', command: 'claude', managed: false },
|
|
1592
|
+
{ id: 'acpx', command: 'acpx', managed: false },
|
|
1593
|
+
{ id: 'libreoffice', command: 'libreoffice', managed: false },
|
|
1594
|
+
{ id: 'dotnet', command: 'dotnet', managed: false },
|
|
1595
|
+
{ id: 'tiwater-docx', command: 'tiwater-docx', managed: false },
|
|
1596
|
+
{ id: 'node', command: 'node', managed: false },
|
|
1597
|
+
{ id: 'pnpm', command: 'pnpm', managed: false },
|
|
1598
|
+
{ id: 'cursor', command: 'cursor-agent', managed: false },
|
|
1599
|
+
{ id: 'gh', command: 'gh', managed: false },
|
|
1600
|
+
{ id: 'git', command: 'git', managed: false },
|
|
1601
|
+
];
|
|
1602
|
+
const codexConnectRuntime = {
|
|
1603
|
+
state: 'idle',
|
|
1604
|
+
updatedAt: null,
|
|
1605
|
+
logs: [],
|
|
1606
|
+
error: null,
|
|
1607
|
+
child: null,
|
|
1608
|
+
};
|
|
1609
|
+
function nowIso() {
|
|
1610
|
+
return new Date().toISOString();
|
|
1611
|
+
}
|
|
1612
|
+
function normalizeCliName(raw) {
|
|
1613
|
+
const value = typeof raw === 'string' ? raw.trim().toLowerCase() : '';
|
|
1614
|
+
if (value === 'codex' || value === 'gemini' || value === 'claude')
|
|
1615
|
+
return value;
|
|
1616
|
+
return null;
|
|
1617
|
+
}
|
|
1618
|
+
function normalizeCodexTransport(raw) {
|
|
1619
|
+
const value = typeof raw === 'string' ? raw.trim().toLowerCase() : '';
|
|
1620
|
+
if (value === 'app-server' || value === 'exec' || value === 'acpx')
|
|
1621
|
+
return value;
|
|
1622
|
+
return null;
|
|
1623
|
+
}
|
|
1624
|
+
function isManagedCliName(name) {
|
|
1625
|
+
return name === 'codex' || name === 'gemini';
|
|
1626
|
+
}
|
|
1627
|
+
function trimLogNoise(value) {
|
|
1628
|
+
const line = value.trim();
|
|
1629
|
+
if (!line)
|
|
1630
|
+
return '';
|
|
1631
|
+
if (line.startsWith('WARNING: proceeding, even though we could not update PATH'))
|
|
1632
|
+
return '';
|
|
1633
|
+
return line;
|
|
1634
|
+
}
|
|
1635
|
+
function pushCodexConnectLog(chunk) {
|
|
1636
|
+
const lines = chunk
|
|
1637
|
+
.split(/\r?\n/g)
|
|
1638
|
+
.map((line) => trimLogNoise(line))
|
|
1639
|
+
.filter(Boolean);
|
|
1640
|
+
if (lines.length === 0)
|
|
1641
|
+
return;
|
|
1642
|
+
codexConnectRuntime.logs.push(...lines);
|
|
1643
|
+
if (codexConnectRuntime.logs.length > 120) {
|
|
1644
|
+
codexConnectRuntime.logs = codexConnectRuntime.logs.slice(-120);
|
|
1645
|
+
}
|
|
1646
|
+
codexConnectRuntime.updatedAt = nowIso();
|
|
1647
|
+
}
|
|
1648
|
+
function codexConnectSnapshot() {
|
|
1649
|
+
return {
|
|
1650
|
+
state: codexConnectRuntime.state,
|
|
1651
|
+
updated_at: codexConnectRuntime.updatedAt,
|
|
1652
|
+
logs: [...codexConnectRuntime.logs],
|
|
1653
|
+
error: codexConnectRuntime.error,
|
|
1654
|
+
};
|
|
1655
|
+
}
|
|
1656
|
+
function detectCliInstalled(name) {
|
|
1657
|
+
const cmd = spawnSync('sh', ['-lc', `command -v ${name}`], {
|
|
1658
|
+
encoding: 'utf8',
|
|
1659
|
+
timeout: 3000,
|
|
1660
|
+
});
|
|
1661
|
+
if (cmd.status !== 0)
|
|
1662
|
+
return { installed: false, version: null };
|
|
1663
|
+
const versionCmd = spawnSync(name, ['--version'], {
|
|
1664
|
+
encoding: 'utf8',
|
|
1665
|
+
timeout: 5000,
|
|
1666
|
+
});
|
|
1667
|
+
const output = trimLogNoise(`${versionCmd.stdout || ''}\n${versionCmd.stderr || ''}`) || '';
|
|
1668
|
+
return { installed: true, version: output || null };
|
|
1669
|
+
}
|
|
1670
|
+
function detectCodexAuthStatus(installed) {
|
|
1671
|
+
if (!installed)
|
|
1672
|
+
return { authenticated: false, summary: 'codex not installed' };
|
|
1673
|
+
const result = spawnSync('codex', ['login', 'status'], {
|
|
1674
|
+
encoding: 'utf8',
|
|
1675
|
+
timeout: 8000,
|
|
1676
|
+
});
|
|
1677
|
+
const text = `${result.stdout || ''}\n${result.stderr || ''}`
|
|
1678
|
+
.split(/\r?\n/g)
|
|
1679
|
+
.map((line) => trimLogNoise(line))
|
|
1680
|
+
.filter(Boolean)
|
|
1681
|
+
.join('\n');
|
|
1682
|
+
const authenticated = /logged in/i.test(text) && !/not logged in/i.test(text);
|
|
1683
|
+
const summary = text.split(/\r?\n/g).find((line) => line.trim().length > 0) || null;
|
|
1684
|
+
return { authenticated, summary };
|
|
1685
|
+
}
|
|
1686
|
+
function readCodexDefaultModel() {
|
|
1687
|
+
const codexHome = resolveCodexHome();
|
|
1688
|
+
const configPath = path.join(codexHome, 'config.toml');
|
|
1689
|
+
try {
|
|
1690
|
+
const text = fs.readFileSync(configPath, 'utf8');
|
|
1691
|
+
const match = text.match(/^\s*model\s*=\s*["']([^"']+)["']/m);
|
|
1692
|
+
const model = match?.[1]?.trim();
|
|
1693
|
+
return model || null;
|
|
1694
|
+
}
|
|
1695
|
+
catch {
|
|
1696
|
+
return null;
|
|
1697
|
+
}
|
|
1698
|
+
}
|
|
1699
|
+
function resolveCodexHome() {
|
|
1700
|
+
return process.env.CODEX_HOME && process.env.CODEX_HOME.trim().length > 0
|
|
1701
|
+
? process.env.CODEX_HOME
|
|
1702
|
+
: path.join(os.homedir(), '.codex');
|
|
1703
|
+
}
|
|
1704
|
+
function quoteTomlString(value) {
|
|
1705
|
+
return `"${value.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;
|
|
1706
|
+
}
|
|
1707
|
+
function writeCodexDefaultModel(model) {
|
|
1708
|
+
const nextModel = model.trim();
|
|
1709
|
+
if (!nextModel || /[\r\n]/.test(nextModel)) {
|
|
1710
|
+
throw new Error('Model must be a non-empty single-line value');
|
|
1711
|
+
}
|
|
1712
|
+
const codexHome = resolveCodexHome();
|
|
1713
|
+
const configPath = path.join(codexHome, 'config.toml');
|
|
1714
|
+
const nextLine = `model = ${quoteTomlString(nextModel)}`;
|
|
1715
|
+
fs.mkdirSync(codexHome, { recursive: true });
|
|
1716
|
+
let text = '';
|
|
1717
|
+
try {
|
|
1718
|
+
text = fs.readFileSync(configPath, 'utf8');
|
|
1719
|
+
}
|
|
1720
|
+
catch {
|
|
1721
|
+
fs.writeFileSync(configPath, `${nextLine}\n`, 'utf8');
|
|
1722
|
+
return nextModel;
|
|
1723
|
+
}
|
|
1724
|
+
const normalized = text.replace(/\s+$/u, '');
|
|
1725
|
+
const updated = /^\s*model\s*=.*$/m.test(normalized)
|
|
1726
|
+
? normalized.replace(/^\s*model\s*=.*$/m, nextLine)
|
|
1727
|
+
: `${nextLine}\n${normalized}`;
|
|
1728
|
+
fs.writeFileSync(configPath, `${updated}\n`, 'utf8');
|
|
1729
|
+
return nextModel;
|
|
1730
|
+
}
|
|
1731
|
+
function readCodexModelOptions(defaultModel) {
|
|
1732
|
+
const codexHome = resolveCodexHome();
|
|
1733
|
+
const cachePath = path.join(codexHome, 'models_cache.json');
|
|
1734
|
+
try {
|
|
1735
|
+
const parsed = JSON.parse(fs.readFileSync(cachePath, 'utf8'));
|
|
1736
|
+
if (!Array.isArray(parsed.models))
|
|
1737
|
+
return defaultModel ? fallbackCodexModelOptions(defaultModel) : [];
|
|
1738
|
+
const options = parsed.models
|
|
1739
|
+
.flatMap((entry) => {
|
|
1740
|
+
if (!entry || typeof entry !== 'object')
|
|
1741
|
+
return [];
|
|
1742
|
+
const model = entry;
|
|
1743
|
+
const id = typeof model.slug === 'string' ? model.slug.trim() : '';
|
|
1744
|
+
if (!id || model.visibility !== 'list')
|
|
1745
|
+
return [];
|
|
1746
|
+
const supportedLevels = Array.isArray(model.supported_reasoning_levels)
|
|
1747
|
+
? model.supported_reasoning_levels.flatMap((level) => {
|
|
1748
|
+
if (!level || typeof level !== 'object')
|
|
1749
|
+
return [];
|
|
1750
|
+
const effort = level.effort;
|
|
1751
|
+
return typeof effort === 'string' && effort.trim() ? [effort.trim()] : [];
|
|
1752
|
+
})
|
|
1753
|
+
: [];
|
|
1754
|
+
return [{
|
|
1755
|
+
id,
|
|
1756
|
+
display_name: typeof model.display_name === 'string' && model.display_name.trim()
|
|
1757
|
+
? model.display_name.trim()
|
|
1758
|
+
: id,
|
|
1759
|
+
description: typeof model.description === 'string' && model.description.trim()
|
|
1760
|
+
? model.description.trim()
|
|
1761
|
+
: null,
|
|
1762
|
+
default_reasoning_level: typeof model.default_reasoning_level === 'string' && model.default_reasoning_level.trim()
|
|
1763
|
+
? model.default_reasoning_level.trim()
|
|
1764
|
+
: null,
|
|
1765
|
+
supported_reasoning_levels: supportedLevels,
|
|
1766
|
+
context_window: typeof model.context_window === 'number' && Number.isFinite(model.context_window)
|
|
1767
|
+
? model.context_window
|
|
1768
|
+
: null,
|
|
1769
|
+
priority: typeof model.priority === 'number' && Number.isFinite(model.priority)
|
|
1770
|
+
? model.priority
|
|
1771
|
+
: 999,
|
|
1772
|
+
}];
|
|
1773
|
+
})
|
|
1774
|
+
.sort((a, b) => a.priority - b.priority || a.display_name.localeCompare(b.display_name));
|
|
1775
|
+
if (defaultModel && !options.some((option) => option.id === defaultModel)) {
|
|
1776
|
+
return [...fallbackCodexModelOptions(defaultModel), ...options];
|
|
1777
|
+
}
|
|
1778
|
+
return options;
|
|
1779
|
+
}
|
|
1780
|
+
catch {
|
|
1781
|
+
return defaultModel ? fallbackCodexModelOptions(defaultModel) : [];
|
|
1782
|
+
}
|
|
1783
|
+
}
|
|
1784
|
+
function fallbackCodexModelOptions(model) {
|
|
1785
|
+
return [{
|
|
1786
|
+
id: model,
|
|
1787
|
+
display_name: model,
|
|
1788
|
+
description: null,
|
|
1789
|
+
default_reasoning_level: null,
|
|
1790
|
+
supported_reasoning_levels: [],
|
|
1791
|
+
context_window: null,
|
|
1792
|
+
priority: 0,
|
|
1793
|
+
}];
|
|
1794
|
+
}
|
|
1795
|
+
function readCodingCliStatusPayload() {
|
|
1796
|
+
const clis = CODING_CLI_NAMES.map((name) => {
|
|
1797
|
+
const detected = detectCliInstalled(name);
|
|
1798
|
+
return {
|
|
1799
|
+
name,
|
|
1800
|
+
installed: detected.installed,
|
|
1801
|
+
version: detected.version,
|
|
1802
|
+
};
|
|
1803
|
+
});
|
|
1804
|
+
const codexInstalled = clis.find((cli) => cli.name === 'codex')?.installed ?? false;
|
|
1805
|
+
const codexVersion = clis.find((cli) => cli.name === 'codex')?.version ?? null;
|
|
1806
|
+
const gemini = clis.find((cli) => cli.name === 'gemini');
|
|
1807
|
+
const claude = clis.find((cli) => cli.name === 'claude');
|
|
1808
|
+
const acpxDetected = detectCliInstalled('acpx');
|
|
1809
|
+
const selected_cli = process.env.SUPEN_CODING_CLI || readConfigSummary().coding_cli || 'codex';
|
|
1810
|
+
const codexTransport = normalizeCodexTransport(process.env.SUPEN_CODEX_TRANSPORT) ||
|
|
1811
|
+
normalizeCodexTransport(readConfigSummary().codex_transport) ||
|
|
1812
|
+
'app-server';
|
|
1813
|
+
const codexDefaultModel = readCodexDefaultModel();
|
|
1814
|
+
const codexModelOptions = readCodexModelOptions(codexDefaultModel);
|
|
1815
|
+
const installed_apps = DETECTABLE_SPACE_APPS.map((app) => {
|
|
1816
|
+
const detected = detectCliInstalled(app.command);
|
|
1817
|
+
return {
|
|
1818
|
+
id: app.id,
|
|
1819
|
+
installed: detected.installed,
|
|
1820
|
+
version: detected.version,
|
|
1821
|
+
managed: app.managed,
|
|
1822
|
+
};
|
|
1823
|
+
});
|
|
1824
|
+
return {
|
|
1825
|
+
selected_cli,
|
|
1826
|
+
default_model: selected_cli === 'codex' ? codexDefaultModel : null,
|
|
1827
|
+
model_options: selected_cli === 'codex' ? codexModelOptions : [],
|
|
1828
|
+
clis,
|
|
1829
|
+
agent_runtimes: [
|
|
1830
|
+
{
|
|
1831
|
+
id: 'codex-app-server',
|
|
1832
|
+
label: 'Codex App Server',
|
|
1833
|
+
installed: codexInstalled,
|
|
1834
|
+
version: codexVersion,
|
|
1835
|
+
default_model: codexDefaultModel,
|
|
1836
|
+
model_options: codexModelOptions,
|
|
1837
|
+
transport: 'app-server',
|
|
1838
|
+
recommended: true,
|
|
1839
|
+
default: selected_cli === 'codex' && codexTransport === 'app-server',
|
|
1840
|
+
description: 'Daemon-managed codex app-server process with protocol-native streaming.',
|
|
1841
|
+
},
|
|
1842
|
+
{
|
|
1843
|
+
id: 'codex-exec',
|
|
1844
|
+
label: 'Codex CLI Exec',
|
|
1845
|
+
installed: codexInstalled,
|
|
1846
|
+
version: codexVersion,
|
|
1847
|
+
default_model: codexDefaultModel,
|
|
1848
|
+
model_options: codexModelOptions,
|
|
1849
|
+
transport: 'exec',
|
|
1850
|
+
recommended: false,
|
|
1851
|
+
default: selected_cli === 'codex' && codexTransport === 'exec',
|
|
1852
|
+
description: 'Direct codex exec fallback, typically one process per turn.',
|
|
1853
|
+
},
|
|
1854
|
+
{
|
|
1855
|
+
id: 'codex-acpx',
|
|
1856
|
+
label: 'Codex via ACPX',
|
|
1857
|
+
installed: codexInstalled && acpxDetected.installed,
|
|
1858
|
+
version: acpxDetected.version || codexVersion,
|
|
1859
|
+
default_model: codexDefaultModel,
|
|
1860
|
+
model_options: codexModelOptions,
|
|
1861
|
+
transport: 'acpx',
|
|
1862
|
+
recommended: false,
|
|
1863
|
+
default: selected_cli === 'codex' && codexTransport === 'acpx',
|
|
1864
|
+
description: 'Compatibility bridge through ACPX for hosts that still need it.',
|
|
1865
|
+
},
|
|
1866
|
+
{
|
|
1867
|
+
id: 'claude-code',
|
|
1868
|
+
label: 'Claude Code',
|
|
1869
|
+
installed: Boolean(claude?.installed),
|
|
1870
|
+
version: claude?.version ?? null,
|
|
1871
|
+
transport: 'cli',
|
|
1872
|
+
recommended: false,
|
|
1873
|
+
default: selected_cli === 'claude',
|
|
1874
|
+
description: 'Claude Code CLI runtime.',
|
|
1875
|
+
},
|
|
1876
|
+
{
|
|
1877
|
+
id: 'gemini-cli',
|
|
1878
|
+
label: 'Gemini CLI',
|
|
1879
|
+
installed: Boolean(gemini?.installed),
|
|
1880
|
+
version: gemini?.version ?? null,
|
|
1881
|
+
transport: 'cli',
|
|
1882
|
+
recommended: false,
|
|
1883
|
+
default: selected_cli === 'gemini',
|
|
1884
|
+
description: 'Gemini CLI runtime.',
|
|
1885
|
+
},
|
|
1886
|
+
],
|
|
1887
|
+
installed_apps,
|
|
1888
|
+
codex_auth: detectCodexAuthStatus(codexInstalled),
|
|
1889
|
+
codex_connect: codexConnectSnapshot(),
|
|
1890
|
+
};
|
|
1891
|
+
}
|
|
1892
|
+
function readRuntimeModelStatusPayload() {
|
|
1893
|
+
const selected_cli = process.env.SUPEN_CODING_CLI || readConfigSummary().coding_cli || 'codex';
|
|
1894
|
+
const codexTransport = normalizeCodexTransport(process.env.SUPEN_CODEX_TRANSPORT) ||
|
|
1895
|
+
normalizeCodexTransport(readConfigSummary().codex_transport) ||
|
|
1896
|
+
'app-server';
|
|
1897
|
+
const codexDefaultModel = readCodexDefaultModel();
|
|
1898
|
+
const codexModelOptions = readCodexModelOptions(codexDefaultModel);
|
|
1899
|
+
return {
|
|
1900
|
+
selected_cli,
|
|
1901
|
+
default_model: selected_cli === 'codex' ? codexDefaultModel : null,
|
|
1902
|
+
model_options: selected_cli === 'codex' ? codexModelOptions : [],
|
|
1903
|
+
agent_runtimes: [
|
|
1904
|
+
{
|
|
1905
|
+
id: 'codex-app-server',
|
|
1906
|
+
default: selected_cli === 'codex' && codexTransport === 'app-server',
|
|
1907
|
+
default_model: codexDefaultModel,
|
|
1908
|
+
model_options: codexModelOptions,
|
|
1909
|
+
transport: 'app-server',
|
|
1910
|
+
},
|
|
1911
|
+
{
|
|
1912
|
+
id: 'codex-exec',
|
|
1913
|
+
default: selected_cli === 'codex' && codexTransport === 'exec',
|
|
1914
|
+
default_model: codexDefaultModel,
|
|
1915
|
+
model_options: codexModelOptions,
|
|
1916
|
+
transport: 'exec',
|
|
1917
|
+
},
|
|
1918
|
+
{
|
|
1919
|
+
id: 'codex-acpx',
|
|
1920
|
+
default: selected_cli === 'codex' && codexTransport === 'acpx',
|
|
1921
|
+
default_model: codexDefaultModel,
|
|
1922
|
+
model_options: codexModelOptions,
|
|
1923
|
+
transport: 'acpx',
|
|
1924
|
+
},
|
|
1925
|
+
{
|
|
1926
|
+
id: 'claude-code',
|
|
1927
|
+
default: selected_cli === 'claude',
|
|
1928
|
+
default_model: null,
|
|
1929
|
+
model_options: [],
|
|
1930
|
+
transport: 'cli',
|
|
1931
|
+
},
|
|
1932
|
+
{
|
|
1933
|
+
id: 'gemini-cli',
|
|
1934
|
+
default: selected_cli === 'gemini',
|
|
1935
|
+
default_model: null,
|
|
1936
|
+
model_options: [],
|
|
1937
|
+
transport: 'cli',
|
|
1938
|
+
},
|
|
1939
|
+
],
|
|
1940
|
+
};
|
|
1941
|
+
}
|
|
1942
|
+
function setDefaultCodingCli(cli) {
|
|
1943
|
+
const rawYaml = readConfigYamlFile();
|
|
1944
|
+
const parsed = yaml.parse(rawYaml);
|
|
1945
|
+
const doc = parsed && typeof parsed === 'object' && !Array.isArray(parsed)
|
|
1946
|
+
? parsed
|
|
1947
|
+
: {};
|
|
1948
|
+
doc.coding_cli = cli;
|
|
1949
|
+
const result = writeConfigYamlFile(yaml.stringify(doc));
|
|
1950
|
+
if (result.ok === false) {
|
|
1951
|
+
throw new Error(result.message);
|
|
1952
|
+
}
|
|
1953
|
+
process.env.SUPEN_CODING_CLI = cli;
|
|
1954
|
+
return { selected_cli: readConfigSummary().coding_cli };
|
|
1955
|
+
}
|
|
1956
|
+
function setDefaultCodexTransport(transport) {
|
|
1957
|
+
const rawYaml = readConfigYamlFile();
|
|
1958
|
+
const parsed = yaml.parse(rawYaml);
|
|
1959
|
+
const doc = parsed && typeof parsed === 'object' && !Array.isArray(parsed)
|
|
1960
|
+
? parsed
|
|
1961
|
+
: {};
|
|
1962
|
+
doc.coding_cli = 'codex';
|
|
1963
|
+
doc.codex_transport = transport;
|
|
1964
|
+
const result = writeConfigYamlFile(yaml.stringify(doc));
|
|
1965
|
+
if (result.ok === false) {
|
|
1966
|
+
throw new Error(result.message);
|
|
1967
|
+
}
|
|
1968
|
+
process.env.SUPEN_CODING_CLI = 'codex';
|
|
1969
|
+
process.env.SUPEN_CODEX_TRANSPORT = transport;
|
|
1970
|
+
const summary = readConfigSummary();
|
|
1971
|
+
return { selected_cli: summary.coding_cli, codex_transport: summary.codex_transport };
|
|
1972
|
+
}
|
|
1973
|
+
function startCodexConnectFlow() {
|
|
1974
|
+
if (codexConnectRuntime.child && codexConnectRuntime.state === 'running') {
|
|
1975
|
+
return codexConnectSnapshot();
|
|
1976
|
+
}
|
|
1977
|
+
codexConnectRuntime.state = 'running';
|
|
1978
|
+
codexConnectRuntime.updatedAt = nowIso();
|
|
1979
|
+
codexConnectRuntime.logs = [];
|
|
1980
|
+
codexConnectRuntime.error = null;
|
|
1981
|
+
const child = spawn('codex', ['login', '--device-auth'], {
|
|
1982
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
1983
|
+
env: process.env,
|
|
1984
|
+
});
|
|
1985
|
+
codexConnectRuntime.child = child;
|
|
1986
|
+
child.stdout.on('data', (chunk) => pushCodexConnectLog(String(chunk)));
|
|
1987
|
+
child.stderr.on('data', (chunk) => pushCodexConnectLog(String(chunk)));
|
|
1988
|
+
child.on('error', (err) => {
|
|
1989
|
+
codexConnectRuntime.state = 'failed';
|
|
1990
|
+
codexConnectRuntime.error = err.message || 'Failed to start codex login flow';
|
|
1991
|
+
codexConnectRuntime.updatedAt = nowIso();
|
|
1992
|
+
codexConnectRuntime.child = null;
|
|
1993
|
+
});
|
|
1994
|
+
child.on('close', (code) => {
|
|
1995
|
+
codexConnectRuntime.state = code === 0 ? 'succeeded' : 'failed';
|
|
1996
|
+
if (code !== 0 && !codexConnectRuntime.error) {
|
|
1997
|
+
codexConnectRuntime.error = `codex login exited with code ${code ?? -1}`;
|
|
1998
|
+
}
|
|
1999
|
+
codexConnectRuntime.updatedAt = nowIso();
|
|
2000
|
+
codexConnectRuntime.child = null;
|
|
2001
|
+
});
|
|
2002
|
+
return codexConnectSnapshot();
|
|
2003
|
+
}
|
|
2004
|
+
function cancelCodexConnectFlow() {
|
|
2005
|
+
const child = codexConnectRuntime.child;
|
|
2006
|
+
if (child && codexConnectRuntime.state === 'running') {
|
|
2007
|
+
child.kill('SIGTERM');
|
|
2008
|
+
codexConnectRuntime.state = 'failed';
|
|
2009
|
+
codexConnectRuntime.error = 'Cancelled by user';
|
|
2010
|
+
codexConnectRuntime.updatedAt = nowIso();
|
|
2011
|
+
codexConnectRuntime.child = null;
|
|
2012
|
+
}
|
|
2013
|
+
return codexConnectSnapshot();
|
|
2014
|
+
}
|
|
2015
|
+
export async function handleSystemRoutes(req, res, url, pathname, method) {
|
|
2016
|
+
if (pathname === '/health' && method === 'GET') {
|
|
2017
|
+
writeJson(res, 200, { status: 'ok' });
|
|
2018
|
+
return true;
|
|
2019
|
+
}
|
|
2020
|
+
if (pathname === '/api/computer/openapi.json' && method === 'GET') {
|
|
2021
|
+
writeJson(res, 200, buildDaemonOpenApiSpec());
|
|
2022
|
+
return true;
|
|
2023
|
+
}
|
|
2024
|
+
if (pathname === '/api/computer/models' && method === 'GET') {
|
|
2025
|
+
writeJson(res, 200, { models: MODELS_REGISTRY });
|
|
2026
|
+
return true;
|
|
2027
|
+
}
|
|
2028
|
+
if (pathname === '/api/computer/config/reload' && method === 'POST') {
|
|
2029
|
+
const before = {
|
|
2030
|
+
total_models: MODELS_REGISTRY.length,
|
|
2031
|
+
default_model: DEFAULT_MODEL?.id ?? null,
|
|
2032
|
+
};
|
|
2033
|
+
const after = reloadModelsRegistry();
|
|
2034
|
+
writeJson(res, 200, {
|
|
2035
|
+
ok: true,
|
|
2036
|
+
reloaded: {
|
|
2037
|
+
models: true,
|
|
2038
|
+
},
|
|
2039
|
+
before,
|
|
2040
|
+
after,
|
|
2041
|
+
});
|
|
2042
|
+
return true;
|
|
2043
|
+
}
|
|
2044
|
+
if (pathname === '/api/computer/config-summary' && method === 'GET') {
|
|
2045
|
+
writeJson(res, 200, readConfigSummary());
|
|
2046
|
+
return true;
|
|
2047
|
+
}
|
|
2048
|
+
if (pathname === '/api/computer/logs' && method === 'GET') {
|
|
2049
|
+
const agentId = coerceSingleQueryParam(url.searchParams.get('agent_id'));
|
|
2050
|
+
const sessionId = coerceSingleQueryParam(url.searchParams.get('session_id'));
|
|
2051
|
+
const limitRaw = coerceSingleQueryParam(url.searchParams.get('limit'));
|
|
2052
|
+
const limit = limitRaw ? Number.parseInt(limitRaw, 10) : SPACE_LOG_EVENT_LIMIT;
|
|
2053
|
+
writeJson(res, 200, collectSpaceLogEntries({
|
|
2054
|
+
agentId: agentId?.trim() || null,
|
|
2055
|
+
sessionId: sessionId?.trim() || null,
|
|
2056
|
+
limit: Number.isFinite(limit) ? limit : SPACE_LOG_EVENT_LIMIT,
|
|
2057
|
+
}));
|
|
2058
|
+
return true;
|
|
2059
|
+
}
|
|
2060
|
+
if (pathname === '/api/computer/logs/stream' && method === 'GET') {
|
|
2061
|
+
const agentId = coerceSingleQueryParam(url.searchParams.get('agent_id'));
|
|
2062
|
+
const sessionId = coerceSingleQueryParam(url.searchParams.get('session_id'));
|
|
2063
|
+
const limitRaw = coerceSingleQueryParam(url.searchParams.get('limit'));
|
|
2064
|
+
const limit = limitRaw ? Number.parseInt(limitRaw, 10) : SPACE_LOG_EVENT_LIMIT;
|
|
2065
|
+
const filters = {
|
|
2066
|
+
agentId: agentId?.trim() || null,
|
|
2067
|
+
sessionId: sessionId?.trim() || null,
|
|
2068
|
+
limit: Number.isFinite(limit) ? limit : SPACE_LOG_EVENT_LIMIT,
|
|
2069
|
+
};
|
|
2070
|
+
res.writeHead(200, {
|
|
2071
|
+
'Content-Type': 'text/event-stream; charset=utf-8',
|
|
2072
|
+
'Cache-Control': 'no-cache',
|
|
2073
|
+
'Connection': 'keep-alive',
|
|
2074
|
+
'X-Accel-Buffering': 'no',
|
|
2075
|
+
});
|
|
2076
|
+
res.flushHeaders?.();
|
|
2077
|
+
const seenIds = new Set(collectSpaceLogEntries(filters).entries.map((entry) => entry.id));
|
|
2078
|
+
const emitDiff = () => {
|
|
2079
|
+
const payload = collectSpaceLogEntries(filters);
|
|
2080
|
+
const entries = payload.entries.filter((entry) => !seenIds.has(entry.id));
|
|
2081
|
+
if (entries.length === 0)
|
|
2082
|
+
return;
|
|
2083
|
+
for (const entry of entries)
|
|
2084
|
+
seenIds.add(entry.id);
|
|
2085
|
+
writeSse(res, {
|
|
2086
|
+
type: 'log_entries',
|
|
2087
|
+
generated_at: payload.generated_at,
|
|
2088
|
+
timezone: payload.timezone,
|
|
2089
|
+
entries,
|
|
2090
|
+
});
|
|
2091
|
+
};
|
|
2092
|
+
const pollTimer = setInterval(emitDiff, SPACE_LOG_STREAM_POLL_MS);
|
|
2093
|
+
const pingTimer = setInterval(() => writeSse(res), SPACE_LOG_STREAM_PING_MS);
|
|
2094
|
+
const cleanup = () => {
|
|
2095
|
+
clearInterval(pollTimer);
|
|
2096
|
+
clearInterval(pingTimer);
|
|
2097
|
+
};
|
|
2098
|
+
req.on('close', cleanup);
|
|
2099
|
+
res.on('close', cleanup);
|
|
2100
|
+
writeSse(res);
|
|
2101
|
+
return true;
|
|
2102
|
+
}
|
|
2103
|
+
if (pathname === '/api/computer/config-yaml' && method === 'GET') {
|
|
2104
|
+
try {
|
|
2105
|
+
const yaml = readConfigYamlFile();
|
|
2106
|
+
writeJson(res, 200, { yaml });
|
|
2107
|
+
}
|
|
2108
|
+
catch (err) {
|
|
2109
|
+
writeProtocolError(res, 500, 'config_error', 'read_failed', err?.message || 'Failed to read config.yaml');
|
|
2110
|
+
}
|
|
2111
|
+
return true;
|
|
2112
|
+
}
|
|
2113
|
+
if (pathname === '/api/computer/config-yaml' && method === 'PUT') {
|
|
2114
|
+
try {
|
|
2115
|
+
const parsed = await readJsonBody(req);
|
|
2116
|
+
const yamlText = typeof parsed === 'object' && parsed && typeof parsed.yaml === 'string'
|
|
2117
|
+
? String(parsed.yaml)
|
|
2118
|
+
: '';
|
|
2119
|
+
if (!yamlText.trim()) {
|
|
2120
|
+
writeProtocolError(res, 400, 'validation_error', 'missing_yaml', 'Request body must include a non-empty "yaml" string.');
|
|
2121
|
+
return true;
|
|
2122
|
+
}
|
|
2123
|
+
const result = writeConfigYamlFile(yamlText);
|
|
2124
|
+
if (result.ok === false) {
|
|
2125
|
+
writeProtocolError(res, 400, 'validation_error', 'invalid_yaml', result.message);
|
|
2126
|
+
return true;
|
|
2127
|
+
}
|
|
2128
|
+
const after = reloadModelsRegistry();
|
|
2129
|
+
writeJson(res, 200, {
|
|
2130
|
+
ok: true,
|
|
2131
|
+
summary: readConfigSummary(),
|
|
2132
|
+
reloaded: { models: true, default_model: after.default_model },
|
|
2133
|
+
});
|
|
2134
|
+
}
|
|
2135
|
+
catch (err) {
|
|
2136
|
+
writeProtocolError(res, 500, 'config_error', 'write_failed', err?.message || 'Failed to write config.yaml');
|
|
2137
|
+
}
|
|
2138
|
+
return true;
|
|
2139
|
+
}
|
|
2140
|
+
if (pathname === '/api/computer/gateway-uplink' && method === 'GET') {
|
|
2141
|
+
writeJson(res, 200, gatewayUplinkStatus());
|
|
2142
|
+
return true;
|
|
2143
|
+
}
|
|
2144
|
+
if (pathname === '/api/computer/gateway-uplink' && (method === 'PUT' || method === 'PATCH')) {
|
|
2145
|
+
if (process.env.SUPEN_GATEWAY_URL) {
|
|
2146
|
+
writeProtocolError(res, 409, 'config_error', 'env_override', 'SUPEN_GATEWAY_URL is set; update the daemon environment to change the gateway uplink.');
|
|
2147
|
+
return true;
|
|
2148
|
+
}
|
|
2149
|
+
try {
|
|
2150
|
+
const body = await readJsonBody(req);
|
|
2151
|
+
const rawGatewayUrl = body && typeof body === 'object' && typeof body.gateway_url === 'string'
|
|
2152
|
+
? String(body.gateway_url)
|
|
2153
|
+
: '';
|
|
2154
|
+
const rawTrustToken = body && typeof body === 'object' && typeof body.trust_token === 'string'
|
|
2155
|
+
? String(body.trust_token).trim()
|
|
2156
|
+
: '';
|
|
2157
|
+
const disconnect = body && typeof body === 'object' && body.disconnect === true;
|
|
2158
|
+
const existing = readGatewayConfig();
|
|
2159
|
+
const next = {
|
|
2160
|
+
...existing,
|
|
2161
|
+
};
|
|
2162
|
+
if (disconnect || !rawGatewayUrl.trim()) {
|
|
2163
|
+
delete next.gateway_url;
|
|
2164
|
+
if (disconnect)
|
|
2165
|
+
delete next.trust_token;
|
|
2166
|
+
}
|
|
2167
|
+
else {
|
|
2168
|
+
next.gateway_url = normalizeGatewayUplinkUrl(rawGatewayUrl);
|
|
2169
|
+
}
|
|
2170
|
+
if (rawTrustToken)
|
|
2171
|
+
next.trust_token = rawTrustToken;
|
|
2172
|
+
writeGatewayConfig(next);
|
|
2173
|
+
const gateway = getGatewayInstance();
|
|
2174
|
+
if (gateway) {
|
|
2175
|
+
await gateway.reloadConfigAndReconnect();
|
|
2176
|
+
}
|
|
2177
|
+
writeJson(res, 200, {
|
|
2178
|
+
ok: true,
|
|
2179
|
+
...gatewayUplinkStatus(next),
|
|
2180
|
+
});
|
|
2181
|
+
}
|
|
2182
|
+
catch (err) {
|
|
2183
|
+
writeProtocolError(res, 400, 'validation_error', 'invalid_gateway_uplink', err?.message || 'Invalid gateway uplink config.');
|
|
2184
|
+
}
|
|
2185
|
+
return true;
|
|
2186
|
+
}
|
|
2187
|
+
if (pathname === '/api/computer/env' && method === 'GET') {
|
|
2188
|
+
writeJson(res, 200, { env: readSpaceEnvMap() });
|
|
2189
|
+
return true;
|
|
2190
|
+
}
|
|
2191
|
+
if (pathname === '/api/computer/llm-env' && method === 'GET') {
|
|
2192
|
+
const token = getLlmToken() || process.env.SUPEN_LLM_TOKEN || process.env.SUPEN_LLM_API_KEY || '';
|
|
2193
|
+
const env = withSupenLlmEnv({
|
|
2194
|
+
...process.env,
|
|
2195
|
+
...(token ? { SUPEN_LLM_TOKEN: token, SUPEN_LLM_API_KEY: token } : {}),
|
|
2196
|
+
});
|
|
2197
|
+
const responseEnv = {};
|
|
2198
|
+
for (const key of ['SUPEN_LLM_TOKEN', 'SUPEN_LLM_API_KEY', 'SUPEN_LLM_GATEWAY_URL', 'SUPEN_LLM_BASE_URL']) {
|
|
2199
|
+
const value = env[key];
|
|
2200
|
+
if (typeof value === 'string' && value.trim()) {
|
|
2201
|
+
responseEnv[key] = value.trim();
|
|
2202
|
+
}
|
|
2203
|
+
}
|
|
2204
|
+
writeJson(res, 200, {
|
|
2205
|
+
ok: true,
|
|
2206
|
+
available: Boolean(responseEnv.SUPEN_LLM_TOKEN && (responseEnv.SUPEN_LLM_GATEWAY_URL || responseEnv.SUPEN_LLM_BASE_URL)),
|
|
2207
|
+
env: responseEnv,
|
|
2208
|
+
});
|
|
2209
|
+
return true;
|
|
2210
|
+
}
|
|
2211
|
+
if (pathname === '/api/computer/apps' && method === 'GET') {
|
|
2212
|
+
writeJson(res, 200, readCodingCliStatusPayload());
|
|
2213
|
+
return true;
|
|
2214
|
+
}
|
|
2215
|
+
if (pathname === '/api/computer/runtime-models' && method === 'GET') {
|
|
2216
|
+
writeJson(res, 200, readRuntimeModelStatusPayload());
|
|
2217
|
+
return true;
|
|
2218
|
+
}
|
|
2219
|
+
if (pathname === '/api/computer/runtime-models/default' && method === 'PUT') {
|
|
2220
|
+
try {
|
|
2221
|
+
const parsed = await readJsonBody(req);
|
|
2222
|
+
const model = typeof parsed === 'object' && parsed && typeof parsed.model === 'string'
|
|
2223
|
+
? String(parsed.model).trim()
|
|
2224
|
+
: '';
|
|
2225
|
+
if (!model) {
|
|
2226
|
+
writeProtocolError(res, 400, 'validation_error', 'missing_model', 'Request body must include a non-empty "model" string.');
|
|
2227
|
+
return true;
|
|
2228
|
+
}
|
|
2229
|
+
writeCodexDefaultModel(model);
|
|
2230
|
+
writeJson(res, 200, {
|
|
2231
|
+
ok: true,
|
|
2232
|
+
status: readRuntimeModelStatusPayload(),
|
|
2233
|
+
});
|
|
2234
|
+
}
|
|
2235
|
+
catch (err) {
|
|
2236
|
+
writeProtocolError(res, 500, 'config_error', 'codex_model_default_failed', err?.message || 'Failed to set Codex default model');
|
|
2237
|
+
}
|
|
2238
|
+
return true;
|
|
2239
|
+
}
|
|
2240
|
+
const spaceCodexTransportDefaultMatch = pathname.match(/^\/api\/computer\/apps\/codex\/transports\/([^/]+)\/default$/);
|
|
2241
|
+
if (spaceCodexTransportDefaultMatch && method === 'PUT') {
|
|
2242
|
+
try {
|
|
2243
|
+
const transport = normalizeCodexTransport(decodeURIComponent(spaceCodexTransportDefaultMatch[1] || '').trim());
|
|
2244
|
+
if (!transport) {
|
|
2245
|
+
writeProtocolError(res, 400, 'validation_error', 'invalid_codex_transport', 'transport must be one of: app-server, exec, acpx');
|
|
2246
|
+
return true;
|
|
2247
|
+
}
|
|
2248
|
+
const result = setDefaultCodexTransport(transport);
|
|
2249
|
+
writeJson(res, 200, {
|
|
2250
|
+
ok: true,
|
|
2251
|
+
...result,
|
|
2252
|
+
status: readCodingCliStatusPayload(),
|
|
2253
|
+
});
|
|
2254
|
+
}
|
|
2255
|
+
catch (err) {
|
|
2256
|
+
writeProtocolError(res, 500, 'config_error', 'codex_transport_default_failed', err?.message || 'Failed to set default Codex transport');
|
|
2257
|
+
}
|
|
2258
|
+
return true;
|
|
2259
|
+
}
|
|
2260
|
+
const spaceAppDefaultMatch = pathname.match(/^\/api\/computer\/apps\/([^/]+)\/default$/);
|
|
2261
|
+
if (spaceAppDefaultMatch && method === 'PUT') {
|
|
2262
|
+
try {
|
|
2263
|
+
const cli = normalizeCliName(decodeURIComponent(spaceAppDefaultMatch[1] || '').trim());
|
|
2264
|
+
if (!cli) {
|
|
2265
|
+
writeProtocolError(res, 400, 'validation_error', 'invalid_cli', 'cli must be one of: codex, gemini, claude');
|
|
2266
|
+
return true;
|
|
2267
|
+
}
|
|
2268
|
+
const result = setDefaultCodingCli(cli);
|
|
2269
|
+
writeJson(res, 200, {
|
|
2270
|
+
ok: true,
|
|
2271
|
+
...result,
|
|
2272
|
+
status: readCodingCliStatusPayload(),
|
|
2273
|
+
});
|
|
2274
|
+
}
|
|
2275
|
+
catch (err) {
|
|
2276
|
+
writeProtocolError(res, 500, 'config_error', 'coding_cli_default_failed', err?.message || 'Failed to set default coding CLI');
|
|
2277
|
+
}
|
|
2278
|
+
return true;
|
|
2279
|
+
}
|
|
2280
|
+
const spaceAppInstallMatch = pathname.match(/^\/api\/computer\/apps\/([^/]+)\/install$/);
|
|
2281
|
+
if (spaceAppInstallMatch && method === 'POST') {
|
|
2282
|
+
try {
|
|
2283
|
+
const cli = normalizeCliName(decodeURIComponent(spaceAppInstallMatch[1] || '').trim());
|
|
2284
|
+
if (!cli) {
|
|
2285
|
+
writeProtocolError(res, 400, 'validation_error', 'invalid_cli', 'cli must be one of: codex, gemini, claude');
|
|
2286
|
+
return true;
|
|
2287
|
+
}
|
|
2288
|
+
if (!isManagedCliName(cli)) {
|
|
2289
|
+
writeProtocolError(res, 400, 'validation_error', 'install_not_supported', 'Only codex and gemini support managed install at the moment');
|
|
2290
|
+
return true;
|
|
2291
|
+
}
|
|
2292
|
+
const installSpec = MANAGED_CODING_CLI_INSTALL_COMMANDS[cli];
|
|
2293
|
+
const result = spawnSync(installSpec.command, installSpec.args, {
|
|
2294
|
+
encoding: 'utf8',
|
|
2295
|
+
timeout: 120_000,
|
|
2296
|
+
});
|
|
2297
|
+
if (result.status !== 0) {
|
|
2298
|
+
const output = `${result.stdout || ''}\n${result.stderr || ''}`.trim() || `Failed to install ${cli}`;
|
|
2299
|
+
writeProtocolError(res, 500, 'install_error', 'coding_cli_install_failed', output);
|
|
2300
|
+
return true;
|
|
2301
|
+
}
|
|
2302
|
+
writeJson(res, 200, {
|
|
2303
|
+
ok: true,
|
|
2304
|
+
cli,
|
|
2305
|
+
status: readCodingCliStatusPayload(),
|
|
2306
|
+
});
|
|
2307
|
+
}
|
|
2308
|
+
catch (err) {
|
|
2309
|
+
writeProtocolError(res, 500, 'install_error', 'coding_cli_install_failed', err?.message || 'Failed to install coding cli');
|
|
2310
|
+
}
|
|
2311
|
+
return true;
|
|
2312
|
+
}
|
|
2313
|
+
const spaceAppConnectMatch = pathname.match(/^\/api\/computer\/apps\/([^/]+)\/connect$/);
|
|
2314
|
+
if (spaceAppConnectMatch && method === 'POST') {
|
|
2315
|
+
const cli = normalizeCliName(decodeURIComponent(spaceAppConnectMatch[1] || '').trim());
|
|
2316
|
+
if (cli !== 'codex') {
|
|
2317
|
+
writeProtocolError(res, 400, 'validation_error', 'connect_not_supported', 'Only codex supports connect flow at the moment');
|
|
2318
|
+
return true;
|
|
2319
|
+
}
|
|
2320
|
+
const status = readCodingCliStatusPayload();
|
|
2321
|
+
const codex = status.clis.find((entry) => entry.name === 'codex');
|
|
2322
|
+
if (!codex?.installed) {
|
|
2323
|
+
writeProtocolError(res, 400, 'validation_error', 'codex_not_installed', 'codex CLI is not installed');
|
|
2324
|
+
return true;
|
|
2325
|
+
}
|
|
2326
|
+
writeJson(res, 200, {
|
|
2327
|
+
ok: true,
|
|
2328
|
+
codex_connect: startCodexConnectFlow(),
|
|
2329
|
+
status: readCodingCliStatusPayload(),
|
|
2330
|
+
});
|
|
2331
|
+
return true;
|
|
2332
|
+
}
|
|
2333
|
+
if (spaceAppConnectMatch && method === 'DELETE') {
|
|
2334
|
+
const cli = normalizeCliName(decodeURIComponent(spaceAppConnectMatch[1] || '').trim());
|
|
2335
|
+
if (cli !== 'codex') {
|
|
2336
|
+
writeProtocolError(res, 400, 'validation_error', 'connect_not_supported', 'Only codex supports connect flow at the moment');
|
|
2337
|
+
return true;
|
|
2338
|
+
}
|
|
2339
|
+
writeJson(res, 200, {
|
|
2340
|
+
ok: true,
|
|
2341
|
+
codex_connect: cancelCodexConnectFlow(),
|
|
2342
|
+
status: readCodingCliStatusPayload(),
|
|
2343
|
+
});
|
|
2344
|
+
return true;
|
|
2345
|
+
}
|
|
2346
|
+
const spaceAppSessionMatch = pathname.match(/^\/api\/computer\/apps\/([^/]+)\/session$/);
|
|
2347
|
+
if (spaceAppSessionMatch && method === 'DELETE') {
|
|
2348
|
+
const cli = normalizeCliName(decodeURIComponent(spaceAppSessionMatch[1] || '').trim());
|
|
2349
|
+
if (cli !== 'codex') {
|
|
2350
|
+
writeProtocolError(res, 400, 'validation_error', 'session_not_supported', 'Only codex supports session connect/disconnect at the moment');
|
|
2351
|
+
return true;
|
|
2352
|
+
}
|
|
2353
|
+
const cmd = spawnSync('codex', ['logout'], {
|
|
2354
|
+
encoding: 'utf8',
|
|
2355
|
+
timeout: 15_000,
|
|
2356
|
+
});
|
|
2357
|
+
if (cmd.status !== 0) {
|
|
2358
|
+
const output = `${cmd.stdout || ''}\n${cmd.stderr || ''}`.trim() || 'codex logout failed';
|
|
2359
|
+
writeProtocolError(res, 500, 'auth_error', 'codex_logout_failed', output);
|
|
2360
|
+
return true;
|
|
2361
|
+
}
|
|
2362
|
+
writeJson(res, 200, { ok: true, status: readCodingCliStatusPayload() });
|
|
2363
|
+
return true;
|
|
2364
|
+
}
|
|
2365
|
+
if (pathname === '/api/computer/env' && method === 'PUT') {
|
|
2366
|
+
try {
|
|
2367
|
+
const parsed = await readJsonBody(req);
|
|
2368
|
+
const updates = parsed && typeof parsed === 'object' && parsed.env && typeof parsed.env === 'object' && !Array.isArray(parsed.env)
|
|
2369
|
+
? parsed.env
|
|
2370
|
+
: null;
|
|
2371
|
+
if (!updates) {
|
|
2372
|
+
writeProtocolError(res, 400, 'validation_error', 'invalid_env_payload', 'Request body must include an "env" object.');
|
|
2373
|
+
return true;
|
|
2374
|
+
}
|
|
2375
|
+
const env = updateSpaceEnvMap(updates);
|
|
2376
|
+
writeJson(res, 200, { ok: true, env });
|
|
2377
|
+
}
|
|
2378
|
+
catch (err) {
|
|
2379
|
+
writeProtocolError(res, 500, 'config_error', 'space_env_write_failed', err?.message || 'Failed to update space env vars');
|
|
2380
|
+
}
|
|
2381
|
+
return true;
|
|
2382
|
+
}
|
|
2383
|
+
if (pathname === '/api/computer/hub-snapshot' && method === 'GET') {
|
|
2384
|
+
const spaceId = (url.searchParams.get('space_id') || process.env.SUPEN_SPACE_ID || 'local').trim() || 'local';
|
|
2385
|
+
writeJson(res, 200, buildHubSnapshotForSpace(spaceId));
|
|
2386
|
+
return true;
|
|
2387
|
+
}
|
|
2388
|
+
if (pathname === '/api/computer/usage' && method === 'GET') {
|
|
2389
|
+
writeJson(res, 200, getGlobalUsage());
|
|
2390
|
+
return true;
|
|
2391
|
+
}
|
|
2392
|
+
if (pathname === '/api/computer/usage/daily' && method === 'GET') {
|
|
2393
|
+
writeJson(res, 200, { daily: getDailyUsage() });
|
|
2394
|
+
return true;
|
|
2395
|
+
}
|
|
2396
|
+
if (pathname === '/api/computer/codex/subscription' && method === 'GET') {
|
|
2397
|
+
try {
|
|
2398
|
+
writeJson(res, 200, await readCodexSubscription());
|
|
2399
|
+
}
|
|
2400
|
+
catch (err) {
|
|
2401
|
+
writeProtocolError(res, 503, 'service_unavailable', 'codex_subscription_unavailable', err?.message || 'Unable to read Codex subscription details.');
|
|
2402
|
+
}
|
|
2403
|
+
return true;
|
|
2404
|
+
}
|
|
2405
|
+
if (pathname === '/api/computer/quota-status' && method === 'GET') {
|
|
2406
|
+
writeJson(res, 200, readLatestSpaceQuotaStatus());
|
|
2407
|
+
return true;
|
|
2408
|
+
}
|
|
2409
|
+
if (pathname === '/api/computer/codex/threads' && method === 'GET') {
|
|
2410
|
+
const limitRaw = coerceSingleQueryParam(url.searchParams.get('limit'));
|
|
2411
|
+
const limit = limitRaw ? Number.parseInt(limitRaw, 10) : MIRRORED_THREAD_LIMIT;
|
|
2412
|
+
writeJson(res, 200, readMirroredTaskProjects(Number.isFinite(limit) ? limit : MIRRORED_THREAD_LIMIT));
|
|
2413
|
+
return true;
|
|
2414
|
+
}
|
|
2415
|
+
const mirroredThreadStreamMatch = pathname.match(/^\/api\/computer\/codex\/threads\/([^/]+)\/stream$/);
|
|
2416
|
+
if (mirroredThreadStreamMatch && method === 'GET') {
|
|
2417
|
+
const threadId = decodeURIComponent(mirroredThreadStreamMatch[1] || '').trim();
|
|
2418
|
+
if (!threadId) {
|
|
2419
|
+
writeProtocolError(res, 400, 'validation_error', 'missing_thread_id', 'Thread id is required.');
|
|
2420
|
+
return true;
|
|
2421
|
+
}
|
|
2422
|
+
res.writeHead(200, {
|
|
2423
|
+
'Content-Type': 'text/event-stream; charset=utf-8',
|
|
2424
|
+
'Cache-Control': 'no-cache',
|
|
2425
|
+
'Connection': 'keep-alive',
|
|
2426
|
+
'X-Accel-Buffering': 'no',
|
|
2427
|
+
});
|
|
2428
|
+
const afterSequence = lastEventIdFromRequest(req) ??
|
|
2429
|
+
numericQueryParam(coerceSingleQueryParam(url.searchParams.get('after')));
|
|
2430
|
+
const replayAfter = afterSequence ?? readThreadEventLogHead(threadId);
|
|
2431
|
+
addThreadStreamClient(threadId, res);
|
|
2432
|
+
const pingTimer = setInterval(() => {
|
|
2433
|
+
writeSse(res);
|
|
2434
|
+
res.flush?.();
|
|
2435
|
+
}, SPACE_LOG_STREAM_PING_MS);
|
|
2436
|
+
pingTimer.unref?.();
|
|
2437
|
+
const cleanup = () => {
|
|
2438
|
+
clearInterval(pingTimer);
|
|
2439
|
+
removeThreadStreamClient(threadId, res);
|
|
2440
|
+
};
|
|
2441
|
+
req.on('close', cleanup);
|
|
2442
|
+
res.on('close', cleanup);
|
|
2443
|
+
res.flushHeaders?.();
|
|
2444
|
+
res.write('retry: 1000\n');
|
|
2445
|
+
writeSse(res);
|
|
2446
|
+
for (const event of listRecentThreadEventsAfter(threadId, replayAfter, {
|
|
2447
|
+
limit: MIRRORED_THREAD_STREAM_REPLAY_LIMIT,
|
|
2448
|
+
maxBytes: MIRRORED_THREAD_STREAM_REPLAY_MAX_BYTES,
|
|
2449
|
+
})) {
|
|
2450
|
+
const chunk = threadStreamChunkForEvent(event);
|
|
2451
|
+
if (!chunk)
|
|
2452
|
+
continue;
|
|
2453
|
+
writeThreadStreamEvent(res, chunk, { id: event.sequence });
|
|
2454
|
+
}
|
|
2455
|
+
res.flush?.();
|
|
2456
|
+
return true;
|
|
2457
|
+
}
|
|
2458
|
+
const mirroredThreadHistoryMatch = pathname.match(/^\/api\/computer\/codex\/threads\/([^/]+)\/messages$/);
|
|
2459
|
+
if (mirroredThreadHistoryMatch && method === 'GET') {
|
|
2460
|
+
const threadId = decodeURIComponent(mirroredThreadHistoryMatch[1] || '').trim();
|
|
2461
|
+
const limitRaw = coerceSingleQueryParam(url.searchParams.get('limit'));
|
|
2462
|
+
const limit = limitRaw ? Number.parseInt(limitRaw, 10) : MIRRORED_THREAD_HISTORY_LIMIT;
|
|
2463
|
+
const history = threadId
|
|
2464
|
+
? readCodexThreadHistory(threadId, Number.isFinite(limit) ? limit : MIRRORED_THREAD_HISTORY_LIMIT)
|
|
2465
|
+
: null;
|
|
2466
|
+
if (!history) {
|
|
2467
|
+
writeProtocolError(res, 404, 'not_found', 'mirrored_thread_not_found', 'Mirrored task history was not found.');
|
|
2468
|
+
return true;
|
|
2469
|
+
}
|
|
2470
|
+
writeJson(res, 200, history);
|
|
2471
|
+
return true;
|
|
2472
|
+
}
|
|
2473
|
+
const mirroredThreadAdoptMatch = pathname.match(/^\/api\/computer\/codex\/threads\/([^/]+)\/adopt$/);
|
|
2474
|
+
if (mirroredThreadAdoptMatch && method === 'POST') {
|
|
2475
|
+
const threadId = decodeURIComponent(mirroredThreadAdoptMatch[1] || '').trim();
|
|
2476
|
+
const parsed = await readJsonBody(req);
|
|
2477
|
+
const requestedAgentId = parsed && typeof parsed === 'object' && typeof parsed.agent_id === 'string'
|
|
2478
|
+
? String(parsed.agent_id).trim()
|
|
2479
|
+
: '';
|
|
2480
|
+
const fallbackAgentId = getAllAgents()[0]?.agent_id || 'local-agent-mirror';
|
|
2481
|
+
const agentId = requestedAgentId || fallbackAgentId;
|
|
2482
|
+
const session = threadId ? adoptMirroredThread(threadId, agentId) : null;
|
|
2483
|
+
if (!session) {
|
|
2484
|
+
writeProtocolError(res, 404, 'not_found', 'mirrored_thread_not_found', 'Mirrored task history was not found.');
|
|
2485
|
+
return true;
|
|
2486
|
+
}
|
|
2487
|
+
writeJson(res, 200, { thread: serializeAdoptedMirroredThread(session) });
|
|
2488
|
+
return true;
|
|
2489
|
+
}
|
|
2490
|
+
if (pathname === '/api/computer/codex/projects/open' && method === 'POST') {
|
|
2491
|
+
try {
|
|
2492
|
+
const parsed = await readJsonBody(req);
|
|
2493
|
+
const projectPath = parsed && typeof parsed === 'object' && typeof parsed.path === 'string'
|
|
2494
|
+
? String(parsed.path).trim()
|
|
2495
|
+
: '';
|
|
2496
|
+
if (!projectPath || !path.isAbsolute(projectPath) || !fs.existsSync(projectPath)) {
|
|
2497
|
+
writeProtocolError(res, 400, 'validation_error', 'invalid_project_path', 'Request body must include an existing absolute project path.');
|
|
2498
|
+
return true;
|
|
2499
|
+
}
|
|
2500
|
+
const opener = process.platform === 'darwin'
|
|
2501
|
+
? { command: 'open', args: [projectPath] }
|
|
2502
|
+
: process.platform === 'win32'
|
|
2503
|
+
? { command: 'cmd', args: ['/c', 'start', '', projectPath] }
|
|
2504
|
+
: { command: 'xdg-open', args: [projectPath] };
|
|
2505
|
+
const child = spawn(opener.command, opener.args, {
|
|
2506
|
+
detached: true,
|
|
2507
|
+
stdio: 'ignore',
|
|
2508
|
+
});
|
|
2509
|
+
child.unref();
|
|
2510
|
+
writeJson(res, 200, { ok: true });
|
|
2511
|
+
}
|
|
2512
|
+
catch (err) {
|
|
2513
|
+
writeProtocolError(res, 500, 'open_error', 'project_open_failed', err?.message || 'Failed to open project.');
|
|
2514
|
+
}
|
|
2515
|
+
return true;
|
|
2516
|
+
}
|
|
2517
|
+
if (pathname === '/api/computer/mcp/servers' && method === 'GET') {
|
|
2518
|
+
writeJson(res, 200, { servers: buildMcpSettingsResponse().servers });
|
|
2519
|
+
return true;
|
|
2520
|
+
}
|
|
2521
|
+
/** Tools discovered via MCP `tools/list` for each connected server (in-memory; empty if servers not connected). */
|
|
2522
|
+
if (pathname === '/api/computer/mcp/tool-catalog' && method === 'GET') {
|
|
2523
|
+
try {
|
|
2524
|
+
const force = url.searchParams.get('refresh') === '1' || url.searchParams.get('force') === '1';
|
|
2525
|
+
const mgr = await getMcpManager({ forceConfigRefresh: force });
|
|
2526
|
+
const tools = mgr.getAllTools().map((t) => ({
|
|
2527
|
+
name: t.name,
|
|
2528
|
+
description: t.description,
|
|
2529
|
+
inputSchema: t.inputSchema,
|
|
2530
|
+
serverId: t.serverId,
|
|
2531
|
+
serverName: t.serverName,
|
|
2532
|
+
}));
|
|
2533
|
+
const mcpProcessMeta = mgr.getMcpProcessMetaByServerId();
|
|
2534
|
+
writeJson(res, 200, { tools, mcpProcessMeta });
|
|
2535
|
+
}
|
|
2536
|
+
catch {
|
|
2537
|
+
writeJson(res, 200, { tools: [], mcpProcessMeta: [] });
|
|
2538
|
+
}
|
|
2539
|
+
return true;
|
|
2540
|
+
}
|
|
2541
|
+
if (pathname === '/api/computer/mcp/settings' && method === 'GET') {
|
|
2542
|
+
writeJson(res, 200, buildMcpSettingsResponse());
|
|
2543
|
+
return true;
|
|
2544
|
+
}
|
|
2545
|
+
if (pathname === '/api/computer/mcp/settings' && method === 'PUT') {
|
|
2546
|
+
const parsed = await readJsonBody(req);
|
|
2547
|
+
const allowedKeys = new Set(listMcpEnvKeys());
|
|
2548
|
+
const payloadEnv = parsed && typeof parsed === 'object' && parsed.env && typeof parsed.env === 'object'
|
|
2549
|
+
? parsed.env
|
|
2550
|
+
: {};
|
|
2551
|
+
const updates = {};
|
|
2552
|
+
for (const [key, rawValue] of Object.entries(payloadEnv)) {
|
|
2553
|
+
if (!allowedKeys.has(key))
|
|
2554
|
+
continue;
|
|
2555
|
+
if (typeof rawValue !== 'string') {
|
|
2556
|
+
updates[key] = null;
|
|
2557
|
+
continue;
|
|
2558
|
+
}
|
|
2559
|
+
const normalized = rawValue.trim();
|
|
2560
|
+
updates[key] = normalized.length > 0 ? normalized : null;
|
|
2561
|
+
}
|
|
2562
|
+
updateMcpEnvOverrides(updates);
|
|
2563
|
+
writeJson(res, 200, buildMcpSettingsResponse());
|
|
2564
|
+
return true;
|
|
2565
|
+
}
|
|
2566
|
+
if (pathname === '/api/computer' && method === 'GET') {
|
|
2567
|
+
const cpus = os.cpus();
|
|
2568
|
+
const memTotal = os.totalmem();
|
|
2569
|
+
const memFree = os.freemem();
|
|
2570
|
+
const enroll = readEnrollmentState(DAEMON_HOSTNAME || undefined);
|
|
2571
|
+
let disk_total;
|
|
2572
|
+
let disk_free;
|
|
2573
|
+
let disk_used;
|
|
2574
|
+
try {
|
|
2575
|
+
const stats = fs.statfsSync(SUPEN_HOME);
|
|
2576
|
+
disk_total = stats.bsize * stats.blocks;
|
|
2577
|
+
disk_free = stats.bsize * stats.bavail;
|
|
2578
|
+
disk_used = disk_total - disk_free;
|
|
2579
|
+
}
|
|
2580
|
+
catch { /* ignore */ }
|
|
2581
|
+
writeJson(res, 200, {
|
|
2582
|
+
daemon_id: enroll.daemon_id,
|
|
2583
|
+
hostname: os.hostname(),
|
|
2584
|
+
version: process.env.SUPEN_DAEMON_VERSION || process.env.npm_package_version || null,
|
|
2585
|
+
runtime: {
|
|
2586
|
+
node: process.version,
|
|
2587
|
+
platform: process.platform,
|
|
2588
|
+
arch: process.arch,
|
|
2589
|
+
},
|
|
2590
|
+
build: {
|
|
2591
|
+
git_sha: process.env.SUPEN_DAEMON_GIT_SHA || process.env.RENDER_GIT_COMMIT || null,
|
|
2592
|
+
image: process.env.RENDER_COMPUTER_IMAGE || process.env.SUPEN_DAEMON_IMAGE || null,
|
|
2593
|
+
},
|
|
2594
|
+
enrollment: {
|
|
2595
|
+
trust_state: enroll.trust_state,
|
|
2596
|
+
fingerprint: enroll.daemon_fingerprint,
|
|
2597
|
+
trusted_at: enroll.trusted_at ?? null,
|
|
2598
|
+
failed_attempts: enroll.failed_attempts,
|
|
2599
|
+
},
|
|
2600
|
+
executor: {
|
|
2601
|
+
active_tasks: 0,
|
|
2602
|
+
queued_tasks: 0,
|
|
2603
|
+
total_slots: 0,
|
|
2604
|
+
},
|
|
2605
|
+
os: {
|
|
2606
|
+
hostname: os.hostname(),
|
|
2607
|
+
platform: os.platform(),
|
|
2608
|
+
arch: os.arch(),
|
|
2609
|
+
cpus: cpus.length,
|
|
2610
|
+
cpu_model: cpus[0]?.model || 'Unknown',
|
|
2611
|
+
load_avg: os.loadavg(),
|
|
2612
|
+
mem_total: memTotal,
|
|
2613
|
+
mem_free: memFree,
|
|
2614
|
+
mem_used: memTotal - memFree,
|
|
2615
|
+
uptime: os.uptime(),
|
|
2616
|
+
disk_total,
|
|
2617
|
+
disk_free,
|
|
2618
|
+
disk_used,
|
|
2619
|
+
},
|
|
2620
|
+
});
|
|
2621
|
+
return true;
|
|
2622
|
+
}
|
|
2623
|
+
// ── Enrollment ──
|
|
2624
|
+
if (pathname === '/api/computer/enroll/status' && method === 'GET') {
|
|
2625
|
+
const state = readEnrollmentState(DAEMON_HOSTNAME || undefined);
|
|
2626
|
+
writeJson(res, 200, state);
|
|
2627
|
+
return true;
|
|
2628
|
+
}
|
|
2629
|
+
if (pathname === '/api/computer/enroll/verify' && method === 'POST') {
|
|
2630
|
+
const parsed = await readJsonBody(req);
|
|
2631
|
+
const result = verifyEnrollmentToken({
|
|
2632
|
+
token: parsed.token,
|
|
2633
|
+
daemonFingerprint: parsed.daemon_fingerprint,
|
|
2634
|
+
daemonId: DAEMON_HOSTNAME || undefined,
|
|
2635
|
+
});
|
|
2636
|
+
writeJson(res, result.ok ? 200 : 401, result);
|
|
2637
|
+
return true;
|
|
2638
|
+
}
|
|
2639
|
+
if (pathname === '/api/computer/trust' && method === 'POST') {
|
|
2640
|
+
writeProtocolError(res, 410, 'auth_error', 'trust_endpoint_removed', 'Direct trust elevation is disabled. Use the enrollment flow (/api/computer/enroll/token + /api/computer/enroll/verify) to transition a daemon to trusted state.');
|
|
2641
|
+
return true;
|
|
2642
|
+
}
|
|
2643
|
+
// Create enrollment token
|
|
2644
|
+
if (pathname === '/api/computer/enroll/token' && method === 'POST') {
|
|
2645
|
+
try {
|
|
2646
|
+
const parsed = await readJsonBody(req);
|
|
2647
|
+
const ttlMinutes = parsed?.ttl_minutes || DEFAULT_TOKEN_TTL_MINUTES;
|
|
2648
|
+
const result = createEnrollmentToken({
|
|
2649
|
+
ttlMinutes,
|
|
2650
|
+
daemonId: DAEMON_HOSTNAME || undefined,
|
|
2651
|
+
});
|
|
2652
|
+
writeJson(res, 200, {
|
|
2653
|
+
ok: true,
|
|
2654
|
+
token: result.token,
|
|
2655
|
+
expires_at: result.expires_at,
|
|
2656
|
+
});
|
|
2657
|
+
}
|
|
2658
|
+
catch (err) {
|
|
2659
|
+
writeProtocolError(res, 500, 'enrollment_error', 'token_creation_failed', err.message);
|
|
2660
|
+
}
|
|
2661
|
+
return true;
|
|
2662
|
+
}
|
|
2663
|
+
// Revoke enrollment
|
|
2664
|
+
if (pathname === '/api/computer/enroll/revoke' && method === 'POST') {
|
|
2665
|
+
try {
|
|
2666
|
+
setTrustState('revoked', { daemonId: DAEMON_HOSTNAME || undefined });
|
|
2667
|
+
writeJson(res, 200, { ok: true, trust_state: 'revoked' });
|
|
2668
|
+
}
|
|
2669
|
+
catch (err) {
|
|
2670
|
+
writeProtocolError(res, 500, 'enrollment_error', 'revoke_failed', err.message);
|
|
2671
|
+
}
|
|
2672
|
+
return true;
|
|
2673
|
+
}
|
|
2674
|
+
return false;
|
|
2675
|
+
}
|
|
2676
|
+
//# sourceMappingURL=system.js.map
|