@gajae-code/coding-agent 0.13.2 → 0.14.0
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/CHANGELOG.md +215 -8
- package/README.md +2 -0
- package/bin/gajaessi.js +4 -0
- package/dist/types/async/job-manager.d.ts +55 -2
- package/dist/types/async/support.d.ts +14 -0
- package/dist/types/capability/mcp.d.ts +6 -0
- package/dist/types/capability/types.d.ts +6 -0
- package/dist/types/cli/accounts-cli.d.ts +19 -0
- package/dist/types/cli/args.d.ts +3 -0
- package/dist/types/cli/config-cli.d.ts +6 -0
- package/dist/types/cli/crash-cli.d.ts +10 -0
- package/dist/types/cli/customize-doctor.d.ts +145 -0
- package/dist/types/cli/daemon-cli.d.ts +3 -1
- package/dist/types/cli/mcp-cli.d.ts +11 -0
- package/dist/types/cli/notify-cli.d.ts +1 -1
- package/dist/types/cli/root-flags.d.ts +12 -0
- package/dist/types/cli/setup-cli.d.ts +3 -1
- package/dist/types/cli/skills-cli.d.ts +2 -1
- package/dist/types/cli/update-cli.d.ts +19 -1
- package/dist/types/cli.d.ts +6 -0
- package/dist/types/commands/accounts.d.ts +40 -0
- package/dist/types/commands/crash.d.ts +24 -0
- package/dist/types/commands/customize.d.ts +22 -0
- package/dist/types/commands/daemon.d.ts +1 -1
- package/dist/types/commands/launch.d.ts +6 -0
- package/dist/types/commands/setup.d.ts +6 -0
- package/dist/types/commands/skills.d.ts +6 -1
- package/dist/types/commands/update.d.ts +7 -0
- package/dist/types/config/atomic-yaml-patch-worker.d.ts +25 -0
- package/dist/types/config/atomic-yaml-patch.d.ts +62 -2
- package/dist/types/config/config-schema-version.d.ts +10 -0
- package/dist/types/config/file-lock.d.ts +18 -0
- package/dist/types/config/model-profile-activation.d.ts +3 -1
- package/dist/types/config/model-registry.d.ts +10 -1
- package/dist/types/config/models-config-schema.d.ts +2 -0
- package/dist/types/config/settings-schema.d.ts +73 -9
- package/dist/types/config/settings.d.ts +63 -0
- package/dist/types/config/skill-settings-defaults.d.ts +26 -0
- package/dist/types/config/task-delegation.d.ts +39 -0
- package/dist/types/config/update-channel.d.ts +10 -0
- package/dist/types/coordinator-mcp/server.d.ts +5 -1
- package/dist/types/crash/index-store.d.ts +87 -0
- package/dist/types/crash/nudge.d.ts +50 -0
- package/dist/types/crash/record-loader.d.ts +17 -0
- package/dist/types/crash/report.d.ts +134 -0
- package/dist/types/crash/sanitize.d.ts +25 -0
- package/dist/types/cursor.d.ts +13 -2
- package/dist/types/customization/import.d.ts +37 -0
- package/dist/types/customization/index.d.ts +4 -0
- package/dist/types/customization/inventory.d.ts +25 -0
- package/dist/types/customization/mutations.d.ts +32 -0
- package/dist/types/customization/types.d.ts +115 -0
- package/dist/types/daemon/control-types.d.ts +8 -1
- package/dist/types/daemon/operator-contract.d.ts +4 -4
- package/dist/types/discovery/claude.d.ts +39 -1
- package/dist/types/discovery/codex.d.ts +39 -1
- package/dist/types/discovery/index.d.ts +2 -0
- package/dist/types/discovery/mcp-compat.d.ts +30 -0
- package/dist/types/edit/renderer.d.ts +23 -0
- package/dist/types/extensibility/extensions/runner.d.ts +3 -0
- package/dist/types/extensibility/extensions/types.d.ts +21 -4
- package/dist/types/extensibility/gjc-plugins/observability.d.ts +6 -1
- package/dist/types/extensibility/gjc-plugins/registry.d.ts +8 -1
- package/dist/types/extensibility/hooks/loader.d.ts +19 -5
- package/dist/types/extensibility/runtime-skill-discovery.d.ts +22 -1
- package/dist/types/extensibility/skill-management.d.ts +107 -0
- package/dist/types/gjc-runtime/deep-interview-runtime.d.ts +3 -1
- package/dist/types/gjc-runtime/launch-worktree.d.ts +8 -0
- package/dist/types/gjc-runtime/linux-proc.d.ts +3 -0
- package/dist/types/gjc-runtime/ralplan-runtime.d.ts +10 -6
- package/dist/types/gjc-runtime/session-state-lock.d.ts +104 -0
- package/dist/types/gjc-runtime/session-state-sidecar.d.ts +120 -2
- package/dist/types/gjc-runtime/state-writer.d.ts +10 -1
- package/dist/types/gjc-runtime/ultragoal-guard.d.ts +4 -2
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +8 -6
- package/dist/types/gjc-runtime/workflow-settings.d.ts +109 -0
- package/dist/types/hooks/events.d.ts +60 -0
- package/dist/types/hooks/normalize.d.ts +83 -0
- package/dist/types/hooks/skill-state.d.ts +9 -2
- package/dist/types/main.d.ts +2 -1
- package/dist/types/modes/acp/abort-scope.d.ts +13 -0
- package/dist/types/modes/acp/acp-agent.d.ts +7 -4
- package/dist/types/modes/components/customization/customization-dashboard.d.ts +44 -0
- package/dist/types/modes/components/customization/import-wizard.d.ts +21 -0
- package/dist/types/modes/components/customization/index.d.ts +1 -0
- package/dist/types/modes/components/dynamic-theme-text.d.ts +9 -0
- package/dist/types/modes/components/gajae-pet-widget.d.ts +6 -28
- package/dist/types/modes/components/irc-sidebar.d.ts +1 -0
- package/dist/types/modes/components/iterm-pet-transport.d.ts +122 -0
- package/dist/types/modes/components/oauth-selector.d.ts +10 -4
- package/dist/types/modes/components/pet-capability.d.ts +7 -21
- package/dist/types/modes/components/settings-selector.d.ts +5 -0
- package/dist/types/modes/components/status-line/gh.d.ts +6 -2
- package/dist/types/modes/controllers/extension-ui-controller.d.ts +1 -1
- package/dist/types/modes/controllers/selector-controller.d.ts +7 -8
- package/dist/types/modes/controllers/stt-controller.d.ts +1 -1
- package/dist/types/modes/interactive-mode.d.ts +1 -0
- package/dist/types/modes/theme/defaults/index.d.ts +118 -0
- package/dist/types/modes/types.d.ts +1 -0
- package/dist/types/modes/utils/injected-user-submission.d.ts +10 -0
- package/dist/types/runtime-mcp/config.d.ts +9 -0
- package/dist/types/runtime-mcp/manager.d.ts +26 -1
- package/dist/types/runtime-mcp/oauth-discovery.d.ts +4 -0
- package/dist/types/runtime-mcp/oauth-flow.d.ts +24 -1
- package/dist/types/runtime-mcp/pool-key.d.ts +2 -0
- package/dist/types/runtime-mcp/protocol.d.ts +278 -0
- package/dist/types/runtime-mcp/scope-config.d.ts +29 -0
- package/dist/types/runtime-mcp/tool-bridge.d.ts +15 -13
- package/dist/types/runtime-mcp/transports/http.d.ts +9 -0
- package/dist/types/runtime-mcp/types.d.ts +102 -10
- package/dist/types/sdk/acp/adapter.d.ts +26 -5
- package/dist/types/sdk/broker/broker.d.ts +3 -55
- package/dist/types/sdk/broker/lifecycle.d.ts +18 -12
- package/dist/types/sdk/broker/session-index.d.ts +14 -5
- package/dist/types/sdk/broker/startup-failure.d.ts +16 -1
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +51 -1
- package/dist/types/sdk/bus/chat-daemon-runtime.d.ts +4 -41
- package/dist/types/sdk/bus/config.d.ts +11 -0
- package/dist/types/sdk/bus/conversation-store.d.ts +0 -1
- package/dist/types/sdk/bus/daemon-paths.d.ts +6 -0
- package/dist/types/sdk/bus/discord-conversation.d.ts +2 -0
- package/dist/types/sdk/bus/discord-daemon.d.ts +17 -12
- package/dist/types/sdk/bus/existing-thread-readiness.d.ts +4 -34
- package/dist/types/sdk/bus/inbound-reaction-ordering.d.ts +61 -0
- package/dist/types/sdk/bus/index.d.ts +85 -99
- package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +29 -7
- package/dist/types/sdk/bus/managed-daemon.d.ts +2 -32
- package/dist/types/sdk/bus/notification-service.d.ts +52 -0
- package/dist/types/sdk/bus/operator-runtime.d.ts +1 -1
- package/dist/types/sdk/bus/prompt-reconciliation.d.ts +16 -5
- package/dist/types/sdk/bus/reconciliation-store.d.ts +113 -5
- package/dist/types/sdk/bus/session-control.d.ts +25 -1
- package/dist/types/sdk/bus/slack-conversation.d.ts +4 -0
- package/dist/types/sdk/bus/slack-daemon.d.ts +9 -17
- package/dist/types/sdk/bus/slack-live-provider.d.ts +3 -1
- package/dist/types/sdk/bus/slack-provider.d.ts +4 -0
- package/dist/types/sdk/bus/slack-thread-binding.d.ts +6 -40
- package/dist/types/sdk/bus/telegram-adoption-intent.d.ts +30 -104
- package/dist/types/sdk/bus/telegram-daemon-cli.d.ts +10 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +262 -19
- package/dist/types/sdk/bus/telegram-daemon-orphan-reap.d.ts +125 -0
- package/dist/types/sdk/bus/telegram-daemon-owner-registry.d.ts +22 -0
- package/dist/types/sdk/bus/telegram-daemon.d.ts +119 -310
- package/dist/types/sdk/bus/telegram-reference.d.ts +1 -47
- package/dist/types/sdk/bus/threaded-render.d.ts +1 -0
- package/dist/types/sdk/bus/topic-registry.d.ts +33 -17
- package/dist/types/sdk/cli/rows.d.ts +2 -11
- package/dist/types/sdk/cli/session-cli.d.ts +15 -25
- package/dist/types/sdk/client/client.d.ts +97 -1
- package/dist/types/sdk/host/control/dispatch.d.ts +0 -7
- package/dist/types/sdk/host/control/index.d.ts +1 -1
- package/dist/types/sdk/host/control/operations.d.ts +18 -5
- package/dist/types/sdk/host/control/runtime-gate.d.ts +19 -0
- package/dist/types/sdk/host/host.d.ts +15 -1
- package/dist/types/sdk/host/query/handlers.d.ts +5 -0
- package/dist/types/sdk/host/reverse-leases.d.ts +1 -1
- package/dist/types/sdk/host/session-runtime.d.ts +127 -7
- package/dist/types/sdk/host/types.d.ts +1 -1
- package/dist/types/sdk/host/websocket-transport.d.ts +1 -1
- package/dist/types/sdk/index.d.ts +2 -1
- package/dist/types/sdk/lifecycle/broker-client.d.ts +8 -0
- package/dist/types/sdk/lifecycle/client.d.ts +59 -0
- package/dist/types/sdk/lifecycle/index.d.ts +3 -0
- package/dist/types/sdk/lifecycle/service.d.ts +232 -0
- package/dist/types/sdk/mcp/server.d.ts +9 -6
- package/dist/types/sdk/prompt-deadline-lease.d.ts +43 -0
- package/dist/types/sdk/prompt-deadline-manager.d.ts +28 -0
- package/dist/types/sdk/prompt-status.d.ts +10 -3
- package/dist/types/sdk/protocol/operation-registry.d.ts +5 -0
- package/dist/types/sdk/receipt-state.d.ts +17 -0
- package/dist/types/sdk/reconciliation-extensions.d.ts +2 -0
- package/dist/types/sdk/router/index.d.ts +1 -0
- package/dist/types/sdk/router/session-router.d.ts +152 -0
- package/dist/types/sdk/session-list.d.ts +22 -0
- package/dist/types/sdk/session-reconnect.d.ts +43 -1
- package/dist/types/sdk/session.d.ts +34 -1
- package/dist/types/sdk/transport/serve-cli.d.ts +1 -4
- package/dist/types/session/account-inventory.d.ts +86 -0
- package/dist/types/session/agent-session.d.ts +145 -6
- package/dist/types/session/agent-storage.d.ts +10 -0
- package/dist/types/session/coordinator-tool-label.d.ts +28 -0
- package/dist/types/session/internal/managed-session-storage.d.ts +23 -4
- package/dist/types/session/messages.d.ts +1 -1
- package/dist/types/session/secure-token-file.d.ts +3 -0
- package/dist/types/session/session-manager.d.ts +47 -1
- package/dist/types/session/startup-auth-config.d.ts +44 -0
- package/dist/types/session/terminal-abort.d.ts +378 -0
- package/dist/types/session/yield-queue.d.ts +7 -0
- package/dist/types/session-import/claude.d.ts +26 -0
- package/dist/types/session-import/codex.d.ts +88 -0
- package/dist/types/session-import/command.d.ts +35 -0
- package/dist/types/session-import/detect.d.ts +22 -0
- package/dist/types/session-import/index.d.ts +7 -0
- package/dist/types/session-import/provider-codex.d.ts +22 -0
- package/dist/types/session-import/provider-service.d.ts +38 -0
- package/dist/types/session-import/redact.d.ts +23 -0
- package/dist/types/session-import/service.d.ts +47 -0
- package/dist/types/session-import/types.d.ts +120 -0
- package/dist/types/setup/paseo/check.d.ts +16 -0
- package/dist/types/setup/paseo/install-saga.d.ts +115 -0
- package/dist/types/setup/paseo/json-publisher.d.ts +76 -0
- package/dist/types/setup/paseo/orchestration-preferences.d.ts +24 -0
- package/dist/types/setup/paseo/paseo-ownership.d.ts +87 -0
- package/dist/types/setup/paseo/paseo-setup.d.ts +29 -0
- package/dist/types/setup/paseo/provider-config.d.ts +35 -0
- package/dist/types/setup/paseo/remove.d.ts +14 -0
- package/dist/types/setup/paseo/result-types.d.ts +59 -0
- package/dist/types/setup/paseo/setup-deps.d.ts +73 -0
- package/dist/types/setup/paseo/skills-bridge.d.ts +44 -0
- package/dist/types/skill-state/active-state.d.ts +5 -1
- package/dist/types/slash-commands/acp-builtins.d.ts +5 -1
- package/dist/types/slash-commands/builtin-registry.d.ts +2 -0
- package/dist/types/slash-commands/helpers/credential-switch.d.ts +12 -0
- package/dist/types/slash-commands/helpers/usage-report.d.ts +5 -6
- package/dist/types/slash-commands/types.d.ts +7 -1
- package/dist/types/task/executor.d.ts +10 -0
- package/dist/types/task/index.d.ts +3 -1
- package/dist/types/tools/ask-answer-registry.d.ts +6 -3
- package/dist/types/tools/bash-sleep-advisory.d.ts +14 -0
- package/dist/types/tools/bash.d.ts +2 -6
- package/dist/types/tools/browser/attach.d.ts +11 -0
- package/dist/types/tools/browser/launch.d.ts +20 -0
- package/dist/types/tools/browser/profile-discovery.d.ts +6 -0
- package/dist/types/tools/browser/render.d.ts +1 -0
- package/dist/types/tools/browser.d.ts +3 -1
- package/dist/types/tools/image-gen.d.ts +10 -2
- package/dist/types/tools/index.d.ts +8 -0
- package/dist/types/tools/monitor.d.ts +1 -1
- package/dist/types/tools/skill-discovery.d.ts +8 -1
- package/dist/types/tools/subagent.d.ts +2 -0
- package/dist/types/tools/ultragoal-ask-guard.d.ts +2 -2
- package/dist/types/utils/herdr-pane.d.ts +76 -0
- package/package.json +19 -9
- package/scripts/build-binary.ts +2 -1
- package/scripts/build-sdk-package-smoke.ts +2 -13
- package/scripts/capture-sticky-viewport-showcase.ts +0 -1
- package/scripts/compile-args.ts +2 -0
- package/scripts/generate-sdk-adapter-parity-manifest.ts +31 -16
- package/scripts/generate-sdk-operation-inventory.ts +28 -0
- package/scripts/generate-telegram-baseline-manifest.ts +1 -0
- package/scripts/generate-tool-catalog.ts +6 -4
- package/scripts/verify-gjc-sdk-canonicalization.ts +5 -39
- package/src/async/job-manager.ts +352 -17
- package/src/async/support.ts +25 -0
- package/src/autoresearch/dashboard.ts +28 -13
- package/src/capability/index.ts +3 -1
- package/src/capability/mcp.ts +6 -0
- package/src/capability/types.ts +6 -0
- package/src/cli/accounts-cli.ts +395 -0
- package/src/cli/args.ts +23 -0
- package/src/cli/auth-broker-cli.ts +136 -66
- package/src/cli/auth-gateway-cli.ts +94 -70
- package/src/cli/config-cli.ts +27 -2
- package/src/cli/crash-cli.ts +78 -0
- package/src/cli/customize-doctor.ts +1605 -0
- package/src/cli/daemon-cli.ts +12 -3
- package/src/cli/fast-help.ts +1 -0
- package/src/cli/mcp-cli.ts +128 -60
- package/src/cli/notify-cli.ts +8 -14
- package/src/cli/root-flags.ts +8 -0
- package/src/cli/setup-cli.ts +145 -0
- package/src/cli/skills-cli.ts +38 -2
- package/src/cli/update-cli.ts +167 -12
- package/src/cli.ts +4 -0
- package/src/commands/accounts.ts +71 -0
- package/src/commands/crash.ts +35 -0
- package/src/commands/customize.ts +84 -0
- package/src/commands/daemon.ts +2 -2
- package/src/commands/deep-interview.ts +2 -0
- package/src/commands/launch.ts +1 -0
- package/src/commands/mcp.ts +12 -7
- package/src/commands/ralplan.ts +2 -0
- package/src/commands/sdk.ts +65 -32
- package/src/commands/setup.ts +5 -0
- package/src/commands/skills.ts +14 -4
- package/src/commands/ultragoal.ts +10 -1
- package/src/commands/update.ts +26 -15
- package/src/config/atomic-yaml-patch-worker.ts +57 -0
- package/src/config/atomic-yaml-patch.ts +769 -29
- package/src/config/config-schema-version.ts +10 -0
- package/src/config/file-lock.ts +70 -1
- package/src/config/mcp-schema.json +52 -14
- package/src/config/model-profile-activation.ts +23 -2
- package/src/config/model-profiles.ts +88 -21
- package/src/config/model-registry.ts +147 -21
- package/src/config/models-config-schema.ts +1 -1
- package/src/config/settings-schema.ts +77 -7
- package/src/config/settings.ts +4038 -199
- package/src/config/skill-settings-defaults.ts +41 -3
- package/src/config/task-delegation.ts +87 -0
- package/src/config/update-channel.ts +23 -0
- package/src/coordinator-mcp/server.ts +377 -308
- package/src/crash/index-store.ts +723 -0
- package/src/crash/nudge.ts +89 -0
- package/src/crash/record-loader.ts +149 -0
- package/src/crash/report.ts +551 -0
- package/src/crash/sanitize.ts +129 -0
- package/src/cursor.ts +36 -10
- package/src/customization/import.ts +946 -0
- package/src/customization/index.ts +4 -0
- package/src/customization/inventory.ts +335 -0
- package/src/customization/mutations.ts +141 -0
- package/src/customization/types.ts +223 -0
- package/src/daemon/control-types.ts +8 -1
- package/src/daemon/operator-contract.ts +6 -6
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/models/catalog.ts +31 -6
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +11 -22
- package/src/defaults/gjc/skills/ralplan/SKILL.md +36 -23
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +16 -0
- package/src/discovery/claude-hooks.ts +38 -0
- package/src/discovery/claude.ts +142 -209
- package/src/discovery/codex-hooks.ts +38 -0
- package/src/discovery/codex.ts +132 -198
- package/src/discovery/helpers.ts +8 -1
- package/src/discovery/index.ts +2 -0
- package/src/discovery/mcp-compat.ts +234 -0
- package/src/discovery/mcp-json.ts +6 -0
- package/src/edit/renderer.ts +40 -0
- package/src/edit/streaming.ts +4 -2
- package/src/exec/bash-executor.ts +24 -20
- package/src/export/html/index.ts +51 -2
- package/src/extensibility/extensions/loader.ts +9 -1
- package/src/extensibility/extensions/runner.ts +17 -4
- package/src/extensibility/extensions/types.ts +31 -6
- package/src/extensibility/gjc-plugins/constrained-hooks.ts +33 -0
- package/src/extensibility/gjc-plugins/installer.ts +9 -5
- package/src/extensibility/gjc-plugins/observability.ts +8 -2
- package/src/extensibility/gjc-plugins/registry.ts +13 -2
- package/src/extensibility/gjc-plugins/runtime-adapters.ts +3 -1
- package/src/extensibility/gjc-plugins/schema.ts +222 -39
- package/src/extensibility/hooks/loader.ts +154 -10
- package/src/extensibility/runtime-skill-discovery.ts +257 -60
- package/src/extensibility/skill-management.ts +368 -0
- package/src/extensibility/skills.ts +52 -16
- package/src/gjc-runtime/deep-interview-runtime.ts +45 -61
- package/src/gjc-runtime/launch-worktree.ts +56 -2
- package/src/gjc-runtime/linux-proc.ts +7 -1
- package/src/gjc-runtime/ralplan-runtime.ts +100 -164
- package/src/gjc-runtime/session-state-lock.ts +992 -0
- package/src/gjc-runtime/session-state-sidecar.ts +644 -142
- package/src/gjc-runtime/state-writer.ts +39 -14
- package/src/gjc-runtime/tmux-owner-isolation.ts +5 -4
- package/src/gjc-runtime/ultragoal-guard.ts +11 -4
- package/src/gjc-runtime/ultragoal-runtime.ts +44 -41
- package/src/gjc-runtime/workflow-settings.ts +596 -0
- package/src/goals/tools/goal-tool.ts +6 -1
- package/src/hooks/events.ts +291 -0
- package/src/hooks/native-skill-hook.ts +4 -0
- package/src/hooks/normalize.ts +369 -0
- package/src/hooks/skill-state.ts +165 -46
- package/src/internal-urls/docs-index.generated.ts +41 -32
- package/src/main.ts +36 -5
- package/src/migrate/mcp-mapper.ts +25 -2
- package/src/modes/acp/abort-scope.ts +29 -0
- package/src/modes/acp/acp-agent.ts +423 -98
- package/src/modes/components/assistant-message.ts +6 -1
- package/src/modes/components/chrome.ts +4 -3
- package/src/modes/components/customization/customization-dashboard.ts +443 -0
- package/src/modes/components/customization/import-wizard.ts +372 -0
- package/src/modes/components/customization/index.ts +1 -0
- package/src/modes/components/dynamic-theme-text.ts +22 -0
- package/src/modes/components/gajae-pet-widget.ts +607 -193
- package/src/modes/components/irc-sidebar.ts +11 -0
- package/src/modes/components/iterm-pet-transport.ts +585 -0
- package/src/modes/components/model-selector.ts +1 -6
- package/src/modes/components/oauth-selector.ts +277 -86
- package/src/modes/components/pet-capability.ts +35 -31
- package/src/modes/components/settings-selector.ts +29 -5
- package/src/modes/components/status-line/gh.ts +45 -1
- package/src/modes/components/status-line/segments.ts +19 -4
- package/src/modes/components/tool-status-header.ts +13 -3
- package/src/modes/controllers/command-controller.ts +20 -8
- package/src/modes/controllers/extension-ui-controller.ts +56 -257
- package/src/modes/controllers/input-controller.ts +10 -0
- package/src/modes/controllers/runtime-mcp-command-controller.ts +16 -3
- package/src/modes/controllers/selector-controller.ts +238 -77
- package/src/modes/controllers/stt-controller.ts +3 -3
- package/src/modes/interactive-mode.ts +88 -16
- package/src/modes/print-mode.ts +35 -6
- package/src/modes/runtime-init.ts +21 -68
- package/src/modes/theme/defaults/index.ts +2 -0
- package/src/modes/theme/defaults/ouroboros.json +118 -0
- package/src/modes/types.ts +1 -0
- package/src/modes/utils/injected-user-submission.ts +25 -0
- package/src/modes/utils/terminal-bell.ts +15 -1
- package/src/prompts/agent-fragments/ralplan-persistence.md +1 -1
- package/src/prompts/agent-fragments/restricted-bash.md +1 -1
- package/src/prompts/agents/critic.md +1 -1
- package/src/prompts/system/rlm-research.md +1 -1
- package/src/prompts/system/system-prompt.md +0 -2
- package/src/prompts/tools/bash.md +7 -1
- package/src/prompts/tools/browser.md +1 -1
- package/src/prompts/tools/cron.md +6 -6
- package/src/prompts/tools/read.md +0 -2
- package/src/prompts/tools/skill-discovery.md +4 -2
- package/src/runtime-mcp/client.ts +427 -23
- package/src/runtime-mcp/config.ts +37 -5
- package/src/runtime-mcp/manager-pool.test.ts +56 -0
- package/src/runtime-mcp/manager.ts +52 -2
- package/src/runtime-mcp/oauth-discovery.ts +13 -0
- package/src/runtime-mcp/oauth-flow.ts +46 -1
- package/src/runtime-mcp/pool-key.ts +5 -1
- package/src/runtime-mcp/pool.test.ts +9 -0
- package/src/runtime-mcp/protocol.test.ts +353 -0
- package/src/runtime-mcp/protocol.ts +624 -0
- package/src/runtime-mcp/scope-config.ts +78 -0
- package/src/runtime-mcp/tool-bridge.ts +44 -10
- package/src/runtime-mcp/transports/http.ts +84 -13
- package/src/runtime-mcp/types.ts +123 -12
- package/src/sdk/acp/adapter.ts +328 -97
- package/src/sdk/broker/broker.ts +160 -741
- package/src/sdk/broker/ensure.ts +9 -1
- package/src/sdk/broker/lifecycle.ts +510 -116
- package/src/sdk/broker/operation-receipt-ledger.ts +11 -2
- package/src/sdk/broker/process-incarnation.ts +19 -0
- package/src/sdk/broker/session-index.ts +516 -117
- package/src/sdk/broker/startup-failure.ts +28 -5
- package/src/sdk/broker/transport.ts +1 -13
- package/src/sdk/bus/chat-daemon-cli.ts +4 -2
- package/src/sdk/bus/chat-daemon-control.ts +67 -6
- package/src/sdk/bus/chat-daemon-runtime.ts +183 -859
- package/src/sdk/bus/config.ts +13 -0
- package/src/sdk/bus/conversation-store.ts +59 -171
- package/src/sdk/bus/daemon-paths.ts +20 -0
- package/src/sdk/bus/discord-conversation.ts +2 -0
- package/src/sdk/bus/discord-daemon.ts +524 -126
- package/src/sdk/bus/discord-live-provider.ts +206 -79
- package/src/sdk/bus/existing-thread-readiness.ts +4 -97
- package/src/sdk/bus/inbound-reaction-ordering.ts +122 -0
- package/src/sdk/bus/index.ts +2480 -456
- package/src/sdk/bus/kind-aware-reconciliation.ts +204 -34
- package/src/sdk/bus/lifecycle-commands.ts +2 -2
- package/src/sdk/bus/managed-daemon.ts +2 -145
- package/src/sdk/bus/notification-service.ts +196 -0
- package/src/sdk/bus/operator-runtime.ts +1 -1
- package/src/sdk/bus/prompt-reconciliation.ts +24 -15
- package/src/sdk/bus/reconciliation-store.ts +553 -24
- package/src/sdk/bus/session-control.ts +49 -3
- package/src/sdk/bus/slack-conversation.ts +4 -0
- package/src/sdk/bus/slack-daemon.ts +733 -134
- package/src/sdk/bus/slack-live-provider.ts +65 -15
- package/src/sdk/bus/slack-provider.ts +4 -0
- package/src/sdk/bus/slack-thread-binding.ts +9 -104
- package/src/sdk/bus/telegram-adoption-intent.ts +299 -209
- package/src/sdk/bus/telegram-daemon-cli.ts +194 -7
- package/src/sdk/bus/telegram-daemon-contract.ts +265 -19
- package/src/sdk/bus/telegram-daemon-control.ts +3 -13
- package/src/sdk/bus/telegram-daemon-orphan-reap.ts +498 -0
- package/src/sdk/bus/telegram-daemon-owner-registry.ts +176 -0
- package/src/sdk/bus/telegram-daemon.ts +2552 -2280
- package/src/sdk/bus/telegram-reference.ts +1 -226
- package/src/sdk/bus/telegram-setup.ts +2 -2
- package/src/sdk/bus/threaded-render.ts +1 -0
- package/src/sdk/bus/topic-registry.ts +279 -146
- package/src/sdk/cli/rows.ts +2 -35
- package/src/sdk/cli/session-cli.ts +776 -700
- package/src/sdk/client/client.ts +892 -1
- package/src/sdk/host/control/dispatch.ts +93 -58
- package/src/sdk/host/control/index.ts +1 -1
- package/src/sdk/host/control/operations.ts +18 -10
- package/src/sdk/host/control/runtime-gate.ts +40 -0
- package/src/sdk/host/host.ts +63 -12
- package/src/sdk/host/query/handlers.ts +66 -7
- package/src/sdk/host/reverse-leases.ts +2 -2
- package/src/sdk/host/sdk-surface-parity.test.ts +1 -1
- package/src/sdk/host/session-runtime.test.ts +2787 -34
- package/src/sdk/host/session-runtime.ts +1489 -101
- package/src/sdk/host/surface-policy.ts +12 -8
- package/src/sdk/host/types.ts +1 -1
- package/src/sdk/host/websocket-transport.lifecycle.test.ts +31 -59
- package/src/sdk/host/websocket-transport.ts +85 -50
- package/src/sdk/index.ts +2 -1
- package/src/sdk/lifecycle/broker-client.ts +53 -0
- package/src/sdk/lifecycle/client.ts +234 -0
- package/src/sdk/lifecycle/index.ts +3 -0
- package/src/sdk/{bus/recent-activity.ts → lifecycle/recent-sessions.ts} +5 -2
- package/src/sdk/lifecycle/service.ts +720 -0
- package/src/sdk/mcp/server.ts +226 -188
- package/src/sdk/prompt-deadline-lease.ts +55 -0
- package/src/sdk/prompt-deadline-manager.ts +148 -0
- package/src/sdk/prompt-status.ts +11 -3
- package/src/sdk/protocol/operation-inventory.generated.json +272 -129
- package/src/sdk/protocol/operation-registry.ts +71 -8
- package/src/sdk/receipt-state.ts +43 -0
- package/src/sdk/reconciliation-extensions.ts +14 -0
- package/src/sdk/router/index.ts +1 -0
- package/src/sdk/router/session-router.ts +1658 -0
- package/src/sdk/session-list.ts +93 -0
- package/src/sdk/session-reconnect.ts +45 -1
- package/src/sdk/session.ts +716 -120
- package/src/sdk/transport/serve-cli.ts +18 -34
- package/src/session/account-inventory.ts +603 -0
- package/src/session/agent-session.ts +2873 -559
- package/src/session/agent-storage.ts +35 -0
- package/src/session/blob-store.ts +32 -1
- package/src/session/coordinator-tool-label.ts +40 -0
- package/src/session/internal/managed-session-scope.ts +4 -0
- package/src/session/internal/managed-session-storage.ts +199 -31
- package/src/session/messages.ts +1 -1
- package/src/session/secure-token-file.ts +80 -0
- package/src/session/session-manager.ts +757 -57
- package/src/session/session-storage.ts +25 -16
- package/src/session/startup-auth-config.ts +297 -0
- package/src/session/terminal-abort.ts +1253 -0
- package/src/session/yield-queue.ts +57 -15
- package/src/session-import/claude.ts +334 -0
- package/src/session-import/codex.ts +1033 -0
- package/src/session-import/command.ts +77 -0
- package/src/session-import/detect.ts +133 -0
- package/src/session-import/index.ts +7 -0
- package/src/session-import/provider-codex.ts +327 -0
- package/src/session-import/provider-service.ts +457 -0
- package/src/session-import/redact.ts +128 -0
- package/src/session-import/service.ts +1264 -0
- package/src/session-import/types.ts +148 -0
- package/src/setup/paseo/check.ts +213 -0
- package/src/setup/paseo/install-saga.ts +293 -0
- package/src/setup/paseo/json-publisher.ts +263 -0
- package/src/setup/paseo/orchestration-preferences.ts +86 -0
- package/src/setup/paseo/paseo-ownership.ts +224 -0
- package/src/setup/paseo/paseo-setup.ts +253 -0
- package/src/setup/paseo/provider-config.ts +139 -0
- package/src/setup/paseo/remove.ts +183 -0
- package/src/setup/paseo/result-types.ts +79 -0
- package/src/setup/paseo/setup-deps.ts +175 -0
- package/src/setup/paseo/skills-bridge.ts +260 -0
- package/src/skill-state/active-state.ts +21 -11
- package/src/slash-commands/acp-builtins.ts +9 -1
- package/src/slash-commands/builtin-registry.ts +117 -19
- package/src/slash-commands/helpers/credential-switch.ts +143 -0
- package/src/slash-commands/helpers/mcp.ts +9 -3
- package/src/slash-commands/helpers/usage-report.ts +61 -56
- package/src/slash-commands/types.ts +7 -2
- package/src/task/executor.ts +30 -11
- package/src/task/index.ts +66 -7
- package/src/task/render.ts +23 -9
- package/src/tools/ask-answer-registry.ts +23 -7
- package/src/tools/ask.ts +8 -4
- package/src/tools/bash-sleep-advisory.ts +115 -0
- package/src/tools/bash.ts +98 -18
- package/src/tools/browser/attach.ts +71 -3
- package/src/tools/browser/launch.ts +86 -13
- package/src/tools/browser/profile-discovery.ts +57 -7
- package/src/tools/browser/render.ts +8 -1
- package/src/tools/browser.ts +130 -21
- package/src/tools/computer.enforcement.test.ts +1 -1
- package/src/tools/computer.ts +3 -2
- package/src/tools/cron.ts +12 -1
- package/src/tools/image-gen.ts +349 -142
- package/src/tools/index.ts +8 -0
- package/src/tools/job.ts +33 -6
- package/src/tools/monitor.ts +41 -2
- package/src/tools/skill-discovery.ts +21 -29
- package/src/tools/subagent.ts +142 -24
- package/src/tools/todo-contract.ts +55 -3
- package/src/tools/todo-write.ts +4 -3
- package/src/tools/tool-catalog.generated.ts +6 -6
- package/src/tools/ultragoal-ask-guard.ts +10 -3
- package/src/utils/herdr-pane.ts +345 -0
- package/src/utils/title-generator.ts +2 -0
- package/src/web/scrapers/index.ts +6 -4
- package/dist/types/cli/malloc-env-guard.test.d.ts +0 -1
- package/dist/types/defaults/gjc-defaults.test.d.ts +0 -1
- package/dist/types/memory-backend/lazy-loading.test.d.ts +0 -1
- package/dist/types/runtime/boot-ordering.test.d.ts +0 -1
- package/dist/types/runtime/lazy-service.test.d.ts +0 -1
- package/dist/types/runtime/network-prewarm-service.test.d.ts +0 -1
- package/dist/types/runtime/workspace-tree-service.test.d.ts +0 -1
- package/dist/types/runtime-mcp/config.test.d.ts +0 -1
- package/dist/types/runtime-mcp/http.test.d.ts +0 -1
- package/dist/types/runtime-mcp/manager-pool.test.d.ts +0 -1
- package/dist/types/runtime-mcp/pool-key.test.d.ts +0 -1
- package/dist/types/runtime-mcp/pool.test.d.ts +0 -1
- package/dist/types/sdk/bus/lifecycle-control-runtime.d.ts +0 -117
- package/dist/types/sdk/bus/lifecycle-orchestrator.d.ts +0 -141
- package/dist/types/sdk/bus/telegram-cli.d.ts +0 -31
- package/dist/types/sdk/elevation/allowlist.d.ts +0 -20
- package/dist/types/sdk/elevation/audit.d.ts +0 -93
- package/dist/types/sdk/elevation/capability.d.ts +0 -3
- package/dist/types/sdk/elevation/digest.d.ts +0 -72
- package/dist/types/sdk/elevation/dispatch-receipt.d.ts +0 -85
- package/dist/types/sdk/elevation/grant-ledger.d.ts +0 -163
- package/dist/types/sdk/elevation/owner.d.ts +0 -48
- package/dist/types/sdk/host/import-graph.test.d.ts +0 -1
- package/dist/types/sdk/host/sdk-surface-parity.test.d.ts +0 -1
- package/dist/types/sdk/host/session-runtime.test.d.ts +0 -1
- package/dist/types/sdk/host/websocket-transport.lifecycle.test.d.ts +0 -1
- package/dist/types/session/auth-broker-config.d.ts +0 -13
- package/dist/types/session/heap-eviction-artifacts.test.d.ts +0 -1
- package/dist/types/skills/skills.test.d.ts +0 -1
- package/dist/types/slash-commands/notify-command.test.d.ts +0 -1
- package/dist/types/task/token-log.test.d.ts +0 -1
- package/dist/types/tools/computer.enforcement.test.d.ts +0 -1
- package/dist/types/tools/descriptors.test.d.ts +0 -1
- package/scripts/g005-daemon-smoke.ts +0 -180
- package/scripts/g011-daemon-path-smoke.ts +0 -153
- package/src/sdk/bus/lifecycle-control-runtime.ts +0 -1733
- package/src/sdk/bus/lifecycle-orchestrator.ts +0 -489
- package/src/sdk/bus/telegram-cli.ts +0 -212
- package/src/sdk/elevation/allowlist.ts +0 -54
- package/src/sdk/elevation/audit.ts +0 -208
- package/src/sdk/elevation/capability.ts +0 -41
- package/src/sdk/elevation/digest.ts +0 -129
- package/src/sdk/elevation/dispatch-receipt.ts +0 -184
- package/src/sdk/elevation/grant-ledger.ts +0 -1142
- package/src/sdk/elevation/owner.ts +0 -109
- package/src/session/auth-broker-config.ts +0 -109
- /package/dist/types/{ai-core-import-gate.test.d.ts → discovery/claude-hooks.d.ts} +0 -0
- /package/dist/types/{cli/fixture-report.test.d.ts → discovery/codex-hooks.d.ts} +0 -0
- /package/dist/types/sdk/{bus/recent-activity.d.ts → lifecycle/recent-sessions.d.ts} +0 -0
|
@@ -0,0 +1,1033 @@
|
|
|
1
|
+
import { createHash, type Hash } from "node:crypto";
|
|
2
|
+
import * as nodeFs from "node:fs";
|
|
3
|
+
import * as fs from "node:fs/promises";
|
|
4
|
+
import * as os from "node:os";
|
|
5
|
+
import * as path from "node:path";
|
|
6
|
+
import {
|
|
7
|
+
openRecoveryFsRoot,
|
|
8
|
+
type RecoveryFsFile,
|
|
9
|
+
type RecoveryFsIdentity,
|
|
10
|
+
type RecoveryFsRoot,
|
|
11
|
+
} from "@gajae-code/natives";
|
|
12
|
+
|
|
13
|
+
export const CODEX_PROVIDER_ID = "openai-codex";
|
|
14
|
+
export const CODEX_CONVERTER_VERSION = 1;
|
|
15
|
+
export const CODEX_IMPORT_BATCH_LIMIT = 256;
|
|
16
|
+
export const CODEX_SANITIZER_VERSION = 2;
|
|
17
|
+
export const CODEX_MAPPING_VERSION = 3;
|
|
18
|
+
|
|
19
|
+
const MAX_SOURCE_BYTES = 8 * 1024 * 1024 * 1024;
|
|
20
|
+
const MAX_LINE_BYTES = 8 * 1024 * 1024;
|
|
21
|
+
const MAX_QUARANTINE_BYTES = 8 * 1024 * 1024;
|
|
22
|
+
const MAX_DISCOVERY_ENTRIES = 100_000;
|
|
23
|
+
const MAX_SESSION_INDEX_BYTES = 16 * 1024 * 1024;
|
|
24
|
+
const MAX_SESSION_INDEX_LINE_BYTES = 64 * 1024;
|
|
25
|
+
const MAX_SESSION_TITLE_CHARACTERS = 200;
|
|
26
|
+
const SENSITIVE_KEY = /(?:api[-_]?key|authorization|cookie|credential|password|secret|token)/iu;
|
|
27
|
+
const CODEX_SESSION_ID = /^[A-Za-z0-9-]{1,128}$/u;
|
|
28
|
+
const SECRET_VALUE =
|
|
29
|
+
/(?:Bearer\s+[A-Za-z0-9._~+/=-]{8,}|\b(?:sk[-_]|ghp_|github_pat_)[A-Za-z0-9_-]{12,}|\bAKIA[A-Z0-9]{16}\b)/giu;
|
|
30
|
+
const JWT_VALUE = /\b[A-Za-z0-9_-]{8,2048}\.[A-Za-z0-9_-]{8,8192}\.[A-Za-z0-9_-]{8,2048}\b/gu;
|
|
31
|
+
const SECRET_ASSIGNMENT =
|
|
32
|
+
/(\b(?:password|passwd|secret|token|api[-_]?key|access[-_]?key)\b\s*[:=]\s*)(?:"[^"\r\n]{4,4096}"|'[^'\r\n]{4,4096}'|[^\s,;]{4,4096})/giu;
|
|
33
|
+
const PRIVATE_KEY = /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]{1,65536}?-----END [A-Z ]*PRIVATE KEY-----/gu;
|
|
34
|
+
const URL_CREDENTIAL = /([a-z][a-z0-9+.-]*:\/\/)[^\s/@:]+:[^\s/@]+@/giu;
|
|
35
|
+
const HEADER_CREDENTIAL = /(\b(authorization|cookie|set-cookie)\b[ \t]*:[ \t]*)([^\r\n]*)/giu;
|
|
36
|
+
const JSON_AUTHORIZATION_CREDENTIAL = /((?:\\?")authorization(?:\\?")\s*:\s*(?:\\?")?)([^\\",}\r\n]+)/giu;
|
|
37
|
+
|
|
38
|
+
const COOKIE_PAIR = /(^|[;,][ \t]*)([^=;,\s]+)([ \t]*=[ \t]*)(?:"([^"\r\n]*)"|([^;,\s]*))/gu;
|
|
39
|
+
const SET_COOKIE_PAIR = /^([ \t]*[^=;,\s]+)([ \t]*=[ \t]*)(?:"([^"\r\n]*)"|([^;\r\n]*))/u;
|
|
40
|
+
const REDACTED_CREDENTIAL = "[redacted-credentials]";
|
|
41
|
+
|
|
42
|
+
const ANSI_ESCAPE = /\x1b(?:\[[0-?]*[ -/]*[@-~]|\][^\x07]*(?:\x07|\x1b\\))/gu;
|
|
43
|
+
const RESERVED_CONTROL = /<\|[^|\r\n]{1,128}\|>/gu;
|
|
44
|
+
const HOSTILE_UNICODE = /[\u200b-\u200f\u202a-\u202e\u2060\u2066-\u2069\ufeff]/gu;
|
|
45
|
+
const C0_C1 = /[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f-\u009f]/gu;
|
|
46
|
+
|
|
47
|
+
interface CodexWorkspaceIdentity {
|
|
48
|
+
dev: string;
|
|
49
|
+
ino: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface CodexSessionSource {
|
|
53
|
+
id: string;
|
|
54
|
+
path: string;
|
|
55
|
+
cwd: string;
|
|
56
|
+
timestamp: string;
|
|
57
|
+
title?: string;
|
|
58
|
+
cliVersion?: string;
|
|
59
|
+
modelProvider?: string;
|
|
60
|
+
identity?: RecoveryFsIdentity;
|
|
61
|
+
workspaceIdentity?: CodexWorkspaceIdentity;
|
|
62
|
+
authority?: { root: RecoveryFsRoot; relativePath: string };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export type CodexMappedEvent =
|
|
66
|
+
| { kind: "user"; timestamp: string; text: string }
|
|
67
|
+
| { kind: "assistant"; timestamp: string; text: string }
|
|
68
|
+
| { kind: "tool_call"; timestamp: string; callId: string; name: string; arguments: Record<string, unknown> }
|
|
69
|
+
| { kind: "tool_result"; timestamp: string; callId: string; output: string };
|
|
70
|
+
|
|
71
|
+
export interface CodexQuarantineRecord {
|
|
72
|
+
line: number;
|
|
73
|
+
timestamp?: string;
|
|
74
|
+
eventType: string;
|
|
75
|
+
reason: string;
|
|
76
|
+
payload: unknown;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface CodexConversion {
|
|
80
|
+
source: CodexSessionSource;
|
|
81
|
+
sourceSha256: string;
|
|
82
|
+
sourceBytes: number;
|
|
83
|
+
mapped: CodexMappedEvent[];
|
|
84
|
+
quarantine: CodexQuarantineRecord[];
|
|
85
|
+
quarantineTruncated: boolean;
|
|
86
|
+
counts: { input: number; mapped: number; quarantined: number; dropped: number; redacted: number };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export class CodexImportError extends Error {
|
|
90
|
+
constructor(
|
|
91
|
+
readonly code:
|
|
92
|
+
| "source_not_found"
|
|
93
|
+
| "source_untrusted"
|
|
94
|
+
| "source_changed"
|
|
95
|
+
| "malformed_source"
|
|
96
|
+
| "content_too_large",
|
|
97
|
+
readonly phase: "discovery" | "source" | "source_line" | "source_event" | "quarantine",
|
|
98
|
+
message: string,
|
|
99
|
+
readonly limitBytes?: number,
|
|
100
|
+
readonly observedBytes?: number,
|
|
101
|
+
) {
|
|
102
|
+
super(message);
|
|
103
|
+
this.name = "CodexImportError";
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
export type CodexMappedEventSink = (event: CodexMappedEvent) => void | Promise<void>;
|
|
107
|
+
|
|
108
|
+
export function sanitizeImportedString(value: string): { value: string; redacted: number } {
|
|
109
|
+
let redacted = 0;
|
|
110
|
+
const replace = (input: string, pattern: RegExp, token: string): string =>
|
|
111
|
+
input.replace(pattern, () => {
|
|
112
|
+
redacted++;
|
|
113
|
+
return token;
|
|
114
|
+
});
|
|
115
|
+
let next = value;
|
|
116
|
+
if (next.includes("\u001b")) next = replace(next, ANSI_ESCAPE, "[control-sequence]");
|
|
117
|
+
if (next.search(HOSTILE_UNICODE) >= 0) next = replace(next, HOSTILE_UNICODE, "[unicode-control]");
|
|
118
|
+
if (next.search(C0_C1) >= 0) next = replace(next, C0_C1, "[control-character]");
|
|
119
|
+
if (next.includes("<|")) next = replace(next, RESERVED_CONTROL, "[provider-control]");
|
|
120
|
+
if (next.search(HEADER_CREDENTIAL) >= 0) {
|
|
121
|
+
next = next.replace(
|
|
122
|
+
HEADER_CREDENTIAL,
|
|
123
|
+
(_match: string, prefix: string, headerName: string, raw: string): string => {
|
|
124
|
+
const normalizedHeaderName = headerName.toLowerCase();
|
|
125
|
+
if (normalizedHeaderName === "authorization") {
|
|
126
|
+
const authorization = raw.trim();
|
|
127
|
+
if (!authorization || authorization.toLowerCase() === "basic") return `${prefix}${raw}`;
|
|
128
|
+
const scheme = /^([A-Za-z][A-Za-z0-9_-]{0,63})[ \t]+/u.exec(raw)?.[1];
|
|
129
|
+
redacted++;
|
|
130
|
+
return `${prefix}${scheme ? `${scheme} ${REDACTED_CREDENTIAL}` : REDACTED_CREDENTIAL}`;
|
|
131
|
+
}
|
|
132
|
+
if (normalizedHeaderName === "set-cookie") {
|
|
133
|
+
return `${prefix}${raw.replace(
|
|
134
|
+
SET_COOKIE_PAIR,
|
|
135
|
+
(
|
|
136
|
+
_cookieMatch: string,
|
|
137
|
+
name: string,
|
|
138
|
+
equals: string,
|
|
139
|
+
quotedValue: string | undefined,
|
|
140
|
+
unquotedValue: string | undefined,
|
|
141
|
+
): string => {
|
|
142
|
+
const secret = quotedValue ?? unquotedValue ?? "";
|
|
143
|
+
if (!secret) return _cookieMatch;
|
|
144
|
+
redacted++;
|
|
145
|
+
const quote = quotedValue === undefined ? "" : '"';
|
|
146
|
+
return `${name}${equals}${quote}${REDACTED_CREDENTIAL}${quote}`;
|
|
147
|
+
},
|
|
148
|
+
)}`;
|
|
149
|
+
}
|
|
150
|
+
return `${prefix}${raw.replace(
|
|
151
|
+
COOKIE_PAIR,
|
|
152
|
+
(
|
|
153
|
+
_cookieMatch: string,
|
|
154
|
+
lead: string,
|
|
155
|
+
name: string,
|
|
156
|
+
equals: string,
|
|
157
|
+
quotedValue: string | undefined,
|
|
158
|
+
unquotedValue: string | undefined,
|
|
159
|
+
): string => {
|
|
160
|
+
const secret = quotedValue ?? unquotedValue ?? "";
|
|
161
|
+
if (!secret) return _cookieMatch;
|
|
162
|
+
redacted++;
|
|
163
|
+
const quote = quotedValue === undefined ? "" : '"';
|
|
164
|
+
return `${lead}${name}${equals}${quote}${REDACTED_CREDENTIAL}${quote}`;
|
|
165
|
+
},
|
|
166
|
+
)}`;
|
|
167
|
+
},
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
if (next.search(JSON_AUTHORIZATION_CREDENTIAL) >= 0) {
|
|
171
|
+
next = next.replace(JSON_AUTHORIZATION_CREDENTIAL, (_match: string, prefix: string, raw: string): string => {
|
|
172
|
+
const authorization = raw.trim();
|
|
173
|
+
if (!authorization || authorization.toLowerCase() === "basic") return `${prefix}${raw}`;
|
|
174
|
+
const scheme = /^([A-Za-z][A-Za-z0-9_-]{0,63})[ \t]+/u.exec(raw)?.[1];
|
|
175
|
+
redacted++;
|
|
176
|
+
return `${prefix}${scheme ? `${scheme} ${REDACTED_CREDENTIAL}` : REDACTED_CREDENTIAL}`;
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
if (/(?:Bearer\s|(?:sk[-_]|ghp_|github_pat_)|AKIA[A-Z0-9]{16})/iu.test(next))
|
|
180
|
+
next = replace(next, SECRET_VALUE, "[redacted-secret]");
|
|
181
|
+
if (next.includes(".")) next = replace(next, JWT_VALUE, "[redacted-secret]");
|
|
182
|
+
if (/(?:password|passwd|secret|token|api[-_]?key|access[-_]?key)\s*[:=]/iu.test(next)) {
|
|
183
|
+
next = next.replace(SECRET_ASSIGNMENT, (_match, prefix: string) => {
|
|
184
|
+
redacted++;
|
|
185
|
+
return `${prefix}[redacted-secret]`;
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
if (next.includes("PRIVATE KEY-----")) next = replace(next, PRIVATE_KEY, "[redacted-private-key]");
|
|
189
|
+
if (next.includes("://")) {
|
|
190
|
+
next = next.replace(URL_CREDENTIAL, (_match, scheme: string) => {
|
|
191
|
+
redacted++;
|
|
192
|
+
return `${scheme}[redacted-credentials]@`;
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
return { value: next, redacted };
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export function sanitizeImportedValue(value: unknown, depth = 0): { value: unknown; redacted: number } {
|
|
199
|
+
if (depth > 64) return { value: "[depth-limit]", redacted: 1 };
|
|
200
|
+
if (typeof value === "string") return sanitizeImportedString(value);
|
|
201
|
+
if (Array.isArray(value)) {
|
|
202
|
+
let redacted = 0;
|
|
203
|
+
const mapped = value.slice(0, 65_536).map(item => {
|
|
204
|
+
const sanitized = sanitizeImportedValue(item, depth + 1);
|
|
205
|
+
redacted += sanitized.redacted;
|
|
206
|
+
return sanitized.value;
|
|
207
|
+
});
|
|
208
|
+
if (value.length > mapped.length) {
|
|
209
|
+
mapped.push("[array-limit]");
|
|
210
|
+
redacted++;
|
|
211
|
+
}
|
|
212
|
+
return { value: mapped, redacted };
|
|
213
|
+
}
|
|
214
|
+
if (value && typeof value === "object") {
|
|
215
|
+
let redacted = 0;
|
|
216
|
+
const out: Record<string, unknown> = Object.create(null) as Record<string, unknown>;
|
|
217
|
+
for (const [key, item] of Object.entries(value).slice(0, 4096)) {
|
|
218
|
+
const sanitizedKey = sanitizeImportedString(key);
|
|
219
|
+
redacted += sanitizedKey.redacted;
|
|
220
|
+
const safeKey = ["__proto__", "constructor", "prototype"].includes(sanitizedKey.value)
|
|
221
|
+
? `[redacted-key-${Object.keys(out).length}]`
|
|
222
|
+
: sanitizedKey.value;
|
|
223
|
+
if (safeKey !== sanitizedKey.value) redacted++;
|
|
224
|
+
if (SENSITIVE_KEY.test(key)) {
|
|
225
|
+
out[safeKey] = "[redacted-field]";
|
|
226
|
+
redacted++;
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
const sanitized = sanitizeImportedValue(item, depth + 1);
|
|
230
|
+
out[safeKey] = sanitized.value;
|
|
231
|
+
redacted += sanitized.redacted;
|
|
232
|
+
}
|
|
233
|
+
return { value: out, redacted };
|
|
234
|
+
}
|
|
235
|
+
return { value, redacted: 0 };
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function textContent(content: unknown): string {
|
|
239
|
+
if (!Array.isArray(content)) return "";
|
|
240
|
+
return content
|
|
241
|
+
.flatMap(item => {
|
|
242
|
+
if (!item || typeof item !== "object") return [];
|
|
243
|
+
const record = item as Record<string, unknown>;
|
|
244
|
+
return ["input_text", "output_text", "text"].includes(String(record.type)) && typeof record.text === "string"
|
|
245
|
+
? [record.text]
|
|
246
|
+
: [];
|
|
247
|
+
})
|
|
248
|
+
.join("\n");
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function safeTimestamp(value: unknown, fallback: string): string {
|
|
252
|
+
if (typeof value !== "string" || !Number.isFinite(Date.parse(value))) return fallback;
|
|
253
|
+
return new Date(value).toISOString();
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function mapResponseItem(
|
|
257
|
+
payload: Record<string, unknown>,
|
|
258
|
+
timestamp: string,
|
|
259
|
+
): CodexMappedEvent | { quarantine: string } | { drop: true } {
|
|
260
|
+
const type = String(payload.type ?? "unknown");
|
|
261
|
+
if (type === "message") {
|
|
262
|
+
const role = String(payload.role ?? "");
|
|
263
|
+
if (role !== "user" && role !== "assistant") return { quarantine: `prohibited_role:${role || "missing"}` };
|
|
264
|
+
const text = textContent(payload.content);
|
|
265
|
+
if (!text) return { drop: true };
|
|
266
|
+
return { kind: role, timestamp, text };
|
|
267
|
+
}
|
|
268
|
+
if (type === "function_call" || type === "custom_tool_call") {
|
|
269
|
+
const callId = String(payload.call_id ?? payload.id ?? "");
|
|
270
|
+
const name = String(payload.name ?? "codex_tool");
|
|
271
|
+
if (!callId) return { quarantine: "missing_tool_call_id" };
|
|
272
|
+
const raw = type === "function_call" ? payload.arguments : payload.input;
|
|
273
|
+
let args: unknown = raw;
|
|
274
|
+
if (typeof raw === "string") {
|
|
275
|
+
try {
|
|
276
|
+
args = JSON.parse(raw);
|
|
277
|
+
} catch {
|
|
278
|
+
args = { raw };
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
return {
|
|
282
|
+
kind: "tool_call",
|
|
283
|
+
timestamp,
|
|
284
|
+
callId,
|
|
285
|
+
name,
|
|
286
|
+
arguments:
|
|
287
|
+
args && typeof args === "object" && !Array.isArray(args)
|
|
288
|
+
? (args as Record<string, unknown>)
|
|
289
|
+
: { value: args },
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
if (type === "function_call_output" || type === "custom_tool_call_output") {
|
|
293
|
+
const callId = String(payload.call_id ?? "");
|
|
294
|
+
if (!callId) return { quarantine: "missing_tool_result_id" };
|
|
295
|
+
const output = typeof payload.output === "string" ? payload.output : JSON.stringify(payload.output ?? null);
|
|
296
|
+
return { kind: "tool_result", timestamp, callId, output };
|
|
297
|
+
}
|
|
298
|
+
if (["reasoning", "computer_tool_call", "web_search_call"].includes(type))
|
|
299
|
+
return { quarantine: `unsupported:${type}` };
|
|
300
|
+
return { quarantine: `unknown:${type}` };
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function parseSessionMeta(file: string, buffer: Buffer): CodexSessionSource | null {
|
|
304
|
+
const newline = buffer.indexOf(0x0a);
|
|
305
|
+
if (newline < 0) return null;
|
|
306
|
+
let first: unknown;
|
|
307
|
+
try {
|
|
308
|
+
first = JSON.parse(buffer.subarray(0, newline).toString("utf8"));
|
|
309
|
+
} catch {
|
|
310
|
+
throw new CodexImportError("malformed_source", "discovery", "Codex session metadata was malformed.");
|
|
311
|
+
}
|
|
312
|
+
if (first === null || Array.isArray(first))
|
|
313
|
+
throw new CodexImportError("malformed_source", "discovery", "Codex session metadata was malformed.");
|
|
314
|
+
if (typeof first !== "object") return null;
|
|
315
|
+
const record = first as Record<string, unknown>;
|
|
316
|
+
if (record.type !== "session_meta") return null;
|
|
317
|
+
if (!record.payload || typeof record.payload !== "object" || Array.isArray(record.payload))
|
|
318
|
+
throw new CodexImportError("malformed_source", "discovery", "Codex session metadata was malformed.");
|
|
319
|
+
const payload = record.payload as Record<string, unknown>;
|
|
320
|
+
const idValue = payload.id ?? payload.session_id;
|
|
321
|
+
const cwdValue = payload.cwd;
|
|
322
|
+
if (typeof idValue !== "string" || !CODEX_SESSION_ID.test(idValue) || typeof cwdValue !== "string" || !cwdValue)
|
|
323
|
+
throw new CodexImportError("malformed_source", "discovery", "Codex session metadata was malformed.");
|
|
324
|
+
const id = idValue;
|
|
325
|
+
const cwd = cwdValue;
|
|
326
|
+
return {
|
|
327
|
+
id,
|
|
328
|
+
path: file,
|
|
329
|
+
cwd,
|
|
330
|
+
timestamp: safeTimestamp(payload.timestamp, safeTimestamp(record.timestamp, new Date(0).toISOString())),
|
|
331
|
+
cliVersion: typeof payload.cli_version === "string" ? payload.cli_version : undefined,
|
|
332
|
+
modelProvider: typeof payload.model_provider === "string" ? payload.model_provider : undefined,
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function sameSourceIdentity(left: RecoveryFsIdentity, right: RecoveryFsIdentity): boolean {
|
|
337
|
+
return (
|
|
338
|
+
left.dev === right.dev &&
|
|
339
|
+
left.ino === right.ino &&
|
|
340
|
+
left.nlink === right.nlink &&
|
|
341
|
+
left.size === right.size &&
|
|
342
|
+
left.mtimeNs === right.mtimeNs &&
|
|
343
|
+
left.ctimeNs === right.ctimeNs
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function sameRetainedIdentity(left: RecoveryFsIdentity, right: RecoveryFsIdentity): boolean {
|
|
348
|
+
return (
|
|
349
|
+
left.dev === right.dev &&
|
|
350
|
+
left.ino === right.ino &&
|
|
351
|
+
left.nlink === right.nlink &&
|
|
352
|
+
left.size === right.size &&
|
|
353
|
+
left.mtimeNs === right.mtimeNs &&
|
|
354
|
+
left.ctimeNs === right.ctimeNs
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function boundedSessionTitle(value: string): string | undefined {
|
|
359
|
+
const sanitized = sanitizeImportedString(value).value.trim();
|
|
360
|
+
if (!sanitized) return undefined;
|
|
361
|
+
return Array.from(sanitized).slice(0, MAX_SESSION_TITLE_CHARACTERS).join("");
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function inferredSessionTitle(text: string): string | undefined {
|
|
365
|
+
const trimmed = text.trim();
|
|
366
|
+
if (
|
|
367
|
+
!trimmed ||
|
|
368
|
+
trimmed.startsWith("# AGENTS.md instructions") ||
|
|
369
|
+
trimmed.startsWith("<environment_context>") ||
|
|
370
|
+
trimmed.startsWith("<INSTRUCTIONS>")
|
|
371
|
+
)
|
|
372
|
+
return undefined;
|
|
373
|
+
const firstLine = trimmed
|
|
374
|
+
.split(/\r?\n/u)
|
|
375
|
+
.map(line => line.trim())
|
|
376
|
+
.find(Boolean)
|
|
377
|
+
?.replace(/^#{1,6}\s+/u, "");
|
|
378
|
+
return firstLine ? boundedSessionTitle(firstLine) : undefined;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function readCodexSessionTitles(codexHome: string): Map<string, string> {
|
|
382
|
+
let root: RecoveryFsRoot | undefined;
|
|
383
|
+
let file: RecoveryFsFile | undefined;
|
|
384
|
+
try {
|
|
385
|
+
root = openRecoveryFsRoot(codexHome);
|
|
386
|
+
file = root.openFile("session_index.jsonl");
|
|
387
|
+
const initial = file.identity();
|
|
388
|
+
if (!initial.ok || !initial.identity) return new Map();
|
|
389
|
+
if (BigInt(initial.identity.size) > BigInt(MAX_SESSION_INDEX_BYTES)) return new Map();
|
|
390
|
+
const chunks: Buffer[] = [];
|
|
391
|
+
let offset = 0;
|
|
392
|
+
for (;;) {
|
|
393
|
+
const chunk = file.readChunk(offset, 1024 * 1024);
|
|
394
|
+
if (!chunk.ok || !chunk.data || !chunk.identity || !sameSourceIdentity(initial.identity, chunk.identity))
|
|
395
|
+
return new Map();
|
|
396
|
+
if (chunk.data.byteLength === 0) break;
|
|
397
|
+
offset += chunk.data.byteLength;
|
|
398
|
+
if (offset > MAX_SESSION_INDEX_BYTES) return new Map();
|
|
399
|
+
chunks.push(Buffer.from(chunk.data));
|
|
400
|
+
}
|
|
401
|
+
const terminal = file.identity();
|
|
402
|
+
if (!terminal.ok || !terminal.identity || !sameSourceIdentity(initial.identity, terminal.identity))
|
|
403
|
+
return new Map();
|
|
404
|
+
const titles = new Map<string, string>();
|
|
405
|
+
for (const line of Buffer.concat(chunks).toString("utf8").split("\n")) {
|
|
406
|
+
if (!line.trim() || Buffer.byteLength(line, "utf8") > MAX_SESSION_INDEX_LINE_BYTES) continue;
|
|
407
|
+
let row: unknown;
|
|
408
|
+
try {
|
|
409
|
+
row = JSON.parse(line);
|
|
410
|
+
} catch {
|
|
411
|
+
continue;
|
|
412
|
+
}
|
|
413
|
+
if (!row || typeof row !== "object" || Array.isArray(row)) continue;
|
|
414
|
+
const record = row as Record<string, unknown>;
|
|
415
|
+
if (
|
|
416
|
+
typeof record.id !== "string" ||
|
|
417
|
+
!CODEX_SESSION_ID.test(record.id) ||
|
|
418
|
+
typeof record.thread_name !== "string"
|
|
419
|
+
)
|
|
420
|
+
continue;
|
|
421
|
+
const title = boundedSessionTitle(record.thread_name);
|
|
422
|
+
if (title) titles.set(record.id, title);
|
|
423
|
+
}
|
|
424
|
+
return titles;
|
|
425
|
+
} catch {
|
|
426
|
+
return new Map();
|
|
427
|
+
} finally {
|
|
428
|
+
file?.close();
|
|
429
|
+
if (root) closeRetainedRoot(root);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
async function workspaceIdentity(directory: string): Promise<CodexWorkspaceIdentity | null> {
|
|
434
|
+
const stat = await fs.lstat(directory, { bigint: true }).catch(() => null);
|
|
435
|
+
if (!stat?.isDirectory() || stat.isSymbolicLink()) return null;
|
|
436
|
+
return { dev: stat.dev.toString(), ino: stat.ino.toString() };
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
function sameWorkspaceIdentity(
|
|
440
|
+
left: CodexWorkspaceIdentity | undefined,
|
|
441
|
+
right: CodexWorkspaceIdentity | null,
|
|
442
|
+
): boolean {
|
|
443
|
+
return !!left && !!right && left.dev === right.dev && left.ino === right.ino;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
export async function assertCodexWorkspaceIdentity(source: CodexSessionSource): Promise<void> {
|
|
447
|
+
const current = await workspaceIdentity(source.cwd);
|
|
448
|
+
if (!sameWorkspaceIdentity(source.workspaceIdentity, current))
|
|
449
|
+
throw new CodexImportError(
|
|
450
|
+
"source_untrusted",
|
|
451
|
+
"discovery",
|
|
452
|
+
"The workspace changed after Codex session discovery.",
|
|
453
|
+
);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
async function readSessionMeta(
|
|
457
|
+
file: string,
|
|
458
|
+
): Promise<{ meta: CodexSessionSource; identity: RecoveryFsIdentity } | null> {
|
|
459
|
+
const handle = await fs.open(file, nodeFs.constants.O_RDONLY | nodeFs.constants.O_NOFOLLOW);
|
|
460
|
+
try {
|
|
461
|
+
const initial = await handle.stat({ bigint: true });
|
|
462
|
+
if (!initial.isFile()) return null;
|
|
463
|
+
const buffer = Buffer.alloc(64 * 1024);
|
|
464
|
+
const { bytesRead } = await handle.read(buffer, 0, buffer.length, 0);
|
|
465
|
+
const meta = parseSessionMeta(file, buffer.subarray(0, bytesRead));
|
|
466
|
+
if (!meta) return null;
|
|
467
|
+
const terminal = await handle.stat({ bigint: true });
|
|
468
|
+
const named = await fs.lstat(file, { bigint: true });
|
|
469
|
+
const identity = {
|
|
470
|
+
dev: initial.dev.toString(),
|
|
471
|
+
ino: initial.ino.toString(),
|
|
472
|
+
nlink: initial.nlink.toString(),
|
|
473
|
+
size: initial.size.toString(),
|
|
474
|
+
mtimeNs: initial.mtimeNs.toString(),
|
|
475
|
+
ctimeNs: initial.ctimeNs.toString(),
|
|
476
|
+
};
|
|
477
|
+
const terminalIdentity = {
|
|
478
|
+
dev: terminal.dev.toString(),
|
|
479
|
+
ino: terminal.ino.toString(),
|
|
480
|
+
nlink: terminal.nlink.toString(),
|
|
481
|
+
size: terminal.size.toString(),
|
|
482
|
+
mtimeNs: terminal.mtimeNs.toString(),
|
|
483
|
+
ctimeNs: terminal.ctimeNs.toString(),
|
|
484
|
+
};
|
|
485
|
+
const namedIdentity = {
|
|
486
|
+
dev: named.dev.toString(),
|
|
487
|
+
ino: named.ino.toString(),
|
|
488
|
+
nlink: named.nlink.toString(),
|
|
489
|
+
size: named.size.toString(),
|
|
490
|
+
mtimeNs: named.mtimeNs.toString(),
|
|
491
|
+
ctimeNs: named.ctimeNs.toString(),
|
|
492
|
+
};
|
|
493
|
+
if (
|
|
494
|
+
named.isSymbolicLink() ||
|
|
495
|
+
!sameSourceIdentity(identity, terminalIdentity) ||
|
|
496
|
+
!sameSourceIdentity(identity, namedIdentity)
|
|
497
|
+
)
|
|
498
|
+
return null;
|
|
499
|
+
return { meta, identity };
|
|
500
|
+
} catch (error) {
|
|
501
|
+
if (error instanceof CodexImportError) throw error;
|
|
502
|
+
return null;
|
|
503
|
+
} finally {
|
|
504
|
+
await handle.close();
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
function readRetainedSessionMeta(
|
|
509
|
+
root: RecoveryFsRoot,
|
|
510
|
+
relativePath: string,
|
|
511
|
+
displayPath: string,
|
|
512
|
+
): { meta: CodexSessionSource; identity: RecoveryFsIdentity } | null {
|
|
513
|
+
let file: RecoveryFsFile;
|
|
514
|
+
try {
|
|
515
|
+
file = root.openFile(relativePath);
|
|
516
|
+
} catch {
|
|
517
|
+
return null;
|
|
518
|
+
}
|
|
519
|
+
try {
|
|
520
|
+
const initial = file.identity();
|
|
521
|
+
const chunk = file.readChunk(0, 64 * 1024);
|
|
522
|
+
const terminal = file.identity();
|
|
523
|
+
if (
|
|
524
|
+
!initial.ok ||
|
|
525
|
+
!initial.identity ||
|
|
526
|
+
!chunk.ok ||
|
|
527
|
+
!chunk.data ||
|
|
528
|
+
!chunk.identity ||
|
|
529
|
+
!terminal.ok ||
|
|
530
|
+
!terminal.identity ||
|
|
531
|
+
!sameSourceIdentity(initial.identity, chunk.identity) ||
|
|
532
|
+
!sameSourceIdentity(initial.identity, terminal.identity)
|
|
533
|
+
)
|
|
534
|
+
return null;
|
|
535
|
+
const meta = parseSessionMeta(displayPath, Buffer.from(chunk.data));
|
|
536
|
+
return meta ? { meta, identity: initial.identity } : null;
|
|
537
|
+
} finally {
|
|
538
|
+
file.close();
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
function sameWorkspace(left: string, right: string): boolean {
|
|
543
|
+
const a = path.resolve(left);
|
|
544
|
+
const b = path.resolve(right);
|
|
545
|
+
return process.platform === "win32" ? a.toLowerCase() === b.toLowerCase() : a === b;
|
|
546
|
+
}
|
|
547
|
+
function closeRetainedRoot(root: RecoveryFsRoot): void {
|
|
548
|
+
const closed = root.close();
|
|
549
|
+
if (!closed.ok && closed.code !== "closed") throw new Error(closed.code ?? "source_authority_close_failed");
|
|
550
|
+
}
|
|
551
|
+
function verifyRetainedRootOwnerOnly(root: RecoveryFsRoot): boolean {
|
|
552
|
+
try {
|
|
553
|
+
return root.verifyOwnerOnlyDirectory().ok;
|
|
554
|
+
} catch {
|
|
555
|
+
return false;
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
export async function discoverCodexSessions(
|
|
560
|
+
cwd: string,
|
|
561
|
+
requestedIds: readonly string[],
|
|
562
|
+
codexHome = process.env.CODEX_HOME ?? path.join(os.homedir(), ".codex"),
|
|
563
|
+
retainSourceAuthority = false,
|
|
564
|
+
): Promise<CodexSessionSource[]> {
|
|
565
|
+
const requested = new Set(requestedIds);
|
|
566
|
+
if (requested.size > CODEX_IMPORT_BATCH_LIMIT)
|
|
567
|
+
throw new CodexImportError(
|
|
568
|
+
"content_too_large",
|
|
569
|
+
"discovery",
|
|
570
|
+
`Codex session import batch exceeds the maximum of ${CODEX_IMPORT_BATCH_LIMIT} sessions.`,
|
|
571
|
+
);
|
|
572
|
+
const sessionsRoot = path.join(codexHome, "sessions");
|
|
573
|
+
let rootAuthority: RecoveryFsRoot | undefined;
|
|
574
|
+
let retainedRootIdentity: RecoveryFsIdentity | undefined;
|
|
575
|
+
if (retainSourceAuthority) {
|
|
576
|
+
if (process.platform !== "linux")
|
|
577
|
+
throw new CodexImportError(
|
|
578
|
+
"source_untrusted",
|
|
579
|
+
"discovery",
|
|
580
|
+
"Descriptor-relative Codex source authority is unavailable on this platform.",
|
|
581
|
+
);
|
|
582
|
+
try {
|
|
583
|
+
rootAuthority = openRecoveryFsRoot(sessionsRoot);
|
|
584
|
+
} catch {
|
|
585
|
+
throw new CodexImportError("source_not_found", "discovery", "Codex sessions directory was not found.");
|
|
586
|
+
}
|
|
587
|
+
if (!verifyRetainedRootOwnerOnly(rootAuthority)) {
|
|
588
|
+
closeRetainedRoot(rootAuthority);
|
|
589
|
+
throw new CodexImportError("source_untrusted", "discovery", "Codex sessions directory is not trusted.");
|
|
590
|
+
}
|
|
591
|
+
const retained = rootAuthority.identity();
|
|
592
|
+
if (!retained.ok || !retained.identity) {
|
|
593
|
+
closeRetainedRoot(rootAuthority);
|
|
594
|
+
throw new CodexImportError("source_untrusted", "discovery", "Codex sessions directory is not trusted.");
|
|
595
|
+
}
|
|
596
|
+
retainedRootIdentity = retained.identity;
|
|
597
|
+
}
|
|
598
|
+
try {
|
|
599
|
+
const rootStat = await fs.lstat(sessionsRoot, { bigint: true }).catch(() => null);
|
|
600
|
+
if (!rootStat?.isDirectory() || rootStat.isSymbolicLink())
|
|
601
|
+
throw new CodexImportError("source_not_found", "discovery", "Codex sessions directory was not found.");
|
|
602
|
+
const canonicalSessionsRoot = await fs.realpath(sessionsRoot);
|
|
603
|
+
const canonicalWorkspace = await fs.realpath(cwd).catch(() => null);
|
|
604
|
+
if (!canonicalWorkspace)
|
|
605
|
+
throw new CodexImportError("source_not_found", "discovery", "The current workspace was not found.");
|
|
606
|
+
const discoveredWorkspaceIdentity = await workspaceIdentity(canonicalWorkspace);
|
|
607
|
+
if (!discoveredWorkspaceIdentity)
|
|
608
|
+
throw new CodexImportError("source_untrusted", "discovery", "The current workspace identity is unavailable.");
|
|
609
|
+
const sanitizedWorkspace = sanitizeImportedString(canonicalWorkspace);
|
|
610
|
+
if (sanitizedWorkspace.value !== canonicalWorkspace)
|
|
611
|
+
throw new CodexImportError(
|
|
612
|
+
"source_untrusted",
|
|
613
|
+
"discovery",
|
|
614
|
+
"The canonical workspace path contains secret-shaped or control content.",
|
|
615
|
+
);
|
|
616
|
+
const sessionTitles = readCodexSessionTitles(codexHome);
|
|
617
|
+
const found = new Map<string, CodexSessionSource>();
|
|
618
|
+
let relativePaths: string[];
|
|
619
|
+
if (rootAuthority) {
|
|
620
|
+
const listed = rootAuthority.listFiles(MAX_DISCOVERY_ENTRIES);
|
|
621
|
+
if (
|
|
622
|
+
!listed.ok ||
|
|
623
|
+
!listed.data ||
|
|
624
|
+
!listed.identity ||
|
|
625
|
+
!retainedRootIdentity ||
|
|
626
|
+
!sameRetainedIdentity(retainedRootIdentity, listed.identity)
|
|
627
|
+
)
|
|
628
|
+
throw new CodexImportError(
|
|
629
|
+
"source_untrusted",
|
|
630
|
+
"discovery",
|
|
631
|
+
"Codex session discovery changed while it was being enumerated.",
|
|
632
|
+
);
|
|
633
|
+
let parsed: unknown;
|
|
634
|
+
try {
|
|
635
|
+
parsed = JSON.parse(Buffer.from(listed.data).toString("utf8"));
|
|
636
|
+
} catch {
|
|
637
|
+
throw new CodexImportError("source_untrusted", "discovery", "Codex session discovery was malformed.");
|
|
638
|
+
}
|
|
639
|
+
if (
|
|
640
|
+
!Array.isArray(parsed) ||
|
|
641
|
+
parsed.length > MAX_DISCOVERY_ENTRIES ||
|
|
642
|
+
parsed.some(relative => typeof relative !== "string" || path.isAbsolute(relative))
|
|
643
|
+
)
|
|
644
|
+
throw new CodexImportError("source_untrusted", "discovery", "Codex session discovery was malformed.");
|
|
645
|
+
relativePaths = parsed.filter(relative => relative.endsWith(".jsonl")).sort();
|
|
646
|
+
} else {
|
|
647
|
+
const glob = new Bun.Glob("**/*.jsonl");
|
|
648
|
+
relativePaths = [];
|
|
649
|
+
for await (const relative of glob.scan({ cwd: sessionsRoot, onlyFiles: true, dot: false })) {
|
|
650
|
+
if (relativePaths.length >= MAX_DISCOVERY_ENTRIES)
|
|
651
|
+
throw new CodexImportError(
|
|
652
|
+
"source_untrusted",
|
|
653
|
+
"discovery",
|
|
654
|
+
"Codex session discovery exceeded the bounded entry limit.",
|
|
655
|
+
);
|
|
656
|
+
relativePaths.push(relative);
|
|
657
|
+
}
|
|
658
|
+
relativePaths.sort();
|
|
659
|
+
}
|
|
660
|
+
for (const relative of relativePaths) {
|
|
661
|
+
const displayPath = path.resolve(sessionsRoot, relative);
|
|
662
|
+
if (!displayPath.startsWith(`${path.resolve(sessionsRoot)}${path.sep}`)) continue;
|
|
663
|
+
const read = rootAuthority
|
|
664
|
+
? readRetainedSessionMeta(rootAuthority, relative, displayPath)
|
|
665
|
+
: await readSessionMeta(displayPath);
|
|
666
|
+
if (!read) continue;
|
|
667
|
+
const { meta, identity } = read;
|
|
668
|
+
if (!rootAuthority) {
|
|
669
|
+
const canonicalFile = await fs.realpath(displayPath).catch(() => null);
|
|
670
|
+
const expectedCanonicalFile = path.resolve(canonicalSessionsRoot, relative);
|
|
671
|
+
if (!canonicalFile || !sameWorkspace(canonicalFile, expectedCanonicalFile)) continue;
|
|
672
|
+
}
|
|
673
|
+
meta.identity = identity;
|
|
674
|
+
const canonicalMetadataWorkspace = await fs.realpath(meta.cwd).catch(() => null);
|
|
675
|
+
if (!canonicalMetadataWorkspace || !sameWorkspace(canonicalMetadataWorkspace, canonicalWorkspace)) continue;
|
|
676
|
+
meta.cwd = canonicalMetadataWorkspace;
|
|
677
|
+
meta.title = sessionTitles.get(meta.id);
|
|
678
|
+
meta.workspaceIdentity = discoveredWorkspaceIdentity;
|
|
679
|
+
if (rootAuthority) meta.authority = { root: rootAuthority, relativePath: relative };
|
|
680
|
+
if (requested.size > 0 && !requested.has(meta.id)) continue;
|
|
681
|
+
if (found.has(meta.id))
|
|
682
|
+
throw new CodexImportError(
|
|
683
|
+
"malformed_source",
|
|
684
|
+
"discovery",
|
|
685
|
+
`Multiple Codex session files declare the same session ID: ${meta.id}`,
|
|
686
|
+
);
|
|
687
|
+
if (requested.size === 0 && found.size >= CODEX_IMPORT_BATCH_LIMIT)
|
|
688
|
+
throw new CodexImportError(
|
|
689
|
+
"content_too_large",
|
|
690
|
+
"discovery",
|
|
691
|
+
`Codex session import batch exceeds the maximum of ${CODEX_IMPORT_BATCH_LIMIT} sessions.`,
|
|
692
|
+
);
|
|
693
|
+
found.set(meta.id, meta);
|
|
694
|
+
}
|
|
695
|
+
if (requested.size > 0) {
|
|
696
|
+
const missing = [...requested].filter(id => !found.has(id));
|
|
697
|
+
if (missing.length > 0)
|
|
698
|
+
throw new CodexImportError(
|
|
699
|
+
"source_not_found",
|
|
700
|
+
"discovery",
|
|
701
|
+
`Codex session not found in this workspace: ${missing.join(", ")}`,
|
|
702
|
+
);
|
|
703
|
+
}
|
|
704
|
+
const sources = [...found.values()].sort(
|
|
705
|
+
(a, b) => a.timestamp.localeCompare(b.timestamp) || a.id.localeCompare(b.id),
|
|
706
|
+
);
|
|
707
|
+
if (sources.length === 0 && rootAuthority) {
|
|
708
|
+
closeRetainedRoot(rootAuthority);
|
|
709
|
+
rootAuthority = undefined;
|
|
710
|
+
}
|
|
711
|
+
return sources;
|
|
712
|
+
} catch (error) {
|
|
713
|
+
if (rootAuthority) closeRetainedRoot(rootAuthority);
|
|
714
|
+
throw error;
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
export async function closeCodexSessionAuthorities(sources: readonly CodexSessionSource[]): Promise<void> {
|
|
719
|
+
const roots = new Set(sources.flatMap(source => (source.authority ? [source.authority.root] : [])));
|
|
720
|
+
for (const root of roots) closeRetainedRoot(root);
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
async function closeCodexSourceHandle(handle: fs.FileHandle): Promise<void> {
|
|
724
|
+
try {
|
|
725
|
+
await handle.close();
|
|
726
|
+
} catch (error) {
|
|
727
|
+
if ((error as NodeJS.ErrnoException).code !== "EBADF") throw error;
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
async function* retainedCodexChunks(file: RecoveryFsFile, expected: RecoveryFsIdentity): AsyncGenerator<Uint8Array> {
|
|
731
|
+
let offset = 0;
|
|
732
|
+
for (;;) {
|
|
733
|
+
const chunk = file.readChunk(offset, 1024 * 1024);
|
|
734
|
+
if (!chunk.ok || !chunk.data || !chunk.identity || !sameSourceIdentity(expected, chunk.identity))
|
|
735
|
+
throw new CodexImportError("source_changed", "source", "Retained Codex source read failed.");
|
|
736
|
+
if (chunk.data.byteLength === 0) return;
|
|
737
|
+
offset += chunk.data.byteLength;
|
|
738
|
+
yield chunk.data;
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
async function* boundedCodexLines(stream: AsyncIterable<Uint8Array>, hash: Hash): AsyncGenerator<Buffer> {
|
|
743
|
+
let pending = Buffer.alloc(0);
|
|
744
|
+
for await (const rawChunk of stream) {
|
|
745
|
+
const chunk = Buffer.from(rawChunk);
|
|
746
|
+
hash.update(chunk);
|
|
747
|
+
pending = pending.length === 0 ? chunk : Buffer.concat([pending, chunk], pending.length + chunk.length);
|
|
748
|
+
for (;;) {
|
|
749
|
+
const newline = pending.indexOf(0x0a);
|
|
750
|
+
if (newline < 0) break;
|
|
751
|
+
if (newline > MAX_LINE_BYTES)
|
|
752
|
+
throw new CodexImportError(
|
|
753
|
+
"content_too_large",
|
|
754
|
+
"source_line",
|
|
755
|
+
"Codex session contains an oversized JSONL record.",
|
|
756
|
+
MAX_LINE_BYTES,
|
|
757
|
+
newline,
|
|
758
|
+
);
|
|
759
|
+
const line = Buffer.from(pending.subarray(0, newline));
|
|
760
|
+
pending = Buffer.from(pending.subarray(newline + 1));
|
|
761
|
+
yield line;
|
|
762
|
+
}
|
|
763
|
+
if (pending.length > MAX_LINE_BYTES)
|
|
764
|
+
throw new CodexImportError(
|
|
765
|
+
"content_too_large",
|
|
766
|
+
"source_line",
|
|
767
|
+
"Codex session contains an oversized JSONL record.",
|
|
768
|
+
MAX_LINE_BYTES,
|
|
769
|
+
pending.length,
|
|
770
|
+
);
|
|
771
|
+
}
|
|
772
|
+
if (pending.length > 0) yield pending;
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
export async function convertCodexSession(
|
|
776
|
+
source: CodexSessionSource,
|
|
777
|
+
mappedEventSink?: CodexMappedEventSink,
|
|
778
|
+
): Promise<CodexConversion> {
|
|
779
|
+
if (source.authority && !verifyRetainedRootOwnerOnly(source.authority.root))
|
|
780
|
+
throw new CodexImportError("source_untrusted", "discovery", "Codex sessions directory is not trusted.");
|
|
781
|
+
if (!source.identity)
|
|
782
|
+
throw new CodexImportError("source_untrusted", "source", "Codex session source identity is unavailable.");
|
|
783
|
+
let retainedFile: RecoveryFsFile | undefined;
|
|
784
|
+
let handle: fs.FileHandle | undefined;
|
|
785
|
+
let nodeStream: nodeFs.ReadStream | undefined;
|
|
786
|
+
let nodeInitial: nodeFs.BigIntStats | undefined;
|
|
787
|
+
let initialIdentity: RecoveryFsIdentity;
|
|
788
|
+
let stream: AsyncIterable<Uint8Array>;
|
|
789
|
+
await assertCodexWorkspaceIdentity(source);
|
|
790
|
+
if (source.authority) {
|
|
791
|
+
try {
|
|
792
|
+
retainedFile = source.authority.root.openFile(source.authority.relativePath);
|
|
793
|
+
} catch {
|
|
794
|
+
throw new CodexImportError("source_untrusted", "source", "Retained Codex source authority is unavailable.");
|
|
795
|
+
}
|
|
796
|
+
const retainedIdentity = retainedFile.identity();
|
|
797
|
+
if (
|
|
798
|
+
!retainedIdentity.ok ||
|
|
799
|
+
!retainedIdentity.identity ||
|
|
800
|
+
!sameSourceIdentity(source.identity, retainedIdentity.identity)
|
|
801
|
+
) {
|
|
802
|
+
retainedFile.close();
|
|
803
|
+
throw new CodexImportError("source_untrusted", "source", "Codex session source identity changed.");
|
|
804
|
+
}
|
|
805
|
+
initialIdentity = retainedIdentity.identity;
|
|
806
|
+
stream = retainedCodexChunks(retainedFile, initialIdentity);
|
|
807
|
+
} else {
|
|
808
|
+
handle = await fs.open(source.path, nodeFs.constants.O_RDONLY | nodeFs.constants.O_NOFOLLOW);
|
|
809
|
+
nodeInitial = await handle.stat({ bigint: true });
|
|
810
|
+
const namedInitial = await fs.lstat(source.path, { bigint: true });
|
|
811
|
+
initialIdentity = {
|
|
812
|
+
dev: nodeInitial.dev.toString(),
|
|
813
|
+
ino: nodeInitial.ino.toString(),
|
|
814
|
+
nlink: nodeInitial.nlink.toString(),
|
|
815
|
+
size: nodeInitial.size.toString(),
|
|
816
|
+
mtimeNs: nodeInitial.mtimeNs.toString(),
|
|
817
|
+
ctimeNs: nodeInitial.ctimeNs.toString(),
|
|
818
|
+
};
|
|
819
|
+
const namedIdentity = {
|
|
820
|
+
dev: namedInitial.dev.toString(),
|
|
821
|
+
ino: namedInitial.ino.toString(),
|
|
822
|
+
nlink: namedInitial.nlink.toString(),
|
|
823
|
+
size: namedInitial.size.toString(),
|
|
824
|
+
mtimeNs: namedInitial.mtimeNs.toString(),
|
|
825
|
+
ctimeNs: namedInitial.ctimeNs.toString(),
|
|
826
|
+
};
|
|
827
|
+
if (
|
|
828
|
+
!nodeInitial.isFile() ||
|
|
829
|
+
namedInitial.isSymbolicLink() ||
|
|
830
|
+
!sameSourceIdentity(initialIdentity, namedIdentity) ||
|
|
831
|
+
!sameSourceIdentity(initialIdentity, source.identity)
|
|
832
|
+
) {
|
|
833
|
+
await handle.close();
|
|
834
|
+
throw new CodexImportError(
|
|
835
|
+
"source_untrusted",
|
|
836
|
+
"source",
|
|
837
|
+
"Codex session source is not a trusted regular file.",
|
|
838
|
+
);
|
|
839
|
+
}
|
|
840
|
+
nodeStream = handle.createReadStream({ autoClose: false, highWaterMark: 1024 * 1024 });
|
|
841
|
+
stream = nodeStream;
|
|
842
|
+
}
|
|
843
|
+
const sourceBytes = Number(initialIdentity.size);
|
|
844
|
+
if (BigInt(initialIdentity.size) > BigInt(MAX_SOURCE_BYTES)) {
|
|
845
|
+
if (retainedFile) retainedFile.close();
|
|
846
|
+
if (handle) await handle.close();
|
|
847
|
+
throw new CodexImportError(
|
|
848
|
+
"content_too_large",
|
|
849
|
+
"source",
|
|
850
|
+
"Codex session exceeds the import source limit.",
|
|
851
|
+
MAX_SOURCE_BYTES,
|
|
852
|
+
sourceBytes,
|
|
853
|
+
);
|
|
854
|
+
}
|
|
855
|
+
const hash = createHash("sha256");
|
|
856
|
+
const mapped: CodexMappedEvent[] = [];
|
|
857
|
+
const quarantine: CodexQuarantineRecord[] = [];
|
|
858
|
+
const counts = { input: 0, mapped: 0, quarantined: 0, dropped: 0, redacted: 0 };
|
|
859
|
+
let quarantineBytes = 0;
|
|
860
|
+
let quarantineTruncated = false;
|
|
861
|
+
let lineNumber = 0;
|
|
862
|
+
const seenToolCallIds = new Set<string>();
|
|
863
|
+
const pendingToolCallIds = new Set<string>();
|
|
864
|
+
let metadataValidated = false;
|
|
865
|
+
try {
|
|
866
|
+
for await (const lineBytes of boundedCodexLines(stream, hash)) {
|
|
867
|
+
lineNumber++;
|
|
868
|
+
let line: string;
|
|
869
|
+
try {
|
|
870
|
+
line = new TextDecoder("utf-8", { fatal: true }).decode(lineBytes);
|
|
871
|
+
} catch {
|
|
872
|
+
throw new CodexImportError(
|
|
873
|
+
"malformed_source",
|
|
874
|
+
"source_event",
|
|
875
|
+
`Codex session contains invalid UTF-8 at line ${lineNumber}.`,
|
|
876
|
+
);
|
|
877
|
+
}
|
|
878
|
+
let record: Record<string, unknown>;
|
|
879
|
+
try {
|
|
880
|
+
record = JSON.parse(line) as Record<string, unknown>;
|
|
881
|
+
} catch {
|
|
882
|
+
throw new CodexImportError(
|
|
883
|
+
"malformed_source",
|
|
884
|
+
"source_event",
|
|
885
|
+
`Codex session contains malformed JSON at line ${lineNumber}.`,
|
|
886
|
+
);
|
|
887
|
+
}
|
|
888
|
+
if (!record || typeof record !== "object" || Array.isArray(record))
|
|
889
|
+
throw new CodexImportError(
|
|
890
|
+
"malformed_source",
|
|
891
|
+
"source_event",
|
|
892
|
+
`Codex session contains a non-object JSON value at line ${lineNumber}.`,
|
|
893
|
+
);
|
|
894
|
+
if (record.type === "session_meta") {
|
|
895
|
+
const payload =
|
|
896
|
+
record.payload && typeof record.payload === "object"
|
|
897
|
+
? (record.payload as Record<string, unknown>)
|
|
898
|
+
: undefined;
|
|
899
|
+
const id = String(payload?.id ?? payload?.session_id ?? "");
|
|
900
|
+
const cwd = String(payload?.cwd ?? "");
|
|
901
|
+
const canonicalCwd = await fs.realpath(cwd).catch(() => null);
|
|
902
|
+
if (lineNumber !== 1 || id !== source.id || !canonicalCwd || !sameWorkspace(canonicalCwd, source.cwd))
|
|
903
|
+
throw new CodexImportError(
|
|
904
|
+
"source_changed",
|
|
905
|
+
"source_event",
|
|
906
|
+
"Codex session identity changed between discovery and import.",
|
|
907
|
+
);
|
|
908
|
+
metadataValidated = true;
|
|
909
|
+
continue;
|
|
910
|
+
}
|
|
911
|
+
counts.input++;
|
|
912
|
+
const timestamp = safeTimestamp(record.timestamp, source.timestamp);
|
|
913
|
+
if (record.type !== "response_item" || !record.payload || typeof record.payload !== "object") {
|
|
914
|
+
const sanitized = sanitizeImportedValue(record);
|
|
915
|
+
const eventType = sanitizeImportedString(String(record.type ?? "unknown"));
|
|
916
|
+
counts.redacted += sanitized.redacted + eventType.redacted;
|
|
917
|
+
const reason =
|
|
918
|
+
record.type === "event_msg" || record.type === "turn_context" || record.type === "world_state"
|
|
919
|
+
? "unsupported_record_type"
|
|
920
|
+
: "unsupported_envelope";
|
|
921
|
+
const item: CodexQuarantineRecord = {
|
|
922
|
+
line: lineNumber,
|
|
923
|
+
timestamp,
|
|
924
|
+
eventType: eventType.value,
|
|
925
|
+
reason,
|
|
926
|
+
payload: sanitized.value,
|
|
927
|
+
};
|
|
928
|
+
const itemBytes = Buffer.byteLength(JSON.stringify(item), "utf8") + 1;
|
|
929
|
+
if (quarantineBytes + itemBytes <= MAX_QUARANTINE_BYTES) {
|
|
930
|
+
quarantine.push(item);
|
|
931
|
+
quarantineBytes += itemBytes;
|
|
932
|
+
} else quarantineTruncated = true;
|
|
933
|
+
counts.quarantined++;
|
|
934
|
+
continue;
|
|
935
|
+
}
|
|
936
|
+
const payload = record.payload as Record<string, unknown>;
|
|
937
|
+
let result = mapResponseItem(payload, timestamp);
|
|
938
|
+
if ("kind" in result && result.kind === "tool_call") {
|
|
939
|
+
if (seenToolCallIds.has(result.callId)) result = { quarantine: "duplicate_tool_call_id" };
|
|
940
|
+
else {
|
|
941
|
+
seenToolCallIds.add(result.callId);
|
|
942
|
+
pendingToolCallIds.add(result.callId);
|
|
943
|
+
}
|
|
944
|
+
} else if ("kind" in result && result.kind === "tool_result" && !pendingToolCallIds.delete(result.callId)) {
|
|
945
|
+
result = { quarantine: "orphan_tool_result" };
|
|
946
|
+
}
|
|
947
|
+
if ("drop" in result) {
|
|
948
|
+
counts.dropped++;
|
|
949
|
+
continue;
|
|
950
|
+
}
|
|
951
|
+
if ("quarantine" in result) {
|
|
952
|
+
const sanitized = sanitizeImportedValue(payload);
|
|
953
|
+
const eventType = sanitizeImportedString(String(payload.type ?? "unknown"));
|
|
954
|
+
const reason = sanitizeImportedString(result.quarantine);
|
|
955
|
+
counts.redacted += sanitized.redacted + eventType.redacted + reason.redacted;
|
|
956
|
+
const item: CodexQuarantineRecord = {
|
|
957
|
+
line: lineNumber,
|
|
958
|
+
timestamp,
|
|
959
|
+
eventType: eventType.value,
|
|
960
|
+
reason: reason.value,
|
|
961
|
+
payload: sanitized.value,
|
|
962
|
+
};
|
|
963
|
+
const itemBytes = Buffer.byteLength(JSON.stringify(item), "utf8") + 1;
|
|
964
|
+
if (quarantineBytes + itemBytes <= MAX_QUARANTINE_BYTES) {
|
|
965
|
+
quarantine.push(item);
|
|
966
|
+
quarantineBytes += itemBytes;
|
|
967
|
+
} else quarantineTruncated = true;
|
|
968
|
+
counts.quarantined++;
|
|
969
|
+
continue;
|
|
970
|
+
}
|
|
971
|
+
if (result.kind === "user" && !source.title) source.title = inferredSessionTitle(result.text);
|
|
972
|
+
const sanitized = sanitizeImportedValue(result);
|
|
973
|
+
counts.redacted += sanitized.redacted;
|
|
974
|
+
if (mappedEventSink) await mappedEventSink(sanitized.value as CodexMappedEvent);
|
|
975
|
+
else mapped.push(sanitized.value as CodexMappedEvent);
|
|
976
|
+
counts.mapped++;
|
|
977
|
+
}
|
|
978
|
+
if (!metadataValidated)
|
|
979
|
+
throw new CodexImportError("malformed_source", "source_event", "Codex session metadata is missing.");
|
|
980
|
+
if (retainedFile) {
|
|
981
|
+
const terminal = retainedFile.identity();
|
|
982
|
+
if (!terminal.ok || !terminal.identity || !sameSourceIdentity(initialIdentity, terminal.identity))
|
|
983
|
+
throw new CodexImportError(
|
|
984
|
+
"source_changed",
|
|
985
|
+
"source",
|
|
986
|
+
"Codex session changed while it was being imported.",
|
|
987
|
+
);
|
|
988
|
+
} else {
|
|
989
|
+
if (!handle || !nodeInitial) throw new CodexImportError("source_untrusted", "source", "Codex source closed.");
|
|
990
|
+
const terminal = await handle.stat({ bigint: true });
|
|
991
|
+
const namedTerminal = await fs.lstat(source.path, { bigint: true });
|
|
992
|
+
const terminalIdentity = {
|
|
993
|
+
dev: terminal.dev.toString(),
|
|
994
|
+
ino: terminal.ino.toString(),
|
|
995
|
+
nlink: terminal.nlink.toString(),
|
|
996
|
+
size: terminal.size.toString(),
|
|
997
|
+
mtimeNs: terminal.mtimeNs.toString(),
|
|
998
|
+
ctimeNs: terminal.ctimeNs.toString(),
|
|
999
|
+
};
|
|
1000
|
+
const namedIdentity = {
|
|
1001
|
+
dev: namedTerminal.dev.toString(),
|
|
1002
|
+
ino: namedTerminal.ino.toString(),
|
|
1003
|
+
nlink: namedTerminal.nlink.toString(),
|
|
1004
|
+
size: namedTerminal.size.toString(),
|
|
1005
|
+
mtimeNs: namedTerminal.mtimeNs.toString(),
|
|
1006
|
+
ctimeNs: namedTerminal.ctimeNs.toString(),
|
|
1007
|
+
};
|
|
1008
|
+
if (
|
|
1009
|
+
namedTerminal.isSymbolicLink() ||
|
|
1010
|
+
!sameSourceIdentity(initialIdentity, terminalIdentity) ||
|
|
1011
|
+
!sameSourceIdentity(initialIdentity, namedIdentity)
|
|
1012
|
+
)
|
|
1013
|
+
throw new CodexImportError(
|
|
1014
|
+
"source_changed",
|
|
1015
|
+
"source",
|
|
1016
|
+
"Codex session changed while it was being imported.",
|
|
1017
|
+
);
|
|
1018
|
+
}
|
|
1019
|
+
return {
|
|
1020
|
+
source,
|
|
1021
|
+
sourceSha256: hash.digest("hex"),
|
|
1022
|
+
sourceBytes,
|
|
1023
|
+
mapped,
|
|
1024
|
+
quarantine,
|
|
1025
|
+
quarantineTruncated,
|
|
1026
|
+
counts,
|
|
1027
|
+
};
|
|
1028
|
+
} finally {
|
|
1029
|
+
nodeStream?.destroy();
|
|
1030
|
+
if (retainedFile) retainedFile.close();
|
|
1031
|
+
if (handle) await closeCodexSourceHandle(handle);
|
|
1032
|
+
}
|
|
1033
|
+
}
|