oh-my-codex 0.20.0 → 0.20.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.lock +6 -6
- package/Cargo.toml +1 -1
- package/README.md +10 -1
- package/dist/adapt/__tests__/hermes.test.js.map +1 -1
- package/dist/adapt/contracts.d.ts.map +1 -1
- package/dist/agents/native-config.js.map +1 -1
- package/dist/auth/__tests__/hotswap.test.d.ts +2 -0
- package/dist/auth/__tests__/hotswap.test.d.ts.map +1 -0
- package/dist/auth/__tests__/hotswap.test.js +183 -0
- package/dist/auth/__tests__/hotswap.test.js.map +1 -0
- package/dist/auth/hotswap.d.ts.map +1 -1
- package/dist/auth/hotswap.js +17 -4
- package/dist/auth/hotswap.js.map +1 -1
- package/dist/auth/paths.js.map +1 -1
- package/dist/auth/storage.js.map +1 -1
- package/dist/autopilot/__tests__/ralplan-gate.test.js +4 -4
- package/dist/autopilot/__tests__/ralplan-gate.test.js.map +1 -1
- package/dist/autopilot/completion-gate.js.map +1 -1
- package/dist/autopilot/fsm.d.ts +1 -1
- package/dist/autopilot/fsm.d.ts.map +1 -1
- package/dist/autoresearch/goal.js.map +1 -1
- package/dist/autoresearch/runtime.js.map +1 -1
- package/dist/capabilities/lockfile.js.map +1 -1
- package/dist/catalog/reader.js.map +1 -1
- package/dist/catalog/skill-mirror.js.map +1 -1
- package/dist/cli/__tests__/adapt-help.test.js.map +1 -1
- package/dist/cli/__tests__/agents-init.test.js.map +1 -1
- package/dist/cli/__tests__/agents.test.js.map +1 -1
- package/dist/cli/__tests__/api.test.js.map +1 -1
- package/dist/cli/__tests__/ask.test.js.map +1 -1
- package/dist/cli/__tests__/auth.test.js.map +1 -1
- package/dist/cli/__tests__/autoresearch-guided.test.js.map +1 -1
- package/dist/cli/__tests__/autoresearch.test.js.map +1 -1
- package/dist/cli/__tests__/capabilities.test.js.map +1 -1
- package/dist/cli/__tests__/codex-plugin-layout.test.js +27 -0
- package/dist/cli/__tests__/codex-plugin-layout.test.js.map +1 -1
- package/dist/cli/__tests__/doctor-context-window-warning.test.js +83 -78
- package/dist/cli/__tests__/doctor-context-window-warning.test.js.map +1 -1
- package/dist/cli/__tests__/doctor-invalid-config.test.js +56 -4
- package/dist/cli/__tests__/doctor-invalid-config.test.js.map +1 -1
- package/dist/cli/__tests__/doctor-team.test.js.map +1 -1
- package/dist/cli/__tests__/doctor-warning-copy.test.js +735 -14
- package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
- package/dist/cli/__tests__/exec.test.js.map +1 -1
- package/dist/cli/__tests__/imagegen-continuation.test.js.map +1 -1
- package/dist/cli/__tests__/index.test.js +216 -11
- package/dist/cli/__tests__/index.test.js.map +1 -1
- package/dist/cli/__tests__/launch-fallback.test.js +49 -3
- package/dist/cli/__tests__/launch-fallback.test.js.map +1 -1
- package/dist/cli/__tests__/mcp-parity.test.js +84 -12
- package/dist/cli/__tests__/mcp-parity.test.js.map +1 -1
- package/dist/cli/__tests__/native-hook-claim-journal.test.d.ts +2 -0
- package/dist/cli/__tests__/native-hook-claim-journal.test.d.ts.map +1 -0
- package/dist/cli/__tests__/native-hook-claim-journal.test.js +128 -0
- package/dist/cli/__tests__/native-hook-claim-journal.test.js.map +1 -0
- package/dist/cli/__tests__/nested-help-routing.test.js.map +1 -1
- package/dist/cli/__tests__/question.test.js.map +1 -1
- package/dist/cli/__tests__/ralph-deslop-contract.test.js.map +1 -1
- package/dist/cli/__tests__/ralph-goal-mode-contract.test.js +6 -0
- package/dist/cli/__tests__/ralph-goal-mode-contract.test.js.map +1 -1
- package/dist/cli/__tests__/ralph-prd-deep-interview.test.js.map +1 -1
- package/dist/cli/__tests__/ralph-prd-smoke.test.js.map +1 -1
- package/dist/cli/__tests__/ralph.test.js +8 -3
- package/dist/cli/__tests__/ralph.test.js.map +1 -1
- package/dist/cli/__tests__/ralplan-bootstrap-3181.test.d.ts +2 -0
- package/dist/cli/__tests__/ralplan-bootstrap-3181.test.d.ts.map +1 -0
- package/dist/cli/__tests__/ralplan-bootstrap-3181.test.js +187 -0
- package/dist/cli/__tests__/ralplan-bootstrap-3181.test.js.map +1 -0
- package/dist/cli/__tests__/ralplan.test.d.ts +2 -0
- package/dist/cli/__tests__/ralplan.test.d.ts.map +1 -0
- package/dist/cli/__tests__/ralplan.test.js +129 -0
- package/dist/cli/__tests__/ralplan.test.js.map +1 -0
- package/dist/cli/__tests__/resume.test.js +6 -1
- package/dist/cli/__tests__/resume.test.js.map +1 -1
- package/dist/cli/__tests__/session-scoped-runtime.test.js +25 -0
- package/dist/cli/__tests__/session-scoped-runtime.test.js.map +1 -1
- package/dist/cli/__tests__/session-search-help.test.js.map +1 -1
- package/dist/cli/__tests__/session-search.test.js.map +1 -1
- package/dist/cli/__tests__/setup-agents-overwrite.test.js +79 -0
- package/dist/cli/__tests__/setup-agents-overwrite.test.js.map +1 -1
- package/dist/cli/__tests__/setup-gh-star.test.js.map +1 -1
- package/dist/cli/__tests__/setup-hooks-shared-ownership.test.js +131 -5
- package/dist/cli/__tests__/setup-hooks-shared-ownership.test.js.map +1 -1
- package/dist/cli/__tests__/setup-hooks-trust-e2e.test.d.ts +2 -0
- package/dist/cli/__tests__/setup-hooks-trust-e2e.test.d.ts.map +1 -0
- package/dist/cli/__tests__/setup-hooks-trust-e2e.test.js +285 -0
- package/dist/cli/__tests__/setup-hooks-trust-e2e.test.js.map +1 -0
- package/dist/cli/__tests__/setup-install-mode.test.js +2264 -17
- package/dist/cli/__tests__/setup-install-mode.test.js.map +1 -1
- package/dist/cli/__tests__/setup-preferences.test.d.ts +2 -0
- package/dist/cli/__tests__/setup-preferences.test.d.ts.map +1 -0
- package/dist/cli/__tests__/setup-preferences.test.js +79 -0
- package/dist/cli/__tests__/setup-preferences.test.js.map +1 -0
- package/dist/cli/__tests__/setup-refresh.test.js +168 -5
- package/dist/cli/__tests__/setup-refresh.test.js.map +1 -1
- package/dist/cli/__tests__/setup-scope.test.js +73 -6
- package/dist/cli/__tests__/setup-scope.test.js.map +1 -1
- package/dist/cli/__tests__/sparkshell-cli.test.js.map +1 -1
- package/dist/cli/__tests__/state.test.js +37 -0
- package/dist/cli/__tests__/state.test.js.map +1 -1
- package/dist/cli/__tests__/team.test.js.map +1 -1
- package/dist/cli/__tests__/ultragoal.test.js +61 -1
- package/dist/cli/__tests__/ultragoal.test.js.map +1 -1
- package/dist/cli/__tests__/uninstall.test.js +2067 -26
- package/dist/cli/__tests__/uninstall.test.js.map +1 -1
- package/dist/cli/__tests__/update.test.js +58 -0
- package/dist/cli/__tests__/update.test.js.map +1 -1
- package/dist/cli/__tests__/url.test.js.map +1 -1
- package/dist/cli/__tests__/windows-popup-loop-contract.test.js.map +1 -1
- package/dist/cli/adapt.js.map +1 -1
- package/dist/cli/agents-init.js.map +1 -1
- package/dist/cli/agents.js.map +1 -1
- package/dist/cli/api.js.map +1 -1
- package/dist/cli/ask.d.ts +1 -1
- package/dist/cli/ask.d.ts.map +1 -1
- package/dist/cli/ask.js.map +1 -1
- package/dist/cli/auth.js.map +1 -1
- package/dist/cli/autoresearch-guided.js.map +1 -1
- package/dist/cli/autoresearch-intake.js.map +1 -1
- package/dist/cli/capabilities.js.map +1 -1
- package/dist/cli/cleanup.js.map +1 -1
- package/dist/cli/codex-feature-probe.js.map +1 -1
- package/dist/cli/codex-home.js.map +1 -1
- package/dist/cli/doctor.d.ts +20 -0
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/doctor.js +636 -161
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/explore.js.map +1 -1
- package/dist/cli/index.d.ts +11 -3
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +108 -20
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/mcp-serve.js.map +1 -1
- package/dist/cli/mission.js.map +1 -1
- package/dist/cli/native-assets.js.map +1 -1
- package/dist/cli/native-hook-claim-journal.d.ts +18 -0
- package/dist/cli/native-hook-claim-journal.d.ts.map +1 -0
- package/dist/cli/native-hook-claim-journal.js +239 -0
- package/dist/cli/native-hook-claim-journal.js.map +1 -0
- package/dist/cli/omx.js.map +1 -1
- package/dist/cli/plugin-marketplace.js.map +1 -1
- package/dist/cli/project-runtime-codex-homes.js.map +1 -1
- package/dist/cli/question.js.map +1 -1
- package/dist/cli/ralph.d.ts.map +1 -1
- package/dist/cli/ralph.js +3 -2
- package/dist/cli/ralph.js.map +1 -1
- package/dist/cli/ralplan.d.ts +14 -0
- package/dist/cli/ralplan.d.ts.map +1 -0
- package/dist/cli/ralplan.js +203 -0
- package/dist/cli/ralplan.js.map +1 -0
- package/dist/cli/setup-preferences.d.ts +11 -0
- package/dist/cli/setup-preferences.d.ts.map +1 -1
- package/dist/cli/setup-preferences.js +32 -0
- package/dist/cli/setup-preferences.js.map +1 -1
- package/dist/cli/setup.d.ts +53 -0
- package/dist/cli/setup.d.ts.map +1 -1
- package/dist/cli/setup.js +1403 -400
- package/dist/cli/setup.js.map +1 -1
- package/dist/cli/sparkshell.js.map +1 -1
- package/dist/cli/star-prompt.js.map +1 -1
- package/dist/cli/state.d.ts.map +1 -1
- package/dist/cli/state.js +2 -1
- package/dist/cli/state.js.map +1 -1
- package/dist/cli/team.js.map +1 -1
- package/dist/cli/ultragoal.d.ts +5 -1
- package/dist/cli/ultragoal.d.ts.map +1 -1
- package/dist/cli/ultragoal.js +34 -2
- package/dist/cli/ultragoal.js.map +1 -1
- package/dist/cli/uninstall.d.ts +8 -0
- package/dist/cli/uninstall.d.ts.map +1 -1
- package/dist/cli/uninstall.js +947 -99
- package/dist/cli/uninstall.js.map +1 -1
- package/dist/cli/update.d.ts.map +1 -1
- package/dist/cli/update.js +8 -1
- package/dist/cli/update.js.map +1 -1
- package/dist/cli/url.js.map +1 -1
- package/dist/compat/__tests__/doctor-contract.test.js +5 -1
- package/dist/compat/__tests__/doctor-contract.test.js.map +1 -1
- package/dist/compat/__tests__/rust-runtime-compat.test.js.map +1 -1
- package/dist/config/__tests__/codex-hooks.test.js +1287 -11
- package/dist/config/__tests__/codex-hooks.test.js.map +1 -1
- package/dist/config/__tests__/generator-idempotent.test.js +1049 -135
- package/dist/config/__tests__/generator-idempotent.test.js.map +1 -1
- package/dist/config/__tests__/generator-notify.test.js +28 -49
- package/dist/config/__tests__/generator-notify.test.js.map +1 -1
- package/dist/config/codex-feature-flags.d.ts.map +1 -1
- package/dist/config/codex-feature-flags.js.map +1 -1
- package/dist/config/codex-hooks.d.ts +124 -1
- package/dist/config/codex-hooks.d.ts.map +1 -1
- package/dist/config/codex-hooks.js +2142 -265
- package/dist/config/codex-hooks.js.map +1 -1
- package/dist/config/commit-lore-guard.js.map +1 -1
- package/dist/config/generator.d.ts +40 -26
- package/dist/config/generator.d.ts.map +1 -1
- package/dist/config/generator.js +1159 -288
- package/dist/config/generator.js.map +1 -1
- package/dist/config/mcp-registry.js.map +1 -1
- package/dist/config/models.d.ts +2 -2
- package/dist/config/models.d.ts.map +1 -1
- package/dist/config/models.js.map +1 -1
- package/dist/config/omx-first-party-mcp.js.map +1 -1
- package/dist/config/team-mode.d.ts.map +1 -1
- package/dist/document-refresh/__tests__/enforcer.test.js.map +1 -1
- package/dist/document-refresh/enforcer.js.map +1 -1
- package/dist/exec/followup.js.map +1 -1
- package/dist/goal-workflows/artifacts.js.map +1 -1
- package/dist/hooks/__tests__/agents-overlay.test.js +7 -4
- package/dist/hooks/__tests__/agents-overlay.test.js.map +1 -1
- package/dist/hooks/__tests__/analyze-routing-contract.test.js.map +1 -1
- package/dist/hooks/__tests__/analyze-skill-contract.test.js.map +1 -1
- package/dist/hooks/__tests__/anti-slop-workflow.test.js.map +1 -1
- package/dist/hooks/__tests__/autopilot-skill-contract.test.js +9 -3
- package/dist/hooks/__tests__/autopilot-skill-contract.test.js.map +1 -1
- package/dist/hooks/__tests__/code-review-skill-contract.test.js.map +1 -1
- package/dist/hooks/__tests__/consensus-execution-handoff.test.js.map +1 -1
- package/dist/hooks/__tests__/deep-interview-contract.test.js.map +1 -1
- package/dist/hooks/__tests__/design-skill.test.js.map +1 -1
- package/dist/hooks/__tests__/keyword-detector.test.js +1372 -2
- package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-fallback-watcher.test.js +14 -0
- package/dist/hooks/__tests__/notify-fallback-watcher.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-all-workers-idle.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js +669 -1
- package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-cross-worktree-heartbeat.test.js +2 -0
- package/dist/hooks/__tests__/notify-hook-cross-worktree-heartbeat.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-managed-tmux.test.js +142 -1
- package/dist/hooks/__tests__/notify-hook-managed-tmux.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-modules.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-non-omx-guard.test.js +48 -2
- package/dist/hooks/__tests__/notify-hook-non-omx-guard.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-ralph-resume.test.js +61 -44
- package/dist/hooks/__tests__/notify-hook-ralph-resume.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-regression-205.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-session-idle-dedupe.test.js +3 -2
- package/dist/hooks/__tests__/notify-hook-session-idle-dedupe.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-session-scope.test.js +243 -4
- package/dist/hooks/__tests__/notify-hook-session-scope.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-team-dispatch.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js +8 -1
- package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-team-tmux-guard.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-tmux-scrollback.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-visual-verdict.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-worker-idle.test.js.map +1 -1
- package/dist/hooks/__tests__/openclaw-setup-contract.test.js.map +1 -1
- package/dist/hooks/__tests__/pre-context-gate-skills.test.js.map +1 -1
- package/dist/hooks/__tests__/prometheus-strict-contract.test.js.map +1 -1
- package/dist/hooks/__tests__/prompt-guidance-fragments.test.js.map +1 -1
- package/dist/hooks/__tests__/prompt-guidance-test-helpers.js.map +1 -1
- package/dist/hooks/__tests__/prompt-session-provenance.test.d.ts +2 -0
- package/dist/hooks/__tests__/prompt-session-provenance.test.d.ts.map +1 -0
- package/dist/hooks/__tests__/prompt-session-provenance.test.js +82 -0
- package/dist/hooks/__tests__/prompt-session-provenance.test.js.map +1 -0
- package/dist/hooks/__tests__/session.test.js +532 -11
- package/dist/hooks/__tests__/session.test.js.map +1 -1
- package/dist/hooks/__tests__/skill-catalog-hygiene.test.js.map +1 -1
- package/dist/hooks/__tests__/team-runtime-gating-docs-contract.test.js.map +1 -1
- package/dist/hooks/__tests__/visual-ralph-skill.test.js.map +1 -1
- package/dist/hooks/__tests__/visual-verdict-loop.test.js.map +1 -1
- package/dist/hooks/agents-overlay.js +5 -5
- package/dist/hooks/agents-overlay.js.map +1 -1
- package/dist/hooks/code-simplifier/index.js.map +1 -1
- package/dist/hooks/explore-routing.js.map +1 -1
- package/dist/hooks/extensibility/dispatcher.js.map +1 -1
- package/dist/hooks/extensibility/events.js.map +1 -1
- package/dist/hooks/extensibility/loader.js.map +1 -1
- package/dist/hooks/extensibility/plugin-runner-stdin.js.map +1 -1
- package/dist/hooks/extensibility/sdk/logging.js.map +1 -1
- package/dist/hooks/keyword-detector.d.ts +33 -7
- package/dist/hooks/keyword-detector.d.ts.map +1 -1
- package/dist/hooks/keyword-detector.js +2228 -119
- package/dist/hooks/keyword-detector.js.map +1 -1
- package/dist/hooks/keyword-registry.d.ts +12 -0
- package/dist/hooks/keyword-registry.d.ts.map +1 -1
- package/dist/hooks/keyword-registry.js +41 -0
- package/dist/hooks/keyword-registry.js.map +1 -1
- package/dist/hooks/prompt-session-provenance.d.ts +87 -0
- package/dist/hooks/prompt-session-provenance.d.ts.map +1 -0
- package/dist/hooks/prompt-session-provenance.js +180 -0
- package/dist/hooks/prompt-session-provenance.js.map +1 -0
- package/dist/hooks/session.d.ts +139 -34
- package/dist/hooks/session.d.ts.map +1 -1
- package/dist/hooks/session.js +1239 -317
- package/dist/hooks/session.js.map +1 -1
- package/dist/hooks/task-size-detector.js.map +1 -1
- package/dist/hud/__tests__/state.test.js.map +1 -1
- package/dist/hud/authority.js.map +1 -1
- package/dist/hud/constants.js.map +1 -1
- package/dist/hud/index.js.map +1 -1
- package/dist/hud/reconcile.js.map +1 -1
- package/dist/hud/render.js.map +1 -1
- package/dist/hud/state.js.map +1 -1
- package/dist/hud/tmux.js.map +1 -1
- package/dist/leader/__tests__/contract.test.js +146 -2
- package/dist/leader/__tests__/contract.test.js.map +1 -1
- package/dist/leader/contract.d.ts +26 -5
- package/dist/leader/contract.d.ts.map +1 -1
- package/dist/leader/contract.js +131 -7
- package/dist/leader/contract.js.map +1 -1
- package/dist/mcp/__tests__/bootstrap.test.js.map +1 -1
- package/dist/mcp/__tests__/server-lifecycle.test.js +15 -2
- package/dist/mcp/__tests__/server-lifecycle.test.js.map +1 -1
- package/dist/mcp/__tests__/state-paths.test.js +98 -1
- package/dist/mcp/__tests__/state-paths.test.js.map +1 -1
- package/dist/mcp/bootstrap.js.map +1 -1
- package/dist/mcp/code-intel-server.d.ts +58 -58
- package/dist/mcp/code-intel-server.d.ts.map +1 -1
- package/dist/mcp/code-intel-server.js.map +1 -1
- package/dist/mcp/hermes-bridge.js.map +1 -1
- package/dist/mcp/hermes-server.d.ts +70 -70
- package/dist/mcp/hermes-server.d.ts.map +1 -1
- package/dist/mcp/lifecycle-telemetry.js.map +1 -1
- package/dist/mcp/memory-server.d.ts +34 -34
- package/dist/mcp/memory-server.d.ts.map +1 -1
- package/dist/mcp/memory-server.js.map +1 -1
- package/dist/mcp/state-paths.d.ts +14 -0
- package/dist/mcp/state-paths.d.ts.map +1 -1
- package/dist/mcp/state-paths.js +96 -20
- package/dist/mcp/state-paths.js.map +1 -1
- package/dist/mcp/state-server.d.ts +47 -47
- package/dist/mcp/state-server.d.ts.map +1 -1
- package/dist/mcp/trace-server.d.ts +4 -4
- package/dist/mcp/trace-server.d.ts.map +1 -1
- package/dist/mcp/trace-server.js.map +1 -1
- package/dist/mcp/wiki-server.d.ts +23 -23
- package/dist/mcp/wiki-server.d.ts.map +1 -1
- package/dist/mcp/wiki-server.js.map +1 -1
- package/dist/modes/__tests__/base-session-scope.test.js +53 -1
- package/dist/modes/__tests__/base-session-scope.test.js.map +1 -1
- package/dist/modes/base.d.ts.map +1 -1
- package/dist/modes/base.js +5 -5
- package/dist/modes/base.js.map +1 -1
- package/dist/notifications/__tests__/lifecycle-dedupe.test.js.map +1 -1
- package/dist/notifications/__tests__/reply-listener.test.js.map +1 -1
- package/dist/notifications/__tests__/session-idle-tail-dedupe.test.js +177 -85
- package/dist/notifications/__tests__/session-idle-tail-dedupe.test.js.map +1 -1
- package/dist/notifications/__tests__/session-registry.test.js.map +1 -1
- package/dist/notifications/__tests__/template-engine.test.js.map +1 -1
- package/dist/notifications/__tests__/verbosity.test.js.map +1 -1
- package/dist/notifications/http-client.js.map +1 -1
- package/dist/notifications/index.d.ts +4 -1
- package/dist/notifications/index.d.ts.map +1 -1
- package/dist/notifications/index.js +3 -9
- package/dist/notifications/index.js.map +1 -1
- package/dist/notifications/lifecycle-dedupe.js.map +1 -1
- package/dist/notifications/reply-listener.d.ts.map +1 -1
- package/dist/notifications/reply-listener.js.map +1 -1
- package/dist/notifications/session-registry.js.map +1 -1
- package/dist/notifications/session-status.js.map +1 -1
- package/dist/notifications/temp-contract.js.map +1 -1
- package/dist/notifications/tmux-detector.js.map +1 -1
- package/dist/notifications/tmux.js.map +1 -1
- package/dist/openclaw/config.js.map +1 -1
- package/dist/openclaw/dispatcher.js.map +1 -1
- package/dist/pipeline/__tests__/orchestrator.test.js.map +1 -1
- package/dist/pipeline/__tests__/stages.test.js +8 -5
- package/dist/pipeline/__tests__/stages.test.js.map +1 -1
- package/dist/pipeline/stages/code-review.js.map +1 -1
- package/dist/pipeline/stages/ralph-verify.js.map +1 -1
- package/dist/pipeline/stages/ralplan.js.map +1 -1
- package/dist/pipeline/stages/team-exec.js.map +1 -1
- package/dist/pipeline/stages/ultragoal.js.map +1 -1
- package/dist/pipeline/stages/ultraqa.js.map +1 -1
- package/dist/planning/__tests__/approved-execution-lifecycle-matrix.test.js.map +1 -1
- package/dist/planning/artifact-names.js.map +1 -1
- package/dist/planning/artifacts.js.map +1 -1
- package/dist/question/__tests__/state.test.js.map +1 -1
- package/dist/question/__tests__/ui.test.js.map +1 -1
- package/dist/question/autopilot-wait.js.map +1 -1
- package/dist/question/client.d.ts.map +1 -1
- package/dist/question/client.js.map +1 -1
- package/dist/question/deep-interview.js.map +1 -1
- package/dist/question/events.js.map +1 -1
- package/dist/question/renderer.js.map +1 -1
- package/dist/question/state.d.ts.map +1 -1
- package/dist/question/state.js.map +1 -1
- package/dist/question/ui.js.map +1 -1
- package/dist/ralph/contract.d.ts +1 -1
- package/dist/ralph/contract.d.ts.map +1 -1
- package/dist/ralplan/__tests__/consensus-gate.test.js +403 -8
- package/dist/ralplan/__tests__/consensus-gate.test.js.map +1 -1
- package/dist/ralplan/__tests__/runtime.test.js +112 -14
- package/dist/ralplan/__tests__/runtime.test.js.map +1 -1
- package/dist/ralplan/consensus-gate.d.ts +3 -3
- package/dist/ralplan/consensus-gate.d.ts.map +1 -1
- package/dist/ralplan/consensus-gate.js +155 -60
- package/dist/ralplan/consensus-gate.js.map +1 -1
- package/dist/ralplan/runtime.d.ts +2 -2
- package/dist/ralplan/runtime.d.ts.map +1 -1
- package/dist/ralplan/runtime.js +1 -2
- package/dist/ralplan/runtime.js.map +1 -1
- package/dist/runtime/bridge.d.ts.map +1 -1
- package/dist/runtime/bridge.js.map +1 -1
- package/dist/runtime/process-tree.js.map +1 -1
- package/dist/runtime/run-loop.js.map +1 -1
- package/dist/runtime/run-outcome.d.ts +3 -3
- package/dist/runtime/run-outcome.d.ts.map +1 -1
- package/dist/runtime/run-outcome.js.map +1 -1
- package/dist/runtime/run-state.js.map +1 -1
- package/dist/scripts/__tests__/codex-native-hook.test.js +5414 -801
- package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
- package/dist/scripts/__tests__/fixtures/issue-3138-installed-smoke.d.ts +2 -0
- package/dist/scripts/__tests__/fixtures/issue-3138-installed-smoke.d.ts.map +1 -0
- package/dist/scripts/__tests__/fixtures/issue-3138-installed-smoke.js +634 -0
- package/dist/scripts/__tests__/fixtures/issue-3138-installed-smoke.js.map +1 -0
- package/dist/scripts/__tests__/generate-release-body.test.js.map +1 -1
- package/dist/scripts/__tests__/hook-derived-watcher.test.js.map +1 -1
- package/dist/scripts/__tests__/notify-dispatcher.test.js.map +1 -1
- package/dist/scripts/__tests__/notify-fallback-delivery.test.d.ts +2 -0
- package/dist/scripts/__tests__/notify-fallback-delivery.test.d.ts.map +1 -0
- package/dist/scripts/__tests__/notify-fallback-delivery.test.js +174 -0
- package/dist/scripts/__tests__/notify-fallback-delivery.test.js.map +1 -0
- package/dist/scripts/__tests__/notify-state-io.test.js +57 -1
- package/dist/scripts/__tests__/notify-state-io.test.js.map +1 -1
- package/dist/scripts/__tests__/role-intent-bootstrap-e2e-3181.test.d.ts +2 -0
- package/dist/scripts/__tests__/role-intent-bootstrap-e2e-3181.test.d.ts.map +1 -0
- package/dist/scripts/__tests__/role-intent-bootstrap-e2e-3181.test.js +205 -0
- package/dist/scripts/__tests__/role-intent-bootstrap-e2e-3181.test.js.map +1 -0
- package/dist/scripts/__tests__/role-intent-durable-recovery-3181.test.d.ts +2 -0
- package/dist/scripts/__tests__/role-intent-durable-recovery-3181.test.d.ts.map +1 -0
- package/dist/scripts/__tests__/role-intent-durable-recovery-3181.test.js +175 -0
- package/dist/scripts/__tests__/role-intent-durable-recovery-3181.test.js.map +1 -0
- package/dist/scripts/__tests__/run-test-files.test.js.map +1 -1
- package/dist/scripts/__tests__/smoke-packed-install.test.js +848 -4
- package/dist/scripts/__tests__/smoke-packed-install.test.js.map +1 -1
- package/dist/scripts/__tests__/test-reply-listener-live.test.js.map +1 -1
- package/dist/scripts/build-api.js.map +1 -1
- package/dist/scripts/build-explore-harness.js.map +1 -1
- package/dist/scripts/build-sparkshell.js.map +1 -1
- package/dist/scripts/cleanup-explore-harness.js.map +1 -1
- package/dist/scripts/codex-execution-surface.js.map +1 -1
- package/dist/scripts/codex-native-hook.d.ts.map +1 -1
- package/dist/scripts/codex-native-hook.js +825 -172
- package/dist/scripts/codex-native-hook.js.map +1 -1
- package/dist/scripts/generate-native-release-manifest.js.map +1 -1
- package/dist/scripts/generate-release-body.js.map +1 -1
- package/dist/scripts/hook-payload-guard.d.ts.map +1 -1
- package/dist/scripts/hook-payload-guard.js.map +1 -1
- package/dist/scripts/notify-fallback-delivery.d.ts +48 -0
- package/dist/scripts/notify-fallback-delivery.d.ts.map +1 -0
- package/dist/scripts/notify-fallback-delivery.js +419 -0
- package/dist/scripts/notify-fallback-delivery.js.map +1 -0
- package/dist/scripts/notify-fallback-watcher.js +136 -30
- package/dist/scripts/notify-fallback-watcher.js.map +1 -1
- package/dist/scripts/notify-hook/auto-nudge.d.ts +18 -9
- package/dist/scripts/notify-hook/auto-nudge.d.ts.map +1 -1
- package/dist/scripts/notify-hook/auto-nudge.js +39 -13
- package/dist/scripts/notify-hook/auto-nudge.js.map +1 -1
- package/dist/scripts/notify-hook/managed-tmux.d.ts +23 -0
- package/dist/scripts/notify-hook/managed-tmux.d.ts.map +1 -1
- package/dist/scripts/notify-hook/managed-tmux.js +122 -12
- package/dist/scripts/notify-hook/managed-tmux.js.map +1 -1
- package/dist/scripts/notify-hook/orchestration-intent.d.ts +7 -7
- package/dist/scripts/notify-hook/orchestration-intent.d.ts.map +1 -1
- package/dist/scripts/notify-hook/ralph-session-resume.d.ts +5 -2
- package/dist/scripts/notify-hook/ralph-session-resume.d.ts.map +1 -1
- package/dist/scripts/notify-hook/ralph-session-resume.js +33 -37
- package/dist/scripts/notify-hook/ralph-session-resume.js.map +1 -1
- package/dist/scripts/notify-hook/state-io.d.ts +17 -0
- package/dist/scripts/notify-hook/state-io.d.ts.map +1 -1
- package/dist/scripts/notify-hook/state-io.js +73 -11
- package/dist/scripts/notify-hook/state-io.js.map +1 -1
- package/dist/scripts/notify-hook/team-dispatch.d.ts +1 -1
- package/dist/scripts/notify-hook/team-dispatch.js.map +1 -1
- package/dist/scripts/notify-hook/team-leader-nudge.d.ts +2 -2
- package/dist/scripts/notify-hook/team-tmux-guard.d.ts +4 -4
- package/dist/scripts/notify-hook/team-worker-posttooluse.js.map +1 -1
- package/dist/scripts/notify-hook/team-worker-stop.d.ts +1 -1
- package/dist/scripts/notify-hook/team-worker.d.ts +2 -2
- package/dist/scripts/notify-hook/tmux-injection.d.ts +8 -1
- package/dist/scripts/notify-hook/tmux-injection.d.ts.map +1 -1
- package/dist/scripts/notify-hook/tmux-injection.js +25 -16
- package/dist/scripts/notify-hook/tmux-injection.js.map +1 -1
- package/dist/scripts/notify-hook/visual-verdict.d.ts +1 -1
- package/dist/scripts/notify-hook/visual-verdict.d.ts.map +1 -1
- package/dist/scripts/notify-hook/visual-verdict.js +15 -13
- package/dist/scripts/notify-hook/visual-verdict.js.map +1 -1
- package/dist/scripts/notify-hook.d.ts +24 -1
- package/dist/scripts/notify-hook.d.ts.map +1 -1
- package/dist/scripts/notify-hook.js +266 -90
- package/dist/scripts/notify-hook.js.map +1 -1
- package/dist/scripts/postinstall.js.map +1 -1
- package/dist/scripts/prompt-inventory.js.map +1 -1
- package/dist/scripts/smoke-packed-install.d.ts +879 -2
- package/dist/scripts/smoke-packed-install.d.ts.map +1 -1
- package/dist/scripts/smoke-packed-install.js +1528 -7
- package/dist/scripts/smoke-packed-install.js.map +1 -1
- package/dist/scripts/sync-plugin-mirror.js.map +1 -1
- package/dist/scripts/test-reply-listener-live.js.map +1 -1
- package/dist/scripts/test-sparkshell.js.map +1 -1
- package/dist/scripts/verify-native-agents.js.map +1 -1
- package/dist/session-history/friction.js.map +1 -1
- package/dist/session-history/search.js.map +1 -1
- package/dist/sidecar/__tests__/boundary.test.js.map +1 -1
- package/dist/sidecar/collector.js.map +1 -1
- package/dist/sidecar/index.js.map +1 -1
- package/dist/sidecar/render.js.map +1 -1
- package/dist/sidecar/tmux.js.map +1 -1
- package/dist/state/__tests__/operations.test.js +64 -15
- package/dist/state/__tests__/operations.test.js.map +1 -1
- package/dist/state/__tests__/skill-active.test.js +19 -1
- package/dist/state/__tests__/skill-active.test.js.map +1 -1
- package/dist/state/__tests__/workflow-transition.test.js +61 -0
- package/dist/state/__tests__/workflow-transition.test.js.map +1 -1
- package/dist/state/mode-state-context.js.map +1 -1
- package/dist/state/operations.d.ts +1 -1
- package/dist/state/operations.d.ts.map +1 -1
- package/dist/state/operations.js +49 -55
- package/dist/state/operations.js.map +1 -1
- package/dist/state/paths.d.ts +1 -1
- package/dist/state/paths.d.ts.map +1 -1
- package/dist/state/paths.js +1 -1
- package/dist/state/paths.js.map +1 -1
- package/dist/state/skill-active.d.ts +13 -1
- package/dist/state/skill-active.d.ts.map +1 -1
- package/dist/state/skill-active.js +43 -4
- package/dist/state/skill-active.js.map +1 -1
- package/dist/state/terminal-normalization.js.map +1 -1
- package/dist/state/workflow-transition-reconcile.d.ts.map +1 -1
- package/dist/state/workflow-transition-reconcile.js +20 -4
- package/dist/state/workflow-transition-reconcile.js.map +1 -1
- package/dist/state/workflow-transition.d.ts +1 -1
- package/dist/state/workflow-transition.d.ts.map +1 -1
- package/dist/state/workflow-transition.js.map +1 -1
- package/dist/subagents/__tests__/adapted-role-binding.test.d.ts +2 -0
- package/dist/subagents/__tests__/adapted-role-binding.test.d.ts.map +1 -0
- package/dist/subagents/__tests__/adapted-role-binding.test.js +988 -0
- package/dist/subagents/__tests__/adapted-role-binding.test.js.map +1 -0
- package/dist/subagents/__tests__/leader-bootstrap-3181.test.d.ts +2 -0
- package/dist/subagents/__tests__/leader-bootstrap-3181.test.d.ts.map +1 -0
- package/dist/subagents/__tests__/leader-bootstrap-3181.test.js +273 -0
- package/dist/subagents/__tests__/leader-bootstrap-3181.test.js.map +1 -0
- package/dist/subagents/__tests__/tracker.test.js +1550 -10
- package/dist/subagents/__tests__/tracker.test.js.map +1 -1
- package/dist/subagents/adapted-role-binding.d.ts +17 -0
- package/dist/subagents/adapted-role-binding.d.ts.map +1 -0
- package/dist/subagents/adapted-role-binding.js +75 -0
- package/dist/subagents/adapted-role-binding.js.map +1 -0
- package/dist/subagents/role-routing-marker.d.ts +10 -0
- package/dist/subagents/role-routing-marker.d.ts.map +1 -0
- package/dist/subagents/role-routing-marker.js +136 -0
- package/dist/subagents/role-routing-marker.js.map +1 -0
- package/dist/subagents/tracker.d.ts +172 -0
- package/dist/subagents/tracker.d.ts.map +1 -1
- package/dist/subagents/tracker.js +1077 -61
- package/dist/subagents/tracker.js.map +1 -1
- package/dist/team/__tests__/approved-execution.test.js.map +1 -1
- package/dist/team/__tests__/delivery-e2e-smoke.test.js.map +1 -1
- package/dist/team/__tests__/model-contract.test.js +231 -1
- package/dist/team/__tests__/model-contract.test.js.map +1 -1
- package/dist/team/__tests__/role-router.test.js.map +1 -1
- package/dist/team/__tests__/runtime.test.js +401 -1
- package/dist/team/__tests__/runtime.test.js.map +1 -1
- package/dist/team/__tests__/scaling.test.js +181 -0
- package/dist/team/__tests__/scaling.test.js.map +1 -1
- package/dist/team/__tests__/state-root.test.js.map +1 -1
- package/dist/team/__tests__/tmux-session.test.js +434 -4
- package/dist/team/__tests__/tmux-session.test.js.map +1 -1
- package/dist/team/__tests__/tmux-test-fixture.js.map +1 -1
- package/dist/team/api-interop.d.ts +2 -2
- package/dist/team/api-interop.d.ts.map +1 -1
- package/dist/team/api-interop.js.map +1 -1
- package/dist/team/commit-hygiene.js.map +1 -1
- package/dist/team/contracts.d.ts +5 -5
- package/dist/team/contracts.d.ts.map +1 -1
- package/dist/team/delivery-log.js.map +1 -1
- package/dist/team/followup-planner.js.map +1 -1
- package/dist/team/goal-workflow.js.map +1 -1
- package/dist/team/idle-nudge.d.ts.map +1 -1
- package/dist/team/model-contract.d.ts +20 -1
- package/dist/team/model-contract.d.ts.map +1 -1
- package/dist/team/model-contract.js +267 -30
- package/dist/team/model-contract.js.map +1 -1
- package/dist/team/orchestrator.js.map +1 -1
- package/dist/team/pane-status.js.map +1 -1
- package/dist/team/phase-controller.js.map +1 -1
- package/dist/team/reminder-intents.d.ts +1 -1
- package/dist/team/reminder-intents.d.ts.map +1 -1
- package/dist/team/reminder-intents.js.map +1 -1
- package/dist/team/runtime-cli.js.map +1 -1
- package/dist/team/runtime.d.ts.map +1 -1
- package/dist/team/runtime.js +83 -52
- package/dist/team/runtime.js.map +1 -1
- package/dist/team/scaling.d.ts +9 -7
- package/dist/team/scaling.d.ts.map +1 -1
- package/dist/team/scaling.js +69 -28
- package/dist/team/scaling.js.map +1 -1
- package/dist/team/state/dispatch-lock.js.map +1 -1
- package/dist/team/state/dispatch.js.map +1 -1
- package/dist/team/state/events.js.map +1 -1
- package/dist/team/state/tasks.js.map +1 -1
- package/dist/team/state-root.js.map +1 -1
- package/dist/team/state.js.map +1 -1
- package/dist/team/team-identity.d.ts.map +1 -1
- package/dist/team/team-identity.js.map +1 -1
- package/dist/team/tmux-session.d.ts +2 -0
- package/dist/team/tmux-session.d.ts.map +1 -1
- package/dist/team/tmux-session.js +90 -42
- package/dist/team/tmux-session.js.map +1 -1
- package/dist/team/worker-bootstrap.js.map +1 -1
- package/dist/team/worktree.js.map +1 -1
- package/dist/ultragoal/__tests__/artifacts.test.js.map +1 -1
- package/dist/ultragoal/__tests__/docs-contract.test.js.map +1 -1
- package/dist/ultragoal/artifacts.d.ts.map +1 -1
- package/dist/ultragoal/artifacts.js +4 -1
- package/dist/ultragoal/artifacts.js.map +1 -1
- package/dist/url-reader/index.js.map +1 -1
- package/dist/utils/__tests__/agents-md.test.js +16 -0
- package/dist/utils/__tests__/agents-md.test.js.map +1 -1
- package/dist/utils/agents-md.d.ts.map +1 -1
- package/dist/utils/agents-md.js +4 -3
- package/dist/utils/agents-md.js.map +1 -1
- package/dist/utils/agents-model-table.js.map +1 -1
- package/dist/utils/package.js.map +1 -1
- package/dist/utils/paths.js.map +1 -1
- package/dist/utils/platform-command.js.map +1 -1
- package/dist/utils/repo-deps.js.map +1 -1
- package/dist/utils/version.js.map +1 -1
- package/dist/verification/__tests__/ci-rust-gates.test.js +1 -1
- package/dist/verification/__tests__/dev-merge-issue-close-workflow.test.js.map +1 -1
- package/dist/verification/verifier.js.map +1 -1
- package/dist/visual/constants.d.ts +1 -1
- package/dist/visual/constants.d.ts.map +1 -1
- package/dist/vscode/__tests__/index.test.js.map +1 -1
- package/dist/vscode/index.js.map +1 -1
- package/dist/wiki/__tests__/cjk-tokenize.test.js.map +1 -1
- package/dist/wiki/__tests__/lint.test.js.map +1 -1
- package/dist/wiki/__tests__/query.test.js.map +1 -1
- package/dist/wiki/__tests__/storage.test.js.map +1 -1
- package/dist/wiki/__tests__/test-helpers.d.ts.map +1 -1
- package/dist/wiki/lint.js.map +1 -1
- package/dist/wiki/query.js.map +1 -1
- package/dist/wiki/storage.js.map +1 -1
- package/package.json +2 -2
- package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
- package/plugins/oh-my-codex/skills/omx-setup/SKILL.md +10 -5
- package/plugins/oh-my-codex/skills/ralph/SKILL.md +4 -2
- package/plugins/oh-my-codex/skills/ralplan/SKILL.md +5 -2
- package/skills/omx-setup/SKILL.md +10 -5
- package/skills/ralph/SKILL.md +4 -2
- package/skills/ralplan/SKILL.md +5 -2
- package/src/scripts/__tests__/codex-native-hook.test.ts +20497 -12377
- package/src/scripts/__tests__/fixtures/issue-3138-installed-smoke.ts +734 -0
- package/src/scripts/__tests__/notify-fallback-delivery.test.ts +177 -0
- package/src/scripts/__tests__/notify-state-io.test.ts +73 -0
- package/src/scripts/__tests__/role-intent-bootstrap-e2e-3181.test.ts +215 -0
- package/src/scripts/__tests__/role-intent-durable-recovery-3181.test.ts +193 -0
- package/src/scripts/__tests__/smoke-packed-install.test.ts +991 -4
- package/src/scripts/codex-native-hook.ts +918 -172
- package/src/scripts/notify-fallback-delivery.ts +416 -0
- package/src/scripts/notify-fallback-watcher.ts +132 -40
- package/src/scripts/notify-hook/auto-nudge.ts +43 -12
- package/src/scripts/notify-hook/managed-tmux.ts +149 -12
- package/src/scripts/notify-hook/ralph-session-resume.ts +37 -36
- package/src/scripts/notify-hook/state-io.ts +100 -13
- package/src/scripts/notify-hook/tmux-injection.ts +38 -21
- package/src/scripts/notify-hook/visual-verdict.ts +22 -12
- package/src/scripts/notify-hook.ts +335 -90
- package/src/scripts/smoke-packed-install.ts +1829 -6
|
@@ -8,13 +8,35 @@ import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
|
8
8
|
import { tmpdir } from "node:os";
|
|
9
9
|
import { isAbsolute, join } from "node:path";
|
|
10
10
|
import TOML from "@iarna/toml";
|
|
11
|
-
import { buildMergedConfig, cleanCodexModelAvailabilityNuxIfNeeded, mergeConfig, repairConfigIfNeeded, stripManagedCodexHookTrustState, upsertManagedCodexHookTrustState, } from "../generator.js";
|
|
12
|
-
import { buildManagedCodexHookTrustState } from "../codex-hooks.js";
|
|
11
|
+
import { analyzeLegacyMultiAgentConfig, buildMergedConfig, cleanCodexModelAvailabilityNuxIfNeeded, hasExactOmxSeededBehavioralDefaultsPair, mergeConfig, repairConfigIfNeeded, stripExistingOmxBlocks, stripManagedCodexHookTrustState, stripOmxFeatureFlags, upsertManagedCodexHookTrustState, stripOmxSeededBehavioralDefaults, } from "../generator.js";
|
|
12
|
+
import { MANAGED_HOOK_EVENTS, ManagedCodexHooksPlanError, buildManagedCodexHookTrustState, planManagedCodexHooksMerge, } from "../codex-hooks.js";
|
|
13
13
|
import { OMX_FIRST_PARTY_MCP_SERVER_NAMES } from "../omx-first-party-mcp.js";
|
|
14
14
|
/** Count occurrences of a pattern in text */
|
|
15
15
|
function count(text, pattern) {
|
|
16
16
|
return (text.match(pattern) ?? []).length;
|
|
17
17
|
}
|
|
18
|
+
async function writeSetupGeneratedHookTrustFixture(wd) {
|
|
19
|
+
const configPath = join(wd, "config.toml");
|
|
20
|
+
const hooksPath = join(wd, "hooks.json");
|
|
21
|
+
const hooksPlan = planManagedCodexHooksMerge(null, wd, hooksPath);
|
|
22
|
+
if (!hooksPlan.ok || hooksPlan.finalContent === null) {
|
|
23
|
+
throw new Error("Expected setup hook plan to produce a hooks artifact.");
|
|
24
|
+
}
|
|
25
|
+
await writeFile(hooksPath, hooksPlan.finalContent);
|
|
26
|
+
const config = buildMergedConfig("", wd, {
|
|
27
|
+
codexHooksFile: hooksPath,
|
|
28
|
+
codexHooksContent: hooksPlan.finalContent,
|
|
29
|
+
managedHookTrustState: hooksPlan.finalTrustState,
|
|
30
|
+
priorManagedHookTrustState: hooksPlan.priorTrustState,
|
|
31
|
+
});
|
|
32
|
+
await writeFile(configPath, config);
|
|
33
|
+
return {
|
|
34
|
+
configPath,
|
|
35
|
+
hooksPath,
|
|
36
|
+
config,
|
|
37
|
+
trustState: hooksPlan.finalTrustState,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
18
40
|
/** Assert the current OMX block appears exactly once */
|
|
19
41
|
function assertSingleOmxBlock(toml, options = {}) {
|
|
20
42
|
assert.equal(count(toml, /# oh-my-codex \(OMX\) Configuration/g), 1, "OMX marker should appear once");
|
|
@@ -133,15 +155,75 @@ describe("config generator idempotency (#384)", () => {
|
|
|
133
155
|
await mergeConfig(configPath, wd);
|
|
134
156
|
const toml = await readFile(configPath, "utf-8");
|
|
135
157
|
assertSingleOmxBlock(toml);
|
|
136
|
-
assert.
|
|
158
|
+
assert.doesNotMatch(toml, /^multi_agent\s*=/m);
|
|
137
159
|
assert.match(toml, /^child_agents_md = true$/m);
|
|
138
160
|
assert.match(toml, /^hooks = true$/m);
|
|
139
161
|
assert.match(toml, /^goals = true$/m);
|
|
162
|
+
assert.doesNotMatch(toml, /^\[agents\]$/m);
|
|
163
|
+
assert.doesNotMatch(toml, /^max_threads\s*=/m);
|
|
164
|
+
assert.doesNotMatch(toml, /^max_depth\s*=/m);
|
|
140
165
|
}
|
|
141
166
|
finally {
|
|
142
167
|
await rm(wd, { recursive: true, force: true });
|
|
143
168
|
}
|
|
144
169
|
});
|
|
170
|
+
it("classifies legacy multi-agent keys without mutating config", () => {
|
|
171
|
+
const absent = analyzeLegacyMultiAgentConfig("");
|
|
172
|
+
assert.equal(absent.assessments["features.multi_agent"].state, "absent");
|
|
173
|
+
const legacy = analyzeLegacyMultiAgentConfig([
|
|
174
|
+
"[features]",
|
|
175
|
+
"multi_agent = true",
|
|
176
|
+
"",
|
|
177
|
+
"[agents]",
|
|
178
|
+
"max_threads = 6",
|
|
179
|
+
"max_depth = 2",
|
|
180
|
+
].join("\n"));
|
|
181
|
+
assert.equal(legacy.assessments["features.multi_agent"].state, "retained-legacy");
|
|
182
|
+
assert.equal(legacy.assessments["agents.max_threads"].state, "retained-legacy");
|
|
183
|
+
assert.equal(legacy.assessments["agents.max_depth"].state, "retained-legacy");
|
|
184
|
+
const custom = analyzeLegacyMultiAgentConfig("[features]\nmulti_agent = false\n\n[agents]\nmax_threads = 8\nmax_depth = 3\n");
|
|
185
|
+
assert.equal(custom.assessments["features.multi_agent"].state, "custom");
|
|
186
|
+
assert.equal(custom.assessments["agents.max_threads"].state, "custom");
|
|
187
|
+
assert.equal(custom.assessments["agents.max_depth"].state, "custom");
|
|
188
|
+
const invalid = analyzeLegacyMultiAgentConfig("[features\nmulti_agent = true");
|
|
189
|
+
assert.equal(invalid.assessments["features.multi_agent"].state, "invalid/duplicate");
|
|
190
|
+
const duplicate = analyzeLegacyMultiAgentConfig("[features]\nmulti_agent = true\nmulti_agent = false\n");
|
|
191
|
+
assert.equal(duplicate.assessments["features.multi_agent"].state, "invalid/duplicate");
|
|
192
|
+
assert.equal(duplicate.assessments["features.multi_agent"].reasonCode, "toml-duplicate-key");
|
|
193
|
+
});
|
|
194
|
+
it("preserves legacy multi-agent values and all role tables across fixed-point merges", () => {
|
|
195
|
+
const existing = [
|
|
196
|
+
"[features]",
|
|
197
|
+
"multi_agent = false",
|
|
198
|
+
"",
|
|
199
|
+
"[agents]",
|
|
200
|
+
"max_threads = 8",
|
|
201
|
+
"max_depth = 3",
|
|
202
|
+
"",
|
|
203
|
+
"[agents.executor]",
|
|
204
|
+
'config_file = "/custom/executor.toml"',
|
|
205
|
+
"",
|
|
206
|
+
'[agents."review bot"]',
|
|
207
|
+
'config_file = "/custom/review.toml"',
|
|
208
|
+
"",
|
|
209
|
+
].join("\n");
|
|
210
|
+
const merged = buildMergedConfig(existing, "/tmp/omx");
|
|
211
|
+
assert.match(merged, /^multi_agent = false$/m);
|
|
212
|
+
assert.match(merged, /^max_threads = 8$/m);
|
|
213
|
+
assert.match(merged, /^max_depth = 3$/m);
|
|
214
|
+
assert.match(merged, /^\[agents\.executor\]$/m);
|
|
215
|
+
assert.match(merged, /^\[agents\."review bot"\]$/m);
|
|
216
|
+
assert.match(merged, /^config_file = "\/custom\/executor\.toml"$/m);
|
|
217
|
+
assert.match(merged, /^config_file = "\/custom\/review\.toml"$/m);
|
|
218
|
+
const parsed = TOML.parse(merged);
|
|
219
|
+
assert.equal(parsed.agents?.executor?.config_file, "/custom/executor.toml");
|
|
220
|
+
assert.equal(parsed.agents?.["review bot"]?.config_file, "/custom/review.toml");
|
|
221
|
+
assert.equal(buildMergedConfig(merged, "/tmp/omx"), merged);
|
|
222
|
+
});
|
|
223
|
+
it("can preserve multi_agent while stripping other OMX feature flags", () => {
|
|
224
|
+
const stripped = stripOmxFeatureFlags("[features]\nmulti_agent = false\nchild_agents_md = true\nhooks = true\ngoals = true\n", { preserveMultiAgent: true });
|
|
225
|
+
assert.equal(stripped, "[features]\nmulti_agent = false\n");
|
|
226
|
+
});
|
|
145
227
|
it("emits first-party MCP blocks only when explicitly enabled", () => {
|
|
146
228
|
const toml = buildMergedConfig("", "/tmp/omx", {
|
|
147
229
|
includeFirstPartyMcp: true,
|
|
@@ -354,11 +436,10 @@ describe("config generator idempotency (#384)", () => {
|
|
|
354
436
|
assertSingleOmxBlock(toml);
|
|
355
437
|
assert.match(toml, /^\[user\.custom\]$/m, "user section preserved");
|
|
356
438
|
assert.match(toml, /^name = "kept"$/m, "user key preserved");
|
|
357
|
-
assert.match(toml, /^\[agents\]$/m, "
|
|
358
|
-
assert.match(toml,
|
|
359
|
-
assert.match(toml, /^
|
|
360
|
-
assert.
|
|
361
|
-
assert.doesNotMatch(toml, /^\[agents\.explore\]$/m, "legacy OMX agent entry removed");
|
|
439
|
+
assert.match(toml, /^\[agents\.executor\]$/m, "known agent table preserved");
|
|
440
|
+
assert.match(toml, /^\[agents\.explore\]$/m, "known agent table preserved");
|
|
441
|
+
assert.match(toml, /^config_file = "\/old\/path\/executor\.toml"$/m);
|
|
442
|
+
assert.match(toml, /^config_file = "\/old\/path\/explore\.toml"$/m);
|
|
362
443
|
}
|
|
363
444
|
finally {
|
|
364
445
|
await rm(wd, { recursive: true, force: true });
|
|
@@ -518,128 +599,165 @@ describe("config generator idempotency (#384)", () => {
|
|
|
518
599
|
await rm(wd, { recursive: true, force: true });
|
|
519
600
|
}
|
|
520
601
|
});
|
|
521
|
-
it("
|
|
602
|
+
it("does not seed context defaults and preserves explicit context settings", async () => {
|
|
522
603
|
const wd = await mkdtemp(join(tmpdir(), "omx-idem-"));
|
|
523
604
|
try {
|
|
524
605
|
const configPath = join(wd, "config.toml");
|
|
525
|
-
await writeFile(configPath, '
|
|
606
|
+
await writeFile(configPath, ['model = "gpt-5.6-sol"', "model_context_window = 640000", ""].join("\n"));
|
|
526
607
|
await mergeConfig(configPath, wd);
|
|
527
608
|
const toml = await readFile(configPath, "utf-8");
|
|
528
|
-
assert.match(toml, /^
|
|
529
|
-
assert.
|
|
530
|
-
assert.
|
|
531
|
-
|
|
532
|
-
|
|
609
|
+
assert.match(toml, /^model_context_window = 640000$/m);
|
|
610
|
+
assert.doesNotMatch(toml, /^model_auto_compact_token_limit\s*=/m);
|
|
611
|
+
assert.doesNotMatch(toml, /seeded behavioral defaults/);
|
|
612
|
+
await mergeConfig(configPath, wd);
|
|
613
|
+
const repeated = await readFile(configPath, "utf-8");
|
|
614
|
+
assert.equal(repeated, toml);
|
|
615
|
+
assert.doesNotMatch(repeated, /^model_auto_compact_token_limit\s*=/m);
|
|
616
|
+
assert.doesNotMatch(repeated, /seeded behavioral defaults/);
|
|
533
617
|
}
|
|
534
618
|
finally {
|
|
535
619
|
await rm(wd, { recursive: true, force: true });
|
|
536
620
|
}
|
|
537
621
|
});
|
|
538
|
-
it("
|
|
539
|
-
const
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
622
|
+
it("removes exact legacy source spans with LF, CRLF, and EOF variants", () => {
|
|
623
|
+
const start = "# oh-my-codex seeded behavioral defaults (uninstall removes unchanged defaults)";
|
|
624
|
+
const end = "# End oh-my-codex seeded behavioral defaults";
|
|
625
|
+
const fixtures = [
|
|
626
|
+
{
|
|
627
|
+
input: `before\n${start}\nmodel_context_window = 250000\nmodel_auto_compact_token_limit = 200000\n${end}\nafter`,
|
|
628
|
+
expected: "before\nafter",
|
|
629
|
+
},
|
|
630
|
+
{
|
|
631
|
+
input: `model_auto_compact_token_limit=123\r\n${start}\r\nmodel_context_window = 250000\r\n${end}\r\n[features]\r\nweb_search = true`,
|
|
632
|
+
expected: "model_auto_compact_token_limit=123\r\n[features]\r\nweb_search = true",
|
|
633
|
+
},
|
|
634
|
+
{
|
|
635
|
+
input: `model_context_window = [\n 1,\n]\n${start}\nmodel_auto_compact_token_limit = 200000\n${end}`,
|
|
636
|
+
expected: "model_context_window = [\n 1,\n]\n",
|
|
637
|
+
},
|
|
638
|
+
];
|
|
639
|
+
for (const { input, expected } of fixtures) {
|
|
640
|
+
const stripped = stripOmxSeededBehavioralDefaults(input);
|
|
641
|
+
assert.equal(stripped, expected);
|
|
642
|
+
assert.equal(stripOmxSeededBehavioralDefaults(stripped), stripped);
|
|
553
643
|
}
|
|
554
644
|
});
|
|
555
|
-
it("
|
|
556
|
-
const
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
}
|
|
645
|
+
it("strips bounded customized or siblingless singleton markers and preserves ambiguity", () => {
|
|
646
|
+
const start = "# oh-my-codex seeded behavioral defaults (uninstall removes unchanged defaults)";
|
|
647
|
+
const end = "# End oh-my-codex seeded behavioral defaults";
|
|
648
|
+
const customized = `before\r\n${start}\r\nmodel_context_window = 123\r\n${end}\r\nafter`;
|
|
649
|
+
assert.equal(stripOmxSeededBehavioralDefaults(customized), "before\r\nmodel_context_window = 123\r\nafter");
|
|
650
|
+
const siblingless = `${start}\nmodel_context_window = 250000\n${end}\n[features]\nmodel_auto_compact_token_limit = 999`;
|
|
651
|
+
assert.equal(stripOmxSeededBehavioralDefaults(siblingless), "model_context_window = 250000\n[features]\nmodel_auto_compact_token_limit = 999");
|
|
652
|
+
const ambiguous = `${start}\nmodel_context_window = 250000\n${end}\nmodel_auto_compact_token_limit = 1\nmodel_auto_compact_token_limit = 2`;
|
|
653
|
+
assert.equal(stripOmxSeededBehavioralDefaults(ambiguous), ambiguous);
|
|
654
|
+
const sameKeyOutside = `model_context_window = 1\n${start}\nmodel_context_window = 250000\n${end}\nmodel_auto_compact_token_limit = 2`;
|
|
655
|
+
assert.equal(stripOmxSeededBehavioralDefaults(sameKeyOutside), sameKeyOutside);
|
|
656
|
+
const malformed = `${start}\nmodel_context_window = 250000`;
|
|
657
|
+
assert.equal(stripOmxSeededBehavioralDefaults(malformed), malformed);
|
|
569
658
|
});
|
|
570
|
-
it("
|
|
571
|
-
const
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
659
|
+
it("preserves every byte inside noncanonical bounded blocks", () => {
|
|
660
|
+
const start = "# oh-my-codex seeded behavioral defaults (uninstall removes unchanged defaults)";
|
|
661
|
+
const end = "# End oh-my-codex seeded behavioral defaults";
|
|
662
|
+
const bodies = [
|
|
663
|
+
["model_context_window = 250000", "# user comment", "model_auto_compact_token_limit = 200000"],
|
|
664
|
+
["model_context_window = 250000", "", "model_auto_compact_token_limit = 200000"],
|
|
665
|
+
[" model_context_window = 250000", "model_auto_compact_token_limit = 200000"],
|
|
666
|
+
["model_context_window = 250000", "model_auto_compact_token_limit = 200000 "],
|
|
667
|
+
["model_auto_compact_token_limit = 200000", "model_context_window = 250000"],
|
|
668
|
+
["model_context_window = 250000", "model_context_window = 250000", "model_auto_compact_token_limit = 200000"],
|
|
669
|
+
["model_context_window = 250000", "unexpected = true", "model_auto_compact_token_limit = 200000"],
|
|
670
|
+
["model_context_window = 250_000", "model_auto_compact_token_limit = 200000"],
|
|
671
|
+
["", "model_context_window = 250000", "model_auto_compact_token_limit = 200000", ""],
|
|
672
|
+
];
|
|
673
|
+
for (const body of bodies) {
|
|
674
|
+
const input = ["before", start, ...body, end, "after"].join("\n");
|
|
675
|
+
const expected = ["before", ...body, "after"].join("\n");
|
|
676
|
+
const stripped = stripOmxSeededBehavioralDefaults(input);
|
|
677
|
+
assert.equal(stripped, expected);
|
|
678
|
+
assert.equal(stripOmxSeededBehavioralDefaults(stripped), stripped);
|
|
583
679
|
}
|
|
584
680
|
});
|
|
585
|
-
it("
|
|
586
|
-
const
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
681
|
+
it("fails closed for duplicate assignments and malformed marker topologies", () => {
|
|
682
|
+
const start = "# oh-my-codex seeded behavioral defaults (uninstall removes unchanged defaults)";
|
|
683
|
+
const end = "# End oh-my-codex seeded behavioral defaults";
|
|
684
|
+
const pair = [start, "model_context_window = 250000", "model_auto_compact_token_limit = 200000", end];
|
|
685
|
+
const unchanged = [
|
|
686
|
+
["model_context_window = 999", ...pair, "after"].join("\n"),
|
|
687
|
+
[...pair, "model_auto_compact_token_limit = 999", "after"].join("\n"),
|
|
688
|
+
[start, start, "model_context_window = 250000", "model_auto_compact_token_limit = 200000", end, end].join("\n"),
|
|
689
|
+
[...pair, ...pair].join("\n"),
|
|
690
|
+
];
|
|
691
|
+
for (const input of unchanged) {
|
|
692
|
+
assert.equal(stripOmxSeededBehavioralDefaults(input), input);
|
|
693
|
+
assert.equal(hasExactOmxSeededBehavioralDefaultsPair(input), false);
|
|
598
694
|
}
|
|
695
|
+
const invalidSuffix = ["before", ...pair, "invalid = [", ""].join("\n");
|
|
696
|
+
const expected = "before\ninvalid = [\n";
|
|
697
|
+
assert.equal(stripOmxSeededBehavioralDefaults(invalidSuffix), expected);
|
|
698
|
+
assert.equal(stripOmxSeededBehavioralDefaults(expected), expected);
|
|
599
699
|
});
|
|
600
|
-
it("
|
|
601
|
-
const
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
700
|
+
it("ignores marker-shaped text inside TOML values and fails closed on stray markers", () => {
|
|
701
|
+
const start = "# oh-my-codex seeded behavioral defaults (uninstall removes unchanged defaults)";
|
|
702
|
+
const end = "# End oh-my-codex seeded behavioral defaults";
|
|
703
|
+
const stringContent = [
|
|
704
|
+
'developer_instructions = """',
|
|
705
|
+
start,
|
|
706
|
+
"model_context_window = 250000",
|
|
707
|
+
"model_auto_compact_token_limit = 200000",
|
|
708
|
+
end,
|
|
709
|
+
'"""',
|
|
710
|
+
"",
|
|
711
|
+
].join("\n");
|
|
712
|
+
assert.equal(stripOmxSeededBehavioralDefaults(stringContent), stringContent);
|
|
713
|
+
assert.equal(hasExactOmxSeededBehavioralDefaultsPair(stringContent), false);
|
|
714
|
+
const duplicateAfterTable = [
|
|
715
|
+
start,
|
|
716
|
+
"model_context_window = 250000",
|
|
717
|
+
"model_auto_compact_token_limit = 200000",
|
|
718
|
+
end,
|
|
719
|
+
"[tui]",
|
|
720
|
+
start,
|
|
721
|
+
"",
|
|
722
|
+
].join("\n");
|
|
723
|
+
assert.equal(stripOmxSeededBehavioralDefaults(duplicateAfterTable), duplicateAfterTable);
|
|
724
|
+
assert.equal(hasExactOmxSeededBehavioralDefaultsPair(duplicateAfterTable), false);
|
|
615
725
|
});
|
|
616
|
-
it("
|
|
617
|
-
const
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
726
|
+
it("migrates before reconstruction without incremental normalization", () => {
|
|
727
|
+
const start = "# oh-my-codex seeded behavioral defaults (uninstall removes unchanged defaults)";
|
|
728
|
+
const end = "# End oh-my-codex seeded behavioral defaults";
|
|
729
|
+
const fixtures = [
|
|
730
|
+
{
|
|
731
|
+
baseline: 'approval_policy = "on-failure"\n[features]\nweb_search = true\n',
|
|
732
|
+
migrated: `approval_policy = "on-failure"\n${start}\nmodel_context_window = 250000\nmodel_auto_compact_token_limit = 200000\n${end}\n[features]\nweb_search = true\n`,
|
|
733
|
+
},
|
|
734
|
+
{
|
|
735
|
+
baseline: "model_auto_compact_token_limit = [\n 999,\n]\n[features]\nweb_search = true\n",
|
|
736
|
+
migrated: `model_auto_compact_token_limit = [\n 999,\n]\n${start}\nmodel_context_window = 250000\n${end}\n[features]\nweb_search = true\n`,
|
|
737
|
+
},
|
|
738
|
+
{
|
|
739
|
+
baseline: "# explicit sibling\nmodel_context_window = 640000\n[features]\nweb_search = true\n",
|
|
740
|
+
migrated: `# explicit sibling\nmodel_context_window = 640000\n${start}\nmodel_auto_compact_token_limit = 200000\n${end}\n[features]\nweb_search = true\n`,
|
|
741
|
+
},
|
|
742
|
+
];
|
|
743
|
+
for (const fixture of fixtures) {
|
|
744
|
+
const baseline = buildMergedConfig(fixture.baseline, "/tmp/omx");
|
|
745
|
+
const migrated = buildMergedConfig(fixture.migrated, "/tmp/omx");
|
|
746
|
+
assert.equal(migrated, baseline);
|
|
747
|
+
assert.equal(buildMergedConfig(migrated, "/tmp/omx"), migrated);
|
|
748
|
+
assert.doesNotMatch(migrated, /seeded behavioral defaults/);
|
|
749
|
+
assert.doesNotThrow(() => TOML.parse(migrated));
|
|
631
750
|
}
|
|
632
751
|
});
|
|
633
|
-
it("
|
|
752
|
+
it("does not write retired global [agents] defaults", async () => {
|
|
634
753
|
const wd = await mkdtemp(join(tmpdir(), "omx-idem-"));
|
|
635
754
|
try {
|
|
636
755
|
const configPath = join(wd, "config.toml");
|
|
637
756
|
await mergeConfig(configPath, wd);
|
|
638
757
|
const toml = await readFile(configPath, "utf-8");
|
|
639
|
-
assert.
|
|
640
|
-
assert.
|
|
641
|
-
assert.
|
|
642
|
-
assert.doesNotMatch(toml, /^\[agents\.[^\]]+\]$/m, "legacy per-agent config_file entries omitted");
|
|
758
|
+
assert.doesNotMatch(toml, /^\[agents\]$/m);
|
|
759
|
+
assert.doesNotMatch(toml, /^max_threads\s*=/m);
|
|
760
|
+
assert.doesNotMatch(toml, /^max_depth\s*=/m);
|
|
643
761
|
}
|
|
644
762
|
finally {
|
|
645
763
|
await rm(wd, { recursive: true, force: true });
|
|
@@ -692,6 +810,106 @@ describe("config generator idempotency (#384)", () => {
|
|
|
692
810
|
await rm(wd, { recursive: true, force: true });
|
|
693
811
|
}
|
|
694
812
|
});
|
|
813
|
+
it("strips only complete exact OMX marker blocks outside TOML values", () => {
|
|
814
|
+
const start = "# oh-my-codex (OMX) Configuration";
|
|
815
|
+
const end = "# End oh-my-codex";
|
|
816
|
+
const decoys = [
|
|
817
|
+
`basic = "${start}"`,
|
|
818
|
+
`literal = '${end}'`,
|
|
819
|
+
'multiline_basic = """',
|
|
820
|
+
start,
|
|
821
|
+
end,
|
|
822
|
+
'"""',
|
|
823
|
+
"multiline_literal = '''",
|
|
824
|
+
start,
|
|
825
|
+
end,
|
|
826
|
+
"'''",
|
|
827
|
+
`# Marker-shaped text: ${start}`,
|
|
828
|
+
`# Marker-shaped text: ${end}`,
|
|
829
|
+
];
|
|
830
|
+
const decoyOnly = [...decoys, ""].join("\n");
|
|
831
|
+
assert.deepEqual(stripExistingOmxBlocks(decoyOnly), {
|
|
832
|
+
cleaned: decoyOnly,
|
|
833
|
+
removed: 0,
|
|
834
|
+
});
|
|
835
|
+
const incomplete = [...decoys, "", start, "[user.incomplete]", "enabled = true", ""].join("\n");
|
|
836
|
+
assert.deepEqual(stripExistingOmxBlocks(incomplete), {
|
|
837
|
+
cleaned: incomplete,
|
|
838
|
+
removed: 0,
|
|
839
|
+
});
|
|
840
|
+
const managed = [
|
|
841
|
+
...decoys,
|
|
842
|
+
"",
|
|
843
|
+
"# ============================================================",
|
|
844
|
+
start,
|
|
845
|
+
"[mcp_servers.omx_state]",
|
|
846
|
+
'command = "node"',
|
|
847
|
+
end,
|
|
848
|
+
"",
|
|
849
|
+
"[user.after]",
|
|
850
|
+
'name = "kept"',
|
|
851
|
+
"",
|
|
852
|
+
].join("\n");
|
|
853
|
+
const expected = [...decoys, "", "[user.after]", 'name = "kept"', ""].join("\n");
|
|
854
|
+
assert.deepEqual(stripExistingOmxBlocks(managed), {
|
|
855
|
+
cleaned: expected,
|
|
856
|
+
removed: 1,
|
|
857
|
+
});
|
|
858
|
+
assert.doesNotThrow(() => TOML.parse(expected));
|
|
859
|
+
});
|
|
860
|
+
it("keeps OMX delimiter comments inert inside complete TOML assignments", () => {
|
|
861
|
+
const start = "# oh-my-codex (OMX) Configuration";
|
|
862
|
+
const end = "# End oh-my-codex";
|
|
863
|
+
const fixtures = [
|
|
864
|
+
["root multiline array", ["root = [", start, end, "]"].join("\n")],
|
|
865
|
+
["dotted multiline array", ["outer.value = [", start, end, "]"].join("\n")],
|
|
866
|
+
["quoted multiline array", ['"quoted" = [', start, end, "]"].join("\n")],
|
|
867
|
+
["empty-quoted multiline array", ['"" = [', start, end, "]"].join("\n")],
|
|
868
|
+
["root multiline basic value", ['root_value = """', start, end, '"""'].join("\n")],
|
|
869
|
+
["dotted multiline literal value", ["outer.literal = '''", start, end, "'''"].join("\n")],
|
|
870
|
+
];
|
|
871
|
+
for (const [name, assignment] of fixtures) {
|
|
872
|
+
const config = `${assignment}\n`;
|
|
873
|
+
assert.doesNotThrow(() => TOML.parse(config), `${name} fixture must be valid TOML`);
|
|
874
|
+
assert.deepEqual(stripExistingOmxBlocks(config), { cleaned: config, removed: 0 }, name);
|
|
875
|
+
const merged = buildMergedConfig(config, "/tmp/omx");
|
|
876
|
+
assert.ok(merged.includes(assignment), `${name} bytes must survive merging`);
|
|
877
|
+
assert.doesNotThrow(() => TOML.parse(merged), `${name} merged config must be valid TOML`);
|
|
878
|
+
}
|
|
879
|
+
});
|
|
880
|
+
it("rejects marker-contained hook trust state without ownership proof", () => {
|
|
881
|
+
const start = "# oh-my-codex (OMX) Configuration";
|
|
882
|
+
const end = "# End oh-my-codex";
|
|
883
|
+
const fixtures = [
|
|
884
|
+
["table", ['[hooks.state."foreign"]', 'trusted_hash = "sha256:foreign"'].join("\n")],
|
|
885
|
+
["dotted", 'hooks.state."foreign" = { trusted_hash = "sha256:foreign" }'],
|
|
886
|
+
["inline", 'hooks = { state = { foreign = { trusted_hash = "sha256:foreign" } } }'],
|
|
887
|
+
["scalar", 'hooks.state = "foreign"'],
|
|
888
|
+
];
|
|
889
|
+
const isManagedTrustConflict = (error) => error instanceof ManagedCodexHooksPlanError &&
|
|
890
|
+
error.code === "managed_trust_key_conflict";
|
|
891
|
+
for (const [name, content] of fixtures) {
|
|
892
|
+
const config = [start, content, end, ""].join("\n");
|
|
893
|
+
assert.doesNotThrow(() => TOML.parse(config), `${name} fixture must be valid TOML`);
|
|
894
|
+
assert.throws(() => stripExistingOmxBlocks(config), isManagedTrustConflict, name);
|
|
895
|
+
assert.throws(() => buildMergedConfig(config, "/tmp/omx"), isManagedTrustConflict, name);
|
|
896
|
+
}
|
|
897
|
+
});
|
|
898
|
+
it("matches managed hooks.state keys case-sensitively", () => {
|
|
899
|
+
const start = "# oh-my-codex (OMX) Configuration";
|
|
900
|
+
const end = "# End oh-my-codex";
|
|
901
|
+
const variants = [
|
|
902
|
+
'Hooks.state = { foreign = { trusted_hash = "sha256:foreign" } }',
|
|
903
|
+
'hooks.State = { foreign = { trusted_hash = "sha256:foreign" } }',
|
|
904
|
+
'"Hooks"."State" = { foreign = { trusted_hash = "sha256:foreign" } }',
|
|
905
|
+
];
|
|
906
|
+
for (const variant of variants) {
|
|
907
|
+
const config = [start, variant, end, ""].join("\n");
|
|
908
|
+
assert.doesNotThrow(() => TOML.parse(config));
|
|
909
|
+
assert.equal(stripManagedCodexHookTrustState(config, { managedTrustState: {} }), config, `${variant} must remain foreign`);
|
|
910
|
+
assert.doesNotThrow(() => buildMergedConfig(config, "/tmp/omx"));
|
|
911
|
+
}
|
|
912
|
+
});
|
|
695
913
|
it("mergeConfig removes legacy omx_team_run tables during setup upgrade", async () => {
|
|
696
914
|
const wd = await mkdtemp(join(tmpdir(), "omx-idem-"));
|
|
697
915
|
try {
|
|
@@ -791,6 +1009,74 @@ describe("config generator idempotency (#384)", () => {
|
|
|
791
1009
|
await rm(wd, { recursive: true, force: true });
|
|
792
1010
|
}
|
|
793
1011
|
});
|
|
1012
|
+
it("repairs duplicate TUI config with setup-generated hook trust from the current hooks artifact", async () => {
|
|
1013
|
+
const wd = await mkdtemp(join(tmpdir(), "omx-idem-"));
|
|
1014
|
+
try {
|
|
1015
|
+
const { configPath, config: setupConfig } = await writeSetupGeneratedHookTrustFixture(wd);
|
|
1016
|
+
const trustBlock = setupConfig.match(/# OMX-owned Codex hook trust state\n[\s\S]*?# End OMX-owned Codex hook trust state/)?.[0];
|
|
1017
|
+
assert.ok(trustBlock, "setup fixture must contain fenced managed hook trust");
|
|
1018
|
+
const setupTrustState = TOML.parse(setupConfig).hooks?.state;
|
|
1019
|
+
await writeFile(configPath, `${setupConfig}\n[tui]\nstatus_line = ["git-branch"]\n`);
|
|
1020
|
+
assert.equal(await repairConfigIfNeeded(configPath, wd), true);
|
|
1021
|
+
const repaired = await readFile(configPath, "utf-8");
|
|
1022
|
+
const repairedTrustState = TOML.parse(repaired).hooks?.state;
|
|
1023
|
+
assert.equal(count(repaired, /^\[tui\]$/gm), 1);
|
|
1024
|
+
assert.ok(repaired.includes(trustBlock), "repair must preserve setup-generated trust bytes");
|
|
1025
|
+
assert.deepEqual(repairedTrustState, setupTrustState);
|
|
1026
|
+
}
|
|
1027
|
+
finally {
|
|
1028
|
+
await rm(wd, { recursive: true, force: true });
|
|
1029
|
+
}
|
|
1030
|
+
});
|
|
1031
|
+
it("fails closed without rewriting managed hook trust when launch repair cannot prove it", async () => {
|
|
1032
|
+
const wd = await mkdtemp(join(tmpdir(), "omx-idem-"));
|
|
1033
|
+
try {
|
|
1034
|
+
const { configPath, hooksPath, config: setupConfig, trustState } = await writeSetupGeneratedHookTrustFixture(wd);
|
|
1035
|
+
const [firstTrustState] = Object.values(trustState);
|
|
1036
|
+
assert.ok(firstTrustState);
|
|
1037
|
+
const repairTarget = `${setupConfig}\n[tui]\nstatus_line = ["git-branch"]\n`;
|
|
1038
|
+
const conflicting = repairTarget.replace(firstTrustState.trusted_hash, "sha256:conflicting-user-trust");
|
|
1039
|
+
await writeFile(configPath, conflicting);
|
|
1040
|
+
await assert.rejects(repairConfigIfNeeded(configPath, wd), (error) => error instanceof ManagedCodexHooksPlanError &&
|
|
1041
|
+
error.code === "managed_trust_key_conflict");
|
|
1042
|
+
assert.equal(await readFile(configPath, "utf-8"), conflicting);
|
|
1043
|
+
const unprovenHooks = [
|
|
1044
|
+
["invalid", "{ not valid JSON"],
|
|
1045
|
+
[
|
|
1046
|
+
"ambiguous",
|
|
1047
|
+
JSON.stringify({
|
|
1048
|
+
hooks: {
|
|
1049
|
+
SessionStart: [
|
|
1050
|
+
{
|
|
1051
|
+
hooks: [
|
|
1052
|
+
{
|
|
1053
|
+
type: "command",
|
|
1054
|
+
command: `NODE ${join(wd, "dist", "scripts", "codex-native-hook.js")}`,
|
|
1055
|
+
},
|
|
1056
|
+
],
|
|
1057
|
+
},
|
|
1058
|
+
],
|
|
1059
|
+
},
|
|
1060
|
+
}),
|
|
1061
|
+
],
|
|
1062
|
+
["missing", null],
|
|
1063
|
+
];
|
|
1064
|
+
for (const [name, hooksContent] of unprovenHooks) {
|
|
1065
|
+
await writeFile(configPath, repairTarget);
|
|
1066
|
+
if (hooksContent === null) {
|
|
1067
|
+
await rm(hooksPath);
|
|
1068
|
+
}
|
|
1069
|
+
else {
|
|
1070
|
+
await writeFile(hooksPath, hooksContent);
|
|
1071
|
+
}
|
|
1072
|
+
await assert.rejects(repairConfigIfNeeded(configPath, wd), (error) => error instanceof ManagedCodexHooksPlanError, name);
|
|
1073
|
+
assert.equal(await readFile(configPath, "utf-8"), repairTarget, name);
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
finally {
|
|
1077
|
+
await rm(wd, { recursive: true, force: true });
|
|
1078
|
+
}
|
|
1079
|
+
});
|
|
794
1080
|
it("preserves trailing user config after an unmatched managed hook trust-state start marker", () => {
|
|
795
1081
|
const malformed = [
|
|
796
1082
|
'model = "gpt-5.6-sol"',
|
|
@@ -856,7 +1142,7 @@ describe("config generator idempotency (#384)", () => {
|
|
|
856
1142
|
assert.match(stripped, /^\[desktop\]$/m);
|
|
857
1143
|
assert.doesNotThrow(() => TOML.parse(stripped));
|
|
858
1144
|
});
|
|
859
|
-
it("
|
|
1145
|
+
it("fails closed on conflicting same-key hook trust state", () => {
|
|
860
1146
|
const hooksPath = "/tmp/codex/hooks.json";
|
|
861
1147
|
const config = [
|
|
862
1148
|
'model = "gpt-5.6-sol"',
|
|
@@ -866,13 +1152,10 @@ describe("config generator idempotency (#384)", () => {
|
|
|
866
1152
|
"enabled = false",
|
|
867
1153
|
"",
|
|
868
1154
|
].join("\n");
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
assert.match(refreshed, /^enabled = false$/m);
|
|
872
|
-
assert.equal(count(refreshed, /^\[hooks\.state\."\/tmp\/codex\/hooks\.json:post_compact:0:0"\]$/gm), 1, "preserved same-key user trust state must not be duplicated");
|
|
873
|
-
assert.doesNotThrow(() => TOML.parse(refreshed));
|
|
1155
|
+
assert.throws(() => upsertManagedCodexHookTrustState(config, "/tmp/omx", hooksPath), (error) => error instanceof ManagedCodexHooksPlanError &&
|
|
1156
|
+
error.code === "managed_trust_key_conflict");
|
|
874
1157
|
});
|
|
875
|
-
it("
|
|
1158
|
+
it("fails closed on unproven same-key hook trust-state tables", () => {
|
|
876
1159
|
const hooksPath = "/tmp/codex/hooks.json";
|
|
877
1160
|
const managedTrustState = buildManagedCodexHookTrustState(hooksPath, "/tmp/omx");
|
|
878
1161
|
const fixtures = [
|
|
@@ -922,10 +1205,534 @@ describe("config generator idempotency (#384)", () => {
|
|
|
922
1205
|
],
|
|
923
1206
|
];
|
|
924
1207
|
for (const [name, table] of fixtures) {
|
|
925
|
-
|
|
1208
|
+
assert.throws(() => stripManagedCodexHookTrustState(table.join("\n"), {
|
|
926
1209
|
managedTrustState,
|
|
927
|
-
})
|
|
928
|
-
|
|
1210
|
+
}), (error) => error instanceof ManagedCodexHooksPlanError &&
|
|
1211
|
+
error.code === "managed_trust_key_conflict", name);
|
|
1212
|
+
}
|
|
1213
|
+
});
|
|
1214
|
+
it("handles semantically equivalent TOML trust-key variants before writing a replacement", () => {
|
|
1215
|
+
const hooksPath = "/tmp/codex/hooks.json";
|
|
1216
|
+
const managedTrustState = buildManagedCodexHookTrustState(hooksPath, "/tmp/omx");
|
|
1217
|
+
const key = `${hooksPath}:post_compact:0:0`;
|
|
1218
|
+
const hash = managedTrustState[key]?.trusted_hash;
|
|
1219
|
+
assert.ok(hash);
|
|
1220
|
+
const matchingHeaders = [
|
|
1221
|
+
`[hooks.state.'${key}']`,
|
|
1222
|
+
`[hooks.state."${key.replace(/\//g, "\\u002f")}"]`,
|
|
1223
|
+
];
|
|
1224
|
+
for (const header of matchingHeaders) {
|
|
1225
|
+
const matching = [header, `trusted_hash = "${hash}"`, ""].join("\n");
|
|
1226
|
+
const refreshed = upsertManagedCodexHookTrustState(matching, "/tmp/omx", hooksPath, { managedTrustState });
|
|
1227
|
+
assert.equal(count(refreshed, /^\[hooks\.state\./gm), Object.keys(managedTrustState).length);
|
|
1228
|
+
assert.equal(TOML.parse(refreshed)
|
|
1229
|
+
.hooks?.state?.[key] !== undefined, true);
|
|
1230
|
+
}
|
|
1231
|
+
const conflicting = [
|
|
1232
|
+
matchingHeaders[0],
|
|
1233
|
+
'trusted_hash = "sha256:foreign"',
|
|
1234
|
+
"",
|
|
1235
|
+
].join("\n");
|
|
1236
|
+
assert.throws(() => upsertManagedCodexHookTrustState(conflicting, "/tmp/omx", hooksPath, { managedTrustState }), (error) => error instanceof ManagedCodexHooksPlanError &&
|
|
1237
|
+
error.code === "managed_trust_key_conflict");
|
|
1238
|
+
});
|
|
1239
|
+
it("removes only a single exact proven managed dotted hook trust assignment", () => {
|
|
1240
|
+
const hooksPath = "/tmp/codex/hooks.json";
|
|
1241
|
+
const managedTrustState = buildManagedCodexHookTrustState(hooksPath, "/tmp/omx");
|
|
1242
|
+
const key = `${hooksPath}:post_compact:0:0`;
|
|
1243
|
+
const hash = managedTrustState[key]?.trusted_hash;
|
|
1244
|
+
assert.ok(hash);
|
|
1245
|
+
const exactAssignments = [
|
|
1246
|
+
`hooks.state."${key}".trusted_hash = "${hash}"`,
|
|
1247
|
+
`hooks . state . '${key}' . trusted_hash = "${hash}"`,
|
|
1248
|
+
];
|
|
1249
|
+
for (const exact of exactAssignments) {
|
|
1250
|
+
assert.equal(stripManagedCodexHookTrustState(exact, { managedTrustState }), "");
|
|
1251
|
+
}
|
|
1252
|
+
const exact = exactAssignments[1];
|
|
1253
|
+
const refreshed = upsertManagedCodexHookTrustState(exact, "/tmp/omx", hooksPath, { managedTrustState });
|
|
1254
|
+
assert.doesNotMatch(refreshed, /^hooks\s*\.\s*state\s*\./m);
|
|
1255
|
+
assert.equal(count(refreshed, /^\[hooks\.state\./gm), Object.keys(managedTrustState).length);
|
|
1256
|
+
});
|
|
1257
|
+
it("fails closed for conflicting, commented, or multi-field managed dotted hook trust assignments", () => {
|
|
1258
|
+
const hooksPath = "/tmp/codex/hooks.json";
|
|
1259
|
+
const managedTrustState = buildManagedCodexHookTrustState(hooksPath, "/tmp/omx");
|
|
1260
|
+
const key = `${hooksPath}:post_compact:0:0`;
|
|
1261
|
+
const hash = managedTrustState[key]?.trusted_hash;
|
|
1262
|
+
assert.ok(hash);
|
|
1263
|
+
const assignment = `hooks.state.'${key}'.trusted_hash = "${hash}"`;
|
|
1264
|
+
const fixtures = [
|
|
1265
|
+
assignment.replace(hash, "sha256:foreign"),
|
|
1266
|
+
`${assignment}\nhooks.state.'${key}'.enabled = true`,
|
|
1267
|
+
`${assignment} # user comment`,
|
|
1268
|
+
];
|
|
1269
|
+
for (const fixture of fixtures) {
|
|
1270
|
+
assert.throws(() => stripManagedCodexHookTrustState(fixture, { managedTrustState }), (error) => error instanceof ManagedCodexHooksPlanError &&
|
|
1271
|
+
error.code === "managed_trust_key_conflict");
|
|
1272
|
+
}
|
|
1273
|
+
});
|
|
1274
|
+
it("removes exact inline hook trust state without changing adjacent TOML", () => {
|
|
1275
|
+
const hooksPath = "/tmp/codex/hooks.json";
|
|
1276
|
+
const managedTrustState = buildManagedCodexHookTrustState(hooksPath, "/tmp/omx");
|
|
1277
|
+
const key = `${hooksPath}:post_compact:0:0`;
|
|
1278
|
+
const hash = managedTrustState[key]?.trusted_hash;
|
|
1279
|
+
assert.ok(hash);
|
|
1280
|
+
const exactWholeStatement = `hooks = { state = { "${key}" = { trusted_hash = "${hash}" } } }`;
|
|
1281
|
+
assert.equal(stripManagedCodexHookTrustState(exactWholeStatement, { managedTrustState }), "", "an exact single-field inline hooks state remains removable");
|
|
1282
|
+
const inline = [
|
|
1283
|
+
'model = "gpt-5.6-sol"',
|
|
1284
|
+
`hooks.state.'${key}' = { trusted_hash = '${hash}' }`,
|
|
1285
|
+
"",
|
|
1286
|
+
"[desktop]",
|
|
1287
|
+
"git-create-pull-request-as-draft = true",
|
|
1288
|
+
"",
|
|
1289
|
+
].join("\n");
|
|
1290
|
+
const stripped = stripManagedCodexHookTrustState(inline, { managedTrustState });
|
|
1291
|
+
assert.doesNotMatch(stripped, /hooks\.state/);
|
|
1292
|
+
assert.match(stripped, /^model = "gpt-5\.6-sol"$/m);
|
|
1293
|
+
assert.match(stripped, /^\[desktop\]$/m);
|
|
1294
|
+
assert.match(stripped, /^git-create-pull-request-as-draft = true$/m);
|
|
1295
|
+
assert.doesNotThrow(() => TOML.parse(stripped));
|
|
1296
|
+
const refreshed = upsertManagedCodexHookTrustState(inline, "/tmp/omx", hooksPath, { managedTrustState });
|
|
1297
|
+
assert.equal(Object.keys(TOML.parse(refreshed)
|
|
1298
|
+
.hooks?.state ?? {}).filter((stateKey) => stateKey === key).length, 1);
|
|
1299
|
+
});
|
|
1300
|
+
it("fails closed for inline, dotted, and table hook trust representations with foreign siblings", () => {
|
|
1301
|
+
const hooksPath = "/tmp/codex/hooks.json";
|
|
1302
|
+
const managedTrustState = buildManagedCodexHookTrustState(hooksPath, "/tmp/omx");
|
|
1303
|
+
const key = `${hooksPath}:post_compact:0:0`;
|
|
1304
|
+
const hash = managedTrustState[key]?.trusted_hash;
|
|
1305
|
+
assert.ok(hash);
|
|
1306
|
+
const fixtures = [
|
|
1307
|
+
[
|
|
1308
|
+
"inline",
|
|
1309
|
+
`hooks = { state = { "${key}" = { trusted_hash = "${hash}" } }, foreign = "keep" }`,
|
|
1310
|
+
],
|
|
1311
|
+
[
|
|
1312
|
+
"dotted",
|
|
1313
|
+
`hooks.state = { "${key}" = { trusted_hash = "${hash}" }, foreign = "keep" }`,
|
|
1314
|
+
],
|
|
1315
|
+
[
|
|
1316
|
+
"table",
|
|
1317
|
+
[
|
|
1318
|
+
"[hooks]",
|
|
1319
|
+
`state = { "${key}" = { trusted_hash = "${hash}" } }`,
|
|
1320
|
+
'foreign = "keep"',
|
|
1321
|
+
].join("\n"),
|
|
1322
|
+
],
|
|
1323
|
+
];
|
|
1324
|
+
for (const [name, fixture] of fixtures) {
|
|
1325
|
+
assert.doesNotThrow(() => TOML.parse(fixture), `${name} fixture must be valid TOML`);
|
|
1326
|
+
assert.throws(() => stripManagedCodexHookTrustState(fixture, { managedTrustState }), (error) => error instanceof ManagedCodexHooksPlanError &&
|
|
1327
|
+
error.code === "managed_trust_key_conflict", `${name} cleanup must preserve the entire foreign-containing representation`);
|
|
1328
|
+
assert.throws(() => upsertManagedCodexHookTrustState(fixture, "/tmp/omx", hooksPath, { managedTrustState }), (error) => error instanceof ManagedCodexHooksPlanError &&
|
|
1329
|
+
error.code === "managed_trust_key_conflict", `${name} refresh must preserve the original bytes`);
|
|
1330
|
+
}
|
|
1331
|
+
});
|
|
1332
|
+
it("fails closed for commented trust assignments inside a managed config marker", () => {
|
|
1333
|
+
const fixture = [
|
|
1334
|
+
"# oh-my-codex (OMX) Configuration",
|
|
1335
|
+
'hooks.state."foreign-key".trusted_hash = "sha256:foreign" # preserve me',
|
|
1336
|
+
"# End oh-my-codex",
|
|
1337
|
+
"",
|
|
1338
|
+
].join("\n");
|
|
1339
|
+
assert.throws(() => stripManagedCodexHookTrustState(fixture, { managedTrustState: {} }), (error) => error instanceof ManagedCodexHooksPlanError &&
|
|
1340
|
+
error.code === "managed_trust_key_conflict");
|
|
1341
|
+
});
|
|
1342
|
+
it("fails closed on every non-record hooks.state form inside managed markers", () => {
|
|
1343
|
+
const hooksPath = "/tmp/codex/hooks.json";
|
|
1344
|
+
const managedTrustState = buildManagedCodexHookTrustState(hooksPath, "/tmp/omx");
|
|
1345
|
+
const fixtures = [
|
|
1346
|
+
["scalar", 'hooks.state = "foreign"'],
|
|
1347
|
+
["array", "hooks.state = []"],
|
|
1348
|
+
["quoted state key", 'hooks."state" = "foreign"'],
|
|
1349
|
+
["inline non-record", 'hooks = { state = "foreign" }'],
|
|
1350
|
+
[
|
|
1351
|
+
"array of tables",
|
|
1352
|
+
["[[hooks.state]]", 'trusted_hash = "sha256:foreign"'].join("\n"),
|
|
1353
|
+
],
|
|
1354
|
+
];
|
|
1355
|
+
for (const [name, value] of fixtures) {
|
|
1356
|
+
const fixture = [
|
|
1357
|
+
"# oh-my-codex (OMX) Configuration",
|
|
1358
|
+
value,
|
|
1359
|
+
"# End oh-my-codex",
|
|
1360
|
+
"",
|
|
1361
|
+
].join("\n");
|
|
1362
|
+
assert.doesNotThrow(() => TOML.parse(fixture), `${name} fixture must be valid TOML`);
|
|
1363
|
+
assert.throws(() => stripManagedCodexHookTrustState(fixture, { managedTrustState }), (error) => error instanceof ManagedCodexHooksPlanError &&
|
|
1364
|
+
error.code === "managed_trust_key_conflict", name);
|
|
1365
|
+
}
|
|
1366
|
+
});
|
|
1367
|
+
it("fails closed before stripping malformed hooks.state spellings", () => {
|
|
1368
|
+
const hooksPath = "/tmp/codex/hooks.json";
|
|
1369
|
+
const managedTrustState = buildManagedCodexHookTrustState(hooksPath, "/tmp/omx");
|
|
1370
|
+
const key = `${hooksPath}:post_compact:0:0`;
|
|
1371
|
+
const hash = managedTrustState[key]?.trusted_hash;
|
|
1372
|
+
assert.ok(hash);
|
|
1373
|
+
const spellings = [
|
|
1374
|
+
["quoted state key", 'hooks."state" ='],
|
|
1375
|
+
["quoted hooks key", '"hooks".state ='],
|
|
1376
|
+
["fully quoted dotted key", '"hooks"."state" ='],
|
|
1377
|
+
["whitespace-delimited malformed array", 'hooks \t. "state" \t= ['],
|
|
1378
|
+
["malformed inline table", 'hooks = { "state" = {'],
|
|
1379
|
+
["incomplete table header", '[ "hooks" . "state"'],
|
|
1380
|
+
["incomplete array table header", '[[ "hooks" . "state" ]'],
|
|
1381
|
+
["incomplete hooks table header prefix", "[hooks"],
|
|
1382
|
+
["incomplete hooks array table header prefix", "[[hooks"],
|
|
1383
|
+
["partial hooks array-table closer", "[[hooks] # continuation"],
|
|
1384
|
+
["whitespace-split hooks array-table closer", "[[hooks] ]"],
|
|
1385
|
+
[
|
|
1386
|
+
"split hooks array-table closer",
|
|
1387
|
+
["[[hooks]", "]"].join("\n"),
|
|
1388
|
+
],
|
|
1389
|
+
[
|
|
1390
|
+
"comment-separated hooks assignment continuation",
|
|
1391
|
+
[
|
|
1392
|
+
"hooks # continuation",
|
|
1393
|
+
'= { "state" = { trusted_hash = "sha256:foreign" } }',
|
|
1394
|
+
].join("\n"),
|
|
1395
|
+
],
|
|
1396
|
+
[
|
|
1397
|
+
"bare hooks assignment continuation",
|
|
1398
|
+
[
|
|
1399
|
+
"hooks",
|
|
1400
|
+
'= { "state" = { trusted_hash = "sha256:foreign" } }',
|
|
1401
|
+
].join("\n"),
|
|
1402
|
+
],
|
|
1403
|
+
[
|
|
1404
|
+
"comment-separated hooks dotted-key continuation",
|
|
1405
|
+
[
|
|
1406
|
+
"hooks # continuation",
|
|
1407
|
+
'. "state" = { trusted_hash = "sha256:foreign" }',
|
|
1408
|
+
].join("\n"),
|
|
1409
|
+
],
|
|
1410
|
+
[
|
|
1411
|
+
"comment-separated hooks table-header continuation",
|
|
1412
|
+
["hooks # continuation", '[ "state" ]'].join("\n"),
|
|
1413
|
+
],
|
|
1414
|
+
[
|
|
1415
|
+
"comment-separated hooks array-table-header continuation",
|
|
1416
|
+
["hooks # continuation", '[[ "state" ]]'].join("\n"),
|
|
1417
|
+
],
|
|
1418
|
+
[
|
|
1419
|
+
"split dotted key",
|
|
1420
|
+
["hooks .", '"state" = { trusted_hash = "sha256:foreign" }'].join("\n"),
|
|
1421
|
+
],
|
|
1422
|
+
[
|
|
1423
|
+
"unclosed multiline inline table",
|
|
1424
|
+
["hooks = {", '"state" = { trusted_hash = "sha256:foreign" }'].join("\n"),
|
|
1425
|
+
],
|
|
1426
|
+
[
|
|
1427
|
+
"comment-ended inline table continuation",
|
|
1428
|
+
[
|
|
1429
|
+
"hooks = { # continuation",
|
|
1430
|
+
'"state" = { trusted_hash = "sha256:foreign" }',
|
|
1431
|
+
].join("\n"),
|
|
1432
|
+
],
|
|
1433
|
+
[
|
|
1434
|
+
"bare hooks assignment with inline-table continuation",
|
|
1435
|
+
[
|
|
1436
|
+
"hooks =",
|
|
1437
|
+
'{ "state" = { trusted_hash = "sha256:foreign" } }',
|
|
1438
|
+
].join("\n"),
|
|
1439
|
+
],
|
|
1440
|
+
];
|
|
1441
|
+
const isManagedTrustConflict = (error) => error instanceof ManagedCodexHooksPlanError &&
|
|
1442
|
+
error.code === "managed_trust_key_conflict";
|
|
1443
|
+
for (const [name, spelling] of spellings) {
|
|
1444
|
+
const fixture = [
|
|
1445
|
+
"# oh-my-codex (OMX) Configuration",
|
|
1446
|
+
spelling,
|
|
1447
|
+
"# End oh-my-codex",
|
|
1448
|
+
"",
|
|
1449
|
+
"[user.after]",
|
|
1450
|
+
'value = "preserved"',
|
|
1451
|
+
"",
|
|
1452
|
+
].join("\n");
|
|
1453
|
+
const original = fixture;
|
|
1454
|
+
assert.throws(() => buildMergedConfig(fixture, "/tmp/omx", { managedHookTrustState: managedTrustState }), isManagedTrustConflict, `${name} must fail before OMX block stripping`);
|
|
1455
|
+
assert.equal(fixture, original, `${name} planning must not mutate input`);
|
|
1456
|
+
assert.throws(() => stripExistingOmxBlocks(fixture), isManagedTrustConflict, `${name} must not be erased by direct OMX block stripping`);
|
|
1457
|
+
assert.equal(fixture, original, `${name} stripping must not mutate input`);
|
|
1458
|
+
}
|
|
1459
|
+
const exactQuoted = [
|
|
1460
|
+
"# oh-my-codex (OMX) Configuration",
|
|
1461
|
+
`["hooks"."state"."${key}"]`,
|
|
1462
|
+
`trusted_hash = "${hash}"`,
|
|
1463
|
+
"# End oh-my-codex",
|
|
1464
|
+
"",
|
|
1465
|
+
].join("\n");
|
|
1466
|
+
assert.throws(() => stripExistingOmxBlocks(exactQuoted), isManagedTrustConflict, "direct stripping must not infer ownership without trust expectations");
|
|
1467
|
+
assert.deepEqual(stripExistingOmxBlocks(exactQuoted, { managedTrustState }), {
|
|
1468
|
+
cleaned: "",
|
|
1469
|
+
removed: 1,
|
|
1470
|
+
});
|
|
1471
|
+
assert.doesNotThrow(() => buildMergedConfig(exactQuoted, "/tmp/omx", { managedHookTrustState: managedTrustState }));
|
|
1472
|
+
});
|
|
1473
|
+
it("fails closed when hooks scope crosses parsed assignments or malformed headers", () => {
|
|
1474
|
+
const hooksPath = "/tmp/codex/hooks.json";
|
|
1475
|
+
const managedTrustState = buildManagedCodexHookTrustState(hooksPath, "/tmp/omx");
|
|
1476
|
+
const validNestedArrayAssignment = [
|
|
1477
|
+
"values = [",
|
|
1478
|
+
' ["unrelated"]',
|
|
1479
|
+
"]",
|
|
1480
|
+
];
|
|
1481
|
+
assert.doesNotThrow(() => TOML.parse(["[hooks]", ...validNestedArrayAssignment].join("\n")));
|
|
1482
|
+
const fixtures = [
|
|
1483
|
+
[
|
|
1484
|
+
"parsed nested assignment",
|
|
1485
|
+
["[hooks]", ...validNestedArrayAssignment, "state ="].join("\n"),
|
|
1486
|
+
],
|
|
1487
|
+
[
|
|
1488
|
+
"malformed bracket-prefixed line",
|
|
1489
|
+
["[hooks]", "[broken", "state ="].join("\n"),
|
|
1490
|
+
],
|
|
1491
|
+
];
|
|
1492
|
+
const isManagedTrustConflict = (error) => error instanceof ManagedCodexHooksPlanError &&
|
|
1493
|
+
error.code === "managed_trust_key_conflict";
|
|
1494
|
+
for (const [name, content] of fixtures) {
|
|
1495
|
+
const fixture = [
|
|
1496
|
+
"# oh-my-codex (OMX) Configuration",
|
|
1497
|
+
content,
|
|
1498
|
+
"# End oh-my-codex",
|
|
1499
|
+
"",
|
|
1500
|
+
"[user.after]",
|
|
1501
|
+
'value = "preserved"',
|
|
1502
|
+
"",
|
|
1503
|
+
].join("\n");
|
|
1504
|
+
const original = fixture;
|
|
1505
|
+
assert.throws(() => stripExistingOmxBlocks(fixture), isManagedTrustConflict, `${name} must not be erased by direct OMX block stripping`);
|
|
1506
|
+
assert.equal(fixture, original, `${name} stripping must not mutate input`);
|
|
1507
|
+
assert.throws(() => buildMergedConfig(fixture, "/tmp/omx", { managedHookTrustState: managedTrustState }), isManagedTrustConflict, `${name} must fail before OMX block stripping`);
|
|
1508
|
+
assert.equal(fixture, original, `${name} planning must not mutate input`);
|
|
1509
|
+
}
|
|
1510
|
+
const validNonHooksHeaderExit = [
|
|
1511
|
+
"# oh-my-codex (OMX) Configuration",
|
|
1512
|
+
"[hooks]",
|
|
1513
|
+
"[unrelated]",
|
|
1514
|
+
"state =",
|
|
1515
|
+
"# End oh-my-codex",
|
|
1516
|
+
"",
|
|
1517
|
+
"[user.after]",
|
|
1518
|
+
'value = "preserved"',
|
|
1519
|
+
"",
|
|
1520
|
+
].join("\n");
|
|
1521
|
+
const expectedExit = ["[user.after]", 'value = "preserved"', ""].join("\n");
|
|
1522
|
+
assert.deepEqual(stripExistingOmxBlocks(validNonHooksHeaderExit), {
|
|
1523
|
+
cleaned: expectedExit,
|
|
1524
|
+
removed: 1,
|
|
1525
|
+
});
|
|
1526
|
+
assert.doesNotThrow(() => buildMergedConfig(validNonHooksHeaderExit, "/tmp/omx", {
|
|
1527
|
+
managedHookTrustState: managedTrustState,
|
|
1528
|
+
}));
|
|
1529
|
+
});
|
|
1530
|
+
it("fails closed for hooks array-table openers and preserves unrelated arrays", () => {
|
|
1531
|
+
const hooksPath = "/tmp/codex/hooks.json";
|
|
1532
|
+
const managedTrustState = buildManagedCodexHookTrustState(hooksPath, "/tmp/omx");
|
|
1533
|
+
const arrayTableOpeners = [
|
|
1534
|
+
["adjacent", "[[hooks]]"],
|
|
1535
|
+
["space-split", "[ [hooks]]"],
|
|
1536
|
+
["tab-split", "[\t[hooks]]"],
|
|
1537
|
+
["newline-split", ["[[", "hooks]]"].join("\n")],
|
|
1538
|
+
[
|
|
1539
|
+
"two standalone opening brackets followed by independently parsed state",
|
|
1540
|
+
[
|
|
1541
|
+
"[",
|
|
1542
|
+
"[",
|
|
1543
|
+
"hooks]]",
|
|
1544
|
+
'state = { trusted_hash = "sha256:foreign" }',
|
|
1545
|
+
].join("\n"),
|
|
1546
|
+
],
|
|
1547
|
+
[
|
|
1548
|
+
"three standalone opening brackets followed by independently parsed state",
|
|
1549
|
+
[
|
|
1550
|
+
"[",
|
|
1551
|
+
"[",
|
|
1552
|
+
"[",
|
|
1553
|
+
"hooks]]]",
|
|
1554
|
+
'state = { trusted_hash = "sha256:foreign" }',
|
|
1555
|
+
].join("\n"),
|
|
1556
|
+
],
|
|
1557
|
+
[
|
|
1558
|
+
"comment- and blank-separated standalone opening brackets",
|
|
1559
|
+
[
|
|
1560
|
+
"[",
|
|
1561
|
+
"# opening bracket continuation",
|
|
1562
|
+
"",
|
|
1563
|
+
"[",
|
|
1564
|
+
"",
|
|
1565
|
+
"# another opening bracket continuation",
|
|
1566
|
+
"[",
|
|
1567
|
+
"hooks]]]",
|
|
1568
|
+
'state = { trusted_hash = "sha256:foreign" }',
|
|
1569
|
+
].join("\n"),
|
|
1570
|
+
],
|
|
1571
|
+
["triple", "[[[hooks]]]"],
|
|
1572
|
+
["extra", "[[[[hooks]]]]"],
|
|
1573
|
+
["space-newline-split", ["[ [", "hooks]]"].join("\n")],
|
|
1574
|
+
["tab-newline-split", ["[\t[", "hooks]]"].join("\n")],
|
|
1575
|
+
["triple-newline-split", ["[[[", "hooks]]]"].join("\n")],
|
|
1576
|
+
["comment-ended", ["[[ # continuation", "hooks]]"].join("\n")],
|
|
1577
|
+
];
|
|
1578
|
+
const isManagedTrustConflict = (error) => error instanceof ManagedCodexHooksPlanError &&
|
|
1579
|
+
error.code === "managed_trust_key_conflict";
|
|
1580
|
+
for (const [name, opener] of arrayTableOpeners) {
|
|
1581
|
+
const fixture = [
|
|
1582
|
+
"# oh-my-codex (OMX) Configuration",
|
|
1583
|
+
opener,
|
|
1584
|
+
"# End oh-my-codex",
|
|
1585
|
+
"",
|
|
1586
|
+
"[user.after]",
|
|
1587
|
+
'value = "preserved"',
|
|
1588
|
+
"",
|
|
1589
|
+
].join("\n");
|
|
1590
|
+
const original = fixture;
|
|
1591
|
+
assert.throws(() => buildMergedConfig(fixture, "/tmp/omx", {
|
|
1592
|
+
managedHookTrustState: managedTrustState,
|
|
1593
|
+
}), isManagedTrustConflict, `${name} opener must fail before OMX block stripping`);
|
|
1594
|
+
assert.equal(fixture, original, `${name} planning must not mutate input`);
|
|
1595
|
+
assert.throws(() => stripExistingOmxBlocks(fixture), isManagedTrustConflict, `${name} opener must not be erased by direct OMX block stripping`);
|
|
1596
|
+
assert.equal(fixture, original, `${name} stripping must not mutate input`);
|
|
1597
|
+
}
|
|
1598
|
+
const unrelatedArrays = [
|
|
1599
|
+
"[[user.before]]",
|
|
1600
|
+
'name = "before"',
|
|
1601
|
+
"",
|
|
1602
|
+
"# oh-my-codex (OMX) Configuration",
|
|
1603
|
+
"[[unrelated]]",
|
|
1604
|
+
'name = "inside managed marker"',
|
|
1605
|
+
"# End oh-my-codex",
|
|
1606
|
+
"",
|
|
1607
|
+
"[[user.after]]",
|
|
1608
|
+
'name = "after"',
|
|
1609
|
+
"",
|
|
1610
|
+
].join("\n");
|
|
1611
|
+
const preservedArrays = [
|
|
1612
|
+
"[[user.before]]",
|
|
1613
|
+
'name = "before"',
|
|
1614
|
+
"",
|
|
1615
|
+
"[[user.after]]",
|
|
1616
|
+
'name = "after"',
|
|
1617
|
+
"",
|
|
1618
|
+
].join("\n");
|
|
1619
|
+
assert.deepEqual(stripExistingOmxBlocks(unrelatedArrays), {
|
|
1620
|
+
cleaned: preservedArrays,
|
|
1621
|
+
removed: 1,
|
|
1622
|
+
});
|
|
1623
|
+
const merged = buildMergedConfig(unrelatedArrays, "/tmp/omx");
|
|
1624
|
+
assert.match(merged, /^\[\[user\.before\]\]$/m);
|
|
1625
|
+
assert.match(merged, /^\[\[user\.after\]\]$/m);
|
|
1626
|
+
assert.doesNotMatch(merged, /^\[\[unrelated\]\]$/m);
|
|
1627
|
+
assert.doesNotThrow(() => TOML.parse(merged));
|
|
1628
|
+
});
|
|
1629
|
+
it("removes valid nested hooks arrays inside exact managed marker ranges", () => {
|
|
1630
|
+
const fixture = [
|
|
1631
|
+
"# oh-my-codex (OMX) Configuration",
|
|
1632
|
+
"values = [",
|
|
1633
|
+
" [",
|
|
1634
|
+
"",
|
|
1635
|
+
" # a value named hooks is not a table prefix",
|
|
1636
|
+
' "hooks",',
|
|
1637
|
+
" ],",
|
|
1638
|
+
" # nor is this independently nested array",
|
|
1639
|
+
" [",
|
|
1640
|
+
' "unrelated",',
|
|
1641
|
+
" ],",
|
|
1642
|
+
" [",
|
|
1643
|
+
' [["hooks"]]',
|
|
1644
|
+
" ],",
|
|
1645
|
+
" [",
|
|
1646
|
+
' [ [ "hooks" ] ]',
|
|
1647
|
+
" ],",
|
|
1648
|
+
"]",
|
|
1649
|
+
"outer.values = [",
|
|
1650
|
+
' [["hooks"]]',
|
|
1651
|
+
"]",
|
|
1652
|
+
'"" = [',
|
|
1653
|
+
' [ [ "hooks" ] ]',
|
|
1654
|
+
"]",
|
|
1655
|
+
"# End oh-my-codex",
|
|
1656
|
+
"",
|
|
1657
|
+
"[user.after]",
|
|
1658
|
+
'value = "preserved"',
|
|
1659
|
+
"",
|
|
1660
|
+
].join("\n");
|
|
1661
|
+
const expected = ["[user.after]", 'value = "preserved"', ""].join("\n");
|
|
1662
|
+
assert.doesNotThrow(() => TOML.parse(fixture));
|
|
1663
|
+
assert.deepEqual(stripExistingOmxBlocks(fixture), { cleaned: expected, removed: 1 });
|
|
1664
|
+
const merged = buildMergedConfig(fixture, "/tmp/omx");
|
|
1665
|
+
assert.doesNotMatch(merged, /^values = \[/m);
|
|
1666
|
+
assert.match(merged, /^\[user\.after\]$/m);
|
|
1667
|
+
assert.doesNotThrow(() => TOML.parse(merged));
|
|
1668
|
+
});
|
|
1669
|
+
it("ignores inert hooks.state text inside exact managed marker ranges", () => {
|
|
1670
|
+
const fixture = [
|
|
1671
|
+
"# oh-my-codex (OMX) Configuration",
|
|
1672
|
+
'# hooks."state" =',
|
|
1673
|
+
'basic = "hooks.\'state\' ="',
|
|
1674
|
+
"literal = 'hooks = { state = {'",
|
|
1675
|
+
'multiline = """',
|
|
1676
|
+
'[[ "hooks" . "state" ]',
|
|
1677
|
+
'hooks = { "state" = {',
|
|
1678
|
+
'"""',
|
|
1679
|
+
"# End oh-my-codex",
|
|
1680
|
+
"",
|
|
1681
|
+
"[user.after]",
|
|
1682
|
+
'value = "preserved"',
|
|
1683
|
+
"",
|
|
1684
|
+
].join("\n");
|
|
1685
|
+
const expected = ["[user.after]", 'value = "preserved"', ""].join("\n");
|
|
1686
|
+
assert.deepEqual(stripExistingOmxBlocks(fixture), { cleaned: expected, removed: 1 });
|
|
1687
|
+
assert.doesNotThrow(() => buildMergedConfig(fixture, "/tmp/omx"));
|
|
1688
|
+
});
|
|
1689
|
+
it("removes an exact inline state entry within a hooks.state table", () => {
|
|
1690
|
+
const hooksPath = "/tmp/codex/hooks.json";
|
|
1691
|
+
const managedTrustState = buildManagedCodexHookTrustState(hooksPath, "/tmp/omx");
|
|
1692
|
+
const key = `${hooksPath}:post_compact:0:0`;
|
|
1693
|
+
const hash = managedTrustState[key]?.trusted_hash;
|
|
1694
|
+
assert.ok(hash);
|
|
1695
|
+
const inline = [
|
|
1696
|
+
"[hooks.state]",
|
|
1697
|
+
`"${key}" = { trusted_hash = "${hash}" }`,
|
|
1698
|
+
"",
|
|
1699
|
+
"[desktop]",
|
|
1700
|
+
"git-create-pull-request-as-draft = true",
|
|
1701
|
+
"",
|
|
1702
|
+
].join("\n");
|
|
1703
|
+
const stripped = stripManagedCodexHookTrustState(inline, { managedTrustState });
|
|
1704
|
+
assert.doesNotMatch(stripped, new RegExp(key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")));
|
|
1705
|
+
assert.match(stripped, /^\[hooks\.state\]$/m);
|
|
1706
|
+
assert.match(stripped, /^\[desktop\]$/m);
|
|
1707
|
+
assert.doesNotThrow(() => TOML.parse(stripped));
|
|
1708
|
+
});
|
|
1709
|
+
it("fails closed on multi-field, array-like, and mixed managed trust representations", () => {
|
|
1710
|
+
const hooksPath = "/tmp/codex/hooks.json";
|
|
1711
|
+
const managedTrustState = buildManagedCodexHookTrustState(hooksPath, "/tmp/omx");
|
|
1712
|
+
const key = `${hooksPath}:post_compact:0:0`;
|
|
1713
|
+
const hash = managedTrustState[key]?.trusted_hash;
|
|
1714
|
+
assert.ok(hash);
|
|
1715
|
+
const fixtures = [
|
|
1716
|
+
[
|
|
1717
|
+
`hooks.state."${key}" = { trusted_hash = "${hash}", enabled = false }`,
|
|
1718
|
+
"",
|
|
1719
|
+
].join("\n"),
|
|
1720
|
+
[
|
|
1721
|
+
`hooks.state."${key}" = [{ trusted_hash = "${hash}" }]`,
|
|
1722
|
+
"",
|
|
1723
|
+
].join("\n"),
|
|
1724
|
+
[
|
|
1725
|
+
`hooks.state."${key}" = { trusted_hash = "${hash}" }`,
|
|
1726
|
+
`[hooks.state."${key}"]`,
|
|
1727
|
+
`trusted_hash = "${hash}"`,
|
|
1728
|
+
"",
|
|
1729
|
+
].join("\n"),
|
|
1730
|
+
];
|
|
1731
|
+
for (const fixture of fixtures) {
|
|
1732
|
+
assert.throws(() => stripManagedCodexHookTrustState(fixture, { managedTrustState }), (error) => error instanceof ManagedCodexHooksPlanError &&
|
|
1733
|
+
error.code === "managed_trust_key_conflict");
|
|
1734
|
+
assert.throws(() => buildMergedConfig(fixture, "/tmp/omx", { managedHookTrustState: managedTrustState }), (error) => error instanceof ManagedCodexHooksPlanError &&
|
|
1735
|
+
error.code === "managed_trust_key_conflict");
|
|
929
1736
|
}
|
|
930
1737
|
});
|
|
931
1738
|
it("does not remove unfenced hook trust-state tables without a proof map", () => {
|
|
@@ -955,31 +1762,138 @@ describe("config generator idempotency (#384)", () => {
|
|
|
955
1762
|
assert.match(repaired, /^enabled = false$/m);
|
|
956
1763
|
assertSingleManagedHookTrustState(repaired);
|
|
957
1764
|
});
|
|
958
|
-
it("
|
|
1765
|
+
it("preserves managed-looking trust blocks inside multiline TOML values", () => {
|
|
959
1766
|
const hooksPath = "/tmp/codex/hooks.json";
|
|
960
|
-
const
|
|
961
|
-
|
|
962
|
-
"
|
|
963
|
-
"
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
'trusted_hash = "sha256:stale-plugin-mode"',
|
|
973
|
-
"",
|
|
1767
|
+
const managedTrustState = buildManagedCodexHookTrustState(hooksPath, "/tmp/omx");
|
|
1768
|
+
const embeddedTrustBlock = [
|
|
1769
|
+
"# OMX-owned Codex hook trust state",
|
|
1770
|
+
"# Trusts only setup-managed native hook wrappers.",
|
|
1771
|
+
...Object.entries(managedTrustState)
|
|
1772
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
1773
|
+
.flatMap(([key, state]) => [
|
|
1774
|
+
`[hooks.state."${key}"]`,
|
|
1775
|
+
`trusted_hash = "${state.trusted_hash}"`,
|
|
1776
|
+
"",
|
|
1777
|
+
]),
|
|
1778
|
+
"# End OMX-owned Codex hook trust state",
|
|
974
1779
|
].join("\n");
|
|
975
|
-
const
|
|
1780
|
+
const multilineBasicValue = `basic = """\n${embeddedTrustBlock}\n"""`;
|
|
1781
|
+
const multilineLiteralValue = `literal = '''\n${embeddedTrustBlock}\n'''`;
|
|
1782
|
+
const config = [multilineBasicValue, "", multilineLiteralValue, ""].join("\n");
|
|
1783
|
+
const first = upsertManagedCodexHookTrustState(config, "/tmp/omx", hooksPath, {
|
|
1784
|
+
managedTrustState,
|
|
1785
|
+
});
|
|
1786
|
+
const refreshed = upsertManagedCodexHookTrustState(first, "/tmp/omx", hooksPath, {
|
|
1787
|
+
managedTrustState,
|
|
1788
|
+
});
|
|
1789
|
+
const stripped = stripManagedCodexHookTrustState(refreshed, { managedTrustState });
|
|
1790
|
+
assert.equal(refreshed, first, "the real managed block should still be replaced idempotently");
|
|
1791
|
+
for (const preserved of [first, stripped]) {
|
|
1792
|
+
assert.ok(preserved.includes(multilineBasicValue));
|
|
1793
|
+
assert.ok(preserved.includes(multilineLiteralValue));
|
|
1794
|
+
}
|
|
1795
|
+
const parsed = TOML.parse(stripped);
|
|
1796
|
+
assert.equal(parsed.basic, `${embeddedTrustBlock}\n`);
|
|
1797
|
+
assert.equal(parsed.literal, `${embeddedTrustBlock}\n`);
|
|
1798
|
+
assert.equal(parsed.hooks, undefined, "only the real managed trust tables should be stripped");
|
|
1799
|
+
const unterminatedMultilineValue = multilineBasicValue.slice(0, -3);
|
|
1800
|
+
assert.equal(stripManagedCodexHookTrustState(unterminatedMultilineValue, { managedTrustState }), unterminatedMultilineValue, "an ambiguous unterminated multiline value must not lose managed-looking content");
|
|
1801
|
+
});
|
|
1802
|
+
it("uses strict final hook coordinates for precomputed managed trust", () => {
|
|
1803
|
+
const hooksPath = "/tmp/codex/hooks.json";
|
|
1804
|
+
const foreignGroup = {
|
|
1805
|
+
hooks: [
|
|
1806
|
+
{
|
|
1807
|
+
type: "command",
|
|
1808
|
+
command: "/usr/bin/python3 /tmp/user-hook.py",
|
|
1809
|
+
timeout: 5,
|
|
1810
|
+
},
|
|
1811
|
+
],
|
|
1812
|
+
};
|
|
1813
|
+
const existingHooks = JSON.stringify({
|
|
1814
|
+
hooks: Object.fromEntries(MANAGED_HOOK_EVENTS.map((eventName) => [eventName, [foreignGroup]])),
|
|
1815
|
+
});
|
|
1816
|
+
const plan = planManagedCodexHooksMerge(existingHooks, "/tmp/omx", hooksPath);
|
|
1817
|
+
assert.equal(plan.ok, true);
|
|
1818
|
+
if (!plan.ok)
|
|
1819
|
+
return;
|
|
1820
|
+
const refreshed = buildMergedConfig("", "/tmp/omx", {
|
|
976
1821
|
codexHooksFile: hooksPath,
|
|
1822
|
+
managedHookTrustState: plan.finalTrustState,
|
|
1823
|
+
priorManagedHookTrustState: plan.priorTrustState,
|
|
1824
|
+
legacyHookTrustState: plan.legacyTrustState,
|
|
977
1825
|
});
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
assert.
|
|
1826
|
+
const parsed = TOML.parse(refreshed);
|
|
1827
|
+
const trustKeys = Object.keys(parsed.hooks?.state ?? {}).sort();
|
|
1828
|
+
assert.deepEqual(trustKeys, Object.keys(plan.finalTrustState).sort());
|
|
1829
|
+
for (const eventName of MANAGED_HOOK_EVENTS) {
|
|
1830
|
+
const eventLabel = eventName
|
|
1831
|
+
.replace(/([A-Z])/g, "_$1")
|
|
1832
|
+
.toLowerCase()
|
|
1833
|
+
.replace(/^_/, "");
|
|
1834
|
+
assert.ok(trustKeys.some((key) => key.includes(`:${eventLabel}:1:0`)), `${eventName} should retain the actual final group index after foreign hooks`);
|
|
1835
|
+
}
|
|
1836
|
+
});
|
|
1837
|
+
it("migrates exact legacy hooks.json state in the same precomputed config plan", () => {
|
|
1838
|
+
const hooksPath = "/tmp/codex/hooks.json";
|
|
1839
|
+
const plan = planManagedCodexHooksMerge(JSON.stringify({
|
|
1840
|
+
state: {
|
|
1841
|
+
"legacy:/hooks.json:stop:0:0": {
|
|
1842
|
+
trusted_hash: "sha256:legacy",
|
|
1843
|
+
enabled: false,
|
|
1844
|
+
},
|
|
1845
|
+
},
|
|
1846
|
+
hooks: {},
|
|
1847
|
+
}), "/tmp/omx", hooksPath);
|
|
1848
|
+
assert.equal(plan.ok, true);
|
|
1849
|
+
if (!plan.ok)
|
|
1850
|
+
return;
|
|
1851
|
+
const refreshed = buildMergedConfig("", "/tmp/omx", {
|
|
1852
|
+
codexHooksFile: hooksPath,
|
|
1853
|
+
managedHookTrustState: plan.finalTrustState,
|
|
1854
|
+
priorManagedHookTrustState: plan.priorTrustState,
|
|
1855
|
+
legacyHookTrustState: plan.legacyTrustState,
|
|
1856
|
+
});
|
|
1857
|
+
assert.doesNotMatch(plan.finalContent ?? "", /"state"/);
|
|
1858
|
+
assert.match(refreshed, /^\[hooks\.state\."legacy:\/hooks\.json:stop:0:0"\]$/m);
|
|
1859
|
+
assert.match(refreshed, /^trusted_hash = "sha256:legacy"$/m);
|
|
981
1860
|
assert.match(refreshed, /^enabled = false$/m);
|
|
982
|
-
|
|
1861
|
+
});
|
|
1862
|
+
it("migrates a legacy __proto__ trust key into config.toml", () => {
|
|
1863
|
+
const plan = planManagedCodexHooksMerge('{"state":{"__proto__":{"trusted_hash":"sha256:legacy","enabled":true}}}', "/tmp/omx", "/tmp/codex/hooks.json");
|
|
1864
|
+
assert.equal(plan.ok, true);
|
|
1865
|
+
if (!plan.ok)
|
|
1866
|
+
return;
|
|
1867
|
+
const config = buildMergedConfig("", "/tmp/omx", {
|
|
1868
|
+
legacyHookTrustState: plan.legacyTrustState,
|
|
1869
|
+
});
|
|
1870
|
+
assert.match(config, /^\[hooks\.state\."__proto__"\]$/m);
|
|
1871
|
+
assert.match(config, /^trusted_hash = "sha256:legacy"$/m);
|
|
1872
|
+
assert.match(config, /^enabled = true$/m);
|
|
1873
|
+
});
|
|
1874
|
+
it("coalesces matching legacy hook trust config tables and rejects hash or enabled collisions", () => {
|
|
1875
|
+
const key = "legacy:/hooks.json:stop:0:0";
|
|
1876
|
+
const legacyHookTrustState = {
|
|
1877
|
+
[key]: { trusted_hash: "sha256:legacy", enabled: false },
|
|
1878
|
+
};
|
|
1879
|
+
const matchingConfig = [
|
|
1880
|
+
`[hooks.state."${key}"]`,
|
|
1881
|
+
'trusted_hash = "sha256:legacy"',
|
|
1882
|
+
"enabled = false",
|
|
1883
|
+
"",
|
|
1884
|
+
].join("\n");
|
|
1885
|
+
const matching = buildMergedConfig(matchingConfig, "/tmp/omx", {
|
|
1886
|
+
legacyHookTrustState,
|
|
1887
|
+
});
|
|
1888
|
+
assert.equal(count(matching, /^\[hooks\.state\."legacy:\/hooks\.json:stop:0:0"\]$/gm), 1);
|
|
1889
|
+
for (const conflictingConfig of [
|
|
1890
|
+
matchingConfig.replace("sha256:legacy", "sha256:other"),
|
|
1891
|
+
matchingConfig.replace("enabled = false", "enabled = true"),
|
|
1892
|
+
matchingConfig.replace("\nenabled = false", ""),
|
|
1893
|
+
]) {
|
|
1894
|
+
assert.throws(() => buildMergedConfig(conflictingConfig, "/tmp/omx", { legacyHookTrustState }), (error) => error instanceof ManagedCodexHooksPlanError &&
|
|
1895
|
+
error.code === "managed_trust_key_conflict");
|
|
1896
|
+
}
|
|
983
1897
|
});
|
|
984
1898
|
it("syncs shared MCP registry entries in a dedicated managed block", async () => {
|
|
985
1899
|
const wd = await mkdtemp(join(tmpdir(), "omx-idem-"));
|