oh-my-codex 0.20.1 → 0.20.3
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 +7 -6
- package/Cargo.toml +1 -1
- package/README.md +36 -1
- package/crates/omx-runtime/Cargo.toml +1 -0
- package/crates/omx-runtime/src/main.rs +25 -2
- package/crates/omx-runtime/tests/execution.rs +219 -0
- package/crates/omx-runtime-core/src/dispatch.rs +96 -10
- package/crates/omx-runtime-core/src/engine.rs +839 -29
- package/crates/omx-runtime-core/src/lib.rs +8 -0
- package/dist/adapt/__tests__/hermes.test.js.map +1 -1
- package/dist/adapt/contracts.d.ts.map +1 -1
- package/dist/agents/__tests__/definitions.test.js +81 -0
- package/dist/agents/__tests__/definitions.test.js.map +1 -1
- package/dist/agents/__tests__/native-config.test.js +25 -0
- package/dist/agents/__tests__/native-config.test.js.map +1 -1
- package/dist/agents/native-config.d.ts +2 -1
- package/dist/agents/native-config.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 +6 -0
- package/dist/auth/hotswap.d.ts.map +1 -1
- package/dist/auth/hotswap.js +102 -19
- 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 +5 -5
- 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 +60 -6
- 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 +80 -0
- package/dist/cli/__tests__/codex-plugin-layout.test.js.map +1 -1
- package/dist/cli/__tests__/doctor-context-window-warning.test.js.map +1 -1
- package/dist/cli/__tests__/doctor-invalid-config.test.js +47 -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 +752 -15
- package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
- package/dist/cli/__tests__/exec.test.js +136 -0
- 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 +473 -14
- package/dist/cli/__tests__/index.test.js.map +1 -1
- package/dist/cli/__tests__/launch-fallback.test.js +336 -4
- package/dist/cli/__tests__/launch-fallback.test.js.map +1 -1
- package/dist/cli/__tests__/mcp-parity.test.js +87 -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 +206 -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 +72 -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 +55 -0
- package/dist/cli/__tests__/ralplan.test.js.map +1 -0
- package/dist/cli/__tests__/resume.test.js +17 -11
- 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 +25 -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 +77 -3
- 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 +2579 -294
- 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 +33 -2
- package/dist/cli/__tests__/setup-refresh.test.js.map +1 -1
- package/dist/cli/__tests__/setup-scope.test.js +46 -3
- 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 +105 -7
- 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 +1817 -24
- 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 +2 -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 +21 -0
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/doctor.js +607 -130
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/explore.js.map +1 -1
- package/dist/cli/index.d.ts +14 -3
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +245 -57
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/mcp-parity.js +2 -2
- package/dist/cli/mcp-parity.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 +29 -0
- package/dist/cli/native-hook-claim-journal.d.ts.map +1 -0
- package/dist/cli/native-hook-claim-journal.js +252 -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 +10 -0
- package/dist/cli/ralplan.d.ts.map +1 -0
- package/dist/cli/ralplan.js +110 -0
- package/dist/cli/ralplan.js.map +1 -0
- package/dist/cli/session-search.d.ts +5 -0
- package/dist/cli/session-search.d.ts.map +1 -1
- package/dist/cli/session-search.js +87 -0
- package/dist/cli/session-search.js.map +1 -1
- 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 +58 -0
- package/dist/cli/setup.d.ts.map +1 -1
- package/dist/cli/setup.js +1450 -398
- 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 +17 -0
- package/dist/cli/uninstall.d.ts.map +1 -1
- package/dist/cli/uninstall.js +986 -95
- 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 +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 +1179 -32
- package/dist/config/__tests__/generator-idempotent.test.js.map +1 -1
- package/dist/config/__tests__/generator-notify.test.js +6 -1
- package/dist/config/__tests__/generator-notify.test.js.map +1 -1
- package/dist/config/__tests__/models.test.js +67 -3
- package/dist/config/__tests__/models.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 +23 -34
- package/dist/config/generator.d.ts.map +1 -1
- package/dist/config/generator.js +1480 -246
- package/dist/config/generator.js.map +1 -1
- package/dist/config/mcp-registry.js.map +1 -1
- package/dist/config/models.d.ts +14 -4
- package/dist/config/models.d.ts.map +1 -1
- package/dist/config/models.js +15 -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 +1376 -6
- package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-fallback-watcher.test.js +252 -543
- package/dist/hooks/__tests__/notify-fallback-watcher.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-all-workers-idle.test.js +38 -4
- package/dist/hooks/__tests__/notify-hook-all-workers-idle.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js +765 -10
- 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 +531 -12
- package/dist/hooks/__tests__/notify-hook-team-dispatch.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js +163 -24
- 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 +482 -32
- package/dist/hooks/__tests__/notify-hook-team-tmux-guard.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js +68 -1
- package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-tmux-scrollback.test.js +7 -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 +43 -4
- 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 +878 -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 +163 -34
- package/dist/hooks/session.d.ts.map +1 -1
- package/dist/hooks/session.js +1395 -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 +153 -1
- package/dist/leader/__tests__/contract.test.js.map +1 -1
- package/dist/leader/contract.d.ts +42 -5
- package/dist/leader/contract.d.ts.map +1 -1
- package/dist/leader/contract.js +184 -3
- package/dist/leader/contract.js.map +1 -1
- package/dist/mcp/__tests__/bootstrap.test.js.map +1 -1
- package/dist/mcp/__tests__/code-intel-server.test.js +31 -3
- package/dist/mcp/__tests__/code-intel-server.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/__tests__/wiki-server.test.js +18 -0
- package/dist/mcp/__tests__/wiki-server.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 +13 -3
- 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 +34 -23
- package/dist/mcp/wiki-server.d.ts.map +1 -1
- package/dist/mcp/wiki-server.js +9 -2
- 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/__tests__/base-tmux-pane.test.js +4 -4
- package/dist/modes/__tests__/base-tmux-pane.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.d.ts.map +1 -1
- package/dist/notifications/temp-contract.js +4 -0
- 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 +4 -4
- package/dist/pipeline/__tests__/orchestrator.test.js.map +1 -1
- package/dist/pipeline/__tests__/stages.test.js +16 -13
- 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 +527 -8
- package/dist/ralplan/__tests__/consensus-gate.test.js.map +1 -1
- package/dist/ralplan/__tests__/documented-leader-preflight.test.d.ts +2 -0
- package/dist/ralplan/__tests__/documented-leader-preflight.test.d.ts.map +1 -0
- package/dist/ralplan/__tests__/documented-leader-preflight.test.js +46 -0
- package/dist/ralplan/__tests__/documented-leader-preflight.test.js.map +1 -0
- package/dist/ralplan/__tests__/runtime.test.js +116 -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 +173 -70
- package/dist/ralplan/consensus-gate.js.map +1 -1
- package/dist/ralplan/documented-leader-preflight.d.ts +34 -0
- package/dist/ralplan/documented-leader-preflight.d.ts.map +1 -0
- package/dist/ralplan/documented-leader-preflight.js +58 -0
- package/dist/ralplan/documented-leader-preflight.js.map +1 -0
- package/dist/ralplan/runtime.d.ts +3 -2
- package/dist/ralplan/runtime.d.ts.map +1 -1
- package/dist/ralplan/runtime.js +12 -9
- package/dist/ralplan/runtime.js.map +1 -1
- package/dist/runtime/__tests__/bridge.test.js +42 -1
- package/dist/runtime/__tests__/bridge.test.js.map +1 -1
- package/dist/runtime/bridge.d.ts +12 -0
- package/dist/runtime/bridge.d.ts.map +1 -1
- package/dist/runtime/bridge.js +52 -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 +6657 -995
- 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__/reasoning-artifact-contract.test.d.ts +2 -0
- package/dist/scripts/__tests__/reasoning-artifact-contract.test.d.ts.map +1 -0
- package/dist/scripts/__tests__/reasoning-artifact-contract.test.js +108 -0
- package/dist/scripts/__tests__/reasoning-artifact-contract.test.js.map +1 -0
- 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 +78 -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 +55 -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 +1100 -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 +4 -1
- package/dist/scripts/codex-native-hook.d.ts.map +1 -1
- package/dist/scripts/codex-native-hook.js +11577 -1184
- 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 +218 -138
- 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 +112 -15
- 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 +10 -1
- package/dist/scripts/notify-hook/team-dispatch.d.ts.map +1 -1
- package/dist/scripts/notify-hook/team-dispatch.js +234 -79
- 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-leader-nudge.d.ts.map +1 -1
- package/dist/scripts/notify-hook/team-leader-nudge.js +39 -13
- package/dist/scripts/notify-hook/team-leader-nudge.js.map +1 -1
- package/dist/scripts/notify-hook/team-tmux-guard.d.ts +16 -8
- package/dist/scripts/notify-hook/team-tmux-guard.d.ts.map +1 -1
- package/dist/scripts/notify-hook/team-tmux-guard.js +365 -44
- package/dist/scripts/notify-hook/team-tmux-guard.js.map +1 -1
- 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-stop.d.ts.map +1 -1
- package/dist/scripts/notify-hook/team-worker-stop.js +19 -18
- package/dist/scripts/notify-hook/team-worker-stop.js.map +1 -1
- package/dist/scripts/notify-hook/team-worker.d.ts +5 -2
- package/dist/scripts/notify-hook/team-worker.d.ts.map +1 -1
- package/dist/scripts/notify-hook/team-worker.js +37 -28
- package/dist/scripts/notify-hook/team-worker.js.map +1 -1
- 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 +53 -18
- 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 +267 -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 +927 -5
- package/dist/scripts/smoke-packed-install.d.ts.map +1 -1
- package/dist/scripts/smoke-packed-install.js +4317 -13
- 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__/mode-state-context.test.js +54 -0
- package/dist/state/__tests__/mode-state-context.test.js.map +1 -1
- package/dist/state/__tests__/operations.test.js +66 -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.d.ts +5 -0
- package/dist/state/mode-state-context.d.ts.map +1 -1
- package/dist/state/mode-state-context.js +59 -0
- 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__/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 +52 -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/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 +91 -0
- package/dist/subagents/tracker.d.ts.map +1 -1
- package/dist/subagents/tracker.js +806 -62
- package/dist/subagents/tracker.js.map +1 -1
- package/dist/team/__tests__/api-interop.test.js +91 -1
- package/dist/team/__tests__/api-interop.test.js.map +1 -1
- package/dist/team/__tests__/approved-execution.test.js.map +1 -1
- package/dist/team/__tests__/delivery-e2e-smoke.test.js +52 -10
- package/dist/team/__tests__/delivery-e2e-smoke.test.js.map +1 -1
- package/dist/team/__tests__/idle-nudge.test.js +45 -15
- package/dist/team/__tests__/idle-nudge.test.js.map +1 -1
- package/dist/team/__tests__/mcp-comm.test.js +93 -27
- package/dist/team/__tests__/mcp-comm.test.js.map +1 -1
- package/dist/team/__tests__/model-contract.test.js +341 -4
- 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-cli.test.js +9 -0
- package/dist/team/__tests__/runtime-cli.test.js.map +1 -1
- package/dist/team/__tests__/runtime.test.js +3624 -355
- package/dist/team/__tests__/runtime.test.js.map +1 -1
- package/dist/team/__tests__/scaling.test.js +2516 -118
- 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__/state.test.js +804 -36
- package/dist/team/__tests__/state.test.js.map +1 -1
- package/dist/team/__tests__/team-ops-contract.test.js +6 -0
- package/dist/team/__tests__/team-ops-contract.test.js.map +1 -1
- package/dist/team/__tests__/tmux-session.test.js +3664 -969
- 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/__tests__/worker-runtime-identity.test.js +38 -2
- package/dist/team/__tests__/worker-runtime-identity.test.js.map +1 -1
- package/dist/team/__tests__/worktree.test.js +6 -0
- package/dist/team/__tests__/worktree.test.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 +15 -9
- 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/exact-pane.d.ts +23 -0
- package/dist/team/exact-pane.d.ts.map +1 -0
- package/dist/team/exact-pane.js +133 -0
- package/dist/team/exact-pane.js.map +1 -0
- 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 +12 -1
- package/dist/team/idle-nudge.d.ts.map +1 -1
- package/dist/team/idle-nudge.js +41 -14
- package/dist/team/idle-nudge.js.map +1 -1
- package/dist/team/model-contract.d.ts +18 -2
- package/dist/team/model-contract.d.ts.map +1 -1
- package/dist/team/model-contract.js +314 -35
- 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 +1 -0
- package/dist/team/runtime.d.ts.map +1 -1
- package/dist/team/runtime.js +1326 -231
- package/dist/team/runtime.js.map +1 -1
- package/dist/team/scaling.d.ts +22 -8
- package/dist/team/scaling.d.ts.map +1 -1
- package/dist/team/scaling.js +1463 -376
- package/dist/team/scaling.js.map +1 -1
- package/dist/team/state/dispatch-lock.js.map +1 -1
- package/dist/team/state/dispatch.d.ts.map +1 -1
- package/dist/team/state/dispatch.js +26 -8
- package/dist/team/state/dispatch.js.map +1 -1
- package/dist/team/state/events.js.map +1 -1
- package/dist/team/state/locks.d.ts.map +1 -1
- package/dist/team/state/locks.js +61 -13
- package/dist/team/state/locks.js.map +1 -1
- package/dist/team/state/tasks.d.ts.map +1 -1
- package/dist/team/state/tasks.js +4 -3
- package/dist/team/state/tasks.js.map +1 -1
- package/dist/team/state-root.js.map +1 -1
- package/dist/team/state.d.ts +67 -1
- package/dist/team/state.d.ts.map +1 -1
- package/dist/team/state.js +636 -129
- 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/team-ops.d.ts +5 -0
- package/dist/team/team-ops.d.ts.map +1 -1
- package/dist/team/team-ops.js +4 -0
- package/dist/team/team-ops.js.map +1 -1
- package/dist/team/tmux-session.d.ts +125 -23
- package/dist/team/tmux-session.d.ts.map +1 -1
- package/dist/team/tmux-session.js +1340 -333
- package/dist/team/tmux-session.js.map +1 -1
- package/dist/team/worker-bootstrap.js.map +1 -1
- package/dist/team/worktree.d.ts.map +1 -1
- package/dist/team/worktree.js +4 -0
- 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__/file-durability.test.d.ts +2 -0
- package/dist/utils/__tests__/file-durability.test.d.ts.map +1 -0
- package/dist/utils/__tests__/file-durability.test.js +57 -0
- package/dist/utils/__tests__/file-durability.test.js.map +1 -0
- package/dist/utils/agents-model-table.js.map +1 -1
- package/dist/utils/file-durability.d.ts +15 -0
- package/dist/utils/file-durability.d.ts.map +1 -0
- package/dist/utils/file-durability.js +37 -0
- package/dist/utils/file-durability.js.map +1 -0
- 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 +3 -0
- 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.d.ts +4 -1
- package/dist/wiki/lint.d.ts.map +1 -1
- package/dist/wiki/lint.js +3 -2
- 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/hooks/codex-native-hook.mjs +24 -0
- package/plugins/oh-my-codex/skills/omx-setup/SKILL.md +10 -5
- package/plugins/oh-my-codex/skills/ralph/SKILL.md +6 -2
- package/plugins/oh-my-codex/skills/ralplan/SKILL.md +6 -2
- package/plugins/oh-my-codex/skills/team/SKILL.md +13 -9
- package/skills/omx-setup/SKILL.md +10 -5
- package/skills/ralph/SKILL.md +6 -2
- package/skills/ralplan/SKILL.md +6 -2
- package/skills/team/SKILL.md +13 -9
- package/src/scripts/__tests__/codex-native-hook.test.ts +20048 -10827
- 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__/reasoning-artifact-contract.test.ts +129 -0
- package/src/scripts/__tests__/role-intent-bootstrap-e2e-3181.test.ts +78 -0
- package/src/scripts/__tests__/role-intent-durable-recovery-3181.test.ts +60 -0
- package/src/scripts/__tests__/smoke-packed-install.test.ts +1309 -4
- package/src/scripts/codex-native-hook.ts +11429 -1163
- package/src/scripts/notify-fallback-delivery.ts +416 -0
- package/src/scripts/notify-fallback-watcher.ts +216 -155
- package/src/scripts/notify-hook/auto-nudge.ts +113 -14
- 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/team-dispatch.ts +242 -75
- package/src/scripts/notify-hook/team-leader-nudge.ts +40 -18
- package/src/scripts/notify-hook/team-tmux-guard.ts +366 -39
- package/src/scripts/notify-hook/team-worker-stop.ts +20 -16
- package/src/scripts/notify-hook/team-worker.ts +39 -26
- package/src/scripts/notify-hook/tmux-injection.ts +66 -23
- package/src/scripts/notify-hook/visual-verdict.ts +22 -12
- package/src/scripts/notify-hook.ts +336 -90
- package/src/scripts/smoke-packed-install.ts +5154 -149
package/dist/cli/setup.js
CHANGED
|
@@ -2,17 +2,19 @@
|
|
|
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 as clearNativeHookClaimJournalWithDurability, createNativeHookClaimJournalDurability, persistNativeHookClaimJournal as persistNativeHookClaimJournalWithDurability, recoverNativeHookClaimJournal, syncNativeHookClaimParent as syncNativeHookClaimParentWithDurability, restoreNativeHookClaimNoClobber as restoreNativeHookClaimNoClobberWithDurability, } from "./native-hook-claim-journal.js";
|
|
13
14
|
import { codexHome, codexConfigPath, codexPromptsDir, codexAgentsDir, userSkillsDir, omxStateDir, detectLegacySkillRootOverlap, omxPlansDir, omxLogsDir, } from "../utils/paths.js";
|
|
15
|
+
import { emitDegradedDurabilityWarning, recordRegularFileSyncOutcome, syncRegularFile, } from "../utils/file-durability.js";
|
|
14
16
|
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 {
|
|
17
|
+
import { buildManagedCodexNativeHookWindowsShimContent, buildManagedCodexNativeHookWindowsShimPath, planManagedCodexHooksMerge, planManagedCodexHooksRemoval, classifyManagedCodexNativeHookWindowsShimOwnership, ManagedCodexHooksPlanError, validateCodexHooksConfigStrict, } from "../config/codex-hooks.js";
|
|
16
18
|
import { getLegacyUnifiedMcpRegistryCandidate, getUnifiedMcpRegistryCandidates, loadUnifiedMcpRegistry, planClaudeCodeMcpSettingsSync, } from "../config/mcp-registry.js";
|
|
17
19
|
import { generateAgentToml } from "../agents/native-config.js";
|
|
18
20
|
import { AGENT_DEFINITIONS } from "../agents/definitions.js";
|
|
@@ -25,7 +27,7 @@ import { DEFAULT_FRONTIER_MODEL } from "../config/models.js";
|
|
|
25
27
|
import { teamModeEnabled, } from "../config/team-mode.js";
|
|
26
28
|
import { addGeneratedAgentsMarker, hasOmxAgentsContract, hasOmxManagedAgentsSections, isOmxGeneratedAgentsMd, preserveUserOmxPolicyBlocks, upsertManagedAgentsBlock, } from "../utils/agents-md.js";
|
|
27
29
|
import { DEFAULT_HUD_CONFIG } from "../hud/types.js";
|
|
28
|
-
import { SETUP_INSTALL_MODES, SETUP_MCP_MODES, SETUP_SCOPES, getSetupScopeFilePath, readPersistedSetupPreferences, } from "./setup-preferences.js";
|
|
30
|
+
import { SETUP_INSTALL_MODES, SETUP_MCP_MODES, SETUP_SCOPES, getSetupScopeFilePath, readPersistedSetupPreferences, resolvePersistedSetupMergeAgents, writePersistedSetupPreferences, } from "./setup-preferences.js";
|
|
29
31
|
import { OMX_LOCAL_MARKETPLACE_NAME, OMX_PLUGIN_NAME, materializePackagedOmxPluginCache, resolvePackagedOmxMarketplace, upsertLocalOmxMarketplaceRegistration, upsertLocalOmxPluginEnablement, upsertLocalOmxPluginMcpServerEnablement, hasLocalOmxPluginMcpServerRegistrations, pluginHookCacheMatchesPackaged, } from "./plugin-marketplace.js";
|
|
30
32
|
import { resolveCodexHookFeatureSupportForCli } from "./codex-feature-probe.js";
|
|
31
33
|
async function resolveStatusLinePresetForSetup(projectRoot, options) {
|
|
@@ -195,66 +197,1010 @@ function getBackupContext(scope, projectRoot) {
|
|
|
195
197
|
baseRoot: homedir(),
|
|
196
198
|
};
|
|
197
199
|
}
|
|
198
|
-
function
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
200
|
+
function logManagedCodexHooksPlanDiagnostics(diagnostics, options) {
|
|
201
|
+
if (!options.verbose || !diagnostics)
|
|
202
|
+
return;
|
|
203
|
+
for (const diagnostic of diagnostics) {
|
|
204
|
+
console.log(` hook plan diagnostic: ${diagnostic.message}`);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
let nativeHookTransactionFailureInjector;
|
|
208
|
+
let nativeHookTransactionSequence = 0;
|
|
209
|
+
let nativeHookTransactionPlatformOverride;
|
|
210
|
+
let nativeHookTransactionTemporaryPathOverride;
|
|
211
|
+
let setupLatePhaseFailureInjector;
|
|
212
|
+
let nativeHookTransactionRegularFileSyncOverride;
|
|
213
|
+
let nativeHookClaimJournalDurabilityOverride;
|
|
214
|
+
/** @internal Test seam for deterministic atomic-write and rollback coverage. */
|
|
215
|
+
export function setNativeHookTransactionFailureInjectorForTest(injector) {
|
|
216
|
+
const previous = nativeHookTransactionFailureInjector;
|
|
217
|
+
nativeHookTransactionFailureInjector = injector;
|
|
218
|
+
return () => {
|
|
219
|
+
nativeHookTransactionFailureInjector = previous;
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
/** @internal Test seam proving native artifacts commit only after later setup phases. */
|
|
223
|
+
export function setSetupLatePhaseFailureInjectorForTest(injector) {
|
|
224
|
+
const previous = setupLatePhaseFailureInjector;
|
|
225
|
+
setupLatePhaseFailureInjector = injector;
|
|
226
|
+
return () => {
|
|
227
|
+
setupLatePhaseFailureInjector = previous;
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
/** @internal Test seam for deterministic Windows transaction coverage. */
|
|
231
|
+
export function setNativeHookTransactionPlatformForTest(platform) {
|
|
232
|
+
const previous = nativeHookTransactionPlatformOverride;
|
|
233
|
+
nativeHookTransactionPlatformOverride = platform;
|
|
234
|
+
return () => {
|
|
235
|
+
nativeHookTransactionPlatformOverride = previous;
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
/** @internal Test seam for deterministic native transaction temporary-path coverage. */
|
|
239
|
+
export function setNativeHookTransactionTemporaryPathForTest(resolver) {
|
|
240
|
+
const previous = nativeHookTransactionTemporaryPathOverride;
|
|
241
|
+
nativeHookTransactionTemporaryPathOverride = resolver;
|
|
242
|
+
return () => {
|
|
243
|
+
nativeHookTransactionTemporaryPathOverride = previous;
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
/** @internal Test seam for deterministic regular-file fsync coverage. */
|
|
247
|
+
export function setNativeHookTransactionRegularFileSyncForTest(sync) {
|
|
248
|
+
const previous = nativeHookTransactionRegularFileSyncOverride;
|
|
249
|
+
nativeHookTransactionRegularFileSyncOverride = sync;
|
|
250
|
+
return () => {
|
|
251
|
+
nativeHookTransactionRegularFileSyncOverride = previous;
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
/** @internal Test seam for deterministic claim-journal durability coverage. */
|
|
255
|
+
export function setNativeHookClaimJournalDurabilityForTest(durability) {
|
|
256
|
+
const previous = nativeHookClaimJournalDurabilityOverride;
|
|
257
|
+
nativeHookClaimJournalDurabilityOverride = durability;
|
|
258
|
+
return () => {
|
|
259
|
+
nativeHookClaimJournalDurabilityOverride = previous;
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
function nativeHookPlatform() {
|
|
263
|
+
return nativeHookTransactionPlatformOverride ?? process.platform;
|
|
264
|
+
}
|
|
265
|
+
function nativeHookClaimJournalDurability() {
|
|
266
|
+
return nativeHookClaimJournalDurabilityOverride
|
|
267
|
+
?? createNativeHookClaimJournalDurability(nativeHookPlatform());
|
|
268
|
+
}
|
|
269
|
+
async function clearNativeHookClaimJournal(root) {
|
|
270
|
+
return clearNativeHookClaimJournalWithDurability(root, nativeHookClaimJournalDurability());
|
|
271
|
+
}
|
|
272
|
+
async function persistNativeHookClaimJournal(root, entry) {
|
|
273
|
+
return persistNativeHookClaimJournalWithDurability(root, entry, nativeHookClaimJournalDurability());
|
|
274
|
+
}
|
|
275
|
+
async function restoreNativeHookClaimNoClobber(claimPath, destinationPath) {
|
|
276
|
+
return restoreNativeHookClaimNoClobberWithDurability(claimPath, destinationPath, nativeHookClaimJournalDurability());
|
|
277
|
+
}
|
|
278
|
+
async function syncNativeHookClaimParent(path) {
|
|
279
|
+
return syncNativeHookClaimParentWithDurability(path, nativeHookClaimJournalDurability());
|
|
280
|
+
}
|
|
281
|
+
async function syncNativeHookRegularFile(handle) {
|
|
282
|
+
const platform = nativeHookPlatform();
|
|
283
|
+
if (nativeHookTransactionRegularFileSyncOverride) {
|
|
284
|
+
return syncRegularFile({ sync: () => nativeHookTransactionRegularFileSyncOverride(platform) }, platform);
|
|
285
|
+
}
|
|
286
|
+
return syncRegularFile(handle, platform);
|
|
287
|
+
}
|
|
288
|
+
function hookTransactionBytesEqual(left, right) {
|
|
289
|
+
return left === null || right === null ? left === right : left.equals(right);
|
|
290
|
+
}
|
|
291
|
+
function nativeHookTransactionTopologyEqual(left, right) {
|
|
292
|
+
return left.kind === "absent" || right.kind === "absent"
|
|
293
|
+
? left.kind === right.kind
|
|
294
|
+
: left.mode === right.mode;
|
|
295
|
+
}
|
|
296
|
+
function isMissingPathError(error) {
|
|
297
|
+
return (typeof error === "object" &&
|
|
298
|
+
error !== null &&
|
|
299
|
+
"code" in error &&
|
|
300
|
+
error.code === "ENOENT");
|
|
301
|
+
}
|
|
302
|
+
function nativeHookTransactionArtifactAncestorPaths(controlledRoot, artifactPaths) {
|
|
303
|
+
const paths = new Set([controlledRoot]);
|
|
304
|
+
for (const artifactPath of artifactPaths) {
|
|
305
|
+
const parentPath = dirname(artifactPath);
|
|
306
|
+
const fromControlledRoot = relative(controlledRoot, parentPath);
|
|
307
|
+
if (fromControlledRoot === ".." ||
|
|
308
|
+
fromControlledRoot.startsWith(`..${sep}`) ||
|
|
309
|
+
isAbsolute(fromControlledRoot)) {
|
|
310
|
+
continue;
|
|
311
|
+
}
|
|
312
|
+
let ancestorPath = controlledRoot;
|
|
313
|
+
if (fromControlledRoot !== "") {
|
|
314
|
+
for (const component of fromControlledRoot.split(sep)) {
|
|
315
|
+
ancestorPath = join(ancestorPath, component);
|
|
316
|
+
paths.add(ancestorPath);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
return [...paths];
|
|
321
|
+
}
|
|
322
|
+
async function captureNativeHookTransactionAncestorSnapshots(ancestorPaths) {
|
|
323
|
+
const snapshots = [];
|
|
324
|
+
for (const path of ancestorPaths) {
|
|
325
|
+
let status;
|
|
326
|
+
try {
|
|
327
|
+
status = await lstat(path);
|
|
328
|
+
}
|
|
329
|
+
catch (error) {
|
|
330
|
+
if (isMissingPathError(error)) {
|
|
331
|
+
snapshots.push({ path, topology: { kind: "absent" } });
|
|
332
|
+
continue;
|
|
333
|
+
}
|
|
334
|
+
throw error;
|
|
335
|
+
}
|
|
336
|
+
if (status.isSymbolicLink()) {
|
|
337
|
+
throw new Error(`Refusing native hook transaction: ancestor ${path} is a symbolic link.`);
|
|
338
|
+
}
|
|
339
|
+
if (!status.isDirectory()) {
|
|
340
|
+
throw new Error(`Refusing native hook transaction: ancestor ${path} is not a directory.`);
|
|
341
|
+
}
|
|
342
|
+
snapshots.push({
|
|
343
|
+
path,
|
|
344
|
+
topology: { kind: "directory", device: status.dev, inode: status.ino },
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
return snapshots;
|
|
348
|
+
}
|
|
349
|
+
function nativeHookTransactionAncestorTopologyEqual(left, right) {
|
|
350
|
+
return left.kind === "absent" || right.kind === "absent"
|
|
351
|
+
? left.kind === right.kind
|
|
352
|
+
: left.device === right.device && left.inode === right.inode;
|
|
353
|
+
}
|
|
354
|
+
async function captureNativeHookTransactionAncestorPrecondition(controlledRoot, artifactPaths) {
|
|
355
|
+
const ancestorPaths = nativeHookTransactionArtifactAncestorPaths(controlledRoot, artifactPaths);
|
|
356
|
+
return {
|
|
357
|
+
controlledRoot,
|
|
358
|
+
ancestorPaths,
|
|
359
|
+
snapshots: await captureNativeHookTransactionAncestorSnapshots(ancestorPaths),
|
|
360
|
+
};
|
|
213
361
|
}
|
|
214
|
-
function
|
|
362
|
+
async function assertNativeHookTransactionAncestorPrecondition(precondition) {
|
|
363
|
+
const actual = await captureNativeHookTransactionAncestorSnapshots(precondition.ancestorPaths);
|
|
364
|
+
for (let index = 0; index < precondition.snapshots.length; index += 1) {
|
|
365
|
+
const expected = precondition.snapshots[index];
|
|
366
|
+
const current = actual[index];
|
|
367
|
+
if (!nativeHookTransactionAncestorTopologyEqual(current.topology, expected.topology)) {
|
|
368
|
+
throw new Error(`Native hook transaction precondition changed for ancestor ${expected.path}; refusing to mutate a stale topology.`);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
async function refreshNativeHookTransactionAncestorPrecondition(precondition, artifactPath) {
|
|
373
|
+
const allowedCreatedPaths = new Set(nativeHookTransactionArtifactAncestorPaths(precondition.controlledRoot, [artifactPath]));
|
|
374
|
+
const actual = await captureNativeHookTransactionAncestorSnapshots(precondition.ancestorPaths);
|
|
375
|
+
for (let index = 0; index < precondition.snapshots.length; index += 1) {
|
|
376
|
+
const expected = precondition.snapshots[index];
|
|
377
|
+
const current = actual[index];
|
|
378
|
+
if (nativeHookTransactionAncestorTopologyEqual(current.topology, expected.topology)) {
|
|
379
|
+
continue;
|
|
380
|
+
}
|
|
381
|
+
if (expected.topology.kind === "absent" &&
|
|
382
|
+
current.topology.kind === "directory" &&
|
|
383
|
+
allowedCreatedPaths.has(expected.path)) {
|
|
384
|
+
continue;
|
|
385
|
+
}
|
|
386
|
+
throw new Error(`Native hook transaction precondition changed for ancestor ${expected.path}; refusing to mutate a stale topology.`);
|
|
387
|
+
}
|
|
388
|
+
precondition.snapshots = actual;
|
|
389
|
+
}
|
|
390
|
+
function decodeNativeHookTransactionUtf8(bytes, label) {
|
|
215
391
|
try {
|
|
216
|
-
const
|
|
217
|
-
|
|
392
|
+
const content = new TextDecoder("utf-8", {
|
|
393
|
+
fatal: true,
|
|
394
|
+
ignoreBOM: true,
|
|
395
|
+
}).decode(bytes);
|
|
396
|
+
if (!Buffer.from(content, "utf-8").equals(bytes)) {
|
|
397
|
+
throw new Error("decoded text did not round-trip to the original bytes");
|
|
398
|
+
}
|
|
399
|
+
return content;
|
|
218
400
|
}
|
|
219
|
-
catch {
|
|
220
|
-
|
|
401
|
+
catch (error) {
|
|
402
|
+
throw new ManagedCodexHooksPlanError("invalid_document", `Refusing to read ${label}: invalid UTF-8 (${error instanceof Error ? error.message : String(error)}).`, { label });
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
async function captureNativeHookTransactionArtifact(path, label) {
|
|
406
|
+
let before;
|
|
407
|
+
try {
|
|
408
|
+
before = await lstat(path);
|
|
409
|
+
}
|
|
410
|
+
catch (error) {
|
|
411
|
+
if (isMissingPathError(error)) {
|
|
412
|
+
return { bytes: null, topology: { kind: "absent" } };
|
|
413
|
+
}
|
|
414
|
+
throw error;
|
|
415
|
+
}
|
|
416
|
+
if (!before.isFile() || before.nlink !== 1) {
|
|
417
|
+
throw new Error(`Refusing native hook transaction for ${label}: expected a single-link regular file or absence.`);
|
|
418
|
+
}
|
|
419
|
+
const bytes = await readFile(path);
|
|
420
|
+
const after = await lstat(path);
|
|
421
|
+
const beforeTopology = {
|
|
422
|
+
kind: "regular_file",
|
|
423
|
+
mode: before.mode & 0o7777,
|
|
424
|
+
};
|
|
425
|
+
const afterTopology = {
|
|
426
|
+
kind: "regular_file",
|
|
427
|
+
mode: after.mode & 0o7777,
|
|
428
|
+
};
|
|
429
|
+
if (!after.isFile() ||
|
|
430
|
+
after.nlink !== 1 ||
|
|
431
|
+
!nativeHookTransactionTopologyEqual(beforeTopology, afterTopology) ||
|
|
432
|
+
before.dev !== after.dev ||
|
|
433
|
+
before.ino !== after.ino ||
|
|
434
|
+
before.nlink !== after.nlink) {
|
|
435
|
+
throw new Error(`Refusing native hook transaction for ${label}: path changed while its snapshot was read.`);
|
|
436
|
+
}
|
|
437
|
+
return {
|
|
438
|
+
bytes,
|
|
439
|
+
topology: beforeTopology,
|
|
440
|
+
device: before.dev,
|
|
441
|
+
inode: before.ino,
|
|
442
|
+
links: before.nlink,
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
function nativeHookTransactionSnapshotsEqual(left, right) {
|
|
446
|
+
return (hookTransactionBytesEqual(left.bytes, right.bytes) &&
|
|
447
|
+
nativeHookTransactionTopologyEqual(left.topology, right.topology) &&
|
|
448
|
+
left.device === right.device &&
|
|
449
|
+
left.inode === right.inode &&
|
|
450
|
+
left.links === right.links);
|
|
451
|
+
}
|
|
452
|
+
function nativeHookTransactionSnapshotMatchesExpected(snapshot, expected) {
|
|
453
|
+
return (hookTransactionBytesEqual(snapshot.bytes, expected.bytes) &&
|
|
454
|
+
nativeHookTransactionTopologyEqual(snapshot.topology, expected.topology));
|
|
455
|
+
}
|
|
456
|
+
function nativeHookTransactionOutputTopology(before, after) {
|
|
457
|
+
if (after === null)
|
|
458
|
+
return { kind: "absent" };
|
|
459
|
+
return {
|
|
460
|
+
kind: "regular_file",
|
|
461
|
+
mode: before.topology.kind === "regular_file" ? before.topology.mode : 0o600,
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
function nativeHookTransactionArtifact(kind, path, label, before, after, hookPlatform) {
|
|
465
|
+
return hookTransactionBytesEqual(before.bytes, after)
|
|
466
|
+
? null
|
|
467
|
+
: {
|
|
468
|
+
kind,
|
|
469
|
+
path,
|
|
470
|
+
label,
|
|
471
|
+
before,
|
|
472
|
+
after,
|
|
473
|
+
afterTopology: nativeHookTransactionOutputTopology(before, after),
|
|
474
|
+
hookPlatform,
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
function nativeHookTransactionPrecondition(kind, path, label, before) {
|
|
478
|
+
return { kind, path, label, before };
|
|
479
|
+
}
|
|
480
|
+
function assertWindowsNativeHookShimOwnership(shimPath, before, expected) {
|
|
481
|
+
if (before === null)
|
|
482
|
+
return;
|
|
483
|
+
const ownership = classifyManagedCodexNativeHookWindowsShimOwnership(before, expected);
|
|
484
|
+
if (ownership === "current" || ownership === "historical")
|
|
485
|
+
return;
|
|
486
|
+
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.`);
|
|
487
|
+
}
|
|
488
|
+
function isWindowsShimReferenceRecord(value) {
|
|
489
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
490
|
+
}
|
|
491
|
+
function normalizeWindowsShimReferencePath(path) {
|
|
492
|
+
const windowsPath = path.replace(/\//g, "\\");
|
|
493
|
+
if (windowsPath.length === 0 ||
|
|
494
|
+
/[\0-\x1f\x7f-\x9f`<>"|?*]/.test(windowsPath) ||
|
|
495
|
+
/^\\\\\.(?:\\|$)/.test(windowsPath)) {
|
|
496
|
+
return null;
|
|
497
|
+
}
|
|
498
|
+
const isDriveAbsolute = /^[A-Za-z]:\\(?:[^\\:]+(?:\\[^\\:]+)*)?$/.test(windowsPath);
|
|
499
|
+
const isUncAbsolute = /^\\\\[^\\:]+\\[^\\:]+(?:\\[^\\:]+)*$/.test(windowsPath);
|
|
500
|
+
if (!isDriveAbsolute && !isUncAbsolute)
|
|
501
|
+
return null;
|
|
502
|
+
return win32.normalize(windowsPath).toLowerCase();
|
|
503
|
+
}
|
|
504
|
+
function windowsShimPathBasename(path) {
|
|
505
|
+
const name = win32.basename(path.replace(/\//g, "\\")).replace(/[. ]+$/, "");
|
|
506
|
+
return name.length > 0 ? name.toLowerCase() : null;
|
|
507
|
+
}
|
|
508
|
+
function decodePowerShellSingleQuotedLiteral(value) {
|
|
509
|
+
return value.replace(/''/g, "'");
|
|
510
|
+
}
|
|
511
|
+
/**
|
|
512
|
+
* Parses the only foreign command form that proves its -File target is a
|
|
513
|
+
* static literal. All other PowerShell forms remain ambiguous because they can
|
|
514
|
+
* construct an executable target dynamically or through a nested evaluator.
|
|
515
|
+
*/
|
|
516
|
+
function decodeWindowsShimReferencePath(command) {
|
|
517
|
+
if (/["`]/.test(command))
|
|
518
|
+
return null;
|
|
519
|
+
const match = command.match(/^\s*&\s+'((?:''|[^'\r\n])*)'\s+-noprofile\s+-executionpolicy\s+bypass\s+-file\s+'((?:''|[^'\r\n])*)'\s*$/i);
|
|
520
|
+
if (!match)
|
|
521
|
+
return null;
|
|
522
|
+
const powerShellPath = decodePowerShellSingleQuotedLiteral(match[1]);
|
|
523
|
+
if (windowsShimPathBasename(powerShellPath) !== "powershell.exe" ||
|
|
524
|
+
normalizeWindowsShimReferencePath(powerShellPath) === null ||
|
|
525
|
+
hasPotentialWindowsPathAlias(powerShellPath)) {
|
|
526
|
+
return null;
|
|
221
527
|
}
|
|
528
|
+
return decodePowerShellSingleQuotedLiteral(match[2]);
|
|
529
|
+
}
|
|
530
|
+
function hasPotentialWindowsPathAlias(path) {
|
|
531
|
+
const windowsPath = path.replace(/\//g, "\\");
|
|
532
|
+
return windowsPath.split("\\").some((component) => component === "." ||
|
|
533
|
+
component === ".." ||
|
|
534
|
+
/[. ]$/.test(component) ||
|
|
535
|
+
/~\d+(?:\.[^\\]*)?$/i.test(component));
|
|
536
|
+
}
|
|
537
|
+
function windowsShimCommandReferenceDecision(command, shimPath) {
|
|
538
|
+
const decodedShimPath = decodeWindowsShimReferencePath(command);
|
|
539
|
+
if (decodedShimPath === null)
|
|
540
|
+
return "ambiguous";
|
|
541
|
+
const normalizedDecodedPath = normalizeWindowsShimReferencePath(decodedShimPath);
|
|
542
|
+
const normalizedShimPath = normalizeWindowsShimReferencePath(shimPath);
|
|
543
|
+
if (normalizedDecodedPath === null || normalizedShimPath === null) {
|
|
544
|
+
return "ambiguous";
|
|
545
|
+
}
|
|
546
|
+
if (normalizedDecodedPath === normalizedShimPath)
|
|
547
|
+
return "referenced";
|
|
548
|
+
// A distinct fully qualified spelling is not proof that the foreign target
|
|
549
|
+
// cannot resolve to this shim: reparse points, SUBST, mapped drives, and UNC
|
|
550
|
+
// aliases can all refer to the same file. No race-safe file-identity proof is
|
|
551
|
+
// available here, so preserve the proof-owned shim.
|
|
552
|
+
return "ambiguous";
|
|
222
553
|
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
554
|
+
/**
|
|
555
|
+
* Decides whether a proof-owned Windows shim must remain after a hooks.json
|
|
556
|
+
* transition. JSON is strictly validated and decoded before inspection so
|
|
557
|
+
* escaped paths are handled semantically. Unknown future event members are
|
|
558
|
+
* scanned only through the established executable group/command shape; prompt
|
|
559
|
+
* and agent payloads remain inert metadata.
|
|
560
|
+
*/
|
|
561
|
+
export function decideWindowsNativeHookShimReference(finalHooksContent, shimPath) {
|
|
562
|
+
if (finalHooksContent === null)
|
|
563
|
+
return "not_referenced";
|
|
564
|
+
const validation = validateCodexHooksConfigStrict(finalHooksContent, {
|
|
565
|
+
platform: "win32",
|
|
228
566
|
});
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
567
|
+
if (!validation.ok)
|
|
568
|
+
return "ambiguous";
|
|
569
|
+
const hooks = validation.root.hooks;
|
|
570
|
+
if (!isWindowsShimReferenceRecord(hooks))
|
|
571
|
+
return "not_referenced";
|
|
572
|
+
let ambiguous = false;
|
|
573
|
+
for (const eventGroups of Object.values(hooks)) {
|
|
574
|
+
if (!Array.isArray(eventGroups))
|
|
575
|
+
continue;
|
|
576
|
+
for (const group of eventGroups) {
|
|
577
|
+
if (!isWindowsShimReferenceRecord(group) ||
|
|
578
|
+
!Array.isArray(group.hooks)) {
|
|
579
|
+
continue;
|
|
580
|
+
}
|
|
581
|
+
for (const handler of group.hooks) {
|
|
582
|
+
if (!isWindowsShimReferenceRecord(handler) ||
|
|
583
|
+
handler.type !== "command") {
|
|
584
|
+
continue;
|
|
585
|
+
}
|
|
586
|
+
for (const command of [
|
|
587
|
+
handler.commandWindows,
|
|
588
|
+
handler.command_windows,
|
|
589
|
+
handler.command,
|
|
590
|
+
]) {
|
|
591
|
+
if (typeof command !== "string")
|
|
592
|
+
continue;
|
|
593
|
+
const decision = windowsShimCommandReferenceDecision(command, shimPath);
|
|
594
|
+
if (decision === "referenced")
|
|
595
|
+
return decision;
|
|
596
|
+
if (decision === "ambiguous")
|
|
597
|
+
ambiguous = true;
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
return ambiguous ? "ambiguous" : "not_referenced";
|
|
603
|
+
}
|
|
604
|
+
function preflightManagedCodexHookTrustState(config, priorManagedHookTrustState, managedHookTrustState) {
|
|
605
|
+
stripManagedCodexHookTrustState(config, {
|
|
606
|
+
priorManagedHookTrustState,
|
|
607
|
+
managedTrustState: managedHookTrustState,
|
|
608
|
+
});
|
|
609
|
+
}
|
|
610
|
+
function injectNativeHookTransactionFailure(stage, target) {
|
|
611
|
+
nativeHookTransactionFailureInjector?.(stage, target);
|
|
612
|
+
}
|
|
613
|
+
function nativeHookTransactionTemporaryPath(path, purpose) {
|
|
614
|
+
if (nativeHookTransactionTemporaryPathOverride) {
|
|
615
|
+
return nativeHookTransactionTemporaryPathOverride(path, purpose);
|
|
616
|
+
}
|
|
617
|
+
nativeHookTransactionSequence += 1;
|
|
618
|
+
return join(dirname(path), `.${basename(path)}.omx-${purpose}-${process.pid}-${nativeHookTransactionSequence}.tmp`);
|
|
619
|
+
}
|
|
620
|
+
function nativeHookTransactionClaimPath(path) {
|
|
621
|
+
nativeHookTransactionSequence += 1;
|
|
622
|
+
return join(dirname(path), `.${basename(path)}.omx-claim-${process.pid}-${nativeHookTransactionSequence}.tmp`);
|
|
623
|
+
}
|
|
624
|
+
async function restoreNativeHookClaim(claimPath, destinationPath, tracker) {
|
|
625
|
+
recordRegularFileSyncOutcome(tracker, await restoreNativeHookClaimNoClobber(claimPath, destinationPath));
|
|
626
|
+
}
|
|
627
|
+
async function atomicWriteNativeHookTransactionArtifact(artifact, content, stage, expectedCurrent, ancestorPrecondition, tracker, onWriteApplied, onWriteStabilized, assertApplied) {
|
|
628
|
+
const temporaryPath = nativeHookTransactionTemporaryPath(artifact.path, "write");
|
|
629
|
+
let temporaryCreated = false;
|
|
630
|
+
let temporarySnapshot;
|
|
631
|
+
let claimPath;
|
|
632
|
+
let claimCreated = false;
|
|
633
|
+
let journaledClaim = false;
|
|
634
|
+
try {
|
|
635
|
+
await assertNativeHookTransactionAncestorPrecondition(ancestorPrecondition);
|
|
636
|
+
await assertApplied?.();
|
|
637
|
+
await mkdir(dirname(artifact.path), { recursive: true });
|
|
638
|
+
await refreshNativeHookTransactionAncestorPrecondition(ancestorPrecondition, artifact.path);
|
|
639
|
+
if (stage === "write") {
|
|
640
|
+
injectNativeHookTransactionFailure("before_temp_write", artifact);
|
|
641
|
+
}
|
|
642
|
+
else {
|
|
643
|
+
injectNativeHookTransactionFailure("before_rollback", artifact);
|
|
644
|
+
}
|
|
645
|
+
await assertNativeHookTransactionArtifactSnapshot(artifact.path, artifact.label, expectedCurrent, stage === "write" ? "precondition" : "rollback");
|
|
646
|
+
await assertNativeHookTransactionAncestorPrecondition(ancestorPrecondition);
|
|
647
|
+
await assertApplied?.();
|
|
648
|
+
const handle = await open(temporaryPath, "wx", artifact.afterTopology.kind === "regular_file"
|
|
649
|
+
? artifact.afterTopology.mode
|
|
650
|
+
: 0o600);
|
|
651
|
+
temporaryCreated = true;
|
|
652
|
+
try {
|
|
653
|
+
await handle.writeFile(content);
|
|
654
|
+
recordRegularFileSyncOutcome(tracker, await syncNativeHookRegularFile(handle));
|
|
655
|
+
}
|
|
656
|
+
finally {
|
|
657
|
+
await handle.close();
|
|
658
|
+
}
|
|
659
|
+
if (artifact.afterTopology.kind === "regular_file") {
|
|
660
|
+
await chmod(temporaryPath, artifact.afterTopology.mode);
|
|
661
|
+
}
|
|
662
|
+
temporarySnapshot = await assertNativeHookTransactionArtifactState(temporaryPath, `${artifact.label} temporary`, { bytes: content, topology: artifact.afterTopology }, "read-back");
|
|
663
|
+
if (stage === "write") {
|
|
664
|
+
injectNativeHookTransactionFailure("before_rename", artifact);
|
|
665
|
+
}
|
|
666
|
+
else {
|
|
667
|
+
injectNativeHookTransactionFailure("before_rollback_rename", artifact);
|
|
668
|
+
}
|
|
669
|
+
await assertNativeHookTransactionArtifactSnapshot(artifact.path, artifact.label, expectedCurrent, stage === "write" ? "precondition" : "rollback");
|
|
670
|
+
await assertNativeHookTransactionAncestorPrecondition(ancestorPrecondition);
|
|
671
|
+
await assertNativeHookTransactionArtifactSnapshot(temporaryPath, `${artifact.label} temporary`, temporarySnapshot, "read-back");
|
|
672
|
+
await assertApplied?.();
|
|
673
|
+
if (!temporarySnapshot) {
|
|
674
|
+
throw new Error("temporary path was not fully captured after writing");
|
|
675
|
+
}
|
|
676
|
+
injectNativeHookTransactionFailure(stage === "write"
|
|
677
|
+
? "after_final_rename_validation"
|
|
678
|
+
: "after_final_restore_validation", artifact);
|
|
679
|
+
claimPath = nativeHookTransactionClaimPath(artifact.path);
|
|
680
|
+
const claimRelativePath = relative(ancestorPrecondition.controlledRoot, artifact.path);
|
|
681
|
+
const canJournalClaim = !isAbsolute(claimRelativePath) &&
|
|
682
|
+
claimRelativePath !== ".." &&
|
|
683
|
+
!claimRelativePath.startsWith(`..${sep}`);
|
|
684
|
+
if (expectedCurrent.bytes !== null) {
|
|
685
|
+
if (canJournalClaim) {
|
|
686
|
+
recordRegularFileSyncOutcome(tracker, await persistNativeHookClaimJournal(ancestorPrecondition.controlledRoot, {
|
|
687
|
+
canonicalPath: artifact.path,
|
|
688
|
+
claimPath,
|
|
689
|
+
before: expectedCurrent.bytes,
|
|
690
|
+
after: content,
|
|
691
|
+
}));
|
|
692
|
+
await refreshNativeHookTransactionAncestorPrecondition(ancestorPrecondition, join(ancestorPrecondition.controlledRoot, ".omx", "native-hook-claim-journal.json"));
|
|
693
|
+
journaledClaim = true;
|
|
694
|
+
}
|
|
695
|
+
await rename(artifact.path, claimPath);
|
|
696
|
+
claimCreated = true;
|
|
697
|
+
if (journaledClaim)
|
|
698
|
+
await syncNativeHookClaimParent(claimPath);
|
|
699
|
+
await assertNativeHookTransactionArtifactSnapshot(claimPath, `${artifact.label} replacement claim`, expectedCurrent, stage === "write" ? "precondition" : "rollback");
|
|
700
|
+
}
|
|
701
|
+
await copyFile(temporaryPath, artifact.path, constants.COPYFILE_EXCL);
|
|
702
|
+
if (artifact.afterTopology.kind === "regular_file") {
|
|
703
|
+
await chmod(artifact.path, artifact.afterTopology.mode);
|
|
704
|
+
}
|
|
705
|
+
const installedHandle = await open(artifact.path, "r");
|
|
706
|
+
try {
|
|
707
|
+
recordRegularFileSyncOutcome(tracker, await syncNativeHookRegularFile(installedHandle));
|
|
708
|
+
}
|
|
709
|
+
finally {
|
|
710
|
+
await installedHandle.close();
|
|
711
|
+
}
|
|
712
|
+
await syncNativeHookClaimParent(artifact.path);
|
|
713
|
+
const installedSnapshot = await assertNativeHookTransactionArtifactState(artifact.path, artifact.label, { bytes: content, topology: artifact.afterTopology }, stage === "write" ? "precondition" : "rollback");
|
|
714
|
+
if (claimCreated && claimPath) {
|
|
715
|
+
await rm(claimPath);
|
|
716
|
+
await syncNativeHookClaimParent(claimPath);
|
|
717
|
+
claimCreated = false;
|
|
718
|
+
}
|
|
719
|
+
if (journaledClaim) {
|
|
720
|
+
await clearNativeHookClaimJournal(ancestorPrecondition.controlledRoot);
|
|
721
|
+
journaledClaim = false;
|
|
722
|
+
}
|
|
723
|
+
await rm(temporaryPath);
|
|
724
|
+
temporaryCreated = false;
|
|
725
|
+
onWriteApplied?.(installedSnapshot);
|
|
726
|
+
onWriteStabilized?.(installedSnapshot);
|
|
727
|
+
if (stage === "write") {
|
|
728
|
+
injectNativeHookTransactionFailure("after_rename", artifact);
|
|
729
|
+
}
|
|
730
|
+
await assertNativeHookTransactionArtifactSnapshot(artifact.path, artifact.label, installedSnapshot, stage === "write" ? "precondition" : "rollback");
|
|
731
|
+
}
|
|
732
|
+
catch (error) {
|
|
733
|
+
if (claimCreated && claimPath) {
|
|
734
|
+
try {
|
|
735
|
+
await restoreNativeHookClaim(claimPath, artifact.path, tracker);
|
|
736
|
+
await syncNativeHookClaimParent(artifact.path);
|
|
737
|
+
claimCreated = false;
|
|
738
|
+
if (journaledClaim) {
|
|
739
|
+
await clearNativeHookClaimJournal(ancestorPrecondition.controlledRoot);
|
|
740
|
+
journaledClaim = false;
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
catch (recoveryError) {
|
|
744
|
+
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)}`);
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
if (temporaryCreated) {
|
|
748
|
+
try {
|
|
749
|
+
if (!temporarySnapshot) {
|
|
750
|
+
throw new Error("temporary path was not fully captured after writing");
|
|
751
|
+
}
|
|
752
|
+
await assertNativeHookTransactionArtifactSnapshot(temporaryPath, `${artifact.label} temporary`, temporarySnapshot, "read-back");
|
|
753
|
+
await assertNativeHookTransactionAncestorPrecondition(ancestorPrecondition);
|
|
754
|
+
await rm(temporaryPath);
|
|
755
|
+
}
|
|
756
|
+
catch (cleanupError) {
|
|
757
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
758
|
+
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)}`);
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
throw error;
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
async function assertNativeHookTransactionArtifactState(path, label, expected, context) {
|
|
765
|
+
const actual = await captureNativeHookTransactionArtifact(path, label);
|
|
766
|
+
if (!nativeHookTransactionSnapshotMatchesExpected(actual, expected)) {
|
|
767
|
+
if (context === "rollback") {
|
|
768
|
+
throw new Error(`Native hook transaction rollback preserved ${path} for manual recovery because ${label} no longer matches the transaction-owned version.`);
|
|
769
|
+
}
|
|
770
|
+
throw new Error(`Native hook transaction ${context} changed for ${label}; refusing to overwrite concurrent content.`);
|
|
771
|
+
}
|
|
772
|
+
return actual;
|
|
773
|
+
}
|
|
774
|
+
async function assertNativeHookTransactionArtifactSnapshot(path, label, expected, context) {
|
|
775
|
+
const actual = await captureNativeHookTransactionArtifact(path, label);
|
|
776
|
+
if (!nativeHookTransactionSnapshotsEqual(actual, expected)) {
|
|
777
|
+
if (context === "rollback") {
|
|
778
|
+
throw new Error(`Native hook transaction rollback preserved ${path} for manual recovery because ${label} no longer matches the transaction-owned version.`);
|
|
779
|
+
}
|
|
780
|
+
throw new Error(`Native hook transaction ${context} changed for ${label}; refusing to overwrite concurrent content.`);
|
|
781
|
+
}
|
|
782
|
+
return actual;
|
|
783
|
+
}
|
|
784
|
+
async function assertNativeHookTransactionPrecondition(precondition) {
|
|
785
|
+
await assertNativeHookTransactionArtifactSnapshot(precondition.path, precondition.label, precondition.before, "precondition");
|
|
786
|
+
}
|
|
787
|
+
async function assertUnmutatedNativeHookTransactionPreconditions(preconditions, applied) {
|
|
788
|
+
const mutatedPaths = new Set(applied.map((entry) => entry.artifact.path));
|
|
789
|
+
for (const precondition of preconditions) {
|
|
790
|
+
if (mutatedPaths.has(precondition.path))
|
|
791
|
+
continue;
|
|
792
|
+
await assertNativeHookTransactionPrecondition(precondition);
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
async function assertAppliedNativeHookTransactionSnapshots(applied) {
|
|
796
|
+
for (const entry of applied) {
|
|
797
|
+
await assertNativeHookTransactionArtifactSnapshot(entry.artifact.path, entry.artifact.label, entry.appliedSnapshot, "precondition");
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
async function assertNativeHookTransactionStagedRecoveryCopies(applied) {
|
|
801
|
+
for (const entry of applied) {
|
|
802
|
+
if (!entry.stagedDeletionPath ||
|
|
803
|
+
!entry.stagedDeletionSnapshot ||
|
|
804
|
+
entry.stagedDeletionCleaned) {
|
|
805
|
+
continue;
|
|
806
|
+
}
|
|
807
|
+
await assertNativeHookTransactionArtifactSnapshot(entry.stagedDeletionPath, `${entry.artifact.label} staged deletion`, entry.stagedDeletionSnapshot, "rollback");
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
async function assertNativeHookTransactionRollbackState(applied) {
|
|
811
|
+
await assertNativeHookTransactionStagedRecoveryCopies(applied);
|
|
812
|
+
await assertAppliedNativeHookTransactionSnapshots(applied);
|
|
813
|
+
}
|
|
814
|
+
async function applyNativeHookTransactionArtifact(artifact, ancestorPrecondition, tracker, applied) {
|
|
815
|
+
await assertNativeHookTransactionArtifactSnapshot(artifact.path, artifact.label, artifact.before, "precondition");
|
|
816
|
+
await assertNativeHookTransactionAncestorPrecondition(ancestorPrecondition);
|
|
817
|
+
if (artifact.after !== null) {
|
|
818
|
+
let entry;
|
|
819
|
+
await atomicWriteNativeHookTransactionArtifact(artifact, artifact.after, "write", artifact.before, ancestorPrecondition, tracker, (appliedSnapshot) => {
|
|
820
|
+
entry = { artifact, appliedSnapshot };
|
|
821
|
+
applied.push(entry);
|
|
822
|
+
}, (stabilizedSnapshot) => {
|
|
823
|
+
if (!entry) {
|
|
824
|
+
throw new Error("native hook transaction write was not registered as applied");
|
|
825
|
+
}
|
|
826
|
+
entry.appliedSnapshot = stabilizedSnapshot;
|
|
827
|
+
}, () => assertAppliedNativeHookTransactionSnapshots(applied));
|
|
828
|
+
if (!entry) {
|
|
829
|
+
throw new Error("native hook transaction write was not registered as applied");
|
|
830
|
+
}
|
|
831
|
+
return entry;
|
|
832
|
+
}
|
|
833
|
+
const stagedDeletionPath = nativeHookTransactionTemporaryPath(artifact.path, "delete");
|
|
834
|
+
let stagedDeletionCreated = false;
|
|
835
|
+
let originalRemoved = false;
|
|
836
|
+
let stagedDeletionSnapshot;
|
|
837
|
+
try {
|
|
838
|
+
injectNativeHookTransactionFailure("before_temp_write", artifact);
|
|
839
|
+
await assertNativeHookTransactionArtifactSnapshot(artifact.path, artifact.label, artifact.before, "precondition");
|
|
840
|
+
await assertNativeHookTransactionAncestorPrecondition(ancestorPrecondition);
|
|
841
|
+
await assertAppliedNativeHookTransactionSnapshots(applied);
|
|
842
|
+
const handle = await open(stagedDeletionPath, "wx", artifact.before.topology.kind === "regular_file"
|
|
843
|
+
? artifact.before.topology.mode
|
|
844
|
+
: 0o600);
|
|
845
|
+
stagedDeletionCreated = true;
|
|
846
|
+
try {
|
|
847
|
+
await handle.writeFile(artifact.before.bytes);
|
|
848
|
+
recordRegularFileSyncOutcome(tracker, await syncNativeHookRegularFile(handle));
|
|
849
|
+
}
|
|
850
|
+
finally {
|
|
851
|
+
await handle.close();
|
|
852
|
+
}
|
|
853
|
+
if (artifact.before.topology.kind === "regular_file") {
|
|
854
|
+
await chmod(stagedDeletionPath, artifact.before.topology.mode);
|
|
855
|
+
}
|
|
856
|
+
stagedDeletionSnapshot = await assertNativeHookTransactionArtifactState(stagedDeletionPath, `${artifact.label} staged deletion`, { bytes: artifact.before.bytes, topology: artifact.before.topology }, "read-back");
|
|
857
|
+
injectNativeHookTransactionFailure("before_remove", artifact);
|
|
858
|
+
await assertNativeHookTransactionArtifactSnapshot(artifact.path, artifact.label, artifact.before, "precondition");
|
|
859
|
+
await assertNativeHookTransactionAncestorPrecondition(ancestorPrecondition);
|
|
860
|
+
await assertNativeHookTransactionArtifactSnapshot(stagedDeletionPath, `${artifact.label} staged deletion`, stagedDeletionSnapshot, "read-back");
|
|
861
|
+
await assertAppliedNativeHookTransactionSnapshots(applied);
|
|
862
|
+
injectNativeHookTransactionFailure("after_final_remove_validation", artifact);
|
|
863
|
+
await assertNativeHookTransactionArtifactSnapshot(stagedDeletionPath, `${artifact.label} staged deletion`, stagedDeletionSnapshot, "read-back");
|
|
864
|
+
const claimPath = nativeHookTransactionClaimPath(artifact.path);
|
|
865
|
+
const claimRelativePath = relative(ancestorPrecondition.controlledRoot, artifact.path);
|
|
866
|
+
const journaledClaim = !isAbsolute(claimRelativePath) &&
|
|
867
|
+
claimRelativePath !== ".." &&
|
|
868
|
+
!claimRelativePath.startsWith(`..${sep}`);
|
|
869
|
+
if (journaledClaim) {
|
|
870
|
+
recordRegularFileSyncOutcome(tracker, await persistNativeHookClaimJournal(ancestorPrecondition.controlledRoot, {
|
|
871
|
+
canonicalPath: artifact.path,
|
|
872
|
+
claimPath,
|
|
873
|
+
before: artifact.before.bytes,
|
|
874
|
+
after: null,
|
|
875
|
+
}));
|
|
876
|
+
}
|
|
877
|
+
await rename(artifact.path, claimPath);
|
|
878
|
+
if (journaledClaim)
|
|
879
|
+
await syncNativeHookClaimParent(claimPath);
|
|
880
|
+
const entry = {
|
|
881
|
+
artifact,
|
|
882
|
+
appliedSnapshot: { bytes: null, topology: { kind: "absent" } },
|
|
883
|
+
stagedDeletionPath,
|
|
884
|
+
stagedDeletionSnapshot,
|
|
885
|
+
};
|
|
886
|
+
applied.push(entry);
|
|
887
|
+
try {
|
|
888
|
+
await assertNativeHookTransactionAncestorPrecondition(ancestorPrecondition);
|
|
889
|
+
await assertNativeHookTransactionArtifactSnapshot(claimPath, `${artifact.label} removal claim`, artifact.before, "precondition");
|
|
890
|
+
}
|
|
891
|
+
catch (error) {
|
|
892
|
+
try {
|
|
893
|
+
const claimed = await captureNativeHookTransactionArtifact(claimPath, `${artifact.label} removal claim`);
|
|
894
|
+
await restoreNativeHookClaim(claimPath, artifact.path, tracker);
|
|
895
|
+
if (journaledClaim)
|
|
896
|
+
await syncNativeHookClaimParent(artifact.path);
|
|
897
|
+
if (journaledClaim) {
|
|
898
|
+
await clearNativeHookClaimJournal(ancestorPrecondition.controlledRoot);
|
|
899
|
+
}
|
|
900
|
+
await assertNativeHookTransactionArtifactSnapshot(artifact.path, artifact.label, claimed, "precondition");
|
|
901
|
+
const entryIndex = applied.indexOf(entry);
|
|
902
|
+
if (entryIndex >= 0)
|
|
903
|
+
applied.splice(entryIndex, 1);
|
|
904
|
+
}
|
|
905
|
+
catch (recoveryError) {
|
|
906
|
+
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)}`);
|
|
907
|
+
}
|
|
908
|
+
throw error;
|
|
909
|
+
}
|
|
910
|
+
await rm(claimPath);
|
|
911
|
+
if (journaledClaim)
|
|
912
|
+
await syncNativeHookClaimParent(claimPath);
|
|
913
|
+
if (journaledClaim) {
|
|
914
|
+
await clearNativeHookClaimJournal(ancestorPrecondition.controlledRoot);
|
|
915
|
+
}
|
|
916
|
+
originalRemoved = true;
|
|
917
|
+
injectNativeHookTransactionFailure("after_remove", artifact);
|
|
918
|
+
return entry;
|
|
919
|
+
}
|
|
920
|
+
catch (error) {
|
|
921
|
+
if (stagedDeletionCreated && !originalRemoved) {
|
|
922
|
+
try {
|
|
923
|
+
if (!stagedDeletionSnapshot) {
|
|
924
|
+
throw new Error("staged deletion path was not fully captured after writing");
|
|
925
|
+
}
|
|
926
|
+
await assertNativeHookTransactionArtifactSnapshot(stagedDeletionPath, `${artifact.label} staged deletion`, stagedDeletionSnapshot, "read-back");
|
|
927
|
+
await assertNativeHookTransactionAncestorPrecondition(ancestorPrecondition);
|
|
928
|
+
const claimPath = nativeHookTransactionClaimPath(artifact.path);
|
|
929
|
+
await rename(stagedDeletionPath, claimPath);
|
|
930
|
+
try {
|
|
931
|
+
await assertNativeHookTransactionArtifactSnapshot(claimPath, `${artifact.label} staged deletion cleanup claim`, stagedDeletionSnapshot, "read-back");
|
|
932
|
+
}
|
|
933
|
+
catch (claimError) {
|
|
934
|
+
try {
|
|
935
|
+
await restoreNativeHookClaim(claimPath, stagedDeletionPath, tracker);
|
|
936
|
+
}
|
|
937
|
+
catch (recoveryError) {
|
|
938
|
+
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)}`);
|
|
939
|
+
}
|
|
940
|
+
throw claimError;
|
|
941
|
+
}
|
|
942
|
+
await rm(claimPath);
|
|
943
|
+
}
|
|
944
|
+
catch (cleanupError) {
|
|
945
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
946
|
+
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)}`);
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
throw error;
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
async function verifyNativeHookTransactionArtifact(applied) {
|
|
953
|
+
const { artifact } = applied;
|
|
954
|
+
injectNativeHookTransactionFailure("before_readback", artifact);
|
|
955
|
+
const actual = await captureNativeHookTransactionArtifact(artifact.path, artifact.label);
|
|
956
|
+
if (!nativeHookTransactionSnapshotsEqual(actual, applied.appliedSnapshot)) {
|
|
957
|
+
throw new Error(`Native hook transaction read-back changed for ${artifact.label}; refusing to overwrite concurrent content.`);
|
|
958
|
+
}
|
|
959
|
+
if (artifact.after === null)
|
|
247
960
|
return;
|
|
248
|
-
|
|
249
|
-
|
|
961
|
+
const content = decodeNativeHookTransactionUtf8(actual.bytes, artifact.label);
|
|
962
|
+
if (artifact.kind === "hooks") {
|
|
963
|
+
const validation = validateCodexHooksConfigStrict(content, {
|
|
964
|
+
platform: artifact.hookPlatform,
|
|
965
|
+
});
|
|
966
|
+
if (!validation.ok) {
|
|
967
|
+
throw new Error(`Native hook transaction wrote invalid hooks.json: ${validation.error.message}`);
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
if (artifact.kind === "config")
|
|
971
|
+
TOML.parse(content);
|
|
972
|
+
}
|
|
973
|
+
async function restoreNativeHookTransactionArtifact(applied, ancestorPrecondition, tracker, assertRollbackState) {
|
|
974
|
+
const { artifact } = applied;
|
|
975
|
+
const current = await captureNativeHookTransactionArtifact(artifact.path, artifact.label);
|
|
976
|
+
if (!nativeHookTransactionSnapshotsEqual(current, applied.appliedSnapshot)) {
|
|
977
|
+
throw new Error(`Native hook transaction rollback preserved ${artifact.path} for manual recovery because ${artifact.label} no longer matches the transaction-owned version.`);
|
|
978
|
+
}
|
|
979
|
+
if (artifact.before.bytes === null) {
|
|
980
|
+
injectNativeHookTransactionFailure("before_rollback", artifact);
|
|
981
|
+
injectNativeHookTransactionFailure("before_rollback_remove", artifact);
|
|
982
|
+
await assertNativeHookTransactionArtifactSnapshot(artifact.path, artifact.label, applied.appliedSnapshot, "rollback");
|
|
983
|
+
await assertNativeHookTransactionAncestorPrecondition(ancestorPrecondition);
|
|
984
|
+
await assertRollbackState();
|
|
985
|
+
injectNativeHookTransactionFailure("after_final_restore_validation", artifact);
|
|
986
|
+
const claimPath = nativeHookTransactionClaimPath(artifact.path);
|
|
987
|
+
await rename(artifact.path, claimPath);
|
|
988
|
+
applied.appliedSnapshot = { bytes: null, topology: { kind: "absent" } };
|
|
989
|
+
try {
|
|
990
|
+
await assertNativeHookTransactionArtifactSnapshot(claimPath, `${artifact.label} rollback removal claim`, current, "rollback");
|
|
991
|
+
}
|
|
992
|
+
catch (error) {
|
|
993
|
+
try {
|
|
994
|
+
const claimed = await captureNativeHookTransactionArtifact(claimPath, `${artifact.label} rollback removal claim`);
|
|
995
|
+
await restoreNativeHookClaim(claimPath, artifact.path, tracker);
|
|
996
|
+
await assertNativeHookTransactionArtifactSnapshot(artifact.path, artifact.label, claimed, "rollback");
|
|
997
|
+
}
|
|
998
|
+
catch (recoveryError) {
|
|
999
|
+
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)}`);
|
|
1000
|
+
}
|
|
1001
|
+
throw error;
|
|
1002
|
+
}
|
|
1003
|
+
await rm(claimPath);
|
|
250
1004
|
}
|
|
1005
|
+
else {
|
|
1006
|
+
const restoreArtifact = {
|
|
1007
|
+
...artifact,
|
|
1008
|
+
after: artifact.before.bytes,
|
|
1009
|
+
afterTopology: artifact.before.topology,
|
|
1010
|
+
};
|
|
1011
|
+
await atomicWriteNativeHookTransactionArtifact(restoreArtifact, artifact.before.bytes, "rollback", applied.appliedSnapshot, ancestorPrecondition, tracker, (restoredSnapshot) => {
|
|
1012
|
+
applied.appliedSnapshot = restoredSnapshot;
|
|
1013
|
+
}, (stabilizedSnapshot) => {
|
|
1014
|
+
applied.appliedSnapshot = stabilizedSnapshot;
|
|
1015
|
+
}, assertRollbackState);
|
|
1016
|
+
}
|
|
1017
|
+
const restored = await assertNativeHookTransactionArtifactSnapshot(artifact.path, artifact.label, applied.appliedSnapshot, "rollback");
|
|
1018
|
+
if (!nativeHookTransactionSnapshotMatchesExpected(restored, {
|
|
1019
|
+
bytes: artifact.before.bytes,
|
|
1020
|
+
topology: artifact.before.topology,
|
|
1021
|
+
})) {
|
|
1022
|
+
throw new Error(`Native hook transaction rollback preserved ${artifact.path} for manual recovery because ${artifact.label} no longer matches the expected restored version.`);
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
async function cleanupNativeHookTransactionStagedDeletions(applied, ancestorPrecondition, tracker, preconditions, rollbackApplied) {
|
|
1026
|
+
for (const entry of applied) {
|
|
1027
|
+
if (!entry.stagedDeletionPath ||
|
|
1028
|
+
!entry.stagedDeletionSnapshot ||
|
|
1029
|
+
entry.stagedDeletionCleaned) {
|
|
1030
|
+
continue;
|
|
1031
|
+
}
|
|
1032
|
+
injectNativeHookTransactionFailure("before_staged_cleanup", entry.artifact);
|
|
1033
|
+
if (preconditions) {
|
|
1034
|
+
await assertUnmutatedNativeHookTransactionPreconditions(preconditions, applied);
|
|
1035
|
+
await assertAppliedNativeHookTransactionSnapshots(applied);
|
|
1036
|
+
}
|
|
1037
|
+
if (rollbackApplied) {
|
|
1038
|
+
await assertNativeHookTransactionRollbackState(rollbackApplied);
|
|
1039
|
+
}
|
|
1040
|
+
const stagedDeletionPath = entry.stagedDeletionPath;
|
|
1041
|
+
const stagedDeletionSnapshot = entry.stagedDeletionSnapshot;
|
|
1042
|
+
await assertNativeHookTransactionArtifactSnapshot(stagedDeletionPath, `${entry.artifact.label} staged deletion`, stagedDeletionSnapshot, "read-back");
|
|
1043
|
+
await assertNativeHookTransactionAncestorPrecondition(ancestorPrecondition);
|
|
1044
|
+
const claimPath = nativeHookTransactionClaimPath(entry.artifact.path);
|
|
1045
|
+
await rename(stagedDeletionPath, claimPath);
|
|
1046
|
+
try {
|
|
1047
|
+
await assertNativeHookTransactionArtifactSnapshot(claimPath, `${entry.artifact.label} staged deletion cleanup claim`, stagedDeletionSnapshot, "read-back");
|
|
1048
|
+
}
|
|
1049
|
+
catch (error) {
|
|
1050
|
+
try {
|
|
1051
|
+
await restoreNativeHookClaim(claimPath, stagedDeletionPath, tracker);
|
|
1052
|
+
}
|
|
1053
|
+
catch (recoveryError) {
|
|
1054
|
+
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)}`);
|
|
1055
|
+
}
|
|
1056
|
+
throw error;
|
|
1057
|
+
}
|
|
1058
|
+
await rm(claimPath);
|
|
1059
|
+
entry.stagedDeletionCleaned = true;
|
|
1060
|
+
if (rollbackApplied) {
|
|
1061
|
+
await assertNativeHookTransactionRollbackState(rollbackApplied);
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
function nativeHookTransactionBackupPath(artifactPath, backupContext) {
|
|
1066
|
+
const relativePath = relative(backupContext.baseRoot, artifactPath);
|
|
1067
|
+
const safeRelativePath = relativePath.startsWith("..") || relativePath === ""
|
|
1068
|
+
? artifactPath.replace(/^[/]+/, "")
|
|
1069
|
+
: relativePath;
|
|
1070
|
+
return join(backupContext.backupRoot, safeRelativePath);
|
|
1071
|
+
}
|
|
1072
|
+
async function ensureSnapshotBackup(artifact, backupContext, options, tracker) {
|
|
1073
|
+
const bytes = artifact.before.bytes;
|
|
1074
|
+
if (bytes === null)
|
|
1075
|
+
return false;
|
|
1076
|
+
const backupPath = nativeHookTransactionBackupPath(artifact.path, backupContext);
|
|
251
1077
|
if (!options.dryRun) {
|
|
252
|
-
|
|
253
|
-
|
|
1078
|
+
const relativeParent = relative(backupContext.baseRoot, dirname(backupPath));
|
|
1079
|
+
if (isAbsolute(relativeParent) ||
|
|
1080
|
+
relativeParent === ".." ||
|
|
1081
|
+
relativeParent.startsWith(`..${sep}`)) {
|
|
1082
|
+
throw new Error(`Refusing to back up ${artifact.path} outside controlled backup root.`);
|
|
1083
|
+
}
|
|
1084
|
+
let currentPath = backupContext.baseRoot;
|
|
1085
|
+
for (const component of relativeParent.split(sep).filter(Boolean)) {
|
|
1086
|
+
currentPath = join(currentPath, component);
|
|
1087
|
+
try {
|
|
1088
|
+
const currentStat = await lstat(currentPath);
|
|
1089
|
+
if (currentStat.isSymbolicLink() || !currentStat.isDirectory()) {
|
|
1090
|
+
throw new Error(`Refusing to use unsafe backup ancestor ${currentPath}.`);
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
catch (error) {
|
|
1094
|
+
if (!isMissingPathError(error))
|
|
1095
|
+
throw error;
|
|
1096
|
+
await mkdir(currentPath);
|
|
1097
|
+
const createdStat = await lstat(currentPath);
|
|
1098
|
+
if (createdStat.isSymbolicLink() || !createdStat.isDirectory()) {
|
|
1099
|
+
throw new Error(`Refusing to use unsafe created backup ancestor ${currentPath}.`);
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
const handle = await open(backupPath, "wx", 0o600);
|
|
1104
|
+
try {
|
|
1105
|
+
await handle.writeFile(bytes);
|
|
1106
|
+
recordRegularFileSyncOutcome(tracker, await syncNativeHookRegularFile(handle));
|
|
1107
|
+
}
|
|
1108
|
+
finally {
|
|
1109
|
+
await handle.close();
|
|
1110
|
+
}
|
|
1111
|
+
const backupStat = await lstat(backupPath);
|
|
1112
|
+
if (backupStat.isSymbolicLink() || !backupStat.isFile() || backupStat.nlink !== 1) {
|
|
1113
|
+
throw new Error(`Refusing unsafe native hook transaction backup ${backupPath}.`);
|
|
1114
|
+
}
|
|
1115
|
+
const writtenBytes = await readFile(backupPath);
|
|
1116
|
+
if (!writtenBytes.equals(bytes)) {
|
|
1117
|
+
throw new Error(`Native hook transaction backup verification failed for ${backupPath}.`);
|
|
1118
|
+
}
|
|
254
1119
|
}
|
|
255
|
-
summary.updated += 1;
|
|
256
1120
|
if (options.verbose) {
|
|
257
|
-
console.log(` ${
|
|
1121
|
+
console.log(` backup ${artifact.path} -> ${backupPath}`);
|
|
1122
|
+
}
|
|
1123
|
+
return true;
|
|
1124
|
+
}
|
|
1125
|
+
async function commitNativeHookTransaction(artifacts, preconditions, ancestorPrecondition, backupContext, tracker, summary, options) {
|
|
1126
|
+
if (options.dryRun)
|
|
1127
|
+
return;
|
|
1128
|
+
await assertNativeHookTransactionAncestorPrecondition(ancestorPrecondition);
|
|
1129
|
+
for (const precondition of preconditions) {
|
|
1130
|
+
injectNativeHookTransactionFailure("before_precondition", precondition);
|
|
1131
|
+
await assertNativeHookTransactionPrecondition(precondition);
|
|
1132
|
+
}
|
|
1133
|
+
if (artifacts.length === 0)
|
|
1134
|
+
return;
|
|
1135
|
+
for (const artifact of artifacts) {
|
|
1136
|
+
injectNativeHookTransactionFailure("before_backup", artifact);
|
|
1137
|
+
if (await ensureSnapshotBackup(artifact, backupContext, options, tracker)) {
|
|
1138
|
+
summary.backedUp += 1;
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
await assertUnmutatedNativeHookTransactionPreconditions(preconditions, []);
|
|
1142
|
+
await assertNativeHookTransactionAncestorPrecondition(ancestorPrecondition);
|
|
1143
|
+
const applied = [];
|
|
1144
|
+
let stagedCleanupStarted = false;
|
|
1145
|
+
try {
|
|
1146
|
+
for (const artifact of artifacts) {
|
|
1147
|
+
await assertUnmutatedNativeHookTransactionPreconditions(preconditions, applied);
|
|
1148
|
+
await assertAppliedNativeHookTransactionSnapshots(applied);
|
|
1149
|
+
const entry = await applyNativeHookTransactionArtifact(artifact, ancestorPrecondition, tracker, applied);
|
|
1150
|
+
await verifyNativeHookTransactionArtifact(entry);
|
|
1151
|
+
await assertUnmutatedNativeHookTransactionPreconditions(preconditions, applied);
|
|
1152
|
+
await assertAppliedNativeHookTransactionSnapshots(applied);
|
|
1153
|
+
}
|
|
1154
|
+
await assertUnmutatedNativeHookTransactionPreconditions(preconditions, applied);
|
|
1155
|
+
await assertAppliedNativeHookTransactionSnapshots(applied);
|
|
1156
|
+
stagedCleanupStarted = true;
|
|
1157
|
+
await cleanupNativeHookTransactionStagedDeletions(applied, ancestorPrecondition, tracker, preconditions);
|
|
1158
|
+
injectNativeHookTransactionFailure("after_staged_cleanup", artifacts[artifacts.length - 1]);
|
|
1159
|
+
await assertUnmutatedNativeHookTransactionPreconditions(preconditions, applied);
|
|
1160
|
+
await assertNativeHookTransactionAncestorPrecondition(ancestorPrecondition);
|
|
1161
|
+
await assertAppliedNativeHookTransactionSnapshots(applied);
|
|
1162
|
+
}
|
|
1163
|
+
catch (error) {
|
|
1164
|
+
if (stagedCleanupStarted &&
|
|
1165
|
+
applied.some((entry) => entry.stagedDeletionCleaned)) {
|
|
1166
|
+
throw new Error(`Native hook transaction committed but staged deletion cleanup failed during finalization: ${error instanceof Error ? error.message : String(error)}`);
|
|
1167
|
+
}
|
|
1168
|
+
const rollbackFailures = [];
|
|
1169
|
+
const restored = [];
|
|
1170
|
+
try {
|
|
1171
|
+
await assertNativeHookTransactionRollbackState(applied);
|
|
1172
|
+
}
|
|
1173
|
+
catch (rollbackError) {
|
|
1174
|
+
rollbackFailures.push(`recovery preflight: ${rollbackError instanceof Error ? rollbackError.message : String(rollbackError)}`);
|
|
1175
|
+
}
|
|
1176
|
+
if (rollbackFailures.length === 0) {
|
|
1177
|
+
for (const entry of [...applied].reverse()) {
|
|
1178
|
+
try {
|
|
1179
|
+
await assertNativeHookTransactionRollbackState(applied);
|
|
1180
|
+
await restoreNativeHookTransactionArtifact(entry, ancestorPrecondition, tracker, () => assertNativeHookTransactionRollbackState(applied));
|
|
1181
|
+
restored.push(entry);
|
|
1182
|
+
await assertNativeHookTransactionRollbackState(applied);
|
|
1183
|
+
}
|
|
1184
|
+
catch (rollbackError) {
|
|
1185
|
+
rollbackFailures.push(`${entry.artifact.label}: ${rollbackError instanceof Error ? rollbackError.message : String(rollbackError)}`);
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
if (rollbackFailures.length === 0) {
|
|
1190
|
+
try {
|
|
1191
|
+
await assertNativeHookTransactionRollbackState(applied);
|
|
1192
|
+
await cleanupNativeHookTransactionStagedDeletions(restored, ancestorPrecondition, tracker, undefined, applied);
|
|
1193
|
+
await assertNativeHookTransactionRollbackState(applied);
|
|
1194
|
+
}
|
|
1195
|
+
catch (cleanupError) {
|
|
1196
|
+
rollbackFailures.push(`staged deletion cleanup: ${cleanupError instanceof Error ? cleanupError.message : String(cleanupError)}`);
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1200
|
+
if (rollbackFailures.length > 0) {
|
|
1201
|
+
throw new Error(`Native hook transaction failed (${message}) and rollback failed; manual recovery is required (${rollbackFailures.join("; ")}).`);
|
|
1202
|
+
}
|
|
1203
|
+
throw new Error(`Native hook transaction failed and was rolled back: ${message}`);
|
|
258
1204
|
}
|
|
259
1205
|
}
|
|
260
1206
|
async function ensureBackup(destinationPath, contentChanged, backupContext, options) {
|
|
@@ -503,7 +1449,10 @@ async function promptForFirstPartyMcpRemoval(configPath, registrationKinds) {
|
|
|
503
1449
|
}
|
|
504
1450
|
}
|
|
505
1451
|
function hasPersistedSetupPreferences(preferences) {
|
|
506
|
-
return Boolean(preferences?.scope ||
|
|
1452
|
+
return Boolean(preferences?.scope ||
|
|
1453
|
+
preferences?.installMode ||
|
|
1454
|
+
preferences?.teamMode ||
|
|
1455
|
+
typeof preferences?.mergeAgents === "boolean");
|
|
507
1456
|
}
|
|
508
1457
|
function formatPersistedSetupPreferenceSummary(preferences) {
|
|
509
1458
|
const summary = [
|
|
@@ -513,6 +1462,9 @@ function formatPersistedSetupPreferenceSummary(preferences) {
|
|
|
513
1462
|
];
|
|
514
1463
|
if (preferences.teamMode)
|
|
515
1464
|
summary.push(`teamMode=${preferences.teamMode}`);
|
|
1465
|
+
if (typeof preferences.mergeAgents === "boolean") {
|
|
1466
|
+
summary.push(`mergeAgents=${preferences.mergeAgents}`);
|
|
1467
|
+
}
|
|
516
1468
|
return summary.join(", ");
|
|
517
1469
|
}
|
|
518
1470
|
async function promptForPersistedSetupReview(preferences) {
|
|
@@ -612,7 +1564,7 @@ function classifyPluginDeveloperInstructions(value) {
|
|
|
612
1564
|
}
|
|
613
1565
|
if (normalized ===
|
|
614
1566
|
normalizeDeveloperInstructionsText(LEGACY_PLUGIN_DEVELOPER_INSTRUCTIONS)) {
|
|
615
|
-
return "
|
|
1567
|
+
return "historical";
|
|
616
1568
|
}
|
|
617
1569
|
if (normalized === normalizeDeveloperInstructionsText(OMX_DEVELOPER_INSTRUCTIONS)) {
|
|
618
1570
|
return "historical";
|
|
@@ -669,11 +1621,8 @@ function legacyPluginDeveloperInstructionsDecision(choice, state = "missing") {
|
|
|
669
1621
|
reason: "legacy explicit skip policy",
|
|
670
1622
|
};
|
|
671
1623
|
}
|
|
672
|
-
async function resolvePluginDeveloperInstructionsDecision(configPath, options) {
|
|
673
|
-
const
|
|
674
|
-
? await readFile(configPath, "utf-8")
|
|
675
|
-
: "";
|
|
676
|
-
const value = readRootDeveloperInstructions(existing);
|
|
1624
|
+
async function resolvePluginDeveloperInstructionsDecision(existingConfig, configPath, options) {
|
|
1625
|
+
const value = readRootDeveloperInstructions(existingConfig);
|
|
677
1626
|
if (value === undefined) {
|
|
678
1627
|
if (options.pluginDeveloperInstructionsPrompt) {
|
|
679
1628
|
return legacyPluginDeveloperInstructionsDecision(await options.pluginDeveloperInstructionsPrompt(configPath), "missing");
|
|
@@ -1016,8 +1965,7 @@ async function persistSetupPreferences(projectRoot, preferences, options) {
|
|
|
1016
1965
|
console.log(` dry-run: skip persisting ${scopePath}`);
|
|
1017
1966
|
return;
|
|
1018
1967
|
}
|
|
1019
|
-
await
|
|
1020
|
-
await writeFile(scopePath, JSON.stringify(preferences, null, 2) + "\n");
|
|
1968
|
+
await writePersistedSetupPreferences(projectRoot, preferences);
|
|
1021
1969
|
if (options.verbose)
|
|
1022
1970
|
console.log(` Wrote ${scopePath}`);
|
|
1023
1971
|
}
|
|
@@ -1181,176 +2129,246 @@ function insertRootTomlKey(config, line) {
|
|
|
1181
2129
|
const after = lines.slice(firstTableIndex);
|
|
1182
2130
|
return [...before, line, "", ...after].join("\n") + "\n";
|
|
1183
2131
|
}
|
|
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
|
-
}
|
|
2132
|
+
function buildPluginModeHooksConfigPlan(existingConfig, existingHooksContent, pkgRoot, hooksPath, codexHomeDir, options) {
|
|
2133
|
+
const managedHookOptions = {
|
|
2134
|
+
platform: options.platform,
|
|
2135
|
+
codexHomeDir,
|
|
2136
|
+
};
|
|
2137
|
+
const managedHooksPlan = options.pluginScopedHooks
|
|
2138
|
+
? existingHooksContent === null
|
|
2139
|
+
? null
|
|
2140
|
+
: planManagedCodexHooksRemoval(existingHooksContent, hooksPath, managedHookOptions)
|
|
2141
|
+
: planManagedCodexHooksMerge(existingHooksContent, pkgRoot, hooksPath, managedHookOptions);
|
|
2142
|
+
if (managedHooksPlan && !managedHooksPlan.ok)
|
|
2143
|
+
throw managedHooksPlan.error;
|
|
2144
|
+
const managedHookTrustState = managedHooksPlan?.finalTrustState ?? {};
|
|
2145
|
+
const priorManagedHookTrustState = managedHooksPlan?.priorTrustState ?? {};
|
|
2146
|
+
const legacyHookTrustState = managedHooksPlan?.legacyTrustState ?? {};
|
|
2147
|
+
preflightManagedCodexHookTrustState(existingConfig, priorManagedHookTrustState, managedHookTrustState);
|
|
2148
|
+
const configAfterLegacyCleanup = buildPluginModeLegacyConfig(existingConfig, {
|
|
2149
|
+
...options,
|
|
2150
|
+
managedHookTrustState,
|
|
2151
|
+
priorManagedHookTrustState,
|
|
2152
|
+
});
|
|
2153
|
+
const configWithRuntimeFeatures = upsertPluginModeRuntimeFeatureFlags(configAfterLegacyCleanup, options.codexHookFeatureFlag, {
|
|
2154
|
+
pluginScopedHooks: options.pluginScopedHooks,
|
|
2155
|
+
preserveNativeHooks: options.pluginScopedHooks && managedHooksPlan?.hasForeignHooks === true,
|
|
2156
|
+
});
|
|
2157
|
+
return {
|
|
2158
|
+
finalConfig: upsertManagedCodexHookTrustState(configWithRuntimeFeatures, pkgRoot, hooksPath, {
|
|
2159
|
+
...managedHookOptions,
|
|
2160
|
+
managedTrustState: managedHookTrustState,
|
|
2161
|
+
priorManagedHookTrustState,
|
|
2162
|
+
legacyHookTrustState,
|
|
2163
|
+
}),
|
|
2164
|
+
hooksFinalContent: managedHooksPlan ? managedHooksPlan.finalContent : existingHooksContent,
|
|
2165
|
+
hooksRemovedCount: managedHooksPlan?.removedCount ?? 0,
|
|
2166
|
+
cleanedLegacyConfig: configAfterLegacyCleanup !== existingConfig,
|
|
2167
|
+
diagnostics: managedHooksPlan?.diagnostics ?? [],
|
|
2168
|
+
};
|
|
1278
2169
|
}
|
|
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";
|
|
2170
|
+
function buildPluginDeveloperInstructionsConfigPlan(existingConfig, decision) {
|
|
2171
|
+
if (decision.action === "preserve") {
|
|
2172
|
+
return {
|
|
2173
|
+
finalConfig: existingConfig,
|
|
2174
|
+
result: decision.state === "missing" ? "skipped" : "exists",
|
|
2175
|
+
};
|
|
1289
2176
|
}
|
|
1290
2177
|
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);
|
|
1309
|
-
}
|
|
1310
|
-
summary.updated += 1;
|
|
1311
|
-
if (options.verbose) {
|
|
1312
|
-
console.log(` ${options.dryRun ? "would add" : "added"} plugin developer_instructions default to ${configPath}`);
|
|
2178
|
+
const hasExistingDeveloperInstructions = rootHasTomlKey(existingConfig, "developer_instructions");
|
|
2179
|
+
if (hasExistingDeveloperInstructions && decision.action === "add") {
|
|
2180
|
+
return { finalConfig: existingConfig, result: "exists" };
|
|
1313
2181
|
}
|
|
1314
|
-
return
|
|
2182
|
+
return {
|
|
2183
|
+
finalConfig: hasExistingDeveloperInstructions
|
|
2184
|
+
? replaceRootTomlKey(existingConfig, "developer_instructions", line)
|
|
2185
|
+
: insertRootTomlKey(existingConfig, line),
|
|
2186
|
+
result: "updated",
|
|
2187
|
+
};
|
|
1315
2188
|
}
|
|
1316
|
-
|
|
1317
|
-
if (!existsSync(configPath))
|
|
1318
|
-
return false;
|
|
1319
|
-
const original = await readFile(configPath, "utf-8");
|
|
2189
|
+
function buildPluginModeLegacyConfig(original, options) {
|
|
1320
2190
|
const preservedFirstPartyMcp = options.preserveFirstPartyMcp
|
|
1321
2191
|
? extractFirstPartyOmxMcpSections(original)
|
|
1322
2192
|
: "";
|
|
1323
2193
|
let config = original;
|
|
1324
2194
|
config = stripFirstPartyOmxMcpSections(config);
|
|
1325
|
-
config = stripExistingOmxBlocks(config
|
|
2195
|
+
config = stripExistingOmxBlocks(config, {
|
|
2196
|
+
managedTrustState: options.managedHookTrustState,
|
|
2197
|
+
priorManagedHookTrustState: options.priorManagedHookTrustState,
|
|
2198
|
+
}).cleaned;
|
|
1326
2199
|
config = stripExistingSharedMcpRegistryBlock(config).cleaned;
|
|
1327
2200
|
config = stripPluginModeLegacyRootDefaults(config, options.developerInstructionsDecision);
|
|
1328
2201
|
config = stripOmxSeededBehavioralDefaults(config);
|
|
1329
2202
|
config = stripOmxFeatureFlags(config, { preserveMultiAgent: true });
|
|
1330
|
-
config = stripManagedCodexHookTrustState(config
|
|
2203
|
+
config = stripManagedCodexHookTrustState(config, {
|
|
2204
|
+
managedTrustState: options.managedHookTrustState,
|
|
2205
|
+
priorManagedHookTrustState: options.priorManagedHookTrustState,
|
|
2206
|
+
});
|
|
1331
2207
|
config = stripOmxEnvSettings(config);
|
|
1332
2208
|
if (preservedFirstPartyMcp) {
|
|
1333
2209
|
config = `${config.trimEnd()}\n\n${preservedFirstPartyMcp}\n`;
|
|
1334
2210
|
}
|
|
1335
2211
|
config = config.trim();
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
2212
|
+
return config.length > 0 ? `${config}\n` : "";
|
|
2213
|
+
}
|
|
2214
|
+
async function planNativeHookSetupTransaction(options) {
|
|
2215
|
+
const existingConfigSnapshot = options.configSnapshot;
|
|
2216
|
+
const existingHooksSnapshot = await captureNativeHookTransactionArtifact(options.hooksPath, `native hooks ${options.hooksPath}`);
|
|
2217
|
+
const existingConfig = existingConfigSnapshot.bytes
|
|
2218
|
+
? decodeNativeHookTransactionUtf8(existingConfigSnapshot.bytes, `config ${options.configPath}`)
|
|
2219
|
+
: "";
|
|
2220
|
+
const existingHooksContent = existingHooksSnapshot.bytes
|
|
2221
|
+
? decodeNativeHookTransactionUtf8(existingHooksSnapshot.bytes, `native hooks ${options.hooksPath}`)
|
|
2222
|
+
: null;
|
|
2223
|
+
let finalConfig = existingConfig;
|
|
2224
|
+
let finalHooksContent = existingHooksContent;
|
|
2225
|
+
let hooksRemovedCount = 0;
|
|
2226
|
+
let cleanedLegacyConfig = false;
|
|
2227
|
+
let pluginMarketplaceResult = "unchanged";
|
|
2228
|
+
let pluginDeveloperInstructionsResult = "skipped";
|
|
2229
|
+
let diagnostics = [];
|
|
2230
|
+
let notifyMetadataArtifact = null;
|
|
2231
|
+
let notifyMetadataPrecondition = null;
|
|
2232
|
+
let modelUpgrade;
|
|
2233
|
+
let repairedLegacyTeamRunTable = false;
|
|
2234
|
+
if (options.isPluginInstallMode) {
|
|
2235
|
+
const pluginPlan = buildPluginModeHooksConfigPlan(existingConfig, existingHooksContent, options.pkgRoot, options.hooksPath, options.codexHomeDir, {
|
|
2236
|
+
codexHookFeatureFlag: options.codexHookFeatureFlag,
|
|
2237
|
+
pluginScopedHooks: options.pluginScopedHooks,
|
|
2238
|
+
preserveFirstPartyMcp: options.preserveFirstPartyMcp,
|
|
2239
|
+
developerInstructionsDecision: options.pluginDeveloperInstructionsDecision,
|
|
2240
|
+
platform: options.platform,
|
|
2241
|
+
});
|
|
2242
|
+
finalConfig = pluginPlan.finalConfig;
|
|
2243
|
+
finalHooksContent = pluginPlan.hooksFinalContent;
|
|
2244
|
+
hooksRemovedCount = pluginPlan.hooksRemovedCount;
|
|
2245
|
+
cleanedLegacyConfig = pluginPlan.cleanedLegacyConfig;
|
|
2246
|
+
diagnostics = pluginPlan.diagnostics;
|
|
2247
|
+
if (options.pluginMarketplaceAvailable) {
|
|
2248
|
+
const withMarketplace = upsertLocalOmxMarketplaceRegistration(upsertLocalOmxPluginMcpServerEnablement(upsertLocalOmxPluginEnablement(finalConfig), options.mcpMode === "compat", { removeWhenDisabled: options.removeFirstPartyMcp }), options.pkgRoot);
|
|
2249
|
+
pluginMarketplaceResult =
|
|
2250
|
+
withMarketplace === finalConfig ? "unchanged" : "updated";
|
|
2251
|
+
finalConfig = withMarketplace;
|
|
1345
2252
|
}
|
|
1346
2253
|
else {
|
|
1347
|
-
|
|
2254
|
+
pluginMarketplaceResult = "unavailable";
|
|
1348
2255
|
}
|
|
2256
|
+
const developerInstructionsPlan = buildPluginDeveloperInstructionsConfigPlan(finalConfig, options.pluginDeveloperInstructionsDecision);
|
|
2257
|
+
finalConfig = developerInstructionsPlan.finalConfig;
|
|
2258
|
+
pluginDeveloperInstructionsResult = developerInstructionsPlan.result;
|
|
1349
2259
|
}
|
|
1350
|
-
|
|
1351
|
-
|
|
2260
|
+
else {
|
|
2261
|
+
const managedHooksPlan = planManagedCodexHooksMerge(existingHooksContent, options.pkgRoot, options.hooksPath, {
|
|
2262
|
+
platform: options.platform,
|
|
2263
|
+
codexHomeDir: options.codexHomeDir,
|
|
2264
|
+
});
|
|
2265
|
+
if (!managedHooksPlan.ok)
|
|
2266
|
+
throw managedHooksPlan.error;
|
|
2267
|
+
if (managedHooksPlan.finalContent === null) {
|
|
2268
|
+
throw new Error("Native hook merge unexpectedly produced no hooks.json content.");
|
|
2269
|
+
}
|
|
2270
|
+
finalHooksContent = managedHooksPlan.finalContent;
|
|
2271
|
+
hooksRemovedCount = managedHooksPlan.removedCount;
|
|
2272
|
+
diagnostics = managedHooksPlan.diagnostics;
|
|
2273
|
+
const managedConfigPlan = await planManagedConfig(options.hooksPath, options.pkgRoot, options.sharedMcpRegistry, options.mcpMode, options.preserveFirstPartyMcp, options.resolvedScope, options.codexHomeDir, {
|
|
2274
|
+
modelUpgradePrompt: options.modelUpgradePrompt,
|
|
2275
|
+
existingConfig,
|
|
2276
|
+
verbose: false,
|
|
2277
|
+
statusLinePreset: options.statusLinePreset,
|
|
2278
|
+
forceStatusLinePreset: options.forceStatusLinePreset,
|
|
2279
|
+
codexHookFeatureFlag: options.codexHookFeatureFlag,
|
|
2280
|
+
managedHookTrustState: managedHooksPlan.finalTrustState,
|
|
2281
|
+
priorManagedHookTrustState: managedHooksPlan.priorTrustState,
|
|
2282
|
+
legacyHookTrustState: managedHooksPlan.legacyTrustState,
|
|
2283
|
+
hookCommandPlatform: options.platform,
|
|
2284
|
+
notifyMetadataSnapshot: options.notifyMetadataSnapshot,
|
|
2285
|
+
});
|
|
2286
|
+
finalConfig = managedConfigPlan.finalConfig;
|
|
2287
|
+
if (managedConfigPlan.currentModel &&
|
|
2288
|
+
managedConfigPlan.modelOverride &&
|
|
2289
|
+
managedConfigPlan.currentModel !== managedConfigPlan.modelOverride) {
|
|
2290
|
+
modelUpgrade = {
|
|
2291
|
+
currentModel: managedConfigPlan.currentModel,
|
|
2292
|
+
modelOverride: managedConfigPlan.modelOverride,
|
|
2293
|
+
};
|
|
2294
|
+
}
|
|
2295
|
+
repairedLegacyTeamRunTable = managedConfigPlan.repairedLegacyTeamRunTable;
|
|
2296
|
+
const notifyPlan = managedConfigPlan.notifyPlan;
|
|
2297
|
+
if (notifyPlan.metadataSnapshot && notifyPlan.metadataPath) {
|
|
2298
|
+
notifyMetadataPrecondition = nativeHookTransactionPrecondition("metadata", notifyPlan.metadataPath, `notification metadata ${notifyPlan.metadataPath}`, notifyPlan.metadataSnapshot);
|
|
2299
|
+
}
|
|
2300
|
+
if (notifyPlan.metadataPath && notifyPlan.metadata) {
|
|
2301
|
+
const metadataBefore = options.notifyMetadataSnapshot;
|
|
2302
|
+
if (!metadataBefore) {
|
|
2303
|
+
throw new Error(`Missing notification metadata snapshot for ${notifyPlan.metadataPath}; refusing to plan from a newly captured baseline.`);
|
|
2304
|
+
}
|
|
2305
|
+
notifyMetadataArtifact = nativeHookTransactionArtifact("metadata", notifyPlan.metadataPath, `notification metadata ${notifyPlan.metadataPath}`, metadataBefore, Buffer.from(JSON.stringify(notifyPlan.metadata, null, 2) + "\n", "utf-8"));
|
|
2306
|
+
if (!notifyMetadataPrecondition) {
|
|
2307
|
+
notifyMetadataPrecondition = nativeHookTransactionPrecondition("metadata", notifyPlan.metadataPath, `notification metadata ${notifyPlan.metadataPath}`, metadataBefore);
|
|
2308
|
+
}
|
|
2309
|
+
}
|
|
1352
2310
|
}
|
|
1353
|
-
|
|
2311
|
+
if (options.isPluginInstallMode && options.sharedMcpRegistry.servers.length > 0) {
|
|
2312
|
+
finalConfig = mergeSharedMcpRegistryBlock(finalConfig, options.sharedMcpRegistry.servers, options.sharedMcpRegistry.sourcePath);
|
|
2313
|
+
}
|
|
2314
|
+
try {
|
|
2315
|
+
TOML.parse(finalConfig);
|
|
2316
|
+
}
|
|
2317
|
+
catch (error) {
|
|
2318
|
+
throw new Error(`Refusing to write invalid planned config.toml: ${error instanceof Error ? error.message : String(error)}`);
|
|
2319
|
+
}
|
|
2320
|
+
const hookArtifact = nativeHookTransactionArtifact("hooks", options.hooksPath, `native hooks ${options.hooksPath}`, existingHooksSnapshot, finalHooksContent === null ? null : Buffer.from(finalHooksContent, "utf-8"), options.platform);
|
|
2321
|
+
const configArtifact = nativeHookTransactionArtifact("config", options.configPath, `config ${options.configPath}`, existingConfigSnapshot, Buffer.from(finalConfig, "utf-8"));
|
|
2322
|
+
let shimArtifact = null;
|
|
2323
|
+
let shimPrecondition = null;
|
|
2324
|
+
if (options.platform === "win32") {
|
|
2325
|
+
const shimPath = buildManagedCodexNativeHookWindowsShimPath(options.codexHomeDir);
|
|
2326
|
+
const shimSnapshot = await captureNativeHookTransactionArtifact(shimPath, `native hook Windows shim ${shimPath}`);
|
|
2327
|
+
const shimAfter = Buffer.from(buildManagedCodexNativeHookWindowsShimContent(options.pkgRoot), "utf-8");
|
|
2328
|
+
assertWindowsNativeHookShimOwnership(shimPath, shimSnapshot.bytes, shimAfter);
|
|
2329
|
+
const shimReference = decideWindowsNativeHookShimReference(finalHooksContent, shimPath);
|
|
2330
|
+
shimArtifact = nativeHookTransactionArtifact("shim", shimPath, `native hook Windows shim ${shimPath}`, shimSnapshot, options.isPluginInstallMode && options.pluginScopedHooks &&
|
|
2331
|
+
shimReference === "not_referenced"
|
|
2332
|
+
? null
|
|
2333
|
+
: shimAfter);
|
|
2334
|
+
shimPrecondition = nativeHookTransactionPrecondition("shim", shimPath, `native hook Windows shim ${shimPath}`, shimSnapshot);
|
|
2335
|
+
}
|
|
2336
|
+
const windowsShimCreateOrUpdateArtifact = shimArtifact && shimArtifact.after !== null ? shimArtifact : null;
|
|
2337
|
+
const windowsShimDeletionArtifact = shimArtifact && shimArtifact.after === null ? shimArtifact : null;
|
|
2338
|
+
const artifacts = options.platform === "win32"
|
|
2339
|
+
? windowsShimDeletionArtifact
|
|
2340
|
+
? [
|
|
2341
|
+
hookArtifact,
|
|
2342
|
+
windowsShimDeletionArtifact,
|
|
2343
|
+
notifyMetadataArtifact,
|
|
2344
|
+
configArtifact,
|
|
2345
|
+
]
|
|
2346
|
+
: [
|
|
2347
|
+
windowsShimCreateOrUpdateArtifact,
|
|
2348
|
+
hookArtifact,
|
|
2349
|
+
notifyMetadataArtifact,
|
|
2350
|
+
configArtifact,
|
|
2351
|
+
]
|
|
2352
|
+
: [hookArtifact, notifyMetadataArtifact, configArtifact];
|
|
2353
|
+
const preconditions = [
|
|
2354
|
+
shimPrecondition,
|
|
2355
|
+
nativeHookTransactionPrecondition("hooks", options.hooksPath, `native hooks ${options.hooksPath}`, existingHooksSnapshot),
|
|
2356
|
+
nativeHookTransactionPrecondition("config", options.configPath, `config ${options.configPath}`, existingConfigSnapshot),
|
|
2357
|
+
notifyMetadataPrecondition,
|
|
2358
|
+
];
|
|
2359
|
+
return {
|
|
2360
|
+
artifacts: artifacts.filter((artifact) => artifact !== null),
|
|
2361
|
+
preconditions: preconditions.filter((precondition) => precondition !== null),
|
|
2362
|
+
finalConfig,
|
|
2363
|
+
hooksRemovedCount,
|
|
2364
|
+
pluginScopedHooks: options.pluginScopedHooks,
|
|
2365
|
+
cleanedLegacyConfig,
|
|
2366
|
+
pluginMarketplaceResult,
|
|
2367
|
+
pluginDeveloperInstructionsResult,
|
|
2368
|
+
diagnostics,
|
|
2369
|
+
modelUpgrade,
|
|
2370
|
+
repairedLegacyTeamRunTable,
|
|
2371
|
+
};
|
|
1354
2372
|
}
|
|
1355
2373
|
export async function setup(options = {}) {
|
|
1356
2374
|
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 +2390,15 @@ export async function setup(options = {}) {
|
|
|
1372
2390
|
Boolean(persistedPreferences?.teamMode) &&
|
|
1373
2391
|
(!persistedPreferences?.scope ||
|
|
1374
2392
|
persistedPreferences.scope === effectiveScopeForInstallMode);
|
|
2393
|
+
const wouldUsePersistedMergeAgents = !options.mergeAgentsPolicy &&
|
|
2394
|
+
typeof options.mergeAgents !== "boolean" &&
|
|
2395
|
+
resolvePersistedSetupMergeAgents(persistedPreferences, effectiveScopeForInstallMode) !== undefined;
|
|
1375
2396
|
const shouldReviewPersistedSetup = hasPersistedSetupPreferences(persistedPreferences) &&
|
|
1376
|
-
(wouldUsePersistedScope ||
|
|
2397
|
+
(wouldUsePersistedScope ||
|
|
2398
|
+
wouldUsePersistedInstallMode ||
|
|
2399
|
+
wouldUsePersistedMcpMode ||
|
|
2400
|
+
wouldUsePersistedTeamMode ||
|
|
2401
|
+
wouldUsePersistedMergeAgents) &&
|
|
1377
2402
|
(typeof persistedSetupReviewPrompt === "function" ||
|
|
1378
2403
|
(process.stdin.isTTY && process.stdout.isTTY));
|
|
1379
2404
|
if (shouldReviewPersistedSetup) {
|
|
@@ -1383,6 +2408,18 @@ export async function setup(options = {}) {
|
|
|
1383
2408
|
console.log(`Setup preference review: ${persistedReviewDecision} (${formatPersistedSetupPreferenceSummary(persistedPreferences)})\n`);
|
|
1384
2409
|
}
|
|
1385
2410
|
const resolvedScope = await resolveSetupScope(projectRoot, requestedScope, persistedReviewDecision, persistedPreferences, setupScopePrompt);
|
|
2411
|
+
const requestedMergeAgentsPolicy = options.mergeAgentsPolicy ??
|
|
2412
|
+
(typeof options.mergeAgents === "boolean"
|
|
2413
|
+
? { kind: "set", value: options.mergeAgents }
|
|
2414
|
+
: undefined);
|
|
2415
|
+
const inheritedMergeAgents = persistedReviewDecision === "reset"
|
|
2416
|
+
? undefined
|
|
2417
|
+
: resolvePersistedSetupMergeAgents(persistedPreferences, resolvedScope.scope);
|
|
2418
|
+
const effectiveMergeAgents = requestedMergeAgentsPolicy?.kind === "set"
|
|
2419
|
+
? requestedMergeAgentsPolicy.value
|
|
2420
|
+
: requestedMergeAgentsPolicy?.kind === "clear"
|
|
2421
|
+
? undefined
|
|
2422
|
+
: inheritedMergeAgents;
|
|
1386
2423
|
const resolvedInstallMode = await resolveSetupInstallMode(projectRoot, resolvedScope.scope, requestedInstallMode, installModePrompt, persistedReviewDecision, persistedPreferences);
|
|
1387
2424
|
const resolvedMcpMode = resolveSetupMcpMode(resolvedScope.scope, requestedMcpMode, persistedReviewDecision, persistedPreferences);
|
|
1388
2425
|
const resolvedTeamMode = requestedTeamMode
|
|
@@ -1395,9 +2432,32 @@ export async function setup(options = {}) {
|
|
|
1395
2432
|
const skipNativeAgentRefresh = requestedSkipNativeAgentRefresh ||
|
|
1396
2433
|
process.env[SKIP_NATIVE_AGENT_REFRESH_ENV] === "1";
|
|
1397
2434
|
const scopeDirs = resolveScopeDirectories(resolvedScope.scope, projectRoot);
|
|
1398
|
-
|
|
1399
|
-
|
|
2435
|
+
if (!dryRun) {
|
|
2436
|
+
const recoveryTracker = { degraded: false };
|
|
2437
|
+
const recovery = await recoverNativeHookClaimJournal(scopeDirs.codexHomeDir, nativeHookClaimJournalDurability());
|
|
2438
|
+
recordRegularFileSyncOutcome(recoveryTracker, recovery.outcome);
|
|
2439
|
+
if (recovery.recovered) {
|
|
2440
|
+
emitDegradedDurabilityWarning("native-hook claim-journal recovery", recoveryTracker);
|
|
2441
|
+
}
|
|
2442
|
+
}
|
|
2443
|
+
const nativeHookTransactionPlatform = nativeHookPlatform();
|
|
2444
|
+
let nativeHookTransactionAncestorPrecondition = await captureNativeHookTransactionAncestorPrecondition(scopeDirs.codexHomeDir, [
|
|
2445
|
+
scopeDirs.codexConfigFile,
|
|
2446
|
+
scopeDirs.codexHooksFile,
|
|
2447
|
+
getNotifyMetadataPath(scopeDirs.codexHomeDir),
|
|
2448
|
+
...(nativeHookTransactionPlatform === "win32"
|
|
2449
|
+
? [
|
|
2450
|
+
buildManagedCodexNativeHookWindowsShimPath(scopeDirs.codexHomeDir),
|
|
2451
|
+
]
|
|
2452
|
+
: []),
|
|
2453
|
+
]);
|
|
2454
|
+
const existingConfigForMcpMigrationSnapshot = await captureNativeHookTransactionArtifact(scopeDirs.codexConfigFile, `config ${scopeDirs.codexConfigFile}`);
|
|
2455
|
+
const existingConfigForMcpMigration = existingConfigForMcpMigrationSnapshot.bytes
|
|
2456
|
+
? decodeNativeHookTransactionUtf8(existingConfigForMcpMigrationSnapshot.bytes, `config ${scopeDirs.codexConfigFile}`)
|
|
1400
2457
|
: "";
|
|
2458
|
+
const notifyMetadataSnapshot = configRequiresNotificationMetadataSnapshot(existingConfigForMcpMigration, pkgRoot, resolvedScope.scope)
|
|
2459
|
+
? await captureNativeHookTransactionArtifact(getNotifyMetadataPath(scopeDirs.codexHomeDir), `notification metadata ${getNotifyMetadataPath(scopeDirs.codexHomeDir)}`)
|
|
2460
|
+
: undefined;
|
|
1401
2461
|
const firstPartyMcpRegistrationKinds = [
|
|
1402
2462
|
hasFirstPartyOmxMcpRegistrations(existingConfigForMcpMigration)
|
|
1403
2463
|
? "config.toml [mcp_servers.omx_*]"
|
|
@@ -1425,7 +2485,7 @@ export async function setup(options = {}) {
|
|
|
1425
2485
|
? join(projectRoot, "AGENTS.md")
|
|
1426
2486
|
: join(scopeDirs.codexHomeDir, "AGENTS.md");
|
|
1427
2487
|
const pluginDeveloperInstructionsDecision = isPluginInstallMode
|
|
1428
|
-
? await resolvePluginDeveloperInstructionsDecision(scopeDirs.codexConfigFile, { pluginDeveloperInstructionsPrompt })
|
|
2488
|
+
? await resolvePluginDeveloperInstructionsDecision(existingConfigForMcpMigration, scopeDirs.codexConfigFile, { pluginDeveloperInstructionsPrompt })
|
|
1429
2489
|
: {
|
|
1430
2490
|
action: "preserve",
|
|
1431
2491
|
state: "custom",
|
|
@@ -1443,7 +2503,7 @@ export async function setup(options = {}) {
|
|
|
1443
2503
|
pluginAgentsMdIsSymlink = false;
|
|
1444
2504
|
}
|
|
1445
2505
|
const usePluginAgentsMdDefault = isPluginInstallMode
|
|
1446
|
-
?
|
|
2506
|
+
? effectiveMergeAgents || pluginAgentsMdIsSymlink
|
|
1447
2507
|
? false
|
|
1448
2508
|
: force
|
|
1449
2509
|
? true
|
|
@@ -1451,6 +2511,55 @@ export async function setup(options = {}) {
|
|
|
1451
2511
|
? await pluginAgentsMdPrompt(pluginAgentsMdDst)
|
|
1452
2512
|
: await promptForPluginAgentsMdDefault(pluginAgentsMdDst)
|
|
1453
2513
|
: false;
|
|
2514
|
+
const codexHookFeatureSupport = resolveCodexHookFeatureSupportForCli({
|
|
2515
|
+
codexFeaturesProbe: options.codexFeaturesProbe,
|
|
2516
|
+
codexVersionProbe: options.codexVersionProbe,
|
|
2517
|
+
});
|
|
2518
|
+
const codexHookFeatureFlag = codexHookFeatureSupport.hookFeatureFlag;
|
|
2519
|
+
const pluginScopedHooksSupported = codexHookFeatureSupport.pluginScopedHooks;
|
|
2520
|
+
const shouldSyncSharedMcpRegistry = resolvedMcpMode.mcpMode === "compat";
|
|
2521
|
+
const registryCandidates = getUnifiedMcpRegistryCandidates();
|
|
2522
|
+
const defaultRegistryCandidates = registryCandidates.slice(0, 1);
|
|
2523
|
+
const sharedMcpRegistry = shouldSyncSharedMcpRegistry
|
|
2524
|
+
? await loadUnifiedMcpRegistry({
|
|
2525
|
+
candidates: options.mcpRegistryCandidates ?? defaultRegistryCandidates,
|
|
2526
|
+
})
|
|
2527
|
+
: { servers: [], warnings: [] };
|
|
2528
|
+
const legacyRegistryCandidate = getLegacyUnifiedMcpRegistryCandidate();
|
|
2529
|
+
const preflightMarketplace = isPluginInstallMode
|
|
2530
|
+
? await resolvePackagedOmxMarketplace(pkgRoot)
|
|
2531
|
+
: null;
|
|
2532
|
+
const statusLinePreset = isPluginInstallMode
|
|
2533
|
+
? undefined
|
|
2534
|
+
: await resolveStatusLinePresetForSetup(projectRoot, { force });
|
|
2535
|
+
const nativeHookSetupTransaction = await planNativeHookSetupTransaction({
|
|
2536
|
+
configPath: scopeDirs.codexConfigFile,
|
|
2537
|
+
hooksPath: scopeDirs.codexHooksFile,
|
|
2538
|
+
codexHomeDir: scopeDirs.codexHomeDir,
|
|
2539
|
+
pkgRoot,
|
|
2540
|
+
platform: nativeHookTransactionPlatform,
|
|
2541
|
+
isPluginInstallMode,
|
|
2542
|
+
pluginScopedHooks: pluginScopedHooksSupported,
|
|
2543
|
+
codexHookFeatureFlag,
|
|
2544
|
+
preserveFirstPartyMcp: shouldOfferFirstPartyMcpRemoval && !removeFirstPartyMcpRegistrations,
|
|
2545
|
+
removeFirstPartyMcp: removeFirstPartyMcpRegistrations,
|
|
2546
|
+
pluginDeveloperInstructionsDecision,
|
|
2547
|
+
pluginMarketplaceAvailable: preflightMarketplace !== null,
|
|
2548
|
+
sharedMcpRegistry,
|
|
2549
|
+
mcpMode: resolvedMcpMode.mcpMode,
|
|
2550
|
+
resolvedScope: resolvedScope.scope,
|
|
2551
|
+
modelUpgradePrompt,
|
|
2552
|
+
statusLinePreset,
|
|
2553
|
+
forceStatusLinePreset: force,
|
|
2554
|
+
configSnapshot: existingConfigForMcpMigrationSnapshot,
|
|
2555
|
+
notifyMetadataSnapshot,
|
|
2556
|
+
});
|
|
2557
|
+
const summary = createEmptyRunSummary();
|
|
2558
|
+
for (const precondition of nativeHookSetupTransaction.preconditions) {
|
|
2559
|
+
injectNativeHookTransactionFailure("before_precondition", precondition);
|
|
2560
|
+
await assertNativeHookTransactionPrecondition(precondition);
|
|
2561
|
+
}
|
|
2562
|
+
await assertNativeHookTransactionAncestorPrecondition(nativeHookTransactionAncestorPrecondition);
|
|
1454
2563
|
console.log("oh-my-codex setup");
|
|
1455
2564
|
console.log("=================\n");
|
|
1456
2565
|
console.log(`Using setup scope: ${resolvedScope.scope}${scopeSourceMessage}\n`);
|
|
@@ -1510,11 +2619,8 @@ export async function setup(options = {}) {
|
|
|
1510
2619
|
resolvedInstallMode.installMode === "plugin")
|
|
1511
2620
|
? { installMode: resolvedInstallMode.installMode }
|
|
1512
2621
|
: {}),
|
|
2622
|
+
...(effectiveMergeAgents !== undefined ? { mergeAgents: effectiveMergeAgents } : {}),
|
|
1513
2623
|
};
|
|
1514
|
-
await persistSetupPreferences(projectRoot, setupPreferencesToPersist, {
|
|
1515
|
-
dryRun,
|
|
1516
|
-
verbose,
|
|
1517
|
-
});
|
|
1518
2624
|
console.log(" Done.\n");
|
|
1519
2625
|
if (resolvedScope.scope === "project") {
|
|
1520
2626
|
const gitignoreResult = await ensureProjectGitignore(projectRoot, backupContext, { dryRun, verbose });
|
|
@@ -1526,7 +2632,6 @@ export async function setup(options = {}) {
|
|
|
1526
2632
|
}
|
|
1527
2633
|
}
|
|
1528
2634
|
const catalogCounts = getCatalogHeadlineCounts();
|
|
1529
|
-
const summary = createEmptyRunSummary();
|
|
1530
2635
|
// Step 2: Install agent prompts
|
|
1531
2636
|
console.log("[2/8] Installing agent prompts...");
|
|
1532
2637
|
{
|
|
@@ -1624,27 +2729,12 @@ export async function setup(options = {}) {
|
|
|
1624
2729
|
}
|
|
1625
2730
|
// Step 5: Update config.toml
|
|
1626
2731
|
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;
|
|
2732
|
+
const resolvedConfig = nativeHookSetupTransaction.finalConfig;
|
|
2733
|
+
const omxManagesTui = !isPluginInstallMode;
|
|
1635
2734
|
if (verbose) {
|
|
1636
2735
|
console.log(` Native Codex hook feature flag: [features].${codexHookFeatureFlag}`);
|
|
1637
2736
|
console.log(` Plugin-scoped Codex hooks: ${pluginScopedHooksSupported ? "supported" : "not reported; using legacy setup fallback"}`);
|
|
1638
2737
|
}
|
|
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
2738
|
if (shouldSyncSharedMcpRegistry &&
|
|
1649
2739
|
!options.mcpRegistryCandidates &&
|
|
1650
2740
|
!sharedMcpRegistry.sourcePath &&
|
|
@@ -1658,36 +2748,51 @@ export async function setup(options = {}) {
|
|
|
1658
2748
|
for (const warning of sharedMcpRegistry.warnings) {
|
|
1659
2749
|
console.log(` warning: ${warning}`);
|
|
1660
2750
|
}
|
|
2751
|
+
logManagedCodexHooksPlanDiagnostics(nativeHookSetupTransaction.diagnostics, {
|
|
2752
|
+
verbose,
|
|
2753
|
+
});
|
|
2754
|
+
const changedHookArtifact = nativeHookSetupTransaction.artifacts.some((artifact) => artifact.kind === "hooks");
|
|
2755
|
+
const changedShimArtifact = nativeHookSetupTransaction.artifacts.some((artifact) => artifact.kind === "shim");
|
|
2756
|
+
const changedConfigArtifact = nativeHookSetupTransaction.artifacts.some((artifact) => artifact.kind === "config");
|
|
2757
|
+
if (changedHookArtifact) {
|
|
2758
|
+
if (isPluginInstallMode &&
|
|
2759
|
+
nativeHookSetupTransaction.pluginScopedHooks &&
|
|
2760
|
+
nativeHookSetupTransaction.hooksRemovedCount > 0) {
|
|
2761
|
+
summary.config.removed += nativeHookSetupTransaction.hooksRemovedCount;
|
|
2762
|
+
}
|
|
2763
|
+
else {
|
|
2764
|
+
summary.config.updated += 1;
|
|
2765
|
+
}
|
|
2766
|
+
}
|
|
2767
|
+
else if (!isPluginInstallMode || !nativeHookSetupTransaction.pluginScopedHooks) {
|
|
2768
|
+
summary.config.unchanged += 1;
|
|
2769
|
+
}
|
|
2770
|
+
if (changedShimArtifact)
|
|
2771
|
+
summary.config.updated += 1;
|
|
2772
|
+
if (changedConfigArtifact)
|
|
2773
|
+
summary.config.updated += 1;
|
|
2774
|
+
else
|
|
2775
|
+
summary.config.unchanged += 1;
|
|
2776
|
+
if (verbose && nativeHookSetupTransaction.modelUpgrade) {
|
|
2777
|
+
console.log(` ${dryRun ? "would update" : "updated"} root model from ${nativeHookSetupTransaction.modelUpgrade.currentModel} to ${nativeHookSetupTransaction.modelUpgrade.modelOverride}`);
|
|
2778
|
+
}
|
|
1661
2779
|
if (isPluginInstallMode) {
|
|
1662
|
-
|
|
1663
|
-
dryRun,
|
|
1664
|
-
verbose,
|
|
1665
|
-
preserveFirstPartyMcp: shouldOfferFirstPartyMcpRemoval &&
|
|
1666
|
-
!removeFirstPartyMcpRegistrations,
|
|
1667
|
-
developerInstructionsDecision: pluginDeveloperInstructionsDecision,
|
|
1668
|
-
});
|
|
1669
|
-
if (configCleaned)
|
|
2780
|
+
if (nativeHookSetupTransaction.cleanedLegacyConfig) {
|
|
1670
2781
|
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") {
|
|
2782
|
+
console.log(` ${dryRun ? "Would clean" : "Cleaned"} legacy OMX config entries for plugin mode.\n`);
|
|
2783
|
+
}
|
|
2784
|
+
else {
|
|
2785
|
+
console.log(" Config refresh skipped; no legacy OMX config entries found.\n");
|
|
2786
|
+
}
|
|
2787
|
+
if (nativeHookSetupTransaction.pluginMarketplaceResult === "unavailable") {
|
|
1682
2788
|
console.log(` warning: packaged ${OMX_LOCAL_MARKETPLACE_NAME} Codex plugin marketplace metadata not found; /skills plugin discovery was not registered.`);
|
|
1683
2789
|
}
|
|
1684
|
-
else if (pluginMarketplaceResult === "updated") {
|
|
2790
|
+
else if (nativeHookSetupTransaction.pluginMarketplaceResult === "updated") {
|
|
1685
2791
|
console.log(` ${dryRun ? "Would register" : "Registered"} local Codex plugin marketplace ${OMX_LOCAL_MARKETPLACE_NAME} (${pkgRoot}).`);
|
|
1686
2792
|
}
|
|
1687
2793
|
else {
|
|
1688
2794
|
console.log(` Local Codex plugin marketplace ${OMX_LOCAL_MARKETPLACE_NAME} already registered (${pkgRoot}).`);
|
|
1689
2795
|
}
|
|
1690
|
-
const packagedMarketplace = await resolvePackagedOmxMarketplace(pkgRoot);
|
|
1691
2796
|
const pluginCacheRefresh = await refreshOmxPluginDiscoveryCache(pkgRoot, {
|
|
1692
2797
|
dryRun,
|
|
1693
2798
|
verbose,
|
|
@@ -1700,38 +2805,25 @@ export async function setup(options = {}) {
|
|
|
1700
2805
|
else if (pluginCacheRefresh.status === "unchanged") {
|
|
1701
2806
|
console.log(" Codex plugin discovery cache already matches packaged plugin metadata.");
|
|
1702
2807
|
}
|
|
1703
|
-
const pluginCacheMaterialize = await materializePackagedOmxPluginCache(scopeDirs.codexHomeDir,
|
|
2808
|
+
const pluginCacheMaterialize = await materializePackagedOmxPluginCache(scopeDirs.codexHomeDir, preflightMarketplace, { dryRun, teamMode: resolvedTeamMode });
|
|
1704
2809
|
if (pluginCacheMaterialize.status === "materialized") {
|
|
1705
2810
|
console.log(` ${dryRun ? "Would install" : "Installed"} local Codex plugin cache for ${OMX_LOCAL_MARKETPLACE_NAME}/${OMX_PLUGIN_NAME} at ${pluginCacheMaterialize.cacheDir}.`);
|
|
1706
2811
|
}
|
|
1707
2812
|
else if (pluginCacheMaterialize.status === "unchanged") {
|
|
1708
2813
|
console.log(" Local Codex plugin cache already exposes packaged OMX skills.");
|
|
1709
2814
|
}
|
|
1710
|
-
if (pluginCacheMaterialize.status === "materialized" ||
|
|
2815
|
+
if (pluginCacheMaterialize.status === "materialized" ||
|
|
2816
|
+
pluginCacheMaterialize.status === "unchanged") {
|
|
1711
2817
|
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
2818
|
}
|
|
1713
|
-
if (shouldSyncSharedMcpRegistry) {
|
|
1714
|
-
|
|
1715
|
-
if (resolvedScope.scope === "user") {
|
|
1716
|
-
await syncClaudeCodeMcpSettings(sharedMcpRegistry, summary.config, backupContext, { dryRun, verbose });
|
|
1717
|
-
}
|
|
2819
|
+
if (shouldSyncSharedMcpRegistry && resolvedScope.scope === "user") {
|
|
2820
|
+
await syncClaudeCodeMcpSettings(sharedMcpRegistry, summary.config, backupContext, { dryRun, verbose });
|
|
1718
2821
|
}
|
|
1719
|
-
resolvedConfig = existsSync(scopeDirs.codexConfigFile)
|
|
1720
|
-
? await readFile(scopeDirs.codexConfigFile, "utf-8")
|
|
1721
|
-
: "";
|
|
1722
2822
|
console.log(pluginScopedHooksSupported
|
|
1723
2823
|
? " Plugin-scoped Codex hooks and runtime feature flags refresh complete (plugin_hooks, goals).\n"
|
|
1724
2824
|
: ` Native Codex hooks fallback and runtime feature flags refresh complete (${scopeDirs.codexHooksFile}; hooks, goals).\n`);
|
|
1725
2825
|
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
|
-
: "";
|
|
2826
|
+
if (nativeHookSetupTransaction.pluginDeveloperInstructionsResult === "updated") {
|
|
1735
2827
|
console.log(` ${dryRun ? "Would add" : "Added"} plugin-mode developer_instructions default (${scopeDirs.codexConfigFile}).\n`);
|
|
1736
2828
|
}
|
|
1737
2829
|
else {
|
|
@@ -1743,31 +2835,13 @@ export async function setup(options = {}) {
|
|
|
1743
2835
|
}
|
|
1744
2836
|
}
|
|
1745
2837
|
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
2838
|
if (shouldSyncSharedMcpRegistry && resolvedScope.scope === "user") {
|
|
1761
2839
|
await syncClaudeCodeMcpSettings(sharedMcpRegistry, summary.config, backupContext, { dryRun, verbose });
|
|
1762
2840
|
}
|
|
2841
|
+
if (nativeHookSetupTransaction.repairedLegacyTeamRunTable) {
|
|
2842
|
+
console.log(" Removed retired [mcp_servers.omx_team_run] config during refresh.");
|
|
2843
|
+
}
|
|
1763
2844
|
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
2845
|
console.log(` Native Codex hooks refresh complete (${scopeDirs.codexHooksFile}).\n`);
|
|
1772
2846
|
}
|
|
1773
2847
|
// Step 5.5: Verify team CLI interop surface is available when Team is enabled.
|
|
@@ -1802,7 +2876,7 @@ export async function setup(options = {}) {
|
|
|
1802
2876
|
});
|
|
1803
2877
|
const modelTableDefinitions = getAgentsModelTableDefinitionsForTeamMode(resolvedTeamMode);
|
|
1804
2878
|
const rewritten = upsertAgentsModelTable(addGeneratedAgentsMarker(applyTeamModeToAgentsTemplate(applyPluginModeWordingToAgentsTemplate(content, resolvedScope.scope), resolvedTeamMode)), modelTableContext, modelTableDefinitions, { codexHomeOverride: scopeDirs.codexHomeDir });
|
|
1805
|
-
if (
|
|
2879
|
+
if (effectiveMergeAgents && pluginAgentsMdExists) {
|
|
1806
2880
|
if (pluginAgentsMdIsSymlink) {
|
|
1807
2881
|
summary.agentsMd.skipped += 1;
|
|
1808
2882
|
console.log(` Skipped plugin-mode AGENTS.md merge for symlinked ${pluginAgentsMdDst}; existing AGENTS.md left untouched.`);
|
|
@@ -1917,7 +2991,7 @@ export async function setup(options = {}) {
|
|
|
1917
2991
|
console.log(` WARNING: Existing AGENTS.md at ${agentsMdDst} lacks OMX contract markers; it may have been overwritten by another tool.`);
|
|
1918
2992
|
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
2993
|
}
|
|
1920
|
-
if (
|
|
2994
|
+
if (effectiveMergeAgents) {
|
|
1921
2995
|
mergedAgentsContent = upsertManagedAgentsBlock(existing, rewritten);
|
|
1922
2996
|
canApplyManagedAgentsMerge = mergedAgentsContent !== existing;
|
|
1923
2997
|
}
|
|
@@ -1949,7 +3023,7 @@ export async function setup(options = {}) {
|
|
|
1949
3023
|
console.log(" Skipping AGENTS.md overwrite to avoid corrupting runtime overlay.");
|
|
1950
3024
|
console.log(" Stop the active session first, then re-run setup.");
|
|
1951
3025
|
}
|
|
1952
|
-
else if (
|
|
3026
|
+
else if (effectiveMergeAgents &&
|
|
1953
3027
|
agentsMdExists &&
|
|
1954
3028
|
!canApplyManagedAgentsMerge) {
|
|
1955
3029
|
summary.agentsMd.unchanged += 1;
|
|
@@ -2044,6 +3118,16 @@ export async function setup(options = {}) {
|
|
|
2044
3118
|
console.log("Force mode: enabled additional destructive maintenance (for example stale deprecated skill cleanup).");
|
|
2045
3119
|
console.log();
|
|
2046
3120
|
}
|
|
3121
|
+
await persistSetupPreferences(projectRoot, setupPreferencesToPersist, {
|
|
3122
|
+
dryRun,
|
|
3123
|
+
verbose,
|
|
3124
|
+
});
|
|
3125
|
+
setupLatePhaseFailureInjector?.();
|
|
3126
|
+
nativeHookTransactionAncestorPrecondition =
|
|
3127
|
+
await captureNativeHookTransactionAncestorPrecondition(scopeDirs.codexHomeDir, nativeHookSetupTransaction.artifacts.map((artifact) => artifact.path));
|
|
3128
|
+
const nativeHookSetupDurabilityTracker = { degraded: false };
|
|
3129
|
+
await commitNativeHookTransaction(nativeHookSetupTransaction.artifacts, nativeHookSetupTransaction.preconditions, nativeHookTransactionAncestorPrecondition, backupContext, nativeHookSetupDurabilityTracker, summary.config, { dryRun, verbose });
|
|
3130
|
+
emitDegradedDurabilityWarning("native-hook setup", nativeHookSetupDurabilityTracker);
|
|
2047
3131
|
console.log('Setup complete! Run "omx doctor" to verify installation.');
|
|
2048
3132
|
console.log("\nNext steps:");
|
|
2049
3133
|
console.log(" 1. Start Codex CLI in your project directory");
|
|
@@ -2240,13 +3324,6 @@ async function syncNativeAgentToml(content, dstPath, summary, backupContext, opt
|
|
|
2240
3324
|
console.log(` ${options.dryRun ? "would update" : "updated"} ${verboseLabel}`);
|
|
2241
3325
|
}
|
|
2242
3326
|
}
|
|
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
3327
|
async function syncManagedAgentsContent(content, dstPath, summary, backupContext, options) {
|
|
2251
3328
|
const destinationExists = existsSync(dstPath);
|
|
2252
3329
|
let existing = "";
|
|
@@ -2710,7 +3787,34 @@ async function cleanupLegacyManagedSkills(srcDir, dstDir, backupContext, options
|
|
|
2710
3787
|
function getNotifyMetadataPath(codexHomeDir) {
|
|
2711
3788
|
return join(codexHomeDir, ".omx", "notify-dispatch.json");
|
|
2712
3789
|
}
|
|
2713
|
-
|
|
3790
|
+
function isOmxDispatcherNotifyCommand(notifyCommand, pkgRoot) {
|
|
3791
|
+
return Boolean(isOmxManagedNotifyCommand(notifyCommand, pkgRoot) &&
|
|
3792
|
+
notifyCommand?.some((part) => /(?:^|[\\/])notify-dispatcher\.js$/.test(part)));
|
|
3793
|
+
}
|
|
3794
|
+
function configRequiresNotificationMetadataSnapshot(existingConfig, pkgRoot, scope) {
|
|
3795
|
+
if (scope === "project")
|
|
3796
|
+
return false;
|
|
3797
|
+
const existingNotify = getRootTomlArray(existingConfig, "notify");
|
|
3798
|
+
return Boolean(existingNotify &&
|
|
3799
|
+
(!isOmxManagedNotifyCommand(existingNotify, pkgRoot) ||
|
|
3800
|
+
isOmxDispatcherNotifyCommand(existingNotify, pkgRoot)));
|
|
3801
|
+
}
|
|
3802
|
+
function parseNotifyMetadataSnapshot(snapshot, label) {
|
|
3803
|
+
if (snapshot.bytes === null)
|
|
3804
|
+
return null;
|
|
3805
|
+
const content = decodeNativeHookTransactionUtf8(snapshot.bytes, label);
|
|
3806
|
+
try {
|
|
3807
|
+
const parsed = JSON.parse(content);
|
|
3808
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
3809
|
+
throw new Error("expected a JSON object");
|
|
3810
|
+
}
|
|
3811
|
+
return parsed;
|
|
3812
|
+
}
|
|
3813
|
+
catch (error) {
|
|
3814
|
+
throw new ManagedCodexHooksPlanError("invalid_document", `Refusing to read ${label}: invalid JSON (${error instanceof Error ? error.message : String(error)}).`, { label });
|
|
3815
|
+
}
|
|
3816
|
+
}
|
|
3817
|
+
async function buildNotifyMergePlan(existingConfig, pkgRoot, codexHomeDir, scope, metadataSnapshot) {
|
|
2714
3818
|
if (scope === "project") {
|
|
2715
3819
|
return { notifyCommand: false };
|
|
2716
3820
|
}
|
|
@@ -2727,35 +3831,34 @@ async function buildNotifyMergePlan(existingConfig, pkgRoot, codexHomeDir, scope
|
|
|
2727
3831
|
return { notifyCommand: omxNotify };
|
|
2728
3832
|
}
|
|
2729
3833
|
if (isOmxManagedNotifyCommand(existingNotify, pkgRoot)) {
|
|
2730
|
-
if (!existingNotify
|
|
3834
|
+
if (!isOmxDispatcherNotifyCommand(existingNotify, pkgRoot)) {
|
|
2731
3835
|
return { notifyCommand: omxNotify };
|
|
2732
3836
|
}
|
|
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
|
-
}
|
|
3837
|
+
if (!metadataSnapshot) {
|
|
3838
|
+
throw new Error(`Missing notification metadata snapshot for ${metadataPath}; refusing to plan from a newly captured baseline.`);
|
|
2754
3839
|
}
|
|
2755
|
-
|
|
2756
|
-
|
|
3840
|
+
const metadata = parseNotifyMetadataSnapshot(metadataSnapshot, `notification metadata ${metadataPath}`);
|
|
3841
|
+
const previousNotify = metadata?.previousNotify;
|
|
3842
|
+
if (Array.isArray(previousNotify) &&
|
|
3843
|
+
previousNotify.every((item) => typeof item === "string")) {
|
|
3844
|
+
const sanitizedPreviousNotify = sanitizePreviousNotifyCommand(previousNotify, pkgRoot);
|
|
3845
|
+
if (!sanitizedPreviousNotify) {
|
|
3846
|
+
return { notifyCommand: omxNotify, metadataPath, metadataSnapshot };
|
|
3847
|
+
}
|
|
3848
|
+
return {
|
|
3849
|
+
notifyCommand: dispatcherNotify,
|
|
3850
|
+
metadataPath,
|
|
3851
|
+
metadataSnapshot,
|
|
3852
|
+
metadata: {
|
|
3853
|
+
managedBy: "oh-my-codex",
|
|
3854
|
+
version: 1,
|
|
3855
|
+
previousNotify: sanitizedPreviousNotify,
|
|
3856
|
+
omxNotify,
|
|
3857
|
+
dispatcherNotify,
|
|
3858
|
+
},
|
|
3859
|
+
};
|
|
2757
3860
|
}
|
|
2758
|
-
return { notifyCommand: omxNotify };
|
|
3861
|
+
return { notifyCommand: omxNotify, metadataPath, metadataSnapshot };
|
|
2759
3862
|
}
|
|
2760
3863
|
return {
|
|
2761
3864
|
notifyCommand: dispatcherNotify,
|
|
@@ -2769,14 +3872,11 @@ async function buildNotifyMergePlan(existingConfig, pkgRoot, codexHomeDir, scope
|
|
|
2769
3872
|
},
|
|
2770
3873
|
};
|
|
2771
3874
|
}
|
|
2772
|
-
async function
|
|
2773
|
-
const
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
const hadLegacyTeamRunTable = hasLegacyOmxTeamRunTable(existing);
|
|
2777
|
-
const currentModel = getRootModelName(existing);
|
|
3875
|
+
async function planManagedConfig(hooksPath, pkgRoot, sharedMcpRegistry, mcpMode, preserveExistingFirstPartyMcp, scope, codexHomeDir, options) {
|
|
3876
|
+
const existingConfig = options.existingConfig ?? "";
|
|
3877
|
+
const hadLegacyTeamRunTable = hasLegacyOmxTeamRunTable(existingConfig);
|
|
3878
|
+
const currentModel = getRootModelName(existingConfig);
|
|
2778
3879
|
let modelOverride;
|
|
2779
|
-
const omxManagesTui = true;
|
|
2780
3880
|
if (currentModel && LEGACY_SETUP_MODELS.has(currentModel)) {
|
|
2781
3881
|
const shouldPrompt = typeof options.modelUpgradePrompt === "function" ||
|
|
2782
3882
|
(process.stdin.isTTY && process.stdout.isTTY);
|
|
@@ -2789,12 +3889,15 @@ async function updateManagedConfig(configPath, hooksPath, pkgRoot, sharedMcpRegi
|
|
|
2789
3889
|
}
|
|
2790
3890
|
}
|
|
2791
3891
|
}
|
|
2792
|
-
const notifyPlan = await buildNotifyMergePlan(
|
|
2793
|
-
const finalConfig = buildMergedConfig(
|
|
2794
|
-
includeTui:
|
|
3892
|
+
const notifyPlan = await buildNotifyMergePlan(existingConfig, pkgRoot, codexHomeDir, scope, options.notifyMetadataSnapshot);
|
|
3893
|
+
const finalConfig = buildMergedConfig(existingConfig, pkgRoot, {
|
|
3894
|
+
includeTui: true,
|
|
2795
3895
|
codexHooksFile: hooksPath,
|
|
2796
3896
|
codexHomeDir,
|
|
2797
|
-
hookCommandPlatform:
|
|
3897
|
+
hookCommandPlatform: options.hookCommandPlatform,
|
|
3898
|
+
managedHookTrustState: options.managedHookTrustState,
|
|
3899
|
+
priorManagedHookTrustState: options.priorManagedHookTrustState,
|
|
3900
|
+
legacyHookTrustState: options.legacyHookTrustState,
|
|
2798
3901
|
codexHookFeatureFlag: options.codexHookFeatureFlag,
|
|
2799
3902
|
modelOverride,
|
|
2800
3903
|
sharedMcpServers: sharedMcpRegistry.servers,
|
|
@@ -2806,65 +3909,14 @@ async function updateManagedConfig(configPath, hooksPath, pkgRoot, sharedMcpRegi
|
|
|
2806
3909
|
includeFirstPartyMcp: mcpMode === "compat",
|
|
2807
3910
|
preserveExistingFirstPartyMcp,
|
|
2808
3911
|
});
|
|
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
3912
|
return {
|
|
2839
3913
|
finalConfig,
|
|
2840
|
-
|
|
3914
|
+
currentModel,
|
|
3915
|
+
modelOverride,
|
|
3916
|
+
notifyPlan,
|
|
2841
3917
|
repairedLegacyTeamRunTable: hadLegacyTeamRunTable && !hasLegacyOmxTeamRunTable(finalConfig),
|
|
2842
3918
|
};
|
|
2843
3919
|
}
|
|
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
3920
|
function getClaudeCodeSettingsPath(homeDir = homedir()) {
|
|
2869
3921
|
return join(homeDir, ".claude", "settings.json");
|
|
2870
3922
|
}
|