oh-my-codex 0.20.0 → 0.20.2
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/Cargo.lock +6 -6
- package/Cargo.toml +1 -1
- package/README.md +10 -1
- package/dist/adapt/__tests__/hermes.test.js.map +1 -1
- package/dist/adapt/contracts.d.ts.map +1 -1
- package/dist/agents/native-config.js.map +1 -1
- package/dist/auth/__tests__/hotswap.test.d.ts +2 -0
- package/dist/auth/__tests__/hotswap.test.d.ts.map +1 -0
- package/dist/auth/__tests__/hotswap.test.js +183 -0
- package/dist/auth/__tests__/hotswap.test.js.map +1 -0
- package/dist/auth/hotswap.d.ts.map +1 -1
- package/dist/auth/hotswap.js +17 -4
- package/dist/auth/hotswap.js.map +1 -1
- package/dist/auth/paths.js.map +1 -1
- package/dist/auth/storage.js.map +1 -1
- package/dist/autopilot/__tests__/ralplan-gate.test.js +4 -4
- package/dist/autopilot/__tests__/ralplan-gate.test.js.map +1 -1
- package/dist/autopilot/completion-gate.js.map +1 -1
- package/dist/autopilot/fsm.d.ts +1 -1
- package/dist/autopilot/fsm.d.ts.map +1 -1
- package/dist/autoresearch/goal.js.map +1 -1
- package/dist/autoresearch/runtime.js.map +1 -1
- package/dist/capabilities/lockfile.js.map +1 -1
- package/dist/catalog/reader.js.map +1 -1
- package/dist/catalog/skill-mirror.js.map +1 -1
- package/dist/cli/__tests__/adapt-help.test.js.map +1 -1
- package/dist/cli/__tests__/agents-init.test.js.map +1 -1
- package/dist/cli/__tests__/agents.test.js.map +1 -1
- package/dist/cli/__tests__/api.test.js.map +1 -1
- package/dist/cli/__tests__/ask.test.js.map +1 -1
- package/dist/cli/__tests__/auth.test.js.map +1 -1
- package/dist/cli/__tests__/autoresearch-guided.test.js.map +1 -1
- package/dist/cli/__tests__/autoresearch.test.js.map +1 -1
- package/dist/cli/__tests__/capabilities.test.js.map +1 -1
- package/dist/cli/__tests__/codex-plugin-layout.test.js +27 -0
- package/dist/cli/__tests__/codex-plugin-layout.test.js.map +1 -1
- package/dist/cli/__tests__/doctor-context-window-warning.test.js +83 -78
- package/dist/cli/__tests__/doctor-context-window-warning.test.js.map +1 -1
- package/dist/cli/__tests__/doctor-invalid-config.test.js +56 -4
- package/dist/cli/__tests__/doctor-invalid-config.test.js.map +1 -1
- package/dist/cli/__tests__/doctor-team.test.js.map +1 -1
- package/dist/cli/__tests__/doctor-warning-copy.test.js +735 -14
- package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
- package/dist/cli/__tests__/exec.test.js.map +1 -1
- package/dist/cli/__tests__/imagegen-continuation.test.js.map +1 -1
- package/dist/cli/__tests__/index.test.js +216 -11
- package/dist/cli/__tests__/index.test.js.map +1 -1
- package/dist/cli/__tests__/launch-fallback.test.js +49 -3
- package/dist/cli/__tests__/launch-fallback.test.js.map +1 -1
- package/dist/cli/__tests__/mcp-parity.test.js +84 -12
- package/dist/cli/__tests__/mcp-parity.test.js.map +1 -1
- package/dist/cli/__tests__/native-hook-claim-journal.test.d.ts +2 -0
- package/dist/cli/__tests__/native-hook-claim-journal.test.d.ts.map +1 -0
- package/dist/cli/__tests__/native-hook-claim-journal.test.js +128 -0
- package/dist/cli/__tests__/native-hook-claim-journal.test.js.map +1 -0
- package/dist/cli/__tests__/nested-help-routing.test.js.map +1 -1
- package/dist/cli/__tests__/question.test.js.map +1 -1
- package/dist/cli/__tests__/ralph-deslop-contract.test.js.map +1 -1
- package/dist/cli/__tests__/ralph-goal-mode-contract.test.js +6 -0
- package/dist/cli/__tests__/ralph-goal-mode-contract.test.js.map +1 -1
- package/dist/cli/__tests__/ralph-prd-deep-interview.test.js.map +1 -1
- package/dist/cli/__tests__/ralph-prd-smoke.test.js.map +1 -1
- package/dist/cli/__tests__/ralph.test.js +8 -3
- package/dist/cli/__tests__/ralph.test.js.map +1 -1
- package/dist/cli/__tests__/ralplan-bootstrap-3181.test.d.ts +2 -0
- package/dist/cli/__tests__/ralplan-bootstrap-3181.test.d.ts.map +1 -0
- package/dist/cli/__tests__/ralplan-bootstrap-3181.test.js +187 -0
- package/dist/cli/__tests__/ralplan-bootstrap-3181.test.js.map +1 -0
- package/dist/cli/__tests__/ralplan.test.d.ts +2 -0
- package/dist/cli/__tests__/ralplan.test.d.ts.map +1 -0
- package/dist/cli/__tests__/ralplan.test.js +129 -0
- package/dist/cli/__tests__/ralplan.test.js.map +1 -0
- package/dist/cli/__tests__/resume.test.js +6 -1
- package/dist/cli/__tests__/resume.test.js.map +1 -1
- package/dist/cli/__tests__/session-scoped-runtime.test.js +25 -0
- package/dist/cli/__tests__/session-scoped-runtime.test.js.map +1 -1
- package/dist/cli/__tests__/session-search-help.test.js.map +1 -1
- package/dist/cli/__tests__/session-search.test.js.map +1 -1
- package/dist/cli/__tests__/setup-agents-overwrite.test.js +79 -0
- package/dist/cli/__tests__/setup-agents-overwrite.test.js.map +1 -1
- package/dist/cli/__tests__/setup-gh-star.test.js.map +1 -1
- package/dist/cli/__tests__/setup-hooks-shared-ownership.test.js +131 -5
- package/dist/cli/__tests__/setup-hooks-shared-ownership.test.js.map +1 -1
- package/dist/cli/__tests__/setup-hooks-trust-e2e.test.d.ts +2 -0
- package/dist/cli/__tests__/setup-hooks-trust-e2e.test.d.ts.map +1 -0
- package/dist/cli/__tests__/setup-hooks-trust-e2e.test.js +285 -0
- package/dist/cli/__tests__/setup-hooks-trust-e2e.test.js.map +1 -0
- package/dist/cli/__tests__/setup-install-mode.test.js +2264 -17
- package/dist/cli/__tests__/setup-install-mode.test.js.map +1 -1
- package/dist/cli/__tests__/setup-preferences.test.d.ts +2 -0
- package/dist/cli/__tests__/setup-preferences.test.d.ts.map +1 -0
- package/dist/cli/__tests__/setup-preferences.test.js +79 -0
- package/dist/cli/__tests__/setup-preferences.test.js.map +1 -0
- package/dist/cli/__tests__/setup-refresh.test.js +168 -5
- package/dist/cli/__tests__/setup-refresh.test.js.map +1 -1
- package/dist/cli/__tests__/setup-scope.test.js +73 -6
- package/dist/cli/__tests__/setup-scope.test.js.map +1 -1
- package/dist/cli/__tests__/sparkshell-cli.test.js.map +1 -1
- package/dist/cli/__tests__/state.test.js +37 -0
- package/dist/cli/__tests__/state.test.js.map +1 -1
- package/dist/cli/__tests__/team.test.js.map +1 -1
- package/dist/cli/__tests__/ultragoal.test.js +61 -1
- package/dist/cli/__tests__/ultragoal.test.js.map +1 -1
- package/dist/cli/__tests__/uninstall.test.js +2067 -26
- package/dist/cli/__tests__/uninstall.test.js.map +1 -1
- package/dist/cli/__tests__/update.test.js +58 -0
- package/dist/cli/__tests__/update.test.js.map +1 -1
- package/dist/cli/__tests__/url.test.js.map +1 -1
- package/dist/cli/__tests__/windows-popup-loop-contract.test.js.map +1 -1
- package/dist/cli/adapt.js.map +1 -1
- package/dist/cli/agents-init.js.map +1 -1
- package/dist/cli/agents.js.map +1 -1
- package/dist/cli/api.js.map +1 -1
- package/dist/cli/ask.d.ts +1 -1
- package/dist/cli/ask.d.ts.map +1 -1
- package/dist/cli/ask.js.map +1 -1
- package/dist/cli/auth.js.map +1 -1
- package/dist/cli/autoresearch-guided.js.map +1 -1
- package/dist/cli/autoresearch-intake.js.map +1 -1
- package/dist/cli/capabilities.js.map +1 -1
- package/dist/cli/cleanup.js.map +1 -1
- package/dist/cli/codex-feature-probe.js.map +1 -1
- package/dist/cli/codex-home.js.map +1 -1
- package/dist/cli/doctor.d.ts +20 -0
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/doctor.js +636 -161
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/explore.js.map +1 -1
- package/dist/cli/index.d.ts +11 -3
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +108 -20
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/mcp-serve.js.map +1 -1
- package/dist/cli/mission.js.map +1 -1
- package/dist/cli/native-assets.js.map +1 -1
- package/dist/cli/native-hook-claim-journal.d.ts +18 -0
- package/dist/cli/native-hook-claim-journal.d.ts.map +1 -0
- package/dist/cli/native-hook-claim-journal.js +239 -0
- package/dist/cli/native-hook-claim-journal.js.map +1 -0
- package/dist/cli/omx.js.map +1 -1
- package/dist/cli/plugin-marketplace.js.map +1 -1
- package/dist/cli/project-runtime-codex-homes.js.map +1 -1
- package/dist/cli/question.js.map +1 -1
- package/dist/cli/ralph.d.ts.map +1 -1
- package/dist/cli/ralph.js +3 -2
- package/dist/cli/ralph.js.map +1 -1
- package/dist/cli/ralplan.d.ts +14 -0
- package/dist/cli/ralplan.d.ts.map +1 -0
- package/dist/cli/ralplan.js +203 -0
- package/dist/cli/ralplan.js.map +1 -0
- package/dist/cli/setup-preferences.d.ts +11 -0
- package/dist/cli/setup-preferences.d.ts.map +1 -1
- package/dist/cli/setup-preferences.js +32 -0
- package/dist/cli/setup-preferences.js.map +1 -1
- package/dist/cli/setup.d.ts +53 -0
- package/dist/cli/setup.d.ts.map +1 -1
- package/dist/cli/setup.js +1403 -400
- package/dist/cli/setup.js.map +1 -1
- package/dist/cli/sparkshell.js.map +1 -1
- package/dist/cli/star-prompt.js.map +1 -1
- package/dist/cli/state.d.ts.map +1 -1
- package/dist/cli/state.js +2 -1
- package/dist/cli/state.js.map +1 -1
- package/dist/cli/team.js.map +1 -1
- package/dist/cli/ultragoal.d.ts +5 -1
- package/dist/cli/ultragoal.d.ts.map +1 -1
- package/dist/cli/ultragoal.js +34 -2
- package/dist/cli/ultragoal.js.map +1 -1
- package/dist/cli/uninstall.d.ts +8 -0
- package/dist/cli/uninstall.d.ts.map +1 -1
- package/dist/cli/uninstall.js +947 -99
- package/dist/cli/uninstall.js.map +1 -1
- package/dist/cli/update.d.ts.map +1 -1
- package/dist/cli/update.js +8 -1
- package/dist/cli/update.js.map +1 -1
- package/dist/cli/url.js.map +1 -1
- package/dist/compat/__tests__/doctor-contract.test.js +5 -1
- package/dist/compat/__tests__/doctor-contract.test.js.map +1 -1
- package/dist/compat/__tests__/rust-runtime-compat.test.js.map +1 -1
- package/dist/config/__tests__/codex-hooks.test.js +1287 -11
- package/dist/config/__tests__/codex-hooks.test.js.map +1 -1
- package/dist/config/__tests__/generator-idempotent.test.js +1049 -135
- package/dist/config/__tests__/generator-idempotent.test.js.map +1 -1
- package/dist/config/__tests__/generator-notify.test.js +28 -49
- package/dist/config/__tests__/generator-notify.test.js.map +1 -1
- package/dist/config/codex-feature-flags.d.ts.map +1 -1
- package/dist/config/codex-feature-flags.js.map +1 -1
- package/dist/config/codex-hooks.d.ts +124 -1
- package/dist/config/codex-hooks.d.ts.map +1 -1
- package/dist/config/codex-hooks.js +2142 -265
- package/dist/config/codex-hooks.js.map +1 -1
- package/dist/config/commit-lore-guard.js.map +1 -1
- package/dist/config/generator.d.ts +40 -26
- package/dist/config/generator.d.ts.map +1 -1
- package/dist/config/generator.js +1159 -288
- package/dist/config/generator.js.map +1 -1
- package/dist/config/mcp-registry.js.map +1 -1
- package/dist/config/models.d.ts +2 -2
- package/dist/config/models.d.ts.map +1 -1
- package/dist/config/models.js.map +1 -1
- package/dist/config/omx-first-party-mcp.js.map +1 -1
- package/dist/config/team-mode.d.ts.map +1 -1
- package/dist/document-refresh/__tests__/enforcer.test.js.map +1 -1
- package/dist/document-refresh/enforcer.js.map +1 -1
- package/dist/exec/followup.js.map +1 -1
- package/dist/goal-workflows/artifacts.js.map +1 -1
- package/dist/hooks/__tests__/agents-overlay.test.js +7 -4
- package/dist/hooks/__tests__/agents-overlay.test.js.map +1 -1
- package/dist/hooks/__tests__/analyze-routing-contract.test.js.map +1 -1
- package/dist/hooks/__tests__/analyze-skill-contract.test.js.map +1 -1
- package/dist/hooks/__tests__/anti-slop-workflow.test.js.map +1 -1
- package/dist/hooks/__tests__/autopilot-skill-contract.test.js +9 -3
- package/dist/hooks/__tests__/autopilot-skill-contract.test.js.map +1 -1
- package/dist/hooks/__tests__/code-review-skill-contract.test.js.map +1 -1
- package/dist/hooks/__tests__/consensus-execution-handoff.test.js.map +1 -1
- package/dist/hooks/__tests__/deep-interview-contract.test.js.map +1 -1
- package/dist/hooks/__tests__/design-skill.test.js.map +1 -1
- package/dist/hooks/__tests__/keyword-detector.test.js +1372 -2
- package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-fallback-watcher.test.js +14 -0
- package/dist/hooks/__tests__/notify-fallback-watcher.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-all-workers-idle.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js +669 -1
- package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-cross-worktree-heartbeat.test.js +2 -0
- package/dist/hooks/__tests__/notify-hook-cross-worktree-heartbeat.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-managed-tmux.test.js +142 -1
- package/dist/hooks/__tests__/notify-hook-managed-tmux.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-modules.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-non-omx-guard.test.js +48 -2
- package/dist/hooks/__tests__/notify-hook-non-omx-guard.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-ralph-resume.test.js +61 -44
- package/dist/hooks/__tests__/notify-hook-ralph-resume.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-regression-205.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-session-idle-dedupe.test.js +3 -2
- package/dist/hooks/__tests__/notify-hook-session-idle-dedupe.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-session-scope.test.js +243 -4
- package/dist/hooks/__tests__/notify-hook-session-scope.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-team-dispatch.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js +8 -1
- package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-team-tmux-guard.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-tmux-scrollback.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-visual-verdict.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-worker-idle.test.js.map +1 -1
- package/dist/hooks/__tests__/openclaw-setup-contract.test.js.map +1 -1
- package/dist/hooks/__tests__/pre-context-gate-skills.test.js.map +1 -1
- package/dist/hooks/__tests__/prometheus-strict-contract.test.js.map +1 -1
- package/dist/hooks/__tests__/prompt-guidance-fragments.test.js.map +1 -1
- package/dist/hooks/__tests__/prompt-guidance-test-helpers.js.map +1 -1
- package/dist/hooks/__tests__/prompt-session-provenance.test.d.ts +2 -0
- package/dist/hooks/__tests__/prompt-session-provenance.test.d.ts.map +1 -0
- package/dist/hooks/__tests__/prompt-session-provenance.test.js +82 -0
- package/dist/hooks/__tests__/prompt-session-provenance.test.js.map +1 -0
- package/dist/hooks/__tests__/session.test.js +532 -11
- package/dist/hooks/__tests__/session.test.js.map +1 -1
- package/dist/hooks/__tests__/skill-catalog-hygiene.test.js.map +1 -1
- package/dist/hooks/__tests__/team-runtime-gating-docs-contract.test.js.map +1 -1
- package/dist/hooks/__tests__/visual-ralph-skill.test.js.map +1 -1
- package/dist/hooks/__tests__/visual-verdict-loop.test.js.map +1 -1
- package/dist/hooks/agents-overlay.js +5 -5
- package/dist/hooks/agents-overlay.js.map +1 -1
- package/dist/hooks/code-simplifier/index.js.map +1 -1
- package/dist/hooks/explore-routing.js.map +1 -1
- package/dist/hooks/extensibility/dispatcher.js.map +1 -1
- package/dist/hooks/extensibility/events.js.map +1 -1
- package/dist/hooks/extensibility/loader.js.map +1 -1
- package/dist/hooks/extensibility/plugin-runner-stdin.js.map +1 -1
- package/dist/hooks/extensibility/sdk/logging.js.map +1 -1
- package/dist/hooks/keyword-detector.d.ts +33 -7
- package/dist/hooks/keyword-detector.d.ts.map +1 -1
- package/dist/hooks/keyword-detector.js +2228 -119
- package/dist/hooks/keyword-detector.js.map +1 -1
- package/dist/hooks/keyword-registry.d.ts +12 -0
- package/dist/hooks/keyword-registry.d.ts.map +1 -1
- package/dist/hooks/keyword-registry.js +41 -0
- package/dist/hooks/keyword-registry.js.map +1 -1
- package/dist/hooks/prompt-session-provenance.d.ts +87 -0
- package/dist/hooks/prompt-session-provenance.d.ts.map +1 -0
- package/dist/hooks/prompt-session-provenance.js +180 -0
- package/dist/hooks/prompt-session-provenance.js.map +1 -0
- package/dist/hooks/session.d.ts +139 -34
- package/dist/hooks/session.d.ts.map +1 -1
- package/dist/hooks/session.js +1239 -317
- package/dist/hooks/session.js.map +1 -1
- package/dist/hooks/task-size-detector.js.map +1 -1
- package/dist/hud/__tests__/state.test.js.map +1 -1
- package/dist/hud/authority.js.map +1 -1
- package/dist/hud/constants.js.map +1 -1
- package/dist/hud/index.js.map +1 -1
- package/dist/hud/reconcile.js.map +1 -1
- package/dist/hud/render.js.map +1 -1
- package/dist/hud/state.js.map +1 -1
- package/dist/hud/tmux.js.map +1 -1
- package/dist/leader/__tests__/contract.test.js +146 -2
- package/dist/leader/__tests__/contract.test.js.map +1 -1
- package/dist/leader/contract.d.ts +26 -5
- package/dist/leader/contract.d.ts.map +1 -1
- package/dist/leader/contract.js +131 -7
- package/dist/leader/contract.js.map +1 -1
- package/dist/mcp/__tests__/bootstrap.test.js.map +1 -1
- package/dist/mcp/__tests__/server-lifecycle.test.js +15 -2
- package/dist/mcp/__tests__/server-lifecycle.test.js.map +1 -1
- package/dist/mcp/__tests__/state-paths.test.js +98 -1
- package/dist/mcp/__tests__/state-paths.test.js.map +1 -1
- package/dist/mcp/bootstrap.js.map +1 -1
- package/dist/mcp/code-intel-server.d.ts +58 -58
- package/dist/mcp/code-intel-server.d.ts.map +1 -1
- package/dist/mcp/code-intel-server.js.map +1 -1
- package/dist/mcp/hermes-bridge.js.map +1 -1
- package/dist/mcp/hermes-server.d.ts +70 -70
- package/dist/mcp/hermes-server.d.ts.map +1 -1
- package/dist/mcp/lifecycle-telemetry.js.map +1 -1
- package/dist/mcp/memory-server.d.ts +34 -34
- package/dist/mcp/memory-server.d.ts.map +1 -1
- package/dist/mcp/memory-server.js.map +1 -1
- package/dist/mcp/state-paths.d.ts +14 -0
- package/dist/mcp/state-paths.d.ts.map +1 -1
- package/dist/mcp/state-paths.js +96 -20
- package/dist/mcp/state-paths.js.map +1 -1
- package/dist/mcp/state-server.d.ts +47 -47
- package/dist/mcp/state-server.d.ts.map +1 -1
- package/dist/mcp/trace-server.d.ts +4 -4
- package/dist/mcp/trace-server.d.ts.map +1 -1
- package/dist/mcp/trace-server.js.map +1 -1
- package/dist/mcp/wiki-server.d.ts +23 -23
- package/dist/mcp/wiki-server.d.ts.map +1 -1
- package/dist/mcp/wiki-server.js.map +1 -1
- package/dist/modes/__tests__/base-session-scope.test.js +53 -1
- package/dist/modes/__tests__/base-session-scope.test.js.map +1 -1
- package/dist/modes/base.d.ts.map +1 -1
- package/dist/modes/base.js +5 -5
- package/dist/modes/base.js.map +1 -1
- package/dist/notifications/__tests__/lifecycle-dedupe.test.js.map +1 -1
- package/dist/notifications/__tests__/reply-listener.test.js.map +1 -1
- package/dist/notifications/__tests__/session-idle-tail-dedupe.test.js +177 -85
- package/dist/notifications/__tests__/session-idle-tail-dedupe.test.js.map +1 -1
- package/dist/notifications/__tests__/session-registry.test.js.map +1 -1
- package/dist/notifications/__tests__/template-engine.test.js.map +1 -1
- package/dist/notifications/__tests__/verbosity.test.js.map +1 -1
- package/dist/notifications/http-client.js.map +1 -1
- package/dist/notifications/index.d.ts +4 -1
- package/dist/notifications/index.d.ts.map +1 -1
- package/dist/notifications/index.js +3 -9
- package/dist/notifications/index.js.map +1 -1
- package/dist/notifications/lifecycle-dedupe.js.map +1 -1
- package/dist/notifications/reply-listener.d.ts.map +1 -1
- package/dist/notifications/reply-listener.js.map +1 -1
- package/dist/notifications/session-registry.js.map +1 -1
- package/dist/notifications/session-status.js.map +1 -1
- package/dist/notifications/temp-contract.js.map +1 -1
- package/dist/notifications/tmux-detector.js.map +1 -1
- package/dist/notifications/tmux.js.map +1 -1
- package/dist/openclaw/config.js.map +1 -1
- package/dist/openclaw/dispatcher.js.map +1 -1
- package/dist/pipeline/__tests__/orchestrator.test.js.map +1 -1
- package/dist/pipeline/__tests__/stages.test.js +8 -5
- package/dist/pipeline/__tests__/stages.test.js.map +1 -1
- package/dist/pipeline/stages/code-review.js.map +1 -1
- package/dist/pipeline/stages/ralph-verify.js.map +1 -1
- package/dist/pipeline/stages/ralplan.js.map +1 -1
- package/dist/pipeline/stages/team-exec.js.map +1 -1
- package/dist/pipeline/stages/ultragoal.js.map +1 -1
- package/dist/pipeline/stages/ultraqa.js.map +1 -1
- package/dist/planning/__tests__/approved-execution-lifecycle-matrix.test.js.map +1 -1
- package/dist/planning/artifact-names.js.map +1 -1
- package/dist/planning/artifacts.js.map +1 -1
- package/dist/question/__tests__/state.test.js.map +1 -1
- package/dist/question/__tests__/ui.test.js.map +1 -1
- package/dist/question/autopilot-wait.js.map +1 -1
- package/dist/question/client.d.ts.map +1 -1
- package/dist/question/client.js.map +1 -1
- package/dist/question/deep-interview.js.map +1 -1
- package/dist/question/events.js.map +1 -1
- package/dist/question/renderer.js.map +1 -1
- package/dist/question/state.d.ts.map +1 -1
- package/dist/question/state.js.map +1 -1
- package/dist/question/ui.js.map +1 -1
- package/dist/ralph/contract.d.ts +1 -1
- package/dist/ralph/contract.d.ts.map +1 -1
- package/dist/ralplan/__tests__/consensus-gate.test.js +403 -8
- package/dist/ralplan/__tests__/consensus-gate.test.js.map +1 -1
- package/dist/ralplan/__tests__/runtime.test.js +112 -14
- package/dist/ralplan/__tests__/runtime.test.js.map +1 -1
- package/dist/ralplan/consensus-gate.d.ts +3 -3
- package/dist/ralplan/consensus-gate.d.ts.map +1 -1
- package/dist/ralplan/consensus-gate.js +155 -60
- package/dist/ralplan/consensus-gate.js.map +1 -1
- package/dist/ralplan/runtime.d.ts +2 -2
- package/dist/ralplan/runtime.d.ts.map +1 -1
- package/dist/ralplan/runtime.js +1 -2
- package/dist/ralplan/runtime.js.map +1 -1
- package/dist/runtime/bridge.d.ts.map +1 -1
- package/dist/runtime/bridge.js.map +1 -1
- package/dist/runtime/process-tree.js.map +1 -1
- package/dist/runtime/run-loop.js.map +1 -1
- package/dist/runtime/run-outcome.d.ts +3 -3
- package/dist/runtime/run-outcome.d.ts.map +1 -1
- package/dist/runtime/run-outcome.js.map +1 -1
- package/dist/runtime/run-state.js.map +1 -1
- package/dist/scripts/__tests__/codex-native-hook.test.js +5414 -801
- package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
- package/dist/scripts/__tests__/fixtures/issue-3138-installed-smoke.d.ts +2 -0
- package/dist/scripts/__tests__/fixtures/issue-3138-installed-smoke.d.ts.map +1 -0
- package/dist/scripts/__tests__/fixtures/issue-3138-installed-smoke.js +634 -0
- package/dist/scripts/__tests__/fixtures/issue-3138-installed-smoke.js.map +1 -0
- package/dist/scripts/__tests__/generate-release-body.test.js.map +1 -1
- package/dist/scripts/__tests__/hook-derived-watcher.test.js.map +1 -1
- package/dist/scripts/__tests__/notify-dispatcher.test.js.map +1 -1
- package/dist/scripts/__tests__/notify-fallback-delivery.test.d.ts +2 -0
- package/dist/scripts/__tests__/notify-fallback-delivery.test.d.ts.map +1 -0
- package/dist/scripts/__tests__/notify-fallback-delivery.test.js +174 -0
- package/dist/scripts/__tests__/notify-fallback-delivery.test.js.map +1 -0
- package/dist/scripts/__tests__/notify-state-io.test.js +57 -1
- package/dist/scripts/__tests__/notify-state-io.test.js.map +1 -1
- package/dist/scripts/__tests__/role-intent-bootstrap-e2e-3181.test.d.ts +2 -0
- package/dist/scripts/__tests__/role-intent-bootstrap-e2e-3181.test.d.ts.map +1 -0
- package/dist/scripts/__tests__/role-intent-bootstrap-e2e-3181.test.js +205 -0
- package/dist/scripts/__tests__/role-intent-bootstrap-e2e-3181.test.js.map +1 -0
- package/dist/scripts/__tests__/role-intent-durable-recovery-3181.test.d.ts +2 -0
- package/dist/scripts/__tests__/role-intent-durable-recovery-3181.test.d.ts.map +1 -0
- package/dist/scripts/__tests__/role-intent-durable-recovery-3181.test.js +175 -0
- package/dist/scripts/__tests__/role-intent-durable-recovery-3181.test.js.map +1 -0
- package/dist/scripts/__tests__/run-test-files.test.js.map +1 -1
- package/dist/scripts/__tests__/smoke-packed-install.test.js +848 -4
- package/dist/scripts/__tests__/smoke-packed-install.test.js.map +1 -1
- package/dist/scripts/__tests__/test-reply-listener-live.test.js.map +1 -1
- package/dist/scripts/build-api.js.map +1 -1
- package/dist/scripts/build-explore-harness.js.map +1 -1
- package/dist/scripts/build-sparkshell.js.map +1 -1
- package/dist/scripts/cleanup-explore-harness.js.map +1 -1
- package/dist/scripts/codex-execution-surface.js.map +1 -1
- package/dist/scripts/codex-native-hook.d.ts.map +1 -1
- package/dist/scripts/codex-native-hook.js +825 -172
- package/dist/scripts/codex-native-hook.js.map +1 -1
- package/dist/scripts/generate-native-release-manifest.js.map +1 -1
- package/dist/scripts/generate-release-body.js.map +1 -1
- package/dist/scripts/hook-payload-guard.d.ts.map +1 -1
- package/dist/scripts/hook-payload-guard.js.map +1 -1
- package/dist/scripts/notify-fallback-delivery.d.ts +48 -0
- package/dist/scripts/notify-fallback-delivery.d.ts.map +1 -0
- package/dist/scripts/notify-fallback-delivery.js +419 -0
- package/dist/scripts/notify-fallback-delivery.js.map +1 -0
- package/dist/scripts/notify-fallback-watcher.js +136 -30
- package/dist/scripts/notify-fallback-watcher.js.map +1 -1
- package/dist/scripts/notify-hook/auto-nudge.d.ts +18 -9
- package/dist/scripts/notify-hook/auto-nudge.d.ts.map +1 -1
- package/dist/scripts/notify-hook/auto-nudge.js +39 -13
- package/dist/scripts/notify-hook/auto-nudge.js.map +1 -1
- package/dist/scripts/notify-hook/managed-tmux.d.ts +23 -0
- package/dist/scripts/notify-hook/managed-tmux.d.ts.map +1 -1
- package/dist/scripts/notify-hook/managed-tmux.js +122 -12
- package/dist/scripts/notify-hook/managed-tmux.js.map +1 -1
- package/dist/scripts/notify-hook/orchestration-intent.d.ts +7 -7
- package/dist/scripts/notify-hook/orchestration-intent.d.ts.map +1 -1
- package/dist/scripts/notify-hook/ralph-session-resume.d.ts +5 -2
- package/dist/scripts/notify-hook/ralph-session-resume.d.ts.map +1 -1
- package/dist/scripts/notify-hook/ralph-session-resume.js +33 -37
- package/dist/scripts/notify-hook/ralph-session-resume.js.map +1 -1
- package/dist/scripts/notify-hook/state-io.d.ts +17 -0
- package/dist/scripts/notify-hook/state-io.d.ts.map +1 -1
- package/dist/scripts/notify-hook/state-io.js +73 -11
- package/dist/scripts/notify-hook/state-io.js.map +1 -1
- package/dist/scripts/notify-hook/team-dispatch.d.ts +1 -1
- package/dist/scripts/notify-hook/team-dispatch.js.map +1 -1
- package/dist/scripts/notify-hook/team-leader-nudge.d.ts +2 -2
- package/dist/scripts/notify-hook/team-tmux-guard.d.ts +4 -4
- package/dist/scripts/notify-hook/team-worker-posttooluse.js.map +1 -1
- package/dist/scripts/notify-hook/team-worker-stop.d.ts +1 -1
- package/dist/scripts/notify-hook/team-worker.d.ts +2 -2
- package/dist/scripts/notify-hook/tmux-injection.d.ts +8 -1
- package/dist/scripts/notify-hook/tmux-injection.d.ts.map +1 -1
- package/dist/scripts/notify-hook/tmux-injection.js +25 -16
- package/dist/scripts/notify-hook/tmux-injection.js.map +1 -1
- package/dist/scripts/notify-hook/visual-verdict.d.ts +1 -1
- package/dist/scripts/notify-hook/visual-verdict.d.ts.map +1 -1
- package/dist/scripts/notify-hook/visual-verdict.js +15 -13
- package/dist/scripts/notify-hook/visual-verdict.js.map +1 -1
- package/dist/scripts/notify-hook.d.ts +24 -1
- package/dist/scripts/notify-hook.d.ts.map +1 -1
- package/dist/scripts/notify-hook.js +266 -90
- package/dist/scripts/notify-hook.js.map +1 -1
- package/dist/scripts/postinstall.js.map +1 -1
- package/dist/scripts/prompt-inventory.js.map +1 -1
- package/dist/scripts/smoke-packed-install.d.ts +879 -2
- package/dist/scripts/smoke-packed-install.d.ts.map +1 -1
- package/dist/scripts/smoke-packed-install.js +1528 -7
- package/dist/scripts/smoke-packed-install.js.map +1 -1
- package/dist/scripts/sync-plugin-mirror.js.map +1 -1
- package/dist/scripts/test-reply-listener-live.js.map +1 -1
- package/dist/scripts/test-sparkshell.js.map +1 -1
- package/dist/scripts/verify-native-agents.js.map +1 -1
- package/dist/session-history/friction.js.map +1 -1
- package/dist/session-history/search.js.map +1 -1
- package/dist/sidecar/__tests__/boundary.test.js.map +1 -1
- package/dist/sidecar/collector.js.map +1 -1
- package/dist/sidecar/index.js.map +1 -1
- package/dist/sidecar/render.js.map +1 -1
- package/dist/sidecar/tmux.js.map +1 -1
- package/dist/state/__tests__/operations.test.js +64 -15
- package/dist/state/__tests__/operations.test.js.map +1 -1
- package/dist/state/__tests__/skill-active.test.js +19 -1
- package/dist/state/__tests__/skill-active.test.js.map +1 -1
- package/dist/state/__tests__/workflow-transition.test.js +61 -0
- package/dist/state/__tests__/workflow-transition.test.js.map +1 -1
- package/dist/state/mode-state-context.js.map +1 -1
- package/dist/state/operations.d.ts +1 -1
- package/dist/state/operations.d.ts.map +1 -1
- package/dist/state/operations.js +49 -55
- package/dist/state/operations.js.map +1 -1
- package/dist/state/paths.d.ts +1 -1
- package/dist/state/paths.d.ts.map +1 -1
- package/dist/state/paths.js +1 -1
- package/dist/state/paths.js.map +1 -1
- package/dist/state/skill-active.d.ts +13 -1
- package/dist/state/skill-active.d.ts.map +1 -1
- package/dist/state/skill-active.js +43 -4
- package/dist/state/skill-active.js.map +1 -1
- package/dist/state/terminal-normalization.js.map +1 -1
- package/dist/state/workflow-transition-reconcile.d.ts.map +1 -1
- package/dist/state/workflow-transition-reconcile.js +20 -4
- package/dist/state/workflow-transition-reconcile.js.map +1 -1
- package/dist/state/workflow-transition.d.ts +1 -1
- package/dist/state/workflow-transition.d.ts.map +1 -1
- package/dist/state/workflow-transition.js.map +1 -1
- package/dist/subagents/__tests__/adapted-role-binding.test.d.ts +2 -0
- package/dist/subagents/__tests__/adapted-role-binding.test.d.ts.map +1 -0
- package/dist/subagents/__tests__/adapted-role-binding.test.js +988 -0
- package/dist/subagents/__tests__/adapted-role-binding.test.js.map +1 -0
- package/dist/subagents/__tests__/leader-bootstrap-3181.test.d.ts +2 -0
- package/dist/subagents/__tests__/leader-bootstrap-3181.test.d.ts.map +1 -0
- package/dist/subagents/__tests__/leader-bootstrap-3181.test.js +273 -0
- package/dist/subagents/__tests__/leader-bootstrap-3181.test.js.map +1 -0
- package/dist/subagents/__tests__/tracker.test.js +1550 -10
- package/dist/subagents/__tests__/tracker.test.js.map +1 -1
- package/dist/subagents/adapted-role-binding.d.ts +17 -0
- package/dist/subagents/adapted-role-binding.d.ts.map +1 -0
- package/dist/subagents/adapted-role-binding.js +75 -0
- package/dist/subagents/adapted-role-binding.js.map +1 -0
- package/dist/subagents/role-routing-marker.d.ts +10 -0
- package/dist/subagents/role-routing-marker.d.ts.map +1 -0
- package/dist/subagents/role-routing-marker.js +136 -0
- package/dist/subagents/role-routing-marker.js.map +1 -0
- package/dist/subagents/tracker.d.ts +172 -0
- package/dist/subagents/tracker.d.ts.map +1 -1
- package/dist/subagents/tracker.js +1077 -61
- package/dist/subagents/tracker.js.map +1 -1
- package/dist/team/__tests__/approved-execution.test.js.map +1 -1
- package/dist/team/__tests__/delivery-e2e-smoke.test.js.map +1 -1
- package/dist/team/__tests__/model-contract.test.js +231 -1
- package/dist/team/__tests__/model-contract.test.js.map +1 -1
- package/dist/team/__tests__/role-router.test.js.map +1 -1
- package/dist/team/__tests__/runtime.test.js +401 -1
- package/dist/team/__tests__/runtime.test.js.map +1 -1
- package/dist/team/__tests__/scaling.test.js +181 -0
- package/dist/team/__tests__/scaling.test.js.map +1 -1
- package/dist/team/__tests__/state-root.test.js.map +1 -1
- package/dist/team/__tests__/tmux-session.test.js +434 -4
- package/dist/team/__tests__/tmux-session.test.js.map +1 -1
- package/dist/team/__tests__/tmux-test-fixture.js.map +1 -1
- package/dist/team/api-interop.d.ts +2 -2
- package/dist/team/api-interop.d.ts.map +1 -1
- package/dist/team/api-interop.js.map +1 -1
- package/dist/team/commit-hygiene.js.map +1 -1
- package/dist/team/contracts.d.ts +5 -5
- package/dist/team/contracts.d.ts.map +1 -1
- package/dist/team/delivery-log.js.map +1 -1
- package/dist/team/followup-planner.js.map +1 -1
- package/dist/team/goal-workflow.js.map +1 -1
- package/dist/team/idle-nudge.d.ts.map +1 -1
- package/dist/team/model-contract.d.ts +20 -1
- package/dist/team/model-contract.d.ts.map +1 -1
- package/dist/team/model-contract.js +267 -30
- package/dist/team/model-contract.js.map +1 -1
- package/dist/team/orchestrator.js.map +1 -1
- package/dist/team/pane-status.js.map +1 -1
- package/dist/team/phase-controller.js.map +1 -1
- package/dist/team/reminder-intents.d.ts +1 -1
- package/dist/team/reminder-intents.d.ts.map +1 -1
- package/dist/team/reminder-intents.js.map +1 -1
- package/dist/team/runtime-cli.js.map +1 -1
- package/dist/team/runtime.d.ts.map +1 -1
- package/dist/team/runtime.js +83 -52
- package/dist/team/runtime.js.map +1 -1
- package/dist/team/scaling.d.ts +9 -7
- package/dist/team/scaling.d.ts.map +1 -1
- package/dist/team/scaling.js +69 -28
- package/dist/team/scaling.js.map +1 -1
- package/dist/team/state/dispatch-lock.js.map +1 -1
- package/dist/team/state/dispatch.js.map +1 -1
- package/dist/team/state/events.js.map +1 -1
- package/dist/team/state/tasks.js.map +1 -1
- package/dist/team/state-root.js.map +1 -1
- package/dist/team/state.js.map +1 -1
- package/dist/team/team-identity.d.ts.map +1 -1
- package/dist/team/team-identity.js.map +1 -1
- package/dist/team/tmux-session.d.ts +2 -0
- package/dist/team/tmux-session.d.ts.map +1 -1
- package/dist/team/tmux-session.js +90 -42
- package/dist/team/tmux-session.js.map +1 -1
- package/dist/team/worker-bootstrap.js.map +1 -1
- package/dist/team/worktree.js.map +1 -1
- package/dist/ultragoal/__tests__/artifacts.test.js.map +1 -1
- package/dist/ultragoal/__tests__/docs-contract.test.js.map +1 -1
- package/dist/ultragoal/artifacts.d.ts.map +1 -1
- package/dist/ultragoal/artifacts.js +4 -1
- package/dist/ultragoal/artifacts.js.map +1 -1
- package/dist/url-reader/index.js.map +1 -1
- package/dist/utils/__tests__/agents-md.test.js +16 -0
- package/dist/utils/__tests__/agents-md.test.js.map +1 -1
- package/dist/utils/agents-md.d.ts.map +1 -1
- package/dist/utils/agents-md.js +4 -3
- package/dist/utils/agents-md.js.map +1 -1
- package/dist/utils/agents-model-table.js.map +1 -1
- package/dist/utils/package.js.map +1 -1
- package/dist/utils/paths.js.map +1 -1
- package/dist/utils/platform-command.js.map +1 -1
- package/dist/utils/repo-deps.js.map +1 -1
- package/dist/utils/version.js.map +1 -1
- package/dist/verification/__tests__/ci-rust-gates.test.js +1 -1
- package/dist/verification/__tests__/dev-merge-issue-close-workflow.test.js.map +1 -1
- package/dist/verification/verifier.js.map +1 -1
- package/dist/visual/constants.d.ts +1 -1
- package/dist/visual/constants.d.ts.map +1 -1
- package/dist/vscode/__tests__/index.test.js.map +1 -1
- package/dist/vscode/index.js.map +1 -1
- package/dist/wiki/__tests__/cjk-tokenize.test.js.map +1 -1
- package/dist/wiki/__tests__/lint.test.js.map +1 -1
- package/dist/wiki/__tests__/query.test.js.map +1 -1
- package/dist/wiki/__tests__/storage.test.js.map +1 -1
- package/dist/wiki/__tests__/test-helpers.d.ts.map +1 -1
- package/dist/wiki/lint.js.map +1 -1
- package/dist/wiki/query.js.map +1 -1
- package/dist/wiki/storage.js.map +1 -1
- package/package.json +2 -2
- package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
- package/plugins/oh-my-codex/skills/omx-setup/SKILL.md +10 -5
- package/plugins/oh-my-codex/skills/ralph/SKILL.md +4 -2
- package/plugins/oh-my-codex/skills/ralplan/SKILL.md +5 -2
- package/skills/omx-setup/SKILL.md +10 -5
- package/skills/ralph/SKILL.md +4 -2
- package/skills/ralplan/SKILL.md +5 -2
- package/src/scripts/__tests__/codex-native-hook.test.ts +20497 -12377
- package/src/scripts/__tests__/fixtures/issue-3138-installed-smoke.ts +734 -0
- package/src/scripts/__tests__/notify-fallback-delivery.test.ts +177 -0
- package/src/scripts/__tests__/notify-state-io.test.ts +73 -0
- package/src/scripts/__tests__/role-intent-bootstrap-e2e-3181.test.ts +215 -0
- package/src/scripts/__tests__/role-intent-durable-recovery-3181.test.ts +193 -0
- package/src/scripts/__tests__/smoke-packed-install.test.ts +991 -4
- package/src/scripts/codex-native-hook.ts +918 -172
- package/src/scripts/notify-fallback-delivery.ts +416 -0
- package/src/scripts/notify-fallback-watcher.ts +132 -40
- package/src/scripts/notify-hook/auto-nudge.ts +43 -12
- package/src/scripts/notify-hook/managed-tmux.ts +149 -12
- package/src/scripts/notify-hook/ralph-session-resume.ts +37 -36
- package/src/scripts/notify-hook/state-io.ts +100 -13
- package/src/scripts/notify-hook/tmux-injection.ts +38 -21
- package/src/scripts/notify-hook/visual-verdict.ts +22 -12
- package/src/scripts/notify-hook.ts +335 -90
- package/src/scripts/smoke-packed-install.ts +1829 -6
package/dist/cli/setup.js
CHANGED
|
@@ -2,17 +2,18 @@
|
|
|
2
2
|
* omx setup - Automated installation of oh-my-codex
|
|
3
3
|
* Installs skills, prompts, MCP servers config, and AGENTS.md
|
|
4
4
|
*/
|
|
5
|
-
import { mkdir, cp, copyFile, readdir, readFile, rename, writeFile, stat, lstat, rm, } from "fs/promises";
|
|
6
|
-
import { join, dirname, relative, basename } from "path";
|
|
7
|
-
import { existsSync } from "fs";
|
|
5
|
+
import { mkdir, cp, copyFile, readdir, readFile, rename, writeFile, stat, lstat, rm, open, chmod, } from "fs/promises";
|
|
6
|
+
import { join, dirname, relative, basename, isAbsolute, sep, win32 } from "path";
|
|
7
|
+
import { constants, existsSync } from "fs";
|
|
8
8
|
import { spawnSync } from "child_process";
|
|
9
9
|
import { createInterface } from "readline/promises";
|
|
10
10
|
import { homedir } from "os";
|
|
11
11
|
import TOML from "@iarna/toml";
|
|
12
12
|
import { createHash } from "crypto";
|
|
13
|
+
import { clearNativeHookClaimJournal, persistNativeHookClaimJournal, recoverNativeHookClaimJournal, syncNativeHookClaimParent, restoreNativeHookClaimNoClobber, } from "./native-hook-claim-journal.js";
|
|
13
14
|
import { codexHome, codexConfigPath, codexPromptsDir, codexAgentsDir, userSkillsDir, omxStateDir, detectLegacySkillRootOverlap, omxPlansDir, omxLogsDir, } from "../utils/paths.js";
|
|
14
15
|
import { buildMergedConfig, getRootModelName, getRootTomlArray, hasLegacyOmxTeamRunTable, isOmxManagedNotifyCommand, sanitizePreviousNotifyCommand, stripExistingOmxBlocks, stripExistingSharedMcpRegistryBlock, mergeSharedMcpRegistryBlock, stripOmxEnvSettings, stripOmxFeatureFlags, stripOmxSeededBehavioralDefaults, upsertPluginModeRuntimeFeatureFlags, upsertManagedCodexHookTrustState, stripManagedCodexHookTrustState, OMX_DEVELOPER_INSTRUCTIONS, OMX_PLUGIN_DEVELOPER_INSTRUCTIONS, hasFirstPartyOmxMcpRegistrations, extractFirstPartyOmxMcpSections, stripFirstPartyOmxMcpSections, } from "../config/generator.js";
|
|
15
|
-
import {
|
|
16
|
+
import { buildManagedCodexNativeHookWindowsShimContent, buildManagedCodexNativeHookWindowsShimPath, planManagedCodexHooksMerge, planManagedCodexHooksRemoval, classifyManagedCodexNativeHookWindowsShimOwnership, ManagedCodexHooksPlanError, validateCodexHooksConfigStrict, } from "../config/codex-hooks.js";
|
|
16
17
|
import { getLegacyUnifiedMcpRegistryCandidate, getUnifiedMcpRegistryCandidates, loadUnifiedMcpRegistry, planClaudeCodeMcpSettingsSync, } from "../config/mcp-registry.js";
|
|
17
18
|
import { generateAgentToml } from "../agents/native-config.js";
|
|
18
19
|
import { AGENT_DEFINITIONS } from "../agents/definitions.js";
|
|
@@ -25,7 +26,7 @@ import { DEFAULT_FRONTIER_MODEL } from "../config/models.js";
|
|
|
25
26
|
import { teamModeEnabled, } from "../config/team-mode.js";
|
|
26
27
|
import { addGeneratedAgentsMarker, hasOmxAgentsContract, hasOmxManagedAgentsSections, isOmxGeneratedAgentsMd, preserveUserOmxPolicyBlocks, upsertManagedAgentsBlock, } from "../utils/agents-md.js";
|
|
27
28
|
import { DEFAULT_HUD_CONFIG } from "../hud/types.js";
|
|
28
|
-
import { SETUP_INSTALL_MODES, SETUP_MCP_MODES, SETUP_SCOPES, getSetupScopeFilePath, readPersistedSetupPreferences, } from "./setup-preferences.js";
|
|
29
|
+
import { SETUP_INSTALL_MODES, SETUP_MCP_MODES, SETUP_SCOPES, getSetupScopeFilePath, readPersistedSetupPreferences, resolvePersistedSetupMergeAgents, writePersistedSetupPreferences, } from "./setup-preferences.js";
|
|
29
30
|
import { OMX_LOCAL_MARKETPLACE_NAME, OMX_PLUGIN_NAME, materializePackagedOmxPluginCache, resolvePackagedOmxMarketplace, upsertLocalOmxMarketplaceRegistration, upsertLocalOmxPluginEnablement, upsertLocalOmxPluginMcpServerEnablement, hasLocalOmxPluginMcpServerRegistrations, pluginHookCacheMatchesPackaged, } from "./plugin-marketplace.js";
|
|
30
31
|
import { resolveCodexHookFeatureSupportForCli } from "./codex-feature-probe.js";
|
|
31
32
|
async function resolveStatusLinePresetForSetup(projectRoot, options) {
|
|
@@ -195,66 +196,969 @@ function getBackupContext(scope, projectRoot) {
|
|
|
195
196
|
baseRoot: homedir(),
|
|
196
197
|
};
|
|
197
198
|
}
|
|
198
|
-
function
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
199
|
+
function logManagedCodexHooksPlanDiagnostics(diagnostics, options) {
|
|
200
|
+
if (!options.verbose || !diagnostics)
|
|
201
|
+
return;
|
|
202
|
+
for (const diagnostic of diagnostics) {
|
|
203
|
+
console.log(` hook plan diagnostic: ${diagnostic.message}`);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
let nativeHookTransactionFailureInjector;
|
|
207
|
+
let nativeHookTransactionSequence = 0;
|
|
208
|
+
let nativeHookTransactionPlatformOverride;
|
|
209
|
+
let nativeHookTransactionTemporaryPathOverride;
|
|
210
|
+
let setupLatePhaseFailureInjector;
|
|
211
|
+
/** @internal Test seam for deterministic atomic-write and rollback coverage. */
|
|
212
|
+
export function setNativeHookTransactionFailureInjectorForTest(injector) {
|
|
213
|
+
const previous = nativeHookTransactionFailureInjector;
|
|
214
|
+
nativeHookTransactionFailureInjector = injector;
|
|
215
|
+
return () => {
|
|
216
|
+
nativeHookTransactionFailureInjector = previous;
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
/** @internal Test seam proving native artifacts commit only after later setup phases. */
|
|
220
|
+
export function setSetupLatePhaseFailureInjectorForTest(injector) {
|
|
221
|
+
const previous = setupLatePhaseFailureInjector;
|
|
222
|
+
setupLatePhaseFailureInjector = injector;
|
|
223
|
+
return () => {
|
|
224
|
+
setupLatePhaseFailureInjector = previous;
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
/** @internal Test seam for deterministic Windows transaction coverage. */
|
|
228
|
+
export function setNativeHookTransactionPlatformForTest(platform) {
|
|
229
|
+
const previous = nativeHookTransactionPlatformOverride;
|
|
230
|
+
nativeHookTransactionPlatformOverride = platform;
|
|
231
|
+
return () => {
|
|
232
|
+
nativeHookTransactionPlatformOverride = previous;
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
/** @internal Test seam for deterministic native transaction temporary-path coverage. */
|
|
236
|
+
export function setNativeHookTransactionTemporaryPathForTest(resolver) {
|
|
237
|
+
const previous = nativeHookTransactionTemporaryPathOverride;
|
|
238
|
+
nativeHookTransactionTemporaryPathOverride = resolver;
|
|
239
|
+
return () => {
|
|
240
|
+
nativeHookTransactionTemporaryPathOverride = previous;
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
function nativeHookPlatform() {
|
|
244
|
+
return nativeHookTransactionPlatformOverride ?? process.platform;
|
|
245
|
+
}
|
|
246
|
+
function hookTransactionBytesEqual(left, right) {
|
|
247
|
+
return left === null || right === null ? left === right : left.equals(right);
|
|
248
|
+
}
|
|
249
|
+
function nativeHookTransactionTopologyEqual(left, right) {
|
|
250
|
+
return left.kind === "absent" || right.kind === "absent"
|
|
251
|
+
? left.kind === right.kind
|
|
252
|
+
: left.mode === right.mode;
|
|
253
|
+
}
|
|
254
|
+
function isMissingPathError(error) {
|
|
255
|
+
return (typeof error === "object" &&
|
|
256
|
+
error !== null &&
|
|
257
|
+
"code" in error &&
|
|
258
|
+
error.code === "ENOENT");
|
|
259
|
+
}
|
|
260
|
+
function nativeHookTransactionArtifactAncestorPaths(controlledRoot, artifactPaths) {
|
|
261
|
+
const paths = new Set([controlledRoot]);
|
|
262
|
+
for (const artifactPath of artifactPaths) {
|
|
263
|
+
const parentPath = dirname(artifactPath);
|
|
264
|
+
const fromControlledRoot = relative(controlledRoot, parentPath);
|
|
265
|
+
if (fromControlledRoot === ".." ||
|
|
266
|
+
fromControlledRoot.startsWith(`..${sep}`) ||
|
|
267
|
+
isAbsolute(fromControlledRoot)) {
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
let ancestorPath = controlledRoot;
|
|
271
|
+
if (fromControlledRoot !== "") {
|
|
272
|
+
for (const component of fromControlledRoot.split(sep)) {
|
|
273
|
+
ancestorPath = join(ancestorPath, component);
|
|
274
|
+
paths.add(ancestorPath);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
return [...paths];
|
|
279
|
+
}
|
|
280
|
+
async function captureNativeHookTransactionAncestorSnapshots(ancestorPaths) {
|
|
281
|
+
const snapshots = [];
|
|
282
|
+
for (const path of ancestorPaths) {
|
|
283
|
+
let status;
|
|
284
|
+
try {
|
|
285
|
+
status = await lstat(path);
|
|
286
|
+
}
|
|
287
|
+
catch (error) {
|
|
288
|
+
if (isMissingPathError(error)) {
|
|
289
|
+
snapshots.push({ path, topology: { kind: "absent" } });
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
throw error;
|
|
293
|
+
}
|
|
294
|
+
if (status.isSymbolicLink()) {
|
|
295
|
+
throw new Error(`Refusing native hook transaction: ancestor ${path} is a symbolic link.`);
|
|
296
|
+
}
|
|
297
|
+
if (!status.isDirectory()) {
|
|
298
|
+
throw new Error(`Refusing native hook transaction: ancestor ${path} is not a directory.`);
|
|
299
|
+
}
|
|
300
|
+
snapshots.push({
|
|
301
|
+
path,
|
|
302
|
+
topology: { kind: "directory", device: status.dev, inode: status.ino },
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
return snapshots;
|
|
306
|
+
}
|
|
307
|
+
function nativeHookTransactionAncestorTopologyEqual(left, right) {
|
|
308
|
+
return left.kind === "absent" || right.kind === "absent"
|
|
309
|
+
? left.kind === right.kind
|
|
310
|
+
: left.device === right.device && left.inode === right.inode;
|
|
311
|
+
}
|
|
312
|
+
async function captureNativeHookTransactionAncestorPrecondition(controlledRoot, artifactPaths) {
|
|
313
|
+
const ancestorPaths = nativeHookTransactionArtifactAncestorPaths(controlledRoot, artifactPaths);
|
|
314
|
+
return {
|
|
315
|
+
controlledRoot,
|
|
316
|
+
ancestorPaths,
|
|
317
|
+
snapshots: await captureNativeHookTransactionAncestorSnapshots(ancestorPaths),
|
|
318
|
+
};
|
|
213
319
|
}
|
|
214
|
-
function
|
|
320
|
+
async function assertNativeHookTransactionAncestorPrecondition(precondition) {
|
|
321
|
+
const actual = await captureNativeHookTransactionAncestorSnapshots(precondition.ancestorPaths);
|
|
322
|
+
for (let index = 0; index < precondition.snapshots.length; index += 1) {
|
|
323
|
+
const expected = precondition.snapshots[index];
|
|
324
|
+
const current = actual[index];
|
|
325
|
+
if (!nativeHookTransactionAncestorTopologyEqual(current.topology, expected.topology)) {
|
|
326
|
+
throw new Error(`Native hook transaction precondition changed for ancestor ${expected.path}; refusing to mutate a stale topology.`);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
async function refreshNativeHookTransactionAncestorPrecondition(precondition, artifactPath) {
|
|
331
|
+
const allowedCreatedPaths = new Set(nativeHookTransactionArtifactAncestorPaths(precondition.controlledRoot, [artifactPath]));
|
|
332
|
+
const actual = await captureNativeHookTransactionAncestorSnapshots(precondition.ancestorPaths);
|
|
333
|
+
for (let index = 0; index < precondition.snapshots.length; index += 1) {
|
|
334
|
+
const expected = precondition.snapshots[index];
|
|
335
|
+
const current = actual[index];
|
|
336
|
+
if (nativeHookTransactionAncestorTopologyEqual(current.topology, expected.topology)) {
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
339
|
+
if (expected.topology.kind === "absent" &&
|
|
340
|
+
current.topology.kind === "directory" &&
|
|
341
|
+
allowedCreatedPaths.has(expected.path)) {
|
|
342
|
+
continue;
|
|
343
|
+
}
|
|
344
|
+
throw new Error(`Native hook transaction precondition changed for ancestor ${expected.path}; refusing to mutate a stale topology.`);
|
|
345
|
+
}
|
|
346
|
+
precondition.snapshots = actual;
|
|
347
|
+
}
|
|
348
|
+
function decodeNativeHookTransactionUtf8(bytes, label) {
|
|
215
349
|
try {
|
|
216
|
-
const
|
|
217
|
-
|
|
350
|
+
const content = new TextDecoder("utf-8", {
|
|
351
|
+
fatal: true,
|
|
352
|
+
ignoreBOM: true,
|
|
353
|
+
}).decode(bytes);
|
|
354
|
+
if (!Buffer.from(content, "utf-8").equals(bytes)) {
|
|
355
|
+
throw new Error("decoded text did not round-trip to the original bytes");
|
|
356
|
+
}
|
|
357
|
+
return content;
|
|
218
358
|
}
|
|
219
|
-
catch {
|
|
220
|
-
|
|
359
|
+
catch (error) {
|
|
360
|
+
throw new ManagedCodexHooksPlanError("invalid_document", `Refusing to read ${label}: invalid UTF-8 (${error instanceof Error ? error.message : String(error)}).`, { label });
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
async function captureNativeHookTransactionArtifact(path, label) {
|
|
364
|
+
let before;
|
|
365
|
+
try {
|
|
366
|
+
before = await lstat(path);
|
|
367
|
+
}
|
|
368
|
+
catch (error) {
|
|
369
|
+
if (isMissingPathError(error)) {
|
|
370
|
+
return { bytes: null, topology: { kind: "absent" } };
|
|
371
|
+
}
|
|
372
|
+
throw error;
|
|
373
|
+
}
|
|
374
|
+
if (!before.isFile() || before.nlink !== 1) {
|
|
375
|
+
throw new Error(`Refusing native hook transaction for ${label}: expected a single-link regular file or absence.`);
|
|
376
|
+
}
|
|
377
|
+
const bytes = await readFile(path);
|
|
378
|
+
const after = await lstat(path);
|
|
379
|
+
const beforeTopology = {
|
|
380
|
+
kind: "regular_file",
|
|
381
|
+
mode: before.mode & 0o7777,
|
|
382
|
+
};
|
|
383
|
+
const afterTopology = {
|
|
384
|
+
kind: "regular_file",
|
|
385
|
+
mode: after.mode & 0o7777,
|
|
386
|
+
};
|
|
387
|
+
if (!after.isFile() ||
|
|
388
|
+
after.nlink !== 1 ||
|
|
389
|
+
!nativeHookTransactionTopologyEqual(beforeTopology, afterTopology) ||
|
|
390
|
+
before.dev !== after.dev ||
|
|
391
|
+
before.ino !== after.ino ||
|
|
392
|
+
before.nlink !== after.nlink) {
|
|
393
|
+
throw new Error(`Refusing native hook transaction for ${label}: path changed while its snapshot was read.`);
|
|
221
394
|
}
|
|
395
|
+
return {
|
|
396
|
+
bytes,
|
|
397
|
+
topology: beforeTopology,
|
|
398
|
+
device: before.dev,
|
|
399
|
+
inode: before.ino,
|
|
400
|
+
links: before.nlink,
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
function nativeHookTransactionSnapshotsEqual(left, right) {
|
|
404
|
+
return (hookTransactionBytesEqual(left.bytes, right.bytes) &&
|
|
405
|
+
nativeHookTransactionTopologyEqual(left.topology, right.topology) &&
|
|
406
|
+
left.device === right.device &&
|
|
407
|
+
left.inode === right.inode &&
|
|
408
|
+
left.links === right.links);
|
|
222
409
|
}
|
|
223
|
-
function
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
410
|
+
function nativeHookTransactionSnapshotMatchesExpected(snapshot, expected) {
|
|
411
|
+
return (hookTransactionBytesEqual(snapshot.bytes, expected.bytes) &&
|
|
412
|
+
nativeHookTransactionTopologyEqual(snapshot.topology, expected.topology));
|
|
413
|
+
}
|
|
414
|
+
function nativeHookTransactionOutputTopology(before, after) {
|
|
415
|
+
if (after === null)
|
|
416
|
+
return { kind: "absent" };
|
|
417
|
+
return {
|
|
418
|
+
kind: "regular_file",
|
|
419
|
+
mode: before.topology.kind === "regular_file" ? before.topology.mode : 0o600,
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
function nativeHookTransactionArtifact(kind, path, label, before, after, hookPlatform) {
|
|
423
|
+
return hookTransactionBytesEqual(before.bytes, after)
|
|
424
|
+
? null
|
|
425
|
+
: {
|
|
426
|
+
kind,
|
|
427
|
+
path,
|
|
428
|
+
label,
|
|
429
|
+
before,
|
|
430
|
+
after,
|
|
431
|
+
afterTopology: nativeHookTransactionOutputTopology(before, after),
|
|
432
|
+
hookPlatform,
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
function nativeHookTransactionPrecondition(kind, path, label, before) {
|
|
436
|
+
return { kind, path, label, before };
|
|
437
|
+
}
|
|
438
|
+
function assertWindowsNativeHookShimOwnership(shimPath, before, expected) {
|
|
439
|
+
if (before === null)
|
|
440
|
+
return;
|
|
441
|
+
const ownership = classifyManagedCodexNativeHookWindowsShimOwnership(before, expected);
|
|
442
|
+
if (ownership === "current" || ownership === "historical")
|
|
443
|
+
return;
|
|
444
|
+
throw new Error(`Refusing to replace modified Windows native hook shim ${shimPath}. Restore the complete OMX-generated shim or remove it after verifying no foreign hook references it.`);
|
|
445
|
+
}
|
|
446
|
+
function isWindowsShimReferenceRecord(value) {
|
|
447
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
448
|
+
}
|
|
449
|
+
function normalizeWindowsShimReferencePath(path) {
|
|
450
|
+
const windowsPath = path.replace(/\//g, "\\");
|
|
451
|
+
if (windowsPath.length === 0 ||
|
|
452
|
+
/[\0-\x1f\x7f-\x9f`<>"|?*]/.test(windowsPath) ||
|
|
453
|
+
/^\\\\\.(?:\\|$)/.test(windowsPath)) {
|
|
454
|
+
return null;
|
|
455
|
+
}
|
|
456
|
+
const isDriveAbsolute = /^[A-Za-z]:\\(?:[^\\:]+(?:\\[^\\:]+)*)?$/.test(windowsPath);
|
|
457
|
+
const isUncAbsolute = /^\\\\[^\\:]+\\[^\\:]+(?:\\[^\\:]+)*$/.test(windowsPath);
|
|
458
|
+
if (!isDriveAbsolute && !isUncAbsolute)
|
|
459
|
+
return null;
|
|
460
|
+
return win32.normalize(windowsPath).toLowerCase();
|
|
461
|
+
}
|
|
462
|
+
function windowsShimPathBasename(path) {
|
|
463
|
+
const name = win32.basename(path.replace(/\//g, "\\")).replace(/[. ]+$/, "");
|
|
464
|
+
return name.length > 0 ? name.toLowerCase() : null;
|
|
465
|
+
}
|
|
466
|
+
function decodePowerShellSingleQuotedLiteral(value) {
|
|
467
|
+
return value.replace(/''/g, "'");
|
|
468
|
+
}
|
|
469
|
+
/**
|
|
470
|
+
* Parses the only foreign command form that proves its -File target is a
|
|
471
|
+
* static literal. All other PowerShell forms remain ambiguous because they can
|
|
472
|
+
* construct an executable target dynamically or through a nested evaluator.
|
|
473
|
+
*/
|
|
474
|
+
function decodeWindowsShimReferencePath(command) {
|
|
475
|
+
if (/["`]/.test(command))
|
|
476
|
+
return null;
|
|
477
|
+
const match = command.match(/^\s*&\s+'((?:''|[^'\r\n])*)'\s+-noprofile\s+-executionpolicy\s+bypass\s+-file\s+'((?:''|[^'\r\n])*)'\s*$/i);
|
|
478
|
+
if (!match)
|
|
479
|
+
return null;
|
|
480
|
+
const powerShellPath = decodePowerShellSingleQuotedLiteral(match[1]);
|
|
481
|
+
if (windowsShimPathBasename(powerShellPath) !== "powershell.exe" ||
|
|
482
|
+
normalizeWindowsShimReferencePath(powerShellPath) === null ||
|
|
483
|
+
hasPotentialWindowsPathAlias(powerShellPath)) {
|
|
484
|
+
return null;
|
|
485
|
+
}
|
|
486
|
+
return decodePowerShellSingleQuotedLiteral(match[2]);
|
|
487
|
+
}
|
|
488
|
+
function hasPotentialWindowsPathAlias(path) {
|
|
489
|
+
const windowsPath = path.replace(/\//g, "\\");
|
|
490
|
+
return windowsPath.split("\\").some((component) => component === "." ||
|
|
491
|
+
component === ".." ||
|
|
492
|
+
/[. ]$/.test(component) ||
|
|
493
|
+
/~\d+(?:\.[^\\]*)?$/i.test(component));
|
|
494
|
+
}
|
|
495
|
+
function windowsShimCommandReferenceDecision(command, shimPath) {
|
|
496
|
+
const decodedShimPath = decodeWindowsShimReferencePath(command);
|
|
497
|
+
if (decodedShimPath === null)
|
|
498
|
+
return "ambiguous";
|
|
499
|
+
const normalizedDecodedPath = normalizeWindowsShimReferencePath(decodedShimPath);
|
|
500
|
+
const normalizedShimPath = normalizeWindowsShimReferencePath(shimPath);
|
|
501
|
+
if (normalizedDecodedPath === null || normalizedShimPath === null) {
|
|
502
|
+
return "ambiguous";
|
|
503
|
+
}
|
|
504
|
+
if (normalizedDecodedPath === normalizedShimPath)
|
|
505
|
+
return "referenced";
|
|
506
|
+
// A distinct fully qualified spelling is not proof that the foreign target
|
|
507
|
+
// cannot resolve to this shim: reparse points, SUBST, mapped drives, and UNC
|
|
508
|
+
// aliases can all refer to the same file. No race-safe file-identity proof is
|
|
509
|
+
// available here, so preserve the proof-owned shim.
|
|
510
|
+
return "ambiguous";
|
|
511
|
+
}
|
|
512
|
+
/**
|
|
513
|
+
* Decides whether a proof-owned Windows shim must remain after a hooks.json
|
|
514
|
+
* transition. JSON is strictly validated and decoded before inspection so
|
|
515
|
+
* escaped paths are handled semantically. Unknown future event members are
|
|
516
|
+
* scanned only through the established executable group/command shape; prompt
|
|
517
|
+
* and agent payloads remain inert metadata.
|
|
518
|
+
*/
|
|
519
|
+
export function decideWindowsNativeHookShimReference(finalHooksContent, shimPath) {
|
|
520
|
+
if (finalHooksContent === null)
|
|
521
|
+
return "not_referenced";
|
|
522
|
+
const validation = validateCodexHooksConfigStrict(finalHooksContent, {
|
|
523
|
+
platform: "win32",
|
|
228
524
|
});
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
525
|
+
if (!validation.ok)
|
|
526
|
+
return "ambiguous";
|
|
527
|
+
const hooks = validation.root.hooks;
|
|
528
|
+
if (!isWindowsShimReferenceRecord(hooks))
|
|
529
|
+
return "not_referenced";
|
|
530
|
+
let ambiguous = false;
|
|
531
|
+
for (const eventGroups of Object.values(hooks)) {
|
|
532
|
+
if (!Array.isArray(eventGroups))
|
|
533
|
+
continue;
|
|
534
|
+
for (const group of eventGroups) {
|
|
535
|
+
if (!isWindowsShimReferenceRecord(group) ||
|
|
536
|
+
!Array.isArray(group.hooks)) {
|
|
537
|
+
continue;
|
|
538
|
+
}
|
|
539
|
+
for (const handler of group.hooks) {
|
|
540
|
+
if (!isWindowsShimReferenceRecord(handler) ||
|
|
541
|
+
handler.type !== "command") {
|
|
542
|
+
continue;
|
|
543
|
+
}
|
|
544
|
+
for (const command of [
|
|
545
|
+
handler.commandWindows,
|
|
546
|
+
handler.command_windows,
|
|
547
|
+
handler.command,
|
|
548
|
+
]) {
|
|
549
|
+
if (typeof command !== "string")
|
|
550
|
+
continue;
|
|
551
|
+
const decision = windowsShimCommandReferenceDecision(command, shimPath);
|
|
552
|
+
if (decision === "referenced")
|
|
553
|
+
return decision;
|
|
554
|
+
if (decision === "ambiguous")
|
|
555
|
+
ambiguous = true;
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
return ambiguous ? "ambiguous" : "not_referenced";
|
|
561
|
+
}
|
|
562
|
+
function preflightManagedCodexHookTrustState(config, priorManagedHookTrustState, managedHookTrustState) {
|
|
563
|
+
stripManagedCodexHookTrustState(config, {
|
|
564
|
+
priorManagedHookTrustState,
|
|
565
|
+
managedTrustState: managedHookTrustState,
|
|
566
|
+
});
|
|
567
|
+
}
|
|
568
|
+
function injectNativeHookTransactionFailure(stage, target) {
|
|
569
|
+
nativeHookTransactionFailureInjector?.(stage, target);
|
|
570
|
+
}
|
|
571
|
+
function nativeHookTransactionTemporaryPath(path, purpose) {
|
|
572
|
+
if (nativeHookTransactionTemporaryPathOverride) {
|
|
573
|
+
return nativeHookTransactionTemporaryPathOverride(path, purpose);
|
|
574
|
+
}
|
|
575
|
+
nativeHookTransactionSequence += 1;
|
|
576
|
+
return join(dirname(path), `.${basename(path)}.omx-${purpose}-${process.pid}-${nativeHookTransactionSequence}.tmp`);
|
|
577
|
+
}
|
|
578
|
+
function nativeHookTransactionClaimPath(path) {
|
|
579
|
+
nativeHookTransactionSequence += 1;
|
|
580
|
+
return join(dirname(path), `.${basename(path)}.omx-claim-${process.pid}-${nativeHookTransactionSequence}.tmp`);
|
|
581
|
+
}
|
|
582
|
+
async function restoreNativeHookClaim(claimPath, destinationPath) {
|
|
583
|
+
await restoreNativeHookClaimNoClobber(claimPath, destinationPath);
|
|
584
|
+
}
|
|
585
|
+
async function atomicWriteNativeHookTransactionArtifact(artifact, content, stage, expectedCurrent, ancestorPrecondition, onWriteApplied, onWriteStabilized, assertApplied) {
|
|
586
|
+
const temporaryPath = nativeHookTransactionTemporaryPath(artifact.path, "write");
|
|
587
|
+
let temporaryCreated = false;
|
|
588
|
+
let temporarySnapshot;
|
|
589
|
+
let claimPath;
|
|
590
|
+
let claimCreated = false;
|
|
591
|
+
let journaledClaim = false;
|
|
592
|
+
try {
|
|
593
|
+
await assertNativeHookTransactionAncestorPrecondition(ancestorPrecondition);
|
|
594
|
+
await assertApplied?.();
|
|
595
|
+
await mkdir(dirname(artifact.path), { recursive: true });
|
|
596
|
+
await refreshNativeHookTransactionAncestorPrecondition(ancestorPrecondition, artifact.path);
|
|
597
|
+
if (stage === "write") {
|
|
598
|
+
injectNativeHookTransactionFailure("before_temp_write", artifact);
|
|
599
|
+
}
|
|
600
|
+
else {
|
|
601
|
+
injectNativeHookTransactionFailure("before_rollback", artifact);
|
|
602
|
+
}
|
|
603
|
+
await assertNativeHookTransactionArtifactSnapshot(artifact.path, artifact.label, expectedCurrent, stage === "write" ? "precondition" : "rollback");
|
|
604
|
+
await assertNativeHookTransactionAncestorPrecondition(ancestorPrecondition);
|
|
605
|
+
await assertApplied?.();
|
|
606
|
+
const handle = await open(temporaryPath, "wx", artifact.afterTopology.kind === "regular_file"
|
|
607
|
+
? artifact.afterTopology.mode
|
|
608
|
+
: 0o600);
|
|
609
|
+
temporaryCreated = true;
|
|
610
|
+
try {
|
|
611
|
+
await handle.writeFile(content);
|
|
612
|
+
await handle.sync();
|
|
613
|
+
}
|
|
614
|
+
finally {
|
|
615
|
+
await handle.close();
|
|
616
|
+
}
|
|
617
|
+
if (artifact.afterTopology.kind === "regular_file") {
|
|
618
|
+
await chmod(temporaryPath, artifact.afterTopology.mode);
|
|
619
|
+
}
|
|
620
|
+
temporarySnapshot = await assertNativeHookTransactionArtifactState(temporaryPath, `${artifact.label} temporary`, { bytes: content, topology: artifact.afterTopology }, "read-back");
|
|
621
|
+
if (stage === "write") {
|
|
622
|
+
injectNativeHookTransactionFailure("before_rename", artifact);
|
|
623
|
+
}
|
|
624
|
+
else {
|
|
625
|
+
injectNativeHookTransactionFailure("before_rollback_rename", artifact);
|
|
626
|
+
}
|
|
627
|
+
await assertNativeHookTransactionArtifactSnapshot(artifact.path, artifact.label, expectedCurrent, stage === "write" ? "precondition" : "rollback");
|
|
628
|
+
await assertNativeHookTransactionAncestorPrecondition(ancestorPrecondition);
|
|
629
|
+
await assertNativeHookTransactionArtifactSnapshot(temporaryPath, `${artifact.label} temporary`, temporarySnapshot, "read-back");
|
|
630
|
+
await assertApplied?.();
|
|
631
|
+
if (!temporarySnapshot) {
|
|
632
|
+
throw new Error("temporary path was not fully captured after writing");
|
|
633
|
+
}
|
|
634
|
+
injectNativeHookTransactionFailure(stage === "write"
|
|
635
|
+
? "after_final_rename_validation"
|
|
636
|
+
: "after_final_restore_validation", artifact);
|
|
637
|
+
claimPath = nativeHookTransactionClaimPath(artifact.path);
|
|
638
|
+
const claimRelativePath = relative(ancestorPrecondition.controlledRoot, artifact.path);
|
|
639
|
+
const canJournalClaim = !isAbsolute(claimRelativePath) &&
|
|
640
|
+
claimRelativePath !== ".." &&
|
|
641
|
+
!claimRelativePath.startsWith(`..${sep}`);
|
|
642
|
+
if (expectedCurrent.bytes !== null) {
|
|
643
|
+
if (canJournalClaim) {
|
|
644
|
+
await persistNativeHookClaimJournal(ancestorPrecondition.controlledRoot, {
|
|
645
|
+
canonicalPath: artifact.path,
|
|
646
|
+
claimPath,
|
|
647
|
+
before: expectedCurrent.bytes,
|
|
648
|
+
after: content,
|
|
649
|
+
});
|
|
650
|
+
await refreshNativeHookTransactionAncestorPrecondition(ancestorPrecondition, join(ancestorPrecondition.controlledRoot, ".omx", "native-hook-claim-journal.json"));
|
|
651
|
+
journaledClaim = true;
|
|
652
|
+
}
|
|
653
|
+
await rename(artifact.path, claimPath);
|
|
654
|
+
claimCreated = true;
|
|
655
|
+
if (journaledClaim)
|
|
656
|
+
await syncNativeHookClaimParent(claimPath);
|
|
657
|
+
await assertNativeHookTransactionArtifactSnapshot(claimPath, `${artifact.label} replacement claim`, expectedCurrent, stage === "write" ? "precondition" : "rollback");
|
|
658
|
+
}
|
|
659
|
+
await copyFile(temporaryPath, artifact.path, constants.COPYFILE_EXCL);
|
|
660
|
+
if (artifact.afterTopology.kind === "regular_file") {
|
|
661
|
+
await chmod(artifact.path, artifact.afterTopology.mode);
|
|
662
|
+
}
|
|
663
|
+
const installedHandle = await open(artifact.path, "r");
|
|
664
|
+
try {
|
|
665
|
+
await installedHandle.sync();
|
|
666
|
+
}
|
|
667
|
+
finally {
|
|
668
|
+
await installedHandle.close();
|
|
669
|
+
}
|
|
670
|
+
await syncNativeHookClaimParent(artifact.path);
|
|
671
|
+
const installedSnapshot = await assertNativeHookTransactionArtifactState(artifact.path, artifact.label, { bytes: content, topology: artifact.afterTopology }, stage === "write" ? "precondition" : "rollback");
|
|
672
|
+
if (claimCreated && claimPath) {
|
|
673
|
+
await rm(claimPath);
|
|
674
|
+
await syncNativeHookClaimParent(claimPath);
|
|
675
|
+
claimCreated = false;
|
|
676
|
+
}
|
|
677
|
+
if (journaledClaim) {
|
|
678
|
+
await clearNativeHookClaimJournal(ancestorPrecondition.controlledRoot);
|
|
679
|
+
journaledClaim = false;
|
|
680
|
+
}
|
|
681
|
+
await rm(temporaryPath);
|
|
682
|
+
temporaryCreated = false;
|
|
683
|
+
onWriteApplied?.(installedSnapshot);
|
|
684
|
+
onWriteStabilized?.(installedSnapshot);
|
|
685
|
+
if (stage === "write") {
|
|
686
|
+
injectNativeHookTransactionFailure("after_rename", artifact);
|
|
687
|
+
}
|
|
688
|
+
await assertNativeHookTransactionArtifactSnapshot(artifact.path, artifact.label, installedSnapshot, stage === "write" ? "precondition" : "rollback");
|
|
689
|
+
}
|
|
690
|
+
catch (error) {
|
|
691
|
+
if (claimCreated && claimPath) {
|
|
692
|
+
try {
|
|
693
|
+
await restoreNativeHookClaim(claimPath, artifact.path);
|
|
694
|
+
await syncNativeHookClaimParent(artifact.path);
|
|
695
|
+
claimCreated = false;
|
|
696
|
+
if (journaledClaim) {
|
|
697
|
+
await clearNativeHookClaimJournal(ancestorPrecondition.controlledRoot);
|
|
698
|
+
journaledClaim = false;
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
catch (recoveryError) {
|
|
702
|
+
throw new Error(`Native hook transaction ${stage} failed (${error instanceof Error ? error.message : String(error)}) and preserved ${claimPath} for manual recovery: ${recoveryError instanceof Error ? recoveryError.message : String(recoveryError)}`);
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
if (temporaryCreated) {
|
|
706
|
+
try {
|
|
707
|
+
if (!temporarySnapshot) {
|
|
708
|
+
throw new Error("temporary path was not fully captured after writing");
|
|
709
|
+
}
|
|
710
|
+
await assertNativeHookTransactionArtifactSnapshot(temporaryPath, `${artifact.label} temporary`, temporarySnapshot, "read-back");
|
|
711
|
+
await assertNativeHookTransactionAncestorPrecondition(ancestorPrecondition);
|
|
712
|
+
await rm(temporaryPath);
|
|
713
|
+
}
|
|
714
|
+
catch (cleanupError) {
|
|
715
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
716
|
+
throw new Error(`Native hook transaction ${stage} failed (${message}) and preserved temporary ${temporaryPath} for manual recovery after cleanup verification failed: ${cleanupError instanceof Error ? cleanupError.message : String(cleanupError)}`);
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
throw error;
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
async function assertNativeHookTransactionArtifactState(path, label, expected, context) {
|
|
723
|
+
const actual = await captureNativeHookTransactionArtifact(path, label);
|
|
724
|
+
if (!nativeHookTransactionSnapshotMatchesExpected(actual, expected)) {
|
|
725
|
+
if (context === "rollback") {
|
|
726
|
+
throw new Error(`Native hook transaction rollback preserved ${path} for manual recovery because ${label} no longer matches the transaction-owned version.`);
|
|
727
|
+
}
|
|
728
|
+
throw new Error(`Native hook transaction ${context} changed for ${label}; refusing to overwrite concurrent content.`);
|
|
729
|
+
}
|
|
730
|
+
return actual;
|
|
731
|
+
}
|
|
732
|
+
async function assertNativeHookTransactionArtifactSnapshot(path, label, expected, context) {
|
|
733
|
+
const actual = await captureNativeHookTransactionArtifact(path, label);
|
|
734
|
+
if (!nativeHookTransactionSnapshotsEqual(actual, expected)) {
|
|
735
|
+
if (context === "rollback") {
|
|
736
|
+
throw new Error(`Native hook transaction rollback preserved ${path} for manual recovery because ${label} no longer matches the transaction-owned version.`);
|
|
737
|
+
}
|
|
738
|
+
throw new Error(`Native hook transaction ${context} changed for ${label}; refusing to overwrite concurrent content.`);
|
|
739
|
+
}
|
|
740
|
+
return actual;
|
|
741
|
+
}
|
|
742
|
+
async function assertNativeHookTransactionPrecondition(precondition) {
|
|
743
|
+
await assertNativeHookTransactionArtifactSnapshot(precondition.path, precondition.label, precondition.before, "precondition");
|
|
744
|
+
}
|
|
745
|
+
async function assertUnmutatedNativeHookTransactionPreconditions(preconditions, applied) {
|
|
746
|
+
const mutatedPaths = new Set(applied.map((entry) => entry.artifact.path));
|
|
747
|
+
for (const precondition of preconditions) {
|
|
748
|
+
if (mutatedPaths.has(precondition.path))
|
|
749
|
+
continue;
|
|
750
|
+
await assertNativeHookTransactionPrecondition(precondition);
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
async function assertAppliedNativeHookTransactionSnapshots(applied) {
|
|
754
|
+
for (const entry of applied) {
|
|
755
|
+
await assertNativeHookTransactionArtifactSnapshot(entry.artifact.path, entry.artifact.label, entry.appliedSnapshot, "precondition");
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
async function assertNativeHookTransactionStagedRecoveryCopies(applied) {
|
|
759
|
+
for (const entry of applied) {
|
|
760
|
+
if (!entry.stagedDeletionPath ||
|
|
761
|
+
!entry.stagedDeletionSnapshot ||
|
|
762
|
+
entry.stagedDeletionCleaned) {
|
|
763
|
+
continue;
|
|
764
|
+
}
|
|
765
|
+
await assertNativeHookTransactionArtifactSnapshot(entry.stagedDeletionPath, `${entry.artifact.label} staged deletion`, entry.stagedDeletionSnapshot, "rollback");
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
async function assertNativeHookTransactionRollbackState(applied) {
|
|
769
|
+
await assertNativeHookTransactionStagedRecoveryCopies(applied);
|
|
770
|
+
await assertAppliedNativeHookTransactionSnapshots(applied);
|
|
771
|
+
}
|
|
772
|
+
async function applyNativeHookTransactionArtifact(artifact, ancestorPrecondition, applied) {
|
|
773
|
+
await assertNativeHookTransactionArtifactSnapshot(artifact.path, artifact.label, artifact.before, "precondition");
|
|
774
|
+
await assertNativeHookTransactionAncestorPrecondition(ancestorPrecondition);
|
|
775
|
+
if (artifact.after !== null) {
|
|
776
|
+
let entry;
|
|
777
|
+
await atomicWriteNativeHookTransactionArtifact(artifact, artifact.after, "write", artifact.before, ancestorPrecondition, (appliedSnapshot) => {
|
|
778
|
+
entry = { artifact, appliedSnapshot };
|
|
779
|
+
applied.push(entry);
|
|
780
|
+
}, (stabilizedSnapshot) => {
|
|
781
|
+
if (!entry) {
|
|
782
|
+
throw new Error("native hook transaction write was not registered as applied");
|
|
783
|
+
}
|
|
784
|
+
entry.appliedSnapshot = stabilizedSnapshot;
|
|
785
|
+
}, () => assertAppliedNativeHookTransactionSnapshots(applied));
|
|
786
|
+
if (!entry) {
|
|
787
|
+
throw new Error("native hook transaction write was not registered as applied");
|
|
788
|
+
}
|
|
789
|
+
return entry;
|
|
790
|
+
}
|
|
791
|
+
const stagedDeletionPath = nativeHookTransactionTemporaryPath(artifact.path, "delete");
|
|
792
|
+
let stagedDeletionCreated = false;
|
|
793
|
+
let originalRemoved = false;
|
|
794
|
+
let stagedDeletionSnapshot;
|
|
795
|
+
try {
|
|
796
|
+
injectNativeHookTransactionFailure("before_temp_write", artifact);
|
|
797
|
+
await assertNativeHookTransactionArtifactSnapshot(artifact.path, artifact.label, artifact.before, "precondition");
|
|
798
|
+
await assertNativeHookTransactionAncestorPrecondition(ancestorPrecondition);
|
|
799
|
+
await assertAppliedNativeHookTransactionSnapshots(applied);
|
|
800
|
+
const handle = await open(stagedDeletionPath, "wx", artifact.before.topology.kind === "regular_file"
|
|
801
|
+
? artifact.before.topology.mode
|
|
802
|
+
: 0o600);
|
|
803
|
+
stagedDeletionCreated = true;
|
|
804
|
+
try {
|
|
805
|
+
await handle.writeFile(artifact.before.bytes);
|
|
806
|
+
await handle.sync();
|
|
807
|
+
}
|
|
808
|
+
finally {
|
|
809
|
+
await handle.close();
|
|
810
|
+
}
|
|
811
|
+
if (artifact.before.topology.kind === "regular_file") {
|
|
812
|
+
await chmod(stagedDeletionPath, artifact.before.topology.mode);
|
|
813
|
+
}
|
|
814
|
+
stagedDeletionSnapshot = await assertNativeHookTransactionArtifactState(stagedDeletionPath, `${artifact.label} staged deletion`, { bytes: artifact.before.bytes, topology: artifact.before.topology }, "read-back");
|
|
815
|
+
injectNativeHookTransactionFailure("before_remove", artifact);
|
|
816
|
+
await assertNativeHookTransactionArtifactSnapshot(artifact.path, artifact.label, artifact.before, "precondition");
|
|
817
|
+
await assertNativeHookTransactionAncestorPrecondition(ancestorPrecondition);
|
|
818
|
+
await assertNativeHookTransactionArtifactSnapshot(stagedDeletionPath, `${artifact.label} staged deletion`, stagedDeletionSnapshot, "read-back");
|
|
819
|
+
await assertAppliedNativeHookTransactionSnapshots(applied);
|
|
820
|
+
injectNativeHookTransactionFailure("after_final_remove_validation", artifact);
|
|
821
|
+
await assertNativeHookTransactionArtifactSnapshot(stagedDeletionPath, `${artifact.label} staged deletion`, stagedDeletionSnapshot, "read-back");
|
|
822
|
+
const claimPath = nativeHookTransactionClaimPath(artifact.path);
|
|
823
|
+
const claimRelativePath = relative(ancestorPrecondition.controlledRoot, artifact.path);
|
|
824
|
+
const journaledClaim = !isAbsolute(claimRelativePath) &&
|
|
825
|
+
claimRelativePath !== ".." &&
|
|
826
|
+
!claimRelativePath.startsWith(`..${sep}`);
|
|
827
|
+
if (journaledClaim) {
|
|
828
|
+
await persistNativeHookClaimJournal(ancestorPrecondition.controlledRoot, {
|
|
829
|
+
canonicalPath: artifact.path,
|
|
830
|
+
claimPath,
|
|
831
|
+
before: artifact.before.bytes,
|
|
832
|
+
after: null,
|
|
833
|
+
});
|
|
834
|
+
}
|
|
835
|
+
await rename(artifact.path, claimPath);
|
|
836
|
+
if (journaledClaim)
|
|
837
|
+
await syncNativeHookClaimParent(claimPath);
|
|
838
|
+
const entry = {
|
|
839
|
+
artifact,
|
|
840
|
+
appliedSnapshot: { bytes: null, topology: { kind: "absent" } },
|
|
841
|
+
stagedDeletionPath,
|
|
842
|
+
stagedDeletionSnapshot,
|
|
843
|
+
};
|
|
844
|
+
applied.push(entry);
|
|
845
|
+
try {
|
|
846
|
+
await assertNativeHookTransactionAncestorPrecondition(ancestorPrecondition);
|
|
847
|
+
await assertNativeHookTransactionArtifactSnapshot(claimPath, `${artifact.label} removal claim`, artifact.before, "precondition");
|
|
848
|
+
}
|
|
849
|
+
catch (error) {
|
|
850
|
+
try {
|
|
851
|
+
const claimed = await captureNativeHookTransactionArtifact(claimPath, `${artifact.label} removal claim`);
|
|
852
|
+
await restoreNativeHookClaim(claimPath, artifact.path);
|
|
853
|
+
if (journaledClaim)
|
|
854
|
+
await syncNativeHookClaimParent(artifact.path);
|
|
855
|
+
if (journaledClaim) {
|
|
856
|
+
await clearNativeHookClaimJournal(ancestorPrecondition.controlledRoot);
|
|
857
|
+
}
|
|
858
|
+
await assertNativeHookTransactionArtifactSnapshot(artifact.path, artifact.label, claimed, "precondition");
|
|
859
|
+
const entryIndex = applied.indexOf(entry);
|
|
860
|
+
if (entryIndex >= 0)
|
|
861
|
+
applied.splice(entryIndex, 1);
|
|
862
|
+
}
|
|
863
|
+
catch (recoveryError) {
|
|
864
|
+
throw new Error(`Native hook transaction removal claim failed (${error instanceof Error ? error.message : String(error)}) and preserved ${claimPath} for manual recovery: ${recoveryError instanceof Error ? recoveryError.message : String(recoveryError)}`);
|
|
865
|
+
}
|
|
866
|
+
throw error;
|
|
867
|
+
}
|
|
868
|
+
await rm(claimPath);
|
|
869
|
+
if (journaledClaim)
|
|
870
|
+
await syncNativeHookClaimParent(claimPath);
|
|
871
|
+
if (journaledClaim) {
|
|
872
|
+
await clearNativeHookClaimJournal(ancestorPrecondition.controlledRoot);
|
|
873
|
+
}
|
|
874
|
+
originalRemoved = true;
|
|
875
|
+
injectNativeHookTransactionFailure("after_remove", artifact);
|
|
876
|
+
return entry;
|
|
877
|
+
}
|
|
878
|
+
catch (error) {
|
|
879
|
+
if (stagedDeletionCreated && !originalRemoved) {
|
|
880
|
+
try {
|
|
881
|
+
if (!stagedDeletionSnapshot) {
|
|
882
|
+
throw new Error("staged deletion path was not fully captured after writing");
|
|
883
|
+
}
|
|
884
|
+
await assertNativeHookTransactionArtifactSnapshot(stagedDeletionPath, `${artifact.label} staged deletion`, stagedDeletionSnapshot, "read-back");
|
|
885
|
+
await assertNativeHookTransactionAncestorPrecondition(ancestorPrecondition);
|
|
886
|
+
const claimPath = nativeHookTransactionClaimPath(artifact.path);
|
|
887
|
+
await rename(stagedDeletionPath, claimPath);
|
|
888
|
+
try {
|
|
889
|
+
await assertNativeHookTransactionArtifactSnapshot(claimPath, `${artifact.label} staged deletion cleanup claim`, stagedDeletionSnapshot, "read-back");
|
|
890
|
+
}
|
|
891
|
+
catch (claimError) {
|
|
892
|
+
try {
|
|
893
|
+
await restoreNativeHookClaim(claimPath, stagedDeletionPath);
|
|
894
|
+
}
|
|
895
|
+
catch (recoveryError) {
|
|
896
|
+
throw new Error(`Native hook transaction staged deletion cleanup claim failed (${claimError instanceof Error ? claimError.message : String(claimError)}) and preserved ${claimPath} for manual recovery: ${recoveryError instanceof Error ? recoveryError.message : String(recoveryError)}`);
|
|
897
|
+
}
|
|
898
|
+
throw claimError;
|
|
899
|
+
}
|
|
900
|
+
await rm(claimPath);
|
|
901
|
+
}
|
|
902
|
+
catch (cleanupError) {
|
|
903
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
904
|
+
throw new Error(`Native hook transaction failed (${message}) and preserved staged deletion ${stagedDeletionPath} for manual recovery after cleanup verification failed: ${cleanupError instanceof Error ? cleanupError.message : String(cleanupError)}`);
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
throw error;
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
async function verifyNativeHookTransactionArtifact(applied) {
|
|
911
|
+
const { artifact } = applied;
|
|
912
|
+
injectNativeHookTransactionFailure("before_readback", artifact);
|
|
913
|
+
const actual = await captureNativeHookTransactionArtifact(artifact.path, artifact.label);
|
|
914
|
+
if (!nativeHookTransactionSnapshotsEqual(actual, applied.appliedSnapshot)) {
|
|
915
|
+
throw new Error(`Native hook transaction read-back changed for ${artifact.label}; refusing to overwrite concurrent content.`);
|
|
916
|
+
}
|
|
917
|
+
if (artifact.after === null)
|
|
247
918
|
return;
|
|
248
|
-
|
|
249
|
-
|
|
919
|
+
const content = decodeNativeHookTransactionUtf8(actual.bytes, artifact.label);
|
|
920
|
+
if (artifact.kind === "hooks") {
|
|
921
|
+
const validation = validateCodexHooksConfigStrict(content, {
|
|
922
|
+
platform: artifact.hookPlatform,
|
|
923
|
+
});
|
|
924
|
+
if (!validation.ok) {
|
|
925
|
+
throw new Error(`Native hook transaction wrote invalid hooks.json: ${validation.error.message}`);
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
if (artifact.kind === "config")
|
|
929
|
+
TOML.parse(content);
|
|
930
|
+
}
|
|
931
|
+
async function restoreNativeHookTransactionArtifact(applied, ancestorPrecondition, assertRollbackState) {
|
|
932
|
+
const { artifact } = applied;
|
|
933
|
+
const current = await captureNativeHookTransactionArtifact(artifact.path, artifact.label);
|
|
934
|
+
if (!nativeHookTransactionSnapshotsEqual(current, applied.appliedSnapshot)) {
|
|
935
|
+
throw new Error(`Native hook transaction rollback preserved ${artifact.path} for manual recovery because ${artifact.label} no longer matches the transaction-owned version.`);
|
|
936
|
+
}
|
|
937
|
+
if (artifact.before.bytes === null) {
|
|
938
|
+
injectNativeHookTransactionFailure("before_rollback", artifact);
|
|
939
|
+
injectNativeHookTransactionFailure("before_rollback_remove", artifact);
|
|
940
|
+
await assertNativeHookTransactionArtifactSnapshot(artifact.path, artifact.label, applied.appliedSnapshot, "rollback");
|
|
941
|
+
await assertNativeHookTransactionAncestorPrecondition(ancestorPrecondition);
|
|
942
|
+
await assertRollbackState();
|
|
943
|
+
injectNativeHookTransactionFailure("after_final_restore_validation", artifact);
|
|
944
|
+
const claimPath = nativeHookTransactionClaimPath(artifact.path);
|
|
945
|
+
await rename(artifact.path, claimPath);
|
|
946
|
+
applied.appliedSnapshot = { bytes: null, topology: { kind: "absent" } };
|
|
947
|
+
try {
|
|
948
|
+
await assertNativeHookTransactionArtifactSnapshot(claimPath, `${artifact.label} rollback removal claim`, current, "rollback");
|
|
949
|
+
}
|
|
950
|
+
catch (error) {
|
|
951
|
+
try {
|
|
952
|
+
const claimed = await captureNativeHookTransactionArtifact(claimPath, `${artifact.label} rollback removal claim`);
|
|
953
|
+
await restoreNativeHookClaim(claimPath, artifact.path);
|
|
954
|
+
await assertNativeHookTransactionArtifactSnapshot(artifact.path, artifact.label, claimed, "rollback");
|
|
955
|
+
}
|
|
956
|
+
catch (recoveryError) {
|
|
957
|
+
throw new Error(`Native hook transaction rollback removal claim failed (${error instanceof Error ? error.message : String(error)}) and preserved ${claimPath} for manual recovery: ${recoveryError instanceof Error ? recoveryError.message : String(recoveryError)}`);
|
|
958
|
+
}
|
|
959
|
+
throw error;
|
|
960
|
+
}
|
|
961
|
+
await rm(claimPath);
|
|
250
962
|
}
|
|
963
|
+
else {
|
|
964
|
+
const restoreArtifact = {
|
|
965
|
+
...artifact,
|
|
966
|
+
after: artifact.before.bytes,
|
|
967
|
+
afterTopology: artifact.before.topology,
|
|
968
|
+
};
|
|
969
|
+
await atomicWriteNativeHookTransactionArtifact(restoreArtifact, artifact.before.bytes, "rollback", applied.appliedSnapshot, ancestorPrecondition, (restoredSnapshot) => {
|
|
970
|
+
applied.appliedSnapshot = restoredSnapshot;
|
|
971
|
+
}, (stabilizedSnapshot) => {
|
|
972
|
+
applied.appliedSnapshot = stabilizedSnapshot;
|
|
973
|
+
}, assertRollbackState);
|
|
974
|
+
}
|
|
975
|
+
const restored = await assertNativeHookTransactionArtifactSnapshot(artifact.path, artifact.label, applied.appliedSnapshot, "rollback");
|
|
976
|
+
if (!nativeHookTransactionSnapshotMatchesExpected(restored, {
|
|
977
|
+
bytes: artifact.before.bytes,
|
|
978
|
+
topology: artifact.before.topology,
|
|
979
|
+
})) {
|
|
980
|
+
throw new Error(`Native hook transaction rollback preserved ${artifact.path} for manual recovery because ${artifact.label} no longer matches the expected restored version.`);
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
async function cleanupNativeHookTransactionStagedDeletions(applied, ancestorPrecondition, preconditions, rollbackApplied) {
|
|
984
|
+
for (const entry of applied) {
|
|
985
|
+
if (!entry.stagedDeletionPath ||
|
|
986
|
+
!entry.stagedDeletionSnapshot ||
|
|
987
|
+
entry.stagedDeletionCleaned) {
|
|
988
|
+
continue;
|
|
989
|
+
}
|
|
990
|
+
injectNativeHookTransactionFailure("before_staged_cleanup", entry.artifact);
|
|
991
|
+
if (preconditions) {
|
|
992
|
+
await assertUnmutatedNativeHookTransactionPreconditions(preconditions, applied);
|
|
993
|
+
await assertAppliedNativeHookTransactionSnapshots(applied);
|
|
994
|
+
}
|
|
995
|
+
if (rollbackApplied) {
|
|
996
|
+
await assertNativeHookTransactionRollbackState(rollbackApplied);
|
|
997
|
+
}
|
|
998
|
+
const stagedDeletionPath = entry.stagedDeletionPath;
|
|
999
|
+
const stagedDeletionSnapshot = entry.stagedDeletionSnapshot;
|
|
1000
|
+
await assertNativeHookTransactionArtifactSnapshot(stagedDeletionPath, `${entry.artifact.label} staged deletion`, stagedDeletionSnapshot, "read-back");
|
|
1001
|
+
await assertNativeHookTransactionAncestorPrecondition(ancestorPrecondition);
|
|
1002
|
+
const claimPath = nativeHookTransactionClaimPath(entry.artifact.path);
|
|
1003
|
+
await rename(stagedDeletionPath, claimPath);
|
|
1004
|
+
try {
|
|
1005
|
+
await assertNativeHookTransactionArtifactSnapshot(claimPath, `${entry.artifact.label} staged deletion cleanup claim`, stagedDeletionSnapshot, "read-back");
|
|
1006
|
+
}
|
|
1007
|
+
catch (error) {
|
|
1008
|
+
try {
|
|
1009
|
+
await restoreNativeHookClaim(claimPath, stagedDeletionPath);
|
|
1010
|
+
}
|
|
1011
|
+
catch (recoveryError) {
|
|
1012
|
+
throw new Error(`Native hook transaction staged deletion cleanup claim failed (${error instanceof Error ? error.message : String(error)}) and preserved ${claimPath} for manual recovery: ${recoveryError instanceof Error ? recoveryError.message : String(recoveryError)}`);
|
|
1013
|
+
}
|
|
1014
|
+
throw error;
|
|
1015
|
+
}
|
|
1016
|
+
await rm(claimPath);
|
|
1017
|
+
entry.stagedDeletionCleaned = true;
|
|
1018
|
+
if (rollbackApplied) {
|
|
1019
|
+
await assertNativeHookTransactionRollbackState(rollbackApplied);
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
function nativeHookTransactionBackupPath(artifactPath, backupContext) {
|
|
1024
|
+
const relativePath = relative(backupContext.baseRoot, artifactPath);
|
|
1025
|
+
const safeRelativePath = relativePath.startsWith("..") || relativePath === ""
|
|
1026
|
+
? artifactPath.replace(/^[/]+/, "")
|
|
1027
|
+
: relativePath;
|
|
1028
|
+
return join(backupContext.backupRoot, safeRelativePath);
|
|
1029
|
+
}
|
|
1030
|
+
async function ensureSnapshotBackup(artifact, backupContext, options) {
|
|
1031
|
+
const bytes = artifact.before.bytes;
|
|
1032
|
+
if (bytes === null)
|
|
1033
|
+
return false;
|
|
1034
|
+
const backupPath = nativeHookTransactionBackupPath(artifact.path, backupContext);
|
|
251
1035
|
if (!options.dryRun) {
|
|
252
|
-
|
|
253
|
-
|
|
1036
|
+
const relativeParent = relative(backupContext.baseRoot, dirname(backupPath));
|
|
1037
|
+
if (isAbsolute(relativeParent) ||
|
|
1038
|
+
relativeParent === ".." ||
|
|
1039
|
+
relativeParent.startsWith(`..${sep}`)) {
|
|
1040
|
+
throw new Error(`Refusing to back up ${artifact.path} outside controlled backup root.`);
|
|
1041
|
+
}
|
|
1042
|
+
let currentPath = backupContext.baseRoot;
|
|
1043
|
+
for (const component of relativeParent.split(sep).filter(Boolean)) {
|
|
1044
|
+
currentPath = join(currentPath, component);
|
|
1045
|
+
try {
|
|
1046
|
+
const currentStat = await lstat(currentPath);
|
|
1047
|
+
if (currentStat.isSymbolicLink() || !currentStat.isDirectory()) {
|
|
1048
|
+
throw new Error(`Refusing to use unsafe backup ancestor ${currentPath}.`);
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
catch (error) {
|
|
1052
|
+
if (!isMissingPathError(error))
|
|
1053
|
+
throw error;
|
|
1054
|
+
await mkdir(currentPath);
|
|
1055
|
+
const createdStat = await lstat(currentPath);
|
|
1056
|
+
if (createdStat.isSymbolicLink() || !createdStat.isDirectory()) {
|
|
1057
|
+
throw new Error(`Refusing to use unsafe created backup ancestor ${currentPath}.`);
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
const handle = await open(backupPath, "wx", 0o600);
|
|
1062
|
+
try {
|
|
1063
|
+
await handle.writeFile(bytes);
|
|
1064
|
+
await handle.sync();
|
|
1065
|
+
}
|
|
1066
|
+
finally {
|
|
1067
|
+
await handle.close();
|
|
1068
|
+
}
|
|
1069
|
+
const backupStat = await lstat(backupPath);
|
|
1070
|
+
if (backupStat.isSymbolicLink() || !backupStat.isFile() || backupStat.nlink !== 1) {
|
|
1071
|
+
throw new Error(`Refusing unsafe native hook transaction backup ${backupPath}.`);
|
|
1072
|
+
}
|
|
1073
|
+
const writtenBytes = await readFile(backupPath);
|
|
1074
|
+
if (!writtenBytes.equals(bytes)) {
|
|
1075
|
+
throw new Error(`Native hook transaction backup verification failed for ${backupPath}.`);
|
|
1076
|
+
}
|
|
254
1077
|
}
|
|
255
|
-
summary.updated += 1;
|
|
256
1078
|
if (options.verbose) {
|
|
257
|
-
console.log(` ${
|
|
1079
|
+
console.log(` backup ${artifact.path} -> ${backupPath}`);
|
|
1080
|
+
}
|
|
1081
|
+
return true;
|
|
1082
|
+
}
|
|
1083
|
+
async function commitNativeHookTransaction(artifacts, preconditions, ancestorPrecondition, backupContext, summary, options) {
|
|
1084
|
+
if (options.dryRun)
|
|
1085
|
+
return;
|
|
1086
|
+
await assertNativeHookTransactionAncestorPrecondition(ancestorPrecondition);
|
|
1087
|
+
for (const precondition of preconditions) {
|
|
1088
|
+
injectNativeHookTransactionFailure("before_precondition", precondition);
|
|
1089
|
+
await assertNativeHookTransactionPrecondition(precondition);
|
|
1090
|
+
}
|
|
1091
|
+
if (artifacts.length === 0)
|
|
1092
|
+
return;
|
|
1093
|
+
for (const artifact of artifacts) {
|
|
1094
|
+
injectNativeHookTransactionFailure("before_backup", artifact);
|
|
1095
|
+
if (await ensureSnapshotBackup(artifact, backupContext, options)) {
|
|
1096
|
+
summary.backedUp += 1;
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
await assertUnmutatedNativeHookTransactionPreconditions(preconditions, []);
|
|
1100
|
+
await assertNativeHookTransactionAncestorPrecondition(ancestorPrecondition);
|
|
1101
|
+
const applied = [];
|
|
1102
|
+
let stagedCleanupStarted = false;
|
|
1103
|
+
try {
|
|
1104
|
+
for (const artifact of artifacts) {
|
|
1105
|
+
await assertUnmutatedNativeHookTransactionPreconditions(preconditions, applied);
|
|
1106
|
+
await assertAppliedNativeHookTransactionSnapshots(applied);
|
|
1107
|
+
const entry = await applyNativeHookTransactionArtifact(artifact, ancestorPrecondition, applied);
|
|
1108
|
+
await verifyNativeHookTransactionArtifact(entry);
|
|
1109
|
+
await assertUnmutatedNativeHookTransactionPreconditions(preconditions, applied);
|
|
1110
|
+
await assertAppliedNativeHookTransactionSnapshots(applied);
|
|
1111
|
+
}
|
|
1112
|
+
await assertUnmutatedNativeHookTransactionPreconditions(preconditions, applied);
|
|
1113
|
+
await assertAppliedNativeHookTransactionSnapshots(applied);
|
|
1114
|
+
stagedCleanupStarted = true;
|
|
1115
|
+
await cleanupNativeHookTransactionStagedDeletions(applied, ancestorPrecondition, preconditions);
|
|
1116
|
+
injectNativeHookTransactionFailure("after_staged_cleanup", artifacts[artifacts.length - 1]);
|
|
1117
|
+
await assertUnmutatedNativeHookTransactionPreconditions(preconditions, applied);
|
|
1118
|
+
await assertNativeHookTransactionAncestorPrecondition(ancestorPrecondition);
|
|
1119
|
+
await assertAppliedNativeHookTransactionSnapshots(applied);
|
|
1120
|
+
}
|
|
1121
|
+
catch (error) {
|
|
1122
|
+
if (stagedCleanupStarted &&
|
|
1123
|
+
applied.some((entry) => entry.stagedDeletionCleaned)) {
|
|
1124
|
+
throw new Error(`Native hook transaction committed but staged deletion cleanup failed during finalization: ${error instanceof Error ? error.message : String(error)}`);
|
|
1125
|
+
}
|
|
1126
|
+
const rollbackFailures = [];
|
|
1127
|
+
const restored = [];
|
|
1128
|
+
try {
|
|
1129
|
+
await assertNativeHookTransactionRollbackState(applied);
|
|
1130
|
+
}
|
|
1131
|
+
catch (rollbackError) {
|
|
1132
|
+
rollbackFailures.push(`recovery preflight: ${rollbackError instanceof Error ? rollbackError.message : String(rollbackError)}`);
|
|
1133
|
+
}
|
|
1134
|
+
if (rollbackFailures.length === 0) {
|
|
1135
|
+
for (const entry of [...applied].reverse()) {
|
|
1136
|
+
try {
|
|
1137
|
+
await assertNativeHookTransactionRollbackState(applied);
|
|
1138
|
+
await restoreNativeHookTransactionArtifact(entry, ancestorPrecondition, () => assertNativeHookTransactionRollbackState(applied));
|
|
1139
|
+
restored.push(entry);
|
|
1140
|
+
await assertNativeHookTransactionRollbackState(applied);
|
|
1141
|
+
}
|
|
1142
|
+
catch (rollbackError) {
|
|
1143
|
+
rollbackFailures.push(`${entry.artifact.label}: ${rollbackError instanceof Error ? rollbackError.message : String(rollbackError)}`);
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
if (rollbackFailures.length === 0) {
|
|
1148
|
+
try {
|
|
1149
|
+
await assertNativeHookTransactionRollbackState(applied);
|
|
1150
|
+
await cleanupNativeHookTransactionStagedDeletions(restored, ancestorPrecondition, undefined, applied);
|
|
1151
|
+
await assertNativeHookTransactionRollbackState(applied);
|
|
1152
|
+
}
|
|
1153
|
+
catch (cleanupError) {
|
|
1154
|
+
rollbackFailures.push(`staged deletion cleanup: ${cleanupError instanceof Error ? cleanupError.message : String(cleanupError)}`);
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1158
|
+
if (rollbackFailures.length > 0) {
|
|
1159
|
+
throw new Error(`Native hook transaction failed (${message}) and rollback failed; manual recovery is required (${rollbackFailures.join("; ")}).`);
|
|
1160
|
+
}
|
|
1161
|
+
throw new Error(`Native hook transaction failed and was rolled back: ${message}`);
|
|
258
1162
|
}
|
|
259
1163
|
}
|
|
260
1164
|
async function ensureBackup(destinationPath, contentChanged, backupContext, options) {
|
|
@@ -503,7 +1407,10 @@ async function promptForFirstPartyMcpRemoval(configPath, registrationKinds) {
|
|
|
503
1407
|
}
|
|
504
1408
|
}
|
|
505
1409
|
function hasPersistedSetupPreferences(preferences) {
|
|
506
|
-
return Boolean(preferences?.scope ||
|
|
1410
|
+
return Boolean(preferences?.scope ||
|
|
1411
|
+
preferences?.installMode ||
|
|
1412
|
+
preferences?.teamMode ||
|
|
1413
|
+
typeof preferences?.mergeAgents === "boolean");
|
|
507
1414
|
}
|
|
508
1415
|
function formatPersistedSetupPreferenceSummary(preferences) {
|
|
509
1416
|
const summary = [
|
|
@@ -513,6 +1420,9 @@ function formatPersistedSetupPreferenceSummary(preferences) {
|
|
|
513
1420
|
];
|
|
514
1421
|
if (preferences.teamMode)
|
|
515
1422
|
summary.push(`teamMode=${preferences.teamMode}`);
|
|
1423
|
+
if (typeof preferences.mergeAgents === "boolean") {
|
|
1424
|
+
summary.push(`mergeAgents=${preferences.mergeAgents}`);
|
|
1425
|
+
}
|
|
516
1426
|
return summary.join(", ");
|
|
517
1427
|
}
|
|
518
1428
|
async function promptForPersistedSetupReview(preferences) {
|
|
@@ -612,7 +1522,7 @@ function classifyPluginDeveloperInstructions(value) {
|
|
|
612
1522
|
}
|
|
613
1523
|
if (normalized ===
|
|
614
1524
|
normalizeDeveloperInstructionsText(LEGACY_PLUGIN_DEVELOPER_INSTRUCTIONS)) {
|
|
615
|
-
return "
|
|
1525
|
+
return "historical";
|
|
616
1526
|
}
|
|
617
1527
|
if (normalized === normalizeDeveloperInstructionsText(OMX_DEVELOPER_INSTRUCTIONS)) {
|
|
618
1528
|
return "historical";
|
|
@@ -669,11 +1579,8 @@ function legacyPluginDeveloperInstructionsDecision(choice, state = "missing") {
|
|
|
669
1579
|
reason: "legacy explicit skip policy",
|
|
670
1580
|
};
|
|
671
1581
|
}
|
|
672
|
-
async function resolvePluginDeveloperInstructionsDecision(configPath, options) {
|
|
673
|
-
const
|
|
674
|
-
? await readFile(configPath, "utf-8")
|
|
675
|
-
: "";
|
|
676
|
-
const value = readRootDeveloperInstructions(existing);
|
|
1582
|
+
async function resolvePluginDeveloperInstructionsDecision(existingConfig, configPath, options) {
|
|
1583
|
+
const value = readRootDeveloperInstructions(existingConfig);
|
|
677
1584
|
if (value === undefined) {
|
|
678
1585
|
if (options.pluginDeveloperInstructionsPrompt) {
|
|
679
1586
|
return legacyPluginDeveloperInstructionsDecision(await options.pluginDeveloperInstructionsPrompt(configPath), "missing");
|
|
@@ -1016,8 +1923,7 @@ async function persistSetupPreferences(projectRoot, preferences, options) {
|
|
|
1016
1923
|
console.log(` dry-run: skip persisting ${scopePath}`);
|
|
1017
1924
|
return;
|
|
1018
1925
|
}
|
|
1019
|
-
await
|
|
1020
|
-
await writeFile(scopePath, JSON.stringify(preferences, null, 2) + "\n");
|
|
1926
|
+
await writePersistedSetupPreferences(projectRoot, preferences);
|
|
1021
1927
|
if (options.verbose)
|
|
1022
1928
|
console.log(` Wrote ${scopePath}`);
|
|
1023
1929
|
}
|
|
@@ -1181,176 +2087,246 @@ function insertRootTomlKey(config, line) {
|
|
|
1181
2087
|
const after = lines.slice(firstTableIndex);
|
|
1182
2088
|
return [...before, line, "", ...after].join("\n") + "\n";
|
|
1183
2089
|
}
|
|
1184
|
-
|
|
1185
|
-
const
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
}
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
}
|
|
1222
|
-
if (await ensureBackup(hooksPath, true, backupContext, options)) {
|
|
1223
|
-
summary.backedUp += 1;
|
|
1224
|
-
}
|
|
1225
|
-
if (!options.dryRun) {
|
|
1226
|
-
if (removed.nextContent === null) {
|
|
1227
|
-
await rm(hooksPath, { force: true });
|
|
1228
|
-
}
|
|
1229
|
-
else {
|
|
1230
|
-
await writeFile(hooksPath, removed.nextContent);
|
|
1231
|
-
}
|
|
1232
|
-
}
|
|
1233
|
-
summary.removed += removed.removedCount;
|
|
1234
|
-
if (options.verbose) {
|
|
1235
|
-
console.log(` ${options.dryRun ? "would remove" : "removed"} ${removed.removedCount} legacy setup-managed hook wrapper(s) from ${hooksPath}`);
|
|
1236
|
-
}
|
|
1237
|
-
}
|
|
1238
|
-
async function applyPluginModeHooksConfig(configPath, hooksPath, pkgRoot, codexHomeDir, backupContext, summary, options) {
|
|
1239
|
-
const existingConfig = existsSync(configPath)
|
|
1240
|
-
? await readFile(configPath, "utf-8")
|
|
1241
|
-
: "";
|
|
1242
|
-
const managedTrustState = buildManagedCodexHookTrustState(hooksPath, pkgRoot, { platform: process.platform, codexHomeDir });
|
|
1243
|
-
const nextConfigBase = upsertPluginModeRuntimeFeatureFlags(stripManagedCodexHookTrustState(existingConfig, { managedTrustState }), options.codexHookFeatureFlag, { pluginScopedHooks: options.pluginScopedHooks });
|
|
1244
|
-
const nextConfig = options.pluginScopedHooks
|
|
1245
|
-
? nextConfigBase
|
|
1246
|
-
: upsertManagedCodexHookTrustState(nextConfigBase, pkgRoot, hooksPath, { platform: process.platform, codexHomeDir });
|
|
1247
|
-
if (nextConfig !== existingConfig) {
|
|
1248
|
-
if (await ensureBackup(configPath, existsSync(configPath), backupContext, options)) {
|
|
1249
|
-
summary.backedUp += 1;
|
|
1250
|
-
}
|
|
1251
|
-
if (!options.dryRun) {
|
|
1252
|
-
await mkdir(dirname(configPath), { recursive: true });
|
|
1253
|
-
await writeFile(configPath, nextConfig);
|
|
1254
|
-
}
|
|
1255
|
-
summary.updated += 1;
|
|
1256
|
-
}
|
|
1257
|
-
else {
|
|
1258
|
-
summary.unchanged += 1;
|
|
1259
|
-
}
|
|
1260
|
-
const existingHooksContent = existsSync(hooksPath)
|
|
1261
|
-
? await readFile(hooksPath, "utf-8")
|
|
1262
|
-
: null;
|
|
1263
|
-
await migrateLegacyHooksJsonTrustStateToConfig(configPath, existingHooksContent, backupContext, summary, options);
|
|
1264
|
-
if (options.pluginScopedHooks) {
|
|
1265
|
-
await cleanupPluginModeManagedHooksJson(existingHooksContent, hooksPath, backupContext, summary, options);
|
|
1266
|
-
}
|
|
1267
|
-
else {
|
|
1268
|
-
const hooksConfig = mergeManagedCodexHooksConfig(existingHooksContent, pkgRoot, hooksPath, { platform: process.platform, codexHomeDir });
|
|
1269
|
-
await syncManagedContent(hooksConfig, hooksPath, summary, backupContext, options, `native hooks ${hooksPath}`);
|
|
1270
|
-
await syncManagedWindowsNativeHookShim(codexHomeDir, pkgRoot, summary, backupContext, options);
|
|
1271
|
-
}
|
|
1272
|
-
if (options.verbose) {
|
|
1273
|
-
const surface = options.pluginScopedHooks
|
|
1274
|
-
? "official plugin-scoped hooks"
|
|
1275
|
-
: `legacy native hooks at ${hooksPath}`;
|
|
1276
|
-
console.log(` ${options.dryRun ? "would configure" : "configured"} plugin-mode ${surface} and runtime feature flags`);
|
|
1277
|
-
}
|
|
2090
|
+
function buildPluginModeHooksConfigPlan(existingConfig, existingHooksContent, pkgRoot, hooksPath, codexHomeDir, options) {
|
|
2091
|
+
const managedHookOptions = {
|
|
2092
|
+
platform: options.platform,
|
|
2093
|
+
codexHomeDir,
|
|
2094
|
+
};
|
|
2095
|
+
const managedHooksPlan = options.pluginScopedHooks
|
|
2096
|
+
? existingHooksContent === null
|
|
2097
|
+
? null
|
|
2098
|
+
: planManagedCodexHooksRemoval(existingHooksContent, hooksPath, managedHookOptions)
|
|
2099
|
+
: planManagedCodexHooksMerge(existingHooksContent, pkgRoot, hooksPath, managedHookOptions);
|
|
2100
|
+
if (managedHooksPlan && !managedHooksPlan.ok)
|
|
2101
|
+
throw managedHooksPlan.error;
|
|
2102
|
+
const managedHookTrustState = managedHooksPlan?.finalTrustState ?? {};
|
|
2103
|
+
const priorManagedHookTrustState = managedHooksPlan?.priorTrustState ?? {};
|
|
2104
|
+
const legacyHookTrustState = managedHooksPlan?.legacyTrustState ?? {};
|
|
2105
|
+
preflightManagedCodexHookTrustState(existingConfig, priorManagedHookTrustState, managedHookTrustState);
|
|
2106
|
+
const configAfterLegacyCleanup = buildPluginModeLegacyConfig(existingConfig, {
|
|
2107
|
+
...options,
|
|
2108
|
+
managedHookTrustState,
|
|
2109
|
+
priorManagedHookTrustState,
|
|
2110
|
+
});
|
|
2111
|
+
const configWithRuntimeFeatures = upsertPluginModeRuntimeFeatureFlags(configAfterLegacyCleanup, options.codexHookFeatureFlag, {
|
|
2112
|
+
pluginScopedHooks: options.pluginScopedHooks,
|
|
2113
|
+
preserveNativeHooks: options.pluginScopedHooks && managedHooksPlan?.hasForeignHooks === true,
|
|
2114
|
+
});
|
|
2115
|
+
return {
|
|
2116
|
+
finalConfig: upsertManagedCodexHookTrustState(configWithRuntimeFeatures, pkgRoot, hooksPath, {
|
|
2117
|
+
...managedHookOptions,
|
|
2118
|
+
managedTrustState: managedHookTrustState,
|
|
2119
|
+
priorManagedHookTrustState,
|
|
2120
|
+
legacyHookTrustState,
|
|
2121
|
+
}),
|
|
2122
|
+
hooksFinalContent: managedHooksPlan ? managedHooksPlan.finalContent : existingHooksContent,
|
|
2123
|
+
hooksRemovedCount: managedHooksPlan?.removedCount ?? 0,
|
|
2124
|
+
cleanedLegacyConfig: configAfterLegacyCleanup !== existingConfig,
|
|
2125
|
+
diagnostics: managedHooksPlan?.diagnostics ?? [],
|
|
2126
|
+
};
|
|
1278
2127
|
}
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
if (options.verbose) {
|
|
1286
|
-
console.log(` preserved plugin developer_instructions default: ${options.decision.reason}`);
|
|
1287
|
-
}
|
|
1288
|
-
return options.decision.state === "missing" ? "skipped" : "exists";
|
|
2128
|
+
function buildPluginDeveloperInstructionsConfigPlan(existingConfig, decision) {
|
|
2129
|
+
if (decision.action === "preserve") {
|
|
2130
|
+
return {
|
|
2131
|
+
finalConfig: existingConfig,
|
|
2132
|
+
result: decision.state === "missing" ? "skipped" : "exists",
|
|
2133
|
+
};
|
|
1289
2134
|
}
|
|
1290
2135
|
const line = `developer_instructions = ${JSON.stringify(OMX_PLUGIN_DEVELOPER_INSTRUCTIONS)}`;
|
|
1291
|
-
const hasExistingDeveloperInstructions = rootHasTomlKey(
|
|
1292
|
-
if (hasExistingDeveloperInstructions &&
|
|
1293
|
-
|
|
1294
|
-
if (options.verbose) {
|
|
1295
|
-
console.log(" skipped plugin developer_instructions default: root developer_instructions already exists");
|
|
1296
|
-
}
|
|
1297
|
-
return "exists";
|
|
1298
|
-
}
|
|
1299
|
-
const nextConfig = hasExistingDeveloperInstructions
|
|
1300
|
-
? replaceRootTomlKey(existing, "developer_instructions", line)
|
|
1301
|
-
: insertRootTomlKey(existing, line);
|
|
1302
|
-
const destinationExists = existsSync(configPath);
|
|
1303
|
-
if (await ensureBackup(configPath, destinationExists, backupContext, options)) {
|
|
1304
|
-
summary.backedUp += 1;
|
|
1305
|
-
}
|
|
1306
|
-
if (!options.dryRun) {
|
|
1307
|
-
await mkdir(dirname(configPath), { recursive: true });
|
|
1308
|
-
await writeFile(configPath, nextConfig);
|
|
2136
|
+
const hasExistingDeveloperInstructions = rootHasTomlKey(existingConfig, "developer_instructions");
|
|
2137
|
+
if (hasExistingDeveloperInstructions && decision.action === "add") {
|
|
2138
|
+
return { finalConfig: existingConfig, result: "exists" };
|
|
1309
2139
|
}
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
2140
|
+
return {
|
|
2141
|
+
finalConfig: hasExistingDeveloperInstructions
|
|
2142
|
+
? replaceRootTomlKey(existingConfig, "developer_instructions", line)
|
|
2143
|
+
: insertRootTomlKey(existingConfig, line),
|
|
2144
|
+
result: "updated",
|
|
2145
|
+
};
|
|
1315
2146
|
}
|
|
1316
|
-
|
|
1317
|
-
if (!existsSync(configPath))
|
|
1318
|
-
return false;
|
|
1319
|
-
const original = await readFile(configPath, "utf-8");
|
|
2147
|
+
function buildPluginModeLegacyConfig(original, options) {
|
|
1320
2148
|
const preservedFirstPartyMcp = options.preserveFirstPartyMcp
|
|
1321
2149
|
? extractFirstPartyOmxMcpSections(original)
|
|
1322
2150
|
: "";
|
|
1323
2151
|
let config = original;
|
|
1324
2152
|
config = stripFirstPartyOmxMcpSections(config);
|
|
1325
|
-
config = stripExistingOmxBlocks(config
|
|
2153
|
+
config = stripExistingOmxBlocks(config, {
|
|
2154
|
+
managedTrustState: options.managedHookTrustState,
|
|
2155
|
+
priorManagedHookTrustState: options.priorManagedHookTrustState,
|
|
2156
|
+
}).cleaned;
|
|
1326
2157
|
config = stripExistingSharedMcpRegistryBlock(config).cleaned;
|
|
1327
2158
|
config = stripPluginModeLegacyRootDefaults(config, options.developerInstructionsDecision);
|
|
1328
2159
|
config = stripOmxSeededBehavioralDefaults(config);
|
|
1329
|
-
config = stripOmxFeatureFlags(config);
|
|
1330
|
-
config = stripManagedCodexHookTrustState(config
|
|
2160
|
+
config = stripOmxFeatureFlags(config, { preserveMultiAgent: true });
|
|
2161
|
+
config = stripManagedCodexHookTrustState(config, {
|
|
2162
|
+
managedTrustState: options.managedHookTrustState,
|
|
2163
|
+
priorManagedHookTrustState: options.priorManagedHookTrustState,
|
|
2164
|
+
});
|
|
1331
2165
|
config = stripOmxEnvSettings(config);
|
|
1332
2166
|
if (preservedFirstPartyMcp) {
|
|
1333
2167
|
config = `${config.trimEnd()}\n\n${preservedFirstPartyMcp}\n`;
|
|
1334
2168
|
}
|
|
1335
2169
|
config = config.trim();
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
2170
|
+
return config.length > 0 ? `${config}\n` : "";
|
|
2171
|
+
}
|
|
2172
|
+
async function planNativeHookSetupTransaction(options) {
|
|
2173
|
+
const existingConfigSnapshot = options.configSnapshot;
|
|
2174
|
+
const existingHooksSnapshot = await captureNativeHookTransactionArtifact(options.hooksPath, `native hooks ${options.hooksPath}`);
|
|
2175
|
+
const existingConfig = existingConfigSnapshot.bytes
|
|
2176
|
+
? decodeNativeHookTransactionUtf8(existingConfigSnapshot.bytes, `config ${options.configPath}`)
|
|
2177
|
+
: "";
|
|
2178
|
+
const existingHooksContent = existingHooksSnapshot.bytes
|
|
2179
|
+
? decodeNativeHookTransactionUtf8(existingHooksSnapshot.bytes, `native hooks ${options.hooksPath}`)
|
|
2180
|
+
: null;
|
|
2181
|
+
let finalConfig = existingConfig;
|
|
2182
|
+
let finalHooksContent = existingHooksContent;
|
|
2183
|
+
let hooksRemovedCount = 0;
|
|
2184
|
+
let cleanedLegacyConfig = false;
|
|
2185
|
+
let pluginMarketplaceResult = "unchanged";
|
|
2186
|
+
let pluginDeveloperInstructionsResult = "skipped";
|
|
2187
|
+
let diagnostics = [];
|
|
2188
|
+
let notifyMetadataArtifact = null;
|
|
2189
|
+
let notifyMetadataPrecondition = null;
|
|
2190
|
+
let modelUpgrade;
|
|
2191
|
+
let repairedLegacyTeamRunTable = false;
|
|
2192
|
+
if (options.isPluginInstallMode) {
|
|
2193
|
+
const pluginPlan = buildPluginModeHooksConfigPlan(existingConfig, existingHooksContent, options.pkgRoot, options.hooksPath, options.codexHomeDir, {
|
|
2194
|
+
codexHookFeatureFlag: options.codexHookFeatureFlag,
|
|
2195
|
+
pluginScopedHooks: options.pluginScopedHooks,
|
|
2196
|
+
preserveFirstPartyMcp: options.preserveFirstPartyMcp,
|
|
2197
|
+
developerInstructionsDecision: options.pluginDeveloperInstructionsDecision,
|
|
2198
|
+
platform: options.platform,
|
|
2199
|
+
});
|
|
2200
|
+
finalConfig = pluginPlan.finalConfig;
|
|
2201
|
+
finalHooksContent = pluginPlan.hooksFinalContent;
|
|
2202
|
+
hooksRemovedCount = pluginPlan.hooksRemovedCount;
|
|
2203
|
+
cleanedLegacyConfig = pluginPlan.cleanedLegacyConfig;
|
|
2204
|
+
diagnostics = pluginPlan.diagnostics;
|
|
2205
|
+
if (options.pluginMarketplaceAvailable) {
|
|
2206
|
+
const withMarketplace = upsertLocalOmxMarketplaceRegistration(upsertLocalOmxPluginMcpServerEnablement(upsertLocalOmxPluginEnablement(finalConfig), options.mcpMode === "compat", { removeWhenDisabled: options.removeFirstPartyMcp }), options.pkgRoot);
|
|
2207
|
+
pluginMarketplaceResult =
|
|
2208
|
+
withMarketplace === finalConfig ? "unchanged" : "updated";
|
|
2209
|
+
finalConfig = withMarketplace;
|
|
1345
2210
|
}
|
|
1346
2211
|
else {
|
|
1347
|
-
|
|
2212
|
+
pluginMarketplaceResult = "unavailable";
|
|
1348
2213
|
}
|
|
2214
|
+
const developerInstructionsPlan = buildPluginDeveloperInstructionsConfigPlan(finalConfig, options.pluginDeveloperInstructionsDecision);
|
|
2215
|
+
finalConfig = developerInstructionsPlan.finalConfig;
|
|
2216
|
+
pluginDeveloperInstructionsResult = developerInstructionsPlan.result;
|
|
1349
2217
|
}
|
|
1350
|
-
|
|
1351
|
-
|
|
2218
|
+
else {
|
|
2219
|
+
const managedHooksPlan = planManagedCodexHooksMerge(existingHooksContent, options.pkgRoot, options.hooksPath, {
|
|
2220
|
+
platform: options.platform,
|
|
2221
|
+
codexHomeDir: options.codexHomeDir,
|
|
2222
|
+
});
|
|
2223
|
+
if (!managedHooksPlan.ok)
|
|
2224
|
+
throw managedHooksPlan.error;
|
|
2225
|
+
if (managedHooksPlan.finalContent === null) {
|
|
2226
|
+
throw new Error("Native hook merge unexpectedly produced no hooks.json content.");
|
|
2227
|
+
}
|
|
2228
|
+
finalHooksContent = managedHooksPlan.finalContent;
|
|
2229
|
+
hooksRemovedCount = managedHooksPlan.removedCount;
|
|
2230
|
+
diagnostics = managedHooksPlan.diagnostics;
|
|
2231
|
+
const managedConfigPlan = await planManagedConfig(options.hooksPath, options.pkgRoot, options.sharedMcpRegistry, options.mcpMode, options.preserveFirstPartyMcp, options.resolvedScope, options.codexHomeDir, {
|
|
2232
|
+
modelUpgradePrompt: options.modelUpgradePrompt,
|
|
2233
|
+
existingConfig,
|
|
2234
|
+
verbose: false,
|
|
2235
|
+
statusLinePreset: options.statusLinePreset,
|
|
2236
|
+
forceStatusLinePreset: options.forceStatusLinePreset,
|
|
2237
|
+
codexHookFeatureFlag: options.codexHookFeatureFlag,
|
|
2238
|
+
managedHookTrustState: managedHooksPlan.finalTrustState,
|
|
2239
|
+
priorManagedHookTrustState: managedHooksPlan.priorTrustState,
|
|
2240
|
+
legacyHookTrustState: managedHooksPlan.legacyTrustState,
|
|
2241
|
+
hookCommandPlatform: options.platform,
|
|
2242
|
+
notifyMetadataSnapshot: options.notifyMetadataSnapshot,
|
|
2243
|
+
});
|
|
2244
|
+
finalConfig = managedConfigPlan.finalConfig;
|
|
2245
|
+
if (managedConfigPlan.currentModel &&
|
|
2246
|
+
managedConfigPlan.modelOverride &&
|
|
2247
|
+
managedConfigPlan.currentModel !== managedConfigPlan.modelOverride) {
|
|
2248
|
+
modelUpgrade = {
|
|
2249
|
+
currentModel: managedConfigPlan.currentModel,
|
|
2250
|
+
modelOverride: managedConfigPlan.modelOverride,
|
|
2251
|
+
};
|
|
2252
|
+
}
|
|
2253
|
+
repairedLegacyTeamRunTable = managedConfigPlan.repairedLegacyTeamRunTable;
|
|
2254
|
+
const notifyPlan = managedConfigPlan.notifyPlan;
|
|
2255
|
+
if (notifyPlan.metadataSnapshot && notifyPlan.metadataPath) {
|
|
2256
|
+
notifyMetadataPrecondition = nativeHookTransactionPrecondition("metadata", notifyPlan.metadataPath, `notification metadata ${notifyPlan.metadataPath}`, notifyPlan.metadataSnapshot);
|
|
2257
|
+
}
|
|
2258
|
+
if (notifyPlan.metadataPath && notifyPlan.metadata) {
|
|
2259
|
+
const metadataBefore = options.notifyMetadataSnapshot;
|
|
2260
|
+
if (!metadataBefore) {
|
|
2261
|
+
throw new Error(`Missing notification metadata snapshot for ${notifyPlan.metadataPath}; refusing to plan from a newly captured baseline.`);
|
|
2262
|
+
}
|
|
2263
|
+
notifyMetadataArtifact = nativeHookTransactionArtifact("metadata", notifyPlan.metadataPath, `notification metadata ${notifyPlan.metadataPath}`, metadataBefore, Buffer.from(JSON.stringify(notifyPlan.metadata, null, 2) + "\n", "utf-8"));
|
|
2264
|
+
if (!notifyMetadataPrecondition) {
|
|
2265
|
+
notifyMetadataPrecondition = nativeHookTransactionPrecondition("metadata", notifyPlan.metadataPath, `notification metadata ${notifyPlan.metadataPath}`, metadataBefore);
|
|
2266
|
+
}
|
|
2267
|
+
}
|
|
1352
2268
|
}
|
|
1353
|
-
|
|
2269
|
+
if (options.isPluginInstallMode && options.sharedMcpRegistry.servers.length > 0) {
|
|
2270
|
+
finalConfig = mergeSharedMcpRegistryBlock(finalConfig, options.sharedMcpRegistry.servers, options.sharedMcpRegistry.sourcePath);
|
|
2271
|
+
}
|
|
2272
|
+
try {
|
|
2273
|
+
TOML.parse(finalConfig);
|
|
2274
|
+
}
|
|
2275
|
+
catch (error) {
|
|
2276
|
+
throw new Error(`Refusing to write invalid planned config.toml: ${error instanceof Error ? error.message : String(error)}`);
|
|
2277
|
+
}
|
|
2278
|
+
const hookArtifact = nativeHookTransactionArtifact("hooks", options.hooksPath, `native hooks ${options.hooksPath}`, existingHooksSnapshot, finalHooksContent === null ? null : Buffer.from(finalHooksContent, "utf-8"), options.platform);
|
|
2279
|
+
const configArtifact = nativeHookTransactionArtifact("config", options.configPath, `config ${options.configPath}`, existingConfigSnapshot, Buffer.from(finalConfig, "utf-8"));
|
|
2280
|
+
let shimArtifact = null;
|
|
2281
|
+
let shimPrecondition = null;
|
|
2282
|
+
if (options.platform === "win32") {
|
|
2283
|
+
const shimPath = buildManagedCodexNativeHookWindowsShimPath(options.codexHomeDir);
|
|
2284
|
+
const shimSnapshot = await captureNativeHookTransactionArtifact(shimPath, `native hook Windows shim ${shimPath}`);
|
|
2285
|
+
const shimAfter = Buffer.from(buildManagedCodexNativeHookWindowsShimContent(options.pkgRoot), "utf-8");
|
|
2286
|
+
assertWindowsNativeHookShimOwnership(shimPath, shimSnapshot.bytes, shimAfter);
|
|
2287
|
+
const shimReference = decideWindowsNativeHookShimReference(finalHooksContent, shimPath);
|
|
2288
|
+
shimArtifact = nativeHookTransactionArtifact("shim", shimPath, `native hook Windows shim ${shimPath}`, shimSnapshot, options.isPluginInstallMode && options.pluginScopedHooks &&
|
|
2289
|
+
shimReference === "not_referenced"
|
|
2290
|
+
? null
|
|
2291
|
+
: shimAfter);
|
|
2292
|
+
shimPrecondition = nativeHookTransactionPrecondition("shim", shimPath, `native hook Windows shim ${shimPath}`, shimSnapshot);
|
|
2293
|
+
}
|
|
2294
|
+
const windowsShimCreateOrUpdateArtifact = shimArtifact && shimArtifact.after !== null ? shimArtifact : null;
|
|
2295
|
+
const windowsShimDeletionArtifact = shimArtifact && shimArtifact.after === null ? shimArtifact : null;
|
|
2296
|
+
const artifacts = options.platform === "win32"
|
|
2297
|
+
? windowsShimDeletionArtifact
|
|
2298
|
+
? [
|
|
2299
|
+
hookArtifact,
|
|
2300
|
+
windowsShimDeletionArtifact,
|
|
2301
|
+
notifyMetadataArtifact,
|
|
2302
|
+
configArtifact,
|
|
2303
|
+
]
|
|
2304
|
+
: [
|
|
2305
|
+
windowsShimCreateOrUpdateArtifact,
|
|
2306
|
+
hookArtifact,
|
|
2307
|
+
notifyMetadataArtifact,
|
|
2308
|
+
configArtifact,
|
|
2309
|
+
]
|
|
2310
|
+
: [hookArtifact, notifyMetadataArtifact, configArtifact];
|
|
2311
|
+
const preconditions = [
|
|
2312
|
+
shimPrecondition,
|
|
2313
|
+
nativeHookTransactionPrecondition("hooks", options.hooksPath, `native hooks ${options.hooksPath}`, existingHooksSnapshot),
|
|
2314
|
+
nativeHookTransactionPrecondition("config", options.configPath, `config ${options.configPath}`, existingConfigSnapshot),
|
|
2315
|
+
notifyMetadataPrecondition,
|
|
2316
|
+
];
|
|
2317
|
+
return {
|
|
2318
|
+
artifacts: artifacts.filter((artifact) => artifact !== null),
|
|
2319
|
+
preconditions: preconditions.filter((precondition) => precondition !== null),
|
|
2320
|
+
finalConfig,
|
|
2321
|
+
hooksRemovedCount,
|
|
2322
|
+
pluginScopedHooks: options.pluginScopedHooks,
|
|
2323
|
+
cleanedLegacyConfig,
|
|
2324
|
+
pluginMarketplaceResult,
|
|
2325
|
+
pluginDeveloperInstructionsResult,
|
|
2326
|
+
diagnostics,
|
|
2327
|
+
modelUpgrade,
|
|
2328
|
+
repairedLegacyTeamRunTable,
|
|
2329
|
+
};
|
|
1354
2330
|
}
|
|
1355
2331
|
export async function setup(options = {}) {
|
|
1356
2332
|
const { force = false, dryRun = false, installMode: requestedInstallMode, mcpMode: requestedMcpMode, teamMode: requestedTeamMode, scope: requestedScope, verbose = false, skipNativeAgentRefresh: requestedSkipNativeAgentRefresh = false, setupScopePrompt, persistedSetupReviewPrompt, installModePrompt, modelUpgradePrompt, pluginAgentsMdPrompt, pluginDeveloperInstructionsPrompt, firstPartyMcpRemovalPrompt, } = options;
|
|
@@ -1372,8 +2348,15 @@ export async function setup(options = {}) {
|
|
|
1372
2348
|
Boolean(persistedPreferences?.teamMode) &&
|
|
1373
2349
|
(!persistedPreferences?.scope ||
|
|
1374
2350
|
persistedPreferences.scope === effectiveScopeForInstallMode);
|
|
2351
|
+
const wouldUsePersistedMergeAgents = !options.mergeAgentsPolicy &&
|
|
2352
|
+
typeof options.mergeAgents !== "boolean" &&
|
|
2353
|
+
resolvePersistedSetupMergeAgents(persistedPreferences, effectiveScopeForInstallMode) !== undefined;
|
|
1375
2354
|
const shouldReviewPersistedSetup = hasPersistedSetupPreferences(persistedPreferences) &&
|
|
1376
|
-
(wouldUsePersistedScope ||
|
|
2355
|
+
(wouldUsePersistedScope ||
|
|
2356
|
+
wouldUsePersistedInstallMode ||
|
|
2357
|
+
wouldUsePersistedMcpMode ||
|
|
2358
|
+
wouldUsePersistedTeamMode ||
|
|
2359
|
+
wouldUsePersistedMergeAgents) &&
|
|
1377
2360
|
(typeof persistedSetupReviewPrompt === "function" ||
|
|
1378
2361
|
(process.stdin.isTTY && process.stdout.isTTY));
|
|
1379
2362
|
if (shouldReviewPersistedSetup) {
|
|
@@ -1383,6 +2366,18 @@ export async function setup(options = {}) {
|
|
|
1383
2366
|
console.log(`Setup preference review: ${persistedReviewDecision} (${formatPersistedSetupPreferenceSummary(persistedPreferences)})\n`);
|
|
1384
2367
|
}
|
|
1385
2368
|
const resolvedScope = await resolveSetupScope(projectRoot, requestedScope, persistedReviewDecision, persistedPreferences, setupScopePrompt);
|
|
2369
|
+
const requestedMergeAgentsPolicy = options.mergeAgentsPolicy ??
|
|
2370
|
+
(typeof options.mergeAgents === "boolean"
|
|
2371
|
+
? { kind: "set", value: options.mergeAgents }
|
|
2372
|
+
: undefined);
|
|
2373
|
+
const inheritedMergeAgents = persistedReviewDecision === "reset"
|
|
2374
|
+
? undefined
|
|
2375
|
+
: resolvePersistedSetupMergeAgents(persistedPreferences, resolvedScope.scope);
|
|
2376
|
+
const effectiveMergeAgents = requestedMergeAgentsPolicy?.kind === "set"
|
|
2377
|
+
? requestedMergeAgentsPolicy.value
|
|
2378
|
+
: requestedMergeAgentsPolicy?.kind === "clear"
|
|
2379
|
+
? undefined
|
|
2380
|
+
: inheritedMergeAgents;
|
|
1386
2381
|
const resolvedInstallMode = await resolveSetupInstallMode(projectRoot, resolvedScope.scope, requestedInstallMode, installModePrompt, persistedReviewDecision, persistedPreferences);
|
|
1387
2382
|
const resolvedMcpMode = resolveSetupMcpMode(resolvedScope.scope, requestedMcpMode, persistedReviewDecision, persistedPreferences);
|
|
1388
2383
|
const resolvedTeamMode = requestedTeamMode
|
|
@@ -1395,9 +2390,27 @@ export async function setup(options = {}) {
|
|
|
1395
2390
|
const skipNativeAgentRefresh = requestedSkipNativeAgentRefresh ||
|
|
1396
2391
|
process.env[SKIP_NATIVE_AGENT_REFRESH_ENV] === "1";
|
|
1397
2392
|
const scopeDirs = resolveScopeDirectories(resolvedScope.scope, projectRoot);
|
|
1398
|
-
|
|
1399
|
-
|
|
2393
|
+
if (!dryRun) {
|
|
2394
|
+
await recoverNativeHookClaimJournal(scopeDirs.codexHomeDir);
|
|
2395
|
+
}
|
|
2396
|
+
const nativeHookTransactionPlatform = nativeHookPlatform();
|
|
2397
|
+
let nativeHookTransactionAncestorPrecondition = await captureNativeHookTransactionAncestorPrecondition(scopeDirs.codexHomeDir, [
|
|
2398
|
+
scopeDirs.codexConfigFile,
|
|
2399
|
+
scopeDirs.codexHooksFile,
|
|
2400
|
+
getNotifyMetadataPath(scopeDirs.codexHomeDir),
|
|
2401
|
+
...(nativeHookTransactionPlatform === "win32"
|
|
2402
|
+
? [
|
|
2403
|
+
buildManagedCodexNativeHookWindowsShimPath(scopeDirs.codexHomeDir),
|
|
2404
|
+
]
|
|
2405
|
+
: []),
|
|
2406
|
+
]);
|
|
2407
|
+
const existingConfigForMcpMigrationSnapshot = await captureNativeHookTransactionArtifact(scopeDirs.codexConfigFile, `config ${scopeDirs.codexConfigFile}`);
|
|
2408
|
+
const existingConfigForMcpMigration = existingConfigForMcpMigrationSnapshot.bytes
|
|
2409
|
+
? decodeNativeHookTransactionUtf8(existingConfigForMcpMigrationSnapshot.bytes, `config ${scopeDirs.codexConfigFile}`)
|
|
1400
2410
|
: "";
|
|
2411
|
+
const notifyMetadataSnapshot = configRequiresNotificationMetadataSnapshot(existingConfigForMcpMigration, pkgRoot, resolvedScope.scope)
|
|
2412
|
+
? await captureNativeHookTransactionArtifact(getNotifyMetadataPath(scopeDirs.codexHomeDir), `notification metadata ${getNotifyMetadataPath(scopeDirs.codexHomeDir)}`)
|
|
2413
|
+
: undefined;
|
|
1401
2414
|
const firstPartyMcpRegistrationKinds = [
|
|
1402
2415
|
hasFirstPartyOmxMcpRegistrations(existingConfigForMcpMigration)
|
|
1403
2416
|
? "config.toml [mcp_servers.omx_*]"
|
|
@@ -1425,7 +2438,7 @@ export async function setup(options = {}) {
|
|
|
1425
2438
|
? join(projectRoot, "AGENTS.md")
|
|
1426
2439
|
: join(scopeDirs.codexHomeDir, "AGENTS.md");
|
|
1427
2440
|
const pluginDeveloperInstructionsDecision = isPluginInstallMode
|
|
1428
|
-
? await resolvePluginDeveloperInstructionsDecision(scopeDirs.codexConfigFile, { pluginDeveloperInstructionsPrompt })
|
|
2441
|
+
? await resolvePluginDeveloperInstructionsDecision(existingConfigForMcpMigration, scopeDirs.codexConfigFile, { pluginDeveloperInstructionsPrompt })
|
|
1429
2442
|
: {
|
|
1430
2443
|
action: "preserve",
|
|
1431
2444
|
state: "custom",
|
|
@@ -1443,7 +2456,7 @@ export async function setup(options = {}) {
|
|
|
1443
2456
|
pluginAgentsMdIsSymlink = false;
|
|
1444
2457
|
}
|
|
1445
2458
|
const usePluginAgentsMdDefault = isPluginInstallMode
|
|
1446
|
-
?
|
|
2459
|
+
? effectiveMergeAgents || pluginAgentsMdIsSymlink
|
|
1447
2460
|
? false
|
|
1448
2461
|
: force
|
|
1449
2462
|
? true
|
|
@@ -1451,6 +2464,55 @@ export async function setup(options = {}) {
|
|
|
1451
2464
|
? await pluginAgentsMdPrompt(pluginAgentsMdDst)
|
|
1452
2465
|
: await promptForPluginAgentsMdDefault(pluginAgentsMdDst)
|
|
1453
2466
|
: false;
|
|
2467
|
+
const codexHookFeatureSupport = resolveCodexHookFeatureSupportForCli({
|
|
2468
|
+
codexFeaturesProbe: options.codexFeaturesProbe,
|
|
2469
|
+
codexVersionProbe: options.codexVersionProbe,
|
|
2470
|
+
});
|
|
2471
|
+
const codexHookFeatureFlag = codexHookFeatureSupport.hookFeatureFlag;
|
|
2472
|
+
const pluginScopedHooksSupported = codexHookFeatureSupport.pluginScopedHooks;
|
|
2473
|
+
const shouldSyncSharedMcpRegistry = resolvedMcpMode.mcpMode === "compat";
|
|
2474
|
+
const registryCandidates = getUnifiedMcpRegistryCandidates();
|
|
2475
|
+
const defaultRegistryCandidates = registryCandidates.slice(0, 1);
|
|
2476
|
+
const sharedMcpRegistry = shouldSyncSharedMcpRegistry
|
|
2477
|
+
? await loadUnifiedMcpRegistry({
|
|
2478
|
+
candidates: options.mcpRegistryCandidates ?? defaultRegistryCandidates,
|
|
2479
|
+
})
|
|
2480
|
+
: { servers: [], warnings: [] };
|
|
2481
|
+
const legacyRegistryCandidate = getLegacyUnifiedMcpRegistryCandidate();
|
|
2482
|
+
const preflightMarketplace = isPluginInstallMode
|
|
2483
|
+
? await resolvePackagedOmxMarketplace(pkgRoot)
|
|
2484
|
+
: null;
|
|
2485
|
+
const statusLinePreset = isPluginInstallMode
|
|
2486
|
+
? undefined
|
|
2487
|
+
: await resolveStatusLinePresetForSetup(projectRoot, { force });
|
|
2488
|
+
const nativeHookSetupTransaction = await planNativeHookSetupTransaction({
|
|
2489
|
+
configPath: scopeDirs.codexConfigFile,
|
|
2490
|
+
hooksPath: scopeDirs.codexHooksFile,
|
|
2491
|
+
codexHomeDir: scopeDirs.codexHomeDir,
|
|
2492
|
+
pkgRoot,
|
|
2493
|
+
platform: nativeHookTransactionPlatform,
|
|
2494
|
+
isPluginInstallMode,
|
|
2495
|
+
pluginScopedHooks: pluginScopedHooksSupported,
|
|
2496
|
+
codexHookFeatureFlag,
|
|
2497
|
+
preserveFirstPartyMcp: shouldOfferFirstPartyMcpRemoval && !removeFirstPartyMcpRegistrations,
|
|
2498
|
+
removeFirstPartyMcp: removeFirstPartyMcpRegistrations,
|
|
2499
|
+
pluginDeveloperInstructionsDecision,
|
|
2500
|
+
pluginMarketplaceAvailable: preflightMarketplace !== null,
|
|
2501
|
+
sharedMcpRegistry,
|
|
2502
|
+
mcpMode: resolvedMcpMode.mcpMode,
|
|
2503
|
+
resolvedScope: resolvedScope.scope,
|
|
2504
|
+
modelUpgradePrompt,
|
|
2505
|
+
statusLinePreset,
|
|
2506
|
+
forceStatusLinePreset: force,
|
|
2507
|
+
configSnapshot: existingConfigForMcpMigrationSnapshot,
|
|
2508
|
+
notifyMetadataSnapshot,
|
|
2509
|
+
});
|
|
2510
|
+
const summary = createEmptyRunSummary();
|
|
2511
|
+
for (const precondition of nativeHookSetupTransaction.preconditions) {
|
|
2512
|
+
injectNativeHookTransactionFailure("before_precondition", precondition);
|
|
2513
|
+
await assertNativeHookTransactionPrecondition(precondition);
|
|
2514
|
+
}
|
|
2515
|
+
await assertNativeHookTransactionAncestorPrecondition(nativeHookTransactionAncestorPrecondition);
|
|
1454
2516
|
console.log("oh-my-codex setup");
|
|
1455
2517
|
console.log("=================\n");
|
|
1456
2518
|
console.log(`Using setup scope: ${resolvedScope.scope}${scopeSourceMessage}\n`);
|
|
@@ -1510,11 +2572,8 @@ export async function setup(options = {}) {
|
|
|
1510
2572
|
resolvedInstallMode.installMode === "plugin")
|
|
1511
2573
|
? { installMode: resolvedInstallMode.installMode }
|
|
1512
2574
|
: {}),
|
|
2575
|
+
...(effectiveMergeAgents !== undefined ? { mergeAgents: effectiveMergeAgents } : {}),
|
|
1513
2576
|
};
|
|
1514
|
-
await persistSetupPreferences(projectRoot, setupPreferencesToPersist, {
|
|
1515
|
-
dryRun,
|
|
1516
|
-
verbose,
|
|
1517
|
-
});
|
|
1518
2577
|
console.log(" Done.\n");
|
|
1519
2578
|
if (resolvedScope.scope === "project") {
|
|
1520
2579
|
const gitignoreResult = await ensureProjectGitignore(projectRoot, backupContext, { dryRun, verbose });
|
|
@@ -1526,7 +2585,6 @@ export async function setup(options = {}) {
|
|
|
1526
2585
|
}
|
|
1527
2586
|
}
|
|
1528
2587
|
const catalogCounts = getCatalogHeadlineCounts();
|
|
1529
|
-
const summary = createEmptyRunSummary();
|
|
1530
2588
|
// Step 2: Install agent prompts
|
|
1531
2589
|
console.log("[2/8] Installing agent prompts...");
|
|
1532
2590
|
{
|
|
@@ -1624,27 +2682,12 @@ export async function setup(options = {}) {
|
|
|
1624
2682
|
}
|
|
1625
2683
|
// Step 5: Update config.toml
|
|
1626
2684
|
console.log("[5/8] Updating config.toml...");
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
const codexHookFeatureSupport = resolveCodexHookFeatureSupportForCli({
|
|
1630
|
-
codexFeaturesProbe: options.codexFeaturesProbe,
|
|
1631
|
-
codexVersionProbe: options.codexVersionProbe,
|
|
1632
|
-
});
|
|
1633
|
-
const codexHookFeatureFlag = codexHookFeatureSupport.hookFeatureFlag;
|
|
1634
|
-
const pluginScopedHooksSupported = codexHookFeatureSupport.pluginScopedHooks;
|
|
2685
|
+
const resolvedConfig = nativeHookSetupTransaction.finalConfig;
|
|
2686
|
+
const omxManagesTui = !isPluginInstallMode;
|
|
1635
2687
|
if (verbose) {
|
|
1636
2688
|
console.log(` Native Codex hook feature flag: [features].${codexHookFeatureFlag}`);
|
|
1637
2689
|
console.log(` Plugin-scoped Codex hooks: ${pluginScopedHooksSupported ? "supported" : "not reported; using legacy setup fallback"}`);
|
|
1638
2690
|
}
|
|
1639
|
-
const shouldSyncSharedMcpRegistry = resolvedMcpMode.mcpMode === "compat";
|
|
1640
|
-
const registryCandidates = getUnifiedMcpRegistryCandidates();
|
|
1641
|
-
const defaultRegistryCandidates = registryCandidates.slice(0, 1);
|
|
1642
|
-
const sharedMcpRegistry = shouldSyncSharedMcpRegistry
|
|
1643
|
-
? await loadUnifiedMcpRegistry({
|
|
1644
|
-
candidates: options.mcpRegistryCandidates ?? defaultRegistryCandidates,
|
|
1645
|
-
})
|
|
1646
|
-
: { servers: [], warnings: [] };
|
|
1647
|
-
const legacyRegistryCandidate = getLegacyUnifiedMcpRegistryCandidate();
|
|
1648
2691
|
if (shouldSyncSharedMcpRegistry &&
|
|
1649
2692
|
!options.mcpRegistryCandidates &&
|
|
1650
2693
|
!sharedMcpRegistry.sourcePath &&
|
|
@@ -1658,36 +2701,51 @@ export async function setup(options = {}) {
|
|
|
1658
2701
|
for (const warning of sharedMcpRegistry.warnings) {
|
|
1659
2702
|
console.log(` warning: ${warning}`);
|
|
1660
2703
|
}
|
|
2704
|
+
logManagedCodexHooksPlanDiagnostics(nativeHookSetupTransaction.diagnostics, {
|
|
2705
|
+
verbose,
|
|
2706
|
+
});
|
|
2707
|
+
const changedHookArtifact = nativeHookSetupTransaction.artifacts.some((artifact) => artifact.kind === "hooks");
|
|
2708
|
+
const changedShimArtifact = nativeHookSetupTransaction.artifacts.some((artifact) => artifact.kind === "shim");
|
|
2709
|
+
const changedConfigArtifact = nativeHookSetupTransaction.artifacts.some((artifact) => artifact.kind === "config");
|
|
2710
|
+
if (changedHookArtifact) {
|
|
2711
|
+
if (isPluginInstallMode &&
|
|
2712
|
+
nativeHookSetupTransaction.pluginScopedHooks &&
|
|
2713
|
+
nativeHookSetupTransaction.hooksRemovedCount > 0) {
|
|
2714
|
+
summary.config.removed += nativeHookSetupTransaction.hooksRemovedCount;
|
|
2715
|
+
}
|
|
2716
|
+
else {
|
|
2717
|
+
summary.config.updated += 1;
|
|
2718
|
+
}
|
|
2719
|
+
}
|
|
2720
|
+
else if (!isPluginInstallMode || !nativeHookSetupTransaction.pluginScopedHooks) {
|
|
2721
|
+
summary.config.unchanged += 1;
|
|
2722
|
+
}
|
|
2723
|
+
if (changedShimArtifact)
|
|
2724
|
+
summary.config.updated += 1;
|
|
2725
|
+
if (changedConfigArtifact)
|
|
2726
|
+
summary.config.updated += 1;
|
|
2727
|
+
else
|
|
2728
|
+
summary.config.unchanged += 1;
|
|
2729
|
+
if (verbose && nativeHookSetupTransaction.modelUpgrade) {
|
|
2730
|
+
console.log(` ${dryRun ? "would update" : "updated"} root model from ${nativeHookSetupTransaction.modelUpgrade.currentModel} to ${nativeHookSetupTransaction.modelUpgrade.modelOverride}`);
|
|
2731
|
+
}
|
|
1661
2732
|
if (isPluginInstallMode) {
|
|
1662
|
-
|
|
1663
|
-
dryRun,
|
|
1664
|
-
verbose,
|
|
1665
|
-
preserveFirstPartyMcp: shouldOfferFirstPartyMcpRemoval &&
|
|
1666
|
-
!removeFirstPartyMcpRegistrations,
|
|
1667
|
-
developerInstructionsDecision: pluginDeveloperInstructionsDecision,
|
|
1668
|
-
});
|
|
1669
|
-
if (configCleaned)
|
|
2733
|
+
if (nativeHookSetupTransaction.cleanedLegacyConfig) {
|
|
1670
2734
|
summary.config.removed += 1;
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
codexHookFeatureFlag,
|
|
1678
|
-
pluginScopedHooks: pluginScopedHooksSupported,
|
|
1679
|
-
});
|
|
1680
|
-
const pluginMarketplaceResult = await ensurePluginMarketplaceRegistration(scopeDirs.codexConfigFile, pkgRoot, resolvedMcpMode.mcpMode, removeFirstPartyMcpRegistrations, backupContext, summary.config, { dryRun, verbose });
|
|
1681
|
-
if (pluginMarketplaceResult === "unavailable") {
|
|
2735
|
+
console.log(` ${dryRun ? "Would clean" : "Cleaned"} legacy OMX config entries for plugin mode.\n`);
|
|
2736
|
+
}
|
|
2737
|
+
else {
|
|
2738
|
+
console.log(" Config refresh skipped; no legacy OMX config entries found.\n");
|
|
2739
|
+
}
|
|
2740
|
+
if (nativeHookSetupTransaction.pluginMarketplaceResult === "unavailable") {
|
|
1682
2741
|
console.log(` warning: packaged ${OMX_LOCAL_MARKETPLACE_NAME} Codex plugin marketplace metadata not found; /skills plugin discovery was not registered.`);
|
|
1683
2742
|
}
|
|
1684
|
-
else if (pluginMarketplaceResult === "updated") {
|
|
2743
|
+
else if (nativeHookSetupTransaction.pluginMarketplaceResult === "updated") {
|
|
1685
2744
|
console.log(` ${dryRun ? "Would register" : "Registered"} local Codex plugin marketplace ${OMX_LOCAL_MARKETPLACE_NAME} (${pkgRoot}).`);
|
|
1686
2745
|
}
|
|
1687
2746
|
else {
|
|
1688
2747
|
console.log(` Local Codex plugin marketplace ${OMX_LOCAL_MARKETPLACE_NAME} already registered (${pkgRoot}).`);
|
|
1689
2748
|
}
|
|
1690
|
-
const packagedMarketplace = await resolvePackagedOmxMarketplace(pkgRoot);
|
|
1691
2749
|
const pluginCacheRefresh = await refreshOmxPluginDiscoveryCache(pkgRoot, {
|
|
1692
2750
|
dryRun,
|
|
1693
2751
|
verbose,
|
|
@@ -1700,38 +2758,25 @@ export async function setup(options = {}) {
|
|
|
1700
2758
|
else if (pluginCacheRefresh.status === "unchanged") {
|
|
1701
2759
|
console.log(" Codex plugin discovery cache already matches packaged plugin metadata.");
|
|
1702
2760
|
}
|
|
1703
|
-
const pluginCacheMaterialize = await materializePackagedOmxPluginCache(scopeDirs.codexHomeDir,
|
|
2761
|
+
const pluginCacheMaterialize = await materializePackagedOmxPluginCache(scopeDirs.codexHomeDir, preflightMarketplace, { dryRun, teamMode: resolvedTeamMode });
|
|
1704
2762
|
if (pluginCacheMaterialize.status === "materialized") {
|
|
1705
2763
|
console.log(` ${dryRun ? "Would install" : "Installed"} local Codex plugin cache for ${OMX_LOCAL_MARKETPLACE_NAME}/${OMX_PLUGIN_NAME} at ${pluginCacheMaterialize.cacheDir}.`);
|
|
1706
2764
|
}
|
|
1707
2765
|
else if (pluginCacheMaterialize.status === "unchanged") {
|
|
1708
2766
|
console.log(" Local Codex plugin cache already exposes packaged OMX skills.");
|
|
1709
2767
|
}
|
|
1710
|
-
if (pluginCacheMaterialize.status === "materialized" ||
|
|
2768
|
+
if (pluginCacheMaterialize.status === "materialized" ||
|
|
2769
|
+
pluginCacheMaterialize.status === "unchanged") {
|
|
1711
2770
|
console.log(" Start a new Codex session if /skills still shows stale OMX plugin skill metadata; the current session may keep its in-memory plugin registry until restart.");
|
|
1712
2771
|
}
|
|
1713
|
-
if (shouldSyncSharedMcpRegistry) {
|
|
1714
|
-
|
|
1715
|
-
if (resolvedScope.scope === "user") {
|
|
1716
|
-
await syncClaudeCodeMcpSettings(sharedMcpRegistry, summary.config, backupContext, { dryRun, verbose });
|
|
1717
|
-
}
|
|
2772
|
+
if (shouldSyncSharedMcpRegistry && resolvedScope.scope === "user") {
|
|
2773
|
+
await syncClaudeCodeMcpSettings(sharedMcpRegistry, summary.config, backupContext, { dryRun, verbose });
|
|
1718
2774
|
}
|
|
1719
|
-
resolvedConfig = existsSync(scopeDirs.codexConfigFile)
|
|
1720
|
-
? await readFile(scopeDirs.codexConfigFile, "utf-8")
|
|
1721
|
-
: "";
|
|
1722
2775
|
console.log(pluginScopedHooksSupported
|
|
1723
2776
|
? " Plugin-scoped Codex hooks and runtime feature flags refresh complete (plugin_hooks, goals).\n"
|
|
1724
2777
|
: ` Native Codex hooks fallback and runtime feature flags refresh complete (${scopeDirs.codexHooksFile}; hooks, goals).\n`);
|
|
1725
2778
|
if (pluginDeveloperInstructionsDecision.action !== "preserve") {
|
|
1726
|
-
|
|
1727
|
-
dryRun,
|
|
1728
|
-
verbose,
|
|
1729
|
-
decision: pluginDeveloperInstructionsDecision,
|
|
1730
|
-
});
|
|
1731
|
-
if (developerInstructionsResult === "updated") {
|
|
1732
|
-
resolvedConfig = existsSync(scopeDirs.codexConfigFile)
|
|
1733
|
-
? await readFile(scopeDirs.codexConfigFile, "utf-8")
|
|
1734
|
-
: "";
|
|
2779
|
+
if (nativeHookSetupTransaction.pluginDeveloperInstructionsResult === "updated") {
|
|
1735
2780
|
console.log(` ${dryRun ? "Would add" : "Added"} plugin-mode developer_instructions default (${scopeDirs.codexConfigFile}).\n`);
|
|
1736
2781
|
}
|
|
1737
2782
|
else {
|
|
@@ -1743,31 +2788,13 @@ export async function setup(options = {}) {
|
|
|
1743
2788
|
}
|
|
1744
2789
|
}
|
|
1745
2790
|
else {
|
|
1746
|
-
const statusLinePreset = await resolveStatusLinePresetForSetup(projectRoot, { force });
|
|
1747
|
-
const managedConfig = await updateManagedConfig(scopeDirs.codexConfigFile, scopeDirs.codexHooksFile, pkgRoot, sharedMcpRegistry, resolvedMcpMode.mcpMode, shouldOfferFirstPartyMcpRemoval && !removeFirstPartyMcpRegistrations, resolvedScope.scope, scopeDirs.codexHomeDir, summary.config, backupContext, {
|
|
1748
|
-
dryRun,
|
|
1749
|
-
modelUpgradePrompt,
|
|
1750
|
-
verbose,
|
|
1751
|
-
statusLinePreset,
|
|
1752
|
-
forceStatusLinePreset: force,
|
|
1753
|
-
codexHookFeatureFlag,
|
|
1754
|
-
});
|
|
1755
|
-
resolvedConfig = managedConfig.finalConfig;
|
|
1756
|
-
omxManagesTui = managedConfig.omxManagesTui;
|
|
1757
|
-
if (managedConfig.repairedLegacyTeamRunTable) {
|
|
1758
|
-
console.log(" Removed retired [mcp_servers.omx_team_run] config during refresh.");
|
|
1759
|
-
}
|
|
1760
2791
|
if (shouldSyncSharedMcpRegistry && resolvedScope.scope === "user") {
|
|
1761
2792
|
await syncClaudeCodeMcpSettings(sharedMcpRegistry, summary.config, backupContext, { dryRun, verbose });
|
|
1762
2793
|
}
|
|
2794
|
+
if (nativeHookSetupTransaction.repairedLegacyTeamRunTable) {
|
|
2795
|
+
console.log(" Removed retired [mcp_servers.omx_team_run] config during refresh.");
|
|
2796
|
+
}
|
|
1763
2797
|
console.log(` Config refresh complete (${scopeDirs.codexConfigFile}).\n`);
|
|
1764
|
-
const existingHooksContent = existsSync(scopeDirs.codexHooksFile)
|
|
1765
|
-
? await readFile(scopeDirs.codexHooksFile, "utf-8")
|
|
1766
|
-
: null;
|
|
1767
|
-
await migrateLegacyHooksJsonTrustStateToConfig(scopeDirs.codexConfigFile, existingHooksContent, backupContext, summary.config, { dryRun, verbose });
|
|
1768
|
-
const hooksConfig = mergeManagedCodexHooksConfig(existingHooksContent, pkgRoot, scopeDirs.codexHooksFile, { platform: process.platform, codexHomeDir: scopeDirs.codexHomeDir });
|
|
1769
|
-
await syncManagedContent(hooksConfig, scopeDirs.codexHooksFile, summary.config, backupContext, { dryRun, verbose }, `native hooks ${scopeDirs.codexHooksFile}`);
|
|
1770
|
-
await syncManagedWindowsNativeHookShim(scopeDirs.codexHomeDir, pkgRoot, summary.config, backupContext, { dryRun, verbose });
|
|
1771
2798
|
console.log(` Native Codex hooks refresh complete (${scopeDirs.codexHooksFile}).\n`);
|
|
1772
2799
|
}
|
|
1773
2800
|
// Step 5.5: Verify team CLI interop surface is available when Team is enabled.
|
|
@@ -1802,7 +2829,7 @@ export async function setup(options = {}) {
|
|
|
1802
2829
|
});
|
|
1803
2830
|
const modelTableDefinitions = getAgentsModelTableDefinitionsForTeamMode(resolvedTeamMode);
|
|
1804
2831
|
const rewritten = upsertAgentsModelTable(addGeneratedAgentsMarker(applyTeamModeToAgentsTemplate(applyPluginModeWordingToAgentsTemplate(content, resolvedScope.scope), resolvedTeamMode)), modelTableContext, modelTableDefinitions, { codexHomeOverride: scopeDirs.codexHomeDir });
|
|
1805
|
-
if (
|
|
2832
|
+
if (effectiveMergeAgents && pluginAgentsMdExists) {
|
|
1806
2833
|
if (pluginAgentsMdIsSymlink) {
|
|
1807
2834
|
summary.agentsMd.skipped += 1;
|
|
1808
2835
|
console.log(` Skipped plugin-mode AGENTS.md merge for symlinked ${pluginAgentsMdDst}; existing AGENTS.md left untouched.`);
|
|
@@ -1917,7 +2944,7 @@ export async function setup(options = {}) {
|
|
|
1917
2944
|
console.log(` WARNING: Existing AGENTS.md at ${agentsMdDst} lacks OMX contract markers; it may have been overwritten by another tool.`);
|
|
1918
2945
|
console.log(` Repair safely with "omx setup ${scopeFlag} --merge-agents" to preserve local guidance, or "omx setup ${scopeFlag} --force" to replace it after backup.`);
|
|
1919
2946
|
}
|
|
1920
|
-
if (
|
|
2947
|
+
if (effectiveMergeAgents) {
|
|
1921
2948
|
mergedAgentsContent = upsertManagedAgentsBlock(existing, rewritten);
|
|
1922
2949
|
canApplyManagedAgentsMerge = mergedAgentsContent !== existing;
|
|
1923
2950
|
}
|
|
@@ -1949,7 +2976,7 @@ export async function setup(options = {}) {
|
|
|
1949
2976
|
console.log(" Skipping AGENTS.md overwrite to avoid corrupting runtime overlay.");
|
|
1950
2977
|
console.log(" Stop the active session first, then re-run setup.");
|
|
1951
2978
|
}
|
|
1952
|
-
else if (
|
|
2979
|
+
else if (effectiveMergeAgents &&
|
|
1953
2980
|
agentsMdExists &&
|
|
1954
2981
|
!canApplyManagedAgentsMerge) {
|
|
1955
2982
|
summary.agentsMd.unchanged += 1;
|
|
@@ -2044,6 +3071,14 @@ export async function setup(options = {}) {
|
|
|
2044
3071
|
console.log("Force mode: enabled additional destructive maintenance (for example stale deprecated skill cleanup).");
|
|
2045
3072
|
console.log();
|
|
2046
3073
|
}
|
|
3074
|
+
await persistSetupPreferences(projectRoot, setupPreferencesToPersist, {
|
|
3075
|
+
dryRun,
|
|
3076
|
+
verbose,
|
|
3077
|
+
});
|
|
3078
|
+
setupLatePhaseFailureInjector?.();
|
|
3079
|
+
nativeHookTransactionAncestorPrecondition =
|
|
3080
|
+
await captureNativeHookTransactionAncestorPrecondition(scopeDirs.codexHomeDir, nativeHookSetupTransaction.artifacts.map((artifact) => artifact.path));
|
|
3081
|
+
await commitNativeHookTransaction(nativeHookSetupTransaction.artifacts, nativeHookSetupTransaction.preconditions, nativeHookTransactionAncestorPrecondition, backupContext, summary.config, { dryRun, verbose });
|
|
2047
3082
|
console.log('Setup complete! Run "omx doctor" to verify installation.');
|
|
2048
3083
|
console.log("\nNext steps:");
|
|
2049
3084
|
console.log(" 1. Start Codex CLI in your project directory");
|
|
@@ -2057,7 +3092,7 @@ export async function setup(options = {}) {
|
|
|
2057
3092
|
console.log(" 2. Use role/workflow keywords like $architect, $executor, and $plan in Codex");
|
|
2058
3093
|
console.log(" 3. Browse skills with /skills; AGENTS keyword routing can also activate them implicitly");
|
|
2059
3094
|
console.log(" 4. The AGENTS.md orchestration brain is loaded automatically");
|
|
2060
|
-
console.log(" 5. Native agent
|
|
3095
|
+
console.log(" 5. Native agent role TOML files written to .codex/agents/; use explicit agent_type when spawning OMX roles");
|
|
2061
3096
|
}
|
|
2062
3097
|
console.log(' 6. "omx explore" and "omx sparkshell" can hydrate native release binaries on first use; source installs still allow repo-local fallbacks and OMX_EXPLORE_BIN / OMX_SPARKSHELL_BIN overrides');
|
|
2063
3098
|
if (isGitHubCliConfigured()) {
|
|
@@ -2240,13 +3275,6 @@ async function syncNativeAgentToml(content, dstPath, summary, backupContext, opt
|
|
|
2240
3275
|
console.log(` ${options.dryRun ? "would update" : "updated"} ${verboseLabel}`);
|
|
2241
3276
|
}
|
|
2242
3277
|
}
|
|
2243
|
-
async function syncManagedWindowsNativeHookShim(codexHomeDir, pkgRoot, summary, backupContext, options) {
|
|
2244
|
-
if (process.platform !== "win32")
|
|
2245
|
-
return;
|
|
2246
|
-
const shimPath = buildManagedCodexNativeHookWindowsShimPath(codexHomeDir);
|
|
2247
|
-
const shimContent = buildManagedCodexNativeHookWindowsShimContent(pkgRoot);
|
|
2248
|
-
await syncManagedContent(shimContent, shimPath, summary, backupContext, options, `native hook Windows shim ${shimPath}`);
|
|
2249
|
-
}
|
|
2250
3278
|
async function syncManagedAgentsContent(content, dstPath, summary, backupContext, options) {
|
|
2251
3279
|
const destinationExists = existsSync(dstPath);
|
|
2252
3280
|
let existing = "";
|
|
@@ -2710,7 +3738,34 @@ async function cleanupLegacyManagedSkills(srcDir, dstDir, backupContext, options
|
|
|
2710
3738
|
function getNotifyMetadataPath(codexHomeDir) {
|
|
2711
3739
|
return join(codexHomeDir, ".omx", "notify-dispatch.json");
|
|
2712
3740
|
}
|
|
2713
|
-
|
|
3741
|
+
function isOmxDispatcherNotifyCommand(notifyCommand, pkgRoot) {
|
|
3742
|
+
return Boolean(isOmxManagedNotifyCommand(notifyCommand, pkgRoot) &&
|
|
3743
|
+
notifyCommand?.some((part) => /(?:^|[\\/])notify-dispatcher\.js$/.test(part)));
|
|
3744
|
+
}
|
|
3745
|
+
function configRequiresNotificationMetadataSnapshot(existingConfig, pkgRoot, scope) {
|
|
3746
|
+
if (scope === "project")
|
|
3747
|
+
return false;
|
|
3748
|
+
const existingNotify = getRootTomlArray(existingConfig, "notify");
|
|
3749
|
+
return Boolean(existingNotify &&
|
|
3750
|
+
(!isOmxManagedNotifyCommand(existingNotify, pkgRoot) ||
|
|
3751
|
+
isOmxDispatcherNotifyCommand(existingNotify, pkgRoot)));
|
|
3752
|
+
}
|
|
3753
|
+
function parseNotifyMetadataSnapshot(snapshot, label) {
|
|
3754
|
+
if (snapshot.bytes === null)
|
|
3755
|
+
return null;
|
|
3756
|
+
const content = decodeNativeHookTransactionUtf8(snapshot.bytes, label);
|
|
3757
|
+
try {
|
|
3758
|
+
const parsed = JSON.parse(content);
|
|
3759
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
3760
|
+
throw new Error("expected a JSON object");
|
|
3761
|
+
}
|
|
3762
|
+
return parsed;
|
|
3763
|
+
}
|
|
3764
|
+
catch (error) {
|
|
3765
|
+
throw new ManagedCodexHooksPlanError("invalid_document", `Refusing to read ${label}: invalid JSON (${error instanceof Error ? error.message : String(error)}).`, { label });
|
|
3766
|
+
}
|
|
3767
|
+
}
|
|
3768
|
+
async function buildNotifyMergePlan(existingConfig, pkgRoot, codexHomeDir, scope, metadataSnapshot) {
|
|
2714
3769
|
if (scope === "project") {
|
|
2715
3770
|
return { notifyCommand: false };
|
|
2716
3771
|
}
|
|
@@ -2727,35 +3782,34 @@ async function buildNotifyMergePlan(existingConfig, pkgRoot, codexHomeDir, scope
|
|
|
2727
3782
|
return { notifyCommand: omxNotify };
|
|
2728
3783
|
}
|
|
2729
3784
|
if (isOmxManagedNotifyCommand(existingNotify, pkgRoot)) {
|
|
2730
|
-
if (!existingNotify
|
|
3785
|
+
if (!isOmxDispatcherNotifyCommand(existingNotify, pkgRoot)) {
|
|
2731
3786
|
return { notifyCommand: omxNotify };
|
|
2732
3787
|
}
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
const previousNotify = metadata.previousNotify;
|
|
2736
|
-
if (Array.isArray(previousNotify) &&
|
|
2737
|
-
previousNotify.every((item) => typeof item === "string")) {
|
|
2738
|
-
const sanitizedPreviousNotify = sanitizePreviousNotifyCommand(previousNotify, pkgRoot);
|
|
2739
|
-
if (!sanitizedPreviousNotify) {
|
|
2740
|
-
return { notifyCommand: omxNotify };
|
|
2741
|
-
}
|
|
2742
|
-
return {
|
|
2743
|
-
notifyCommand: dispatcherNotify,
|
|
2744
|
-
metadataPath,
|
|
2745
|
-
metadata: {
|
|
2746
|
-
managedBy: "oh-my-codex",
|
|
2747
|
-
version: 1,
|
|
2748
|
-
previousNotify: sanitizedPreviousNotify,
|
|
2749
|
-
omxNotify,
|
|
2750
|
-
dispatcherNotify,
|
|
2751
|
-
},
|
|
2752
|
-
};
|
|
2753
|
-
}
|
|
3788
|
+
if (!metadataSnapshot) {
|
|
3789
|
+
throw new Error(`Missing notification metadata snapshot for ${metadataPath}; refusing to plan from a newly captured baseline.`);
|
|
2754
3790
|
}
|
|
2755
|
-
|
|
2756
|
-
|
|
3791
|
+
const metadata = parseNotifyMetadataSnapshot(metadataSnapshot, `notification metadata ${metadataPath}`);
|
|
3792
|
+
const previousNotify = metadata?.previousNotify;
|
|
3793
|
+
if (Array.isArray(previousNotify) &&
|
|
3794
|
+
previousNotify.every((item) => typeof item === "string")) {
|
|
3795
|
+
const sanitizedPreviousNotify = sanitizePreviousNotifyCommand(previousNotify, pkgRoot);
|
|
3796
|
+
if (!sanitizedPreviousNotify) {
|
|
3797
|
+
return { notifyCommand: omxNotify, metadataPath, metadataSnapshot };
|
|
3798
|
+
}
|
|
3799
|
+
return {
|
|
3800
|
+
notifyCommand: dispatcherNotify,
|
|
3801
|
+
metadataPath,
|
|
3802
|
+
metadataSnapshot,
|
|
3803
|
+
metadata: {
|
|
3804
|
+
managedBy: "oh-my-codex",
|
|
3805
|
+
version: 1,
|
|
3806
|
+
previousNotify: sanitizedPreviousNotify,
|
|
3807
|
+
omxNotify,
|
|
3808
|
+
dispatcherNotify,
|
|
3809
|
+
},
|
|
3810
|
+
};
|
|
2757
3811
|
}
|
|
2758
|
-
return { notifyCommand: omxNotify };
|
|
3812
|
+
return { notifyCommand: omxNotify, metadataPath, metadataSnapshot };
|
|
2759
3813
|
}
|
|
2760
3814
|
return {
|
|
2761
3815
|
notifyCommand: dispatcherNotify,
|
|
@@ -2769,14 +3823,11 @@ async function buildNotifyMergePlan(existingConfig, pkgRoot, codexHomeDir, scope
|
|
|
2769
3823
|
},
|
|
2770
3824
|
};
|
|
2771
3825
|
}
|
|
2772
|
-
async function
|
|
2773
|
-
const
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
const hadLegacyTeamRunTable = hasLegacyOmxTeamRunTable(existing);
|
|
2777
|
-
const currentModel = getRootModelName(existing);
|
|
3826
|
+
async function planManagedConfig(hooksPath, pkgRoot, sharedMcpRegistry, mcpMode, preserveExistingFirstPartyMcp, scope, codexHomeDir, options) {
|
|
3827
|
+
const existingConfig = options.existingConfig ?? "";
|
|
3828
|
+
const hadLegacyTeamRunTable = hasLegacyOmxTeamRunTable(existingConfig);
|
|
3829
|
+
const currentModel = getRootModelName(existingConfig);
|
|
2778
3830
|
let modelOverride;
|
|
2779
|
-
const omxManagesTui = true;
|
|
2780
3831
|
if (currentModel && LEGACY_SETUP_MODELS.has(currentModel)) {
|
|
2781
3832
|
const shouldPrompt = typeof options.modelUpgradePrompt === "function" ||
|
|
2782
3833
|
(process.stdin.isTTY && process.stdout.isTTY);
|
|
@@ -2789,12 +3840,15 @@ async function updateManagedConfig(configPath, hooksPath, pkgRoot, sharedMcpRegi
|
|
|
2789
3840
|
}
|
|
2790
3841
|
}
|
|
2791
3842
|
}
|
|
2792
|
-
const notifyPlan = await buildNotifyMergePlan(
|
|
2793
|
-
const finalConfig = buildMergedConfig(
|
|
2794
|
-
includeTui:
|
|
3843
|
+
const notifyPlan = await buildNotifyMergePlan(existingConfig, pkgRoot, codexHomeDir, scope, options.notifyMetadataSnapshot);
|
|
3844
|
+
const finalConfig = buildMergedConfig(existingConfig, pkgRoot, {
|
|
3845
|
+
includeTui: true,
|
|
2795
3846
|
codexHooksFile: hooksPath,
|
|
2796
3847
|
codexHomeDir,
|
|
2797
|
-
hookCommandPlatform:
|
|
3848
|
+
hookCommandPlatform: options.hookCommandPlatform,
|
|
3849
|
+
managedHookTrustState: options.managedHookTrustState,
|
|
3850
|
+
priorManagedHookTrustState: options.priorManagedHookTrustState,
|
|
3851
|
+
legacyHookTrustState: options.legacyHookTrustState,
|
|
2798
3852
|
codexHookFeatureFlag: options.codexHookFeatureFlag,
|
|
2799
3853
|
modelOverride,
|
|
2800
3854
|
sharedMcpServers: sharedMcpRegistry.servers,
|
|
@@ -2806,65 +3860,14 @@ async function updateManagedConfig(configPath, hooksPath, pkgRoot, sharedMcpRegi
|
|
|
2806
3860
|
includeFirstPartyMcp: mcpMode === "compat",
|
|
2807
3861
|
preserveExistingFirstPartyMcp,
|
|
2808
3862
|
});
|
|
2809
|
-
const changed = existing !== finalConfig;
|
|
2810
|
-
if (!changed) {
|
|
2811
|
-
summary.unchanged += 1;
|
|
2812
|
-
return {
|
|
2813
|
-
finalConfig,
|
|
2814
|
-
omxManagesTui,
|
|
2815
|
-
repairedLegacyTeamRunTable: false,
|
|
2816
|
-
};
|
|
2817
|
-
}
|
|
2818
|
-
if (await ensureBackup(configPath, existsSync(configPath), backupContext, options)) {
|
|
2819
|
-
summary.backedUp += 1;
|
|
2820
|
-
}
|
|
2821
|
-
if (!options.dryRun) {
|
|
2822
|
-
await writeFile(configPath, finalConfig);
|
|
2823
|
-
if (notifyPlan.metadataPath && notifyPlan.metadata) {
|
|
2824
|
-
await mkdir(dirname(notifyPlan.metadataPath), { recursive: true });
|
|
2825
|
-
await writeFile(notifyPlan.metadataPath, JSON.stringify(notifyPlan.metadata, null, 2) + "\n");
|
|
2826
|
-
}
|
|
2827
|
-
}
|
|
2828
|
-
if (options.verbose &&
|
|
2829
|
-
modelOverride &&
|
|
2830
|
-
currentModel &&
|
|
2831
|
-
currentModel !== modelOverride) {
|
|
2832
|
-
console.log(` ${options.dryRun ? "would update" : "updated"} root model from ${currentModel} to ${modelOverride}`);
|
|
2833
|
-
}
|
|
2834
|
-
summary.updated += 1;
|
|
2835
|
-
if (options.verbose) {
|
|
2836
|
-
console.log(` ${options.dryRun ? "would update" : "updated"} config ${configPath}`);
|
|
2837
|
-
}
|
|
2838
3863
|
return {
|
|
2839
3864
|
finalConfig,
|
|
2840
|
-
|
|
3865
|
+
currentModel,
|
|
3866
|
+
modelOverride,
|
|
3867
|
+
notifyPlan,
|
|
2841
3868
|
repairedLegacyTeamRunTable: hadLegacyTeamRunTable && !hasLegacyOmxTeamRunTable(finalConfig),
|
|
2842
3869
|
};
|
|
2843
3870
|
}
|
|
2844
|
-
async function syncSharedMcpRegistryIntoConfig(configPath, sharedMcpRegistry, summary, backupContext, options) {
|
|
2845
|
-
if (sharedMcpRegistry.servers.length === 0) {
|
|
2846
|
-
return existsSync(configPath) ? await readFile(configPath, "utf-8") : "";
|
|
2847
|
-
}
|
|
2848
|
-
const existing = existsSync(configPath)
|
|
2849
|
-
? await readFile(configPath, "utf-8")
|
|
2850
|
-
: "";
|
|
2851
|
-
const finalConfig = mergeSharedMcpRegistryBlock(existing, sharedMcpRegistry.servers, sharedMcpRegistry.sourcePath);
|
|
2852
|
-
if (existing === finalConfig) {
|
|
2853
|
-
summary.unchanged += 1;
|
|
2854
|
-
return finalConfig;
|
|
2855
|
-
}
|
|
2856
|
-
if (await ensureBackup(configPath, existsSync(configPath), backupContext, options)) {
|
|
2857
|
-
summary.backedUp += 1;
|
|
2858
|
-
}
|
|
2859
|
-
if (!options.dryRun) {
|
|
2860
|
-
await writeFile(configPath, finalConfig);
|
|
2861
|
-
}
|
|
2862
|
-
summary.updated += 1;
|
|
2863
|
-
if (options.verbose) {
|
|
2864
|
-
console.log(` ${options.dryRun ? "would sync" : "synced"} shared MCP registry servers into ${configPath}`);
|
|
2865
|
-
}
|
|
2866
|
-
return finalConfig;
|
|
2867
|
-
}
|
|
2868
3871
|
function getClaudeCodeSettingsPath(homeDir = homedir()) {
|
|
2869
3872
|
return join(homeDir, ".claude", "settings.json");
|
|
2870
3873
|
}
|