hosts/codex/host.psd1

@{
    # Identity
    Kind          = 'codex'
    DisplayName   = 'OpenAI Codex CLI'
    Status        = 'supported'
    SchemaVersion = 1
    MenuPriority  = 2  # Interactive host-selection menu priority (iter-011)

    # Binary detection
    Binary           = 'codex'
    InstallUrl       = 'https://developers.openai.com/codex/cli'
    InstallGuidance  = 'Codex CLI not found on PATH. Install: https://developers.openai.com/codex/cli'

    # Runtime layout
    SkillRoot                  = '.agents/skills'
    HasUserSlashCommandSurface = $false   # FR-013: Codex has no user-defined slash-command surface
    SharedSkillRootWith        = @('antigravity')
    AgentDir                   = '.codex/agents/'
    InstructionsFile           = 'AGENTS.md'
    StructuredQuestionPrimitive = 'request_user_input'
    StructuredQuestionGuidance  = 'Use the Codex structured user-input/menu primitive only for clarify questions, when it is available in the current session: never for a workshop question or a boundary verdict.'

    # Spec-kit + Squad coupling
    SpeckitAiFlag  = 'codex'
    PreferredAgent = 'codex'

    # Phase B handler file (not yet present)
    HandlersFile         = 'handlers.ps1'
    CoordinatorRulesFile = 'coordinator-rules.psd1'

    # Feature 171 (FR-013, T014): refocus hook bindings — FULL TRIAD. Verified
    # live 2026-06-07 (research-matrix.md): SessionStart matchers identical to
    # Claude (startup|resume|clear|compact) route B1/B2; UserPromptSubmit
    # (additionalContext) is the per-human-prompt B3 carrier. PreToolUse dormant.
    RefocusHookBindings = @{
        BoundTriggers  = @('b1', 'b2', 'b3')
        Events         = @('SessionStart', 'UserPromptSubmit')
        TurnStartCapability = @{ Mode = 'exact'; NativeEvent = 'UserPromptSubmit'; DispatcherEvent = 'UserPromptSubmit' }
        SettingsFile   = '~/.codex/hooks.json'   # user-level; dispatcher self-gates per project
        OptOutMarkerFile = '.specrew/runtime/refocus-hooks-optout-codex'
        DispatcherPath = '.specify/extensions/specrew-speckit/scripts/specrew-hook-dispatcher.ps1'
        ConfigShape    = 'event-map'
        CommandMode    = 'launcher-file'
        MigrateLegacyTopLevelEventMap = $true
        DispatcherRuntime = @{
            # Feature 202 iteration 002, T016 (FR-028, DA-1): the payload fields ONLY this host sends, per dispatcher event
            # ('Default' for the rest). Measured on every recorded fixture (tests/fixtures/hook-payloads/codex): prompt
            # and Stop carry turn_id; SessionStart carries permission_mode and model, and never scratchpad_dir.
            PayloadSignature = @{
                SessionStart = @{ AllOf = @('hook_event_name', 'session_id', 'permission_mode', 'model'); NoneOf = @('scratchpad_dir', 'prompt_id', 'sessionId') }
                Default      = @{ AllOf = @('hook_event_name', 'session_id', 'turn_id'); NoneOf = @('prompt_id', 'sessionId') }
            }
            BootstrapDeliveryEvents = @('SessionStart')
            B3DeliveryEvents        = @('PostToolUse', 'UserPromptSubmit')
            RefocusTriggerByEvent   = @{ PostToolUse = 'b3'; UserPromptSubmit = 'b3' }
            SuppressedRefocusEvents = @()
            OutputShape             = 'hookSpecificOutput'
            # Codex Stop expects JSON and only permits decision:"block"; non-blocking Stop nudges must be
            # suppressed as `{}` while real stop-blocks short-circuit through StopBlockShape below.
            DecisionOnlyEvents      = @('Stop')
            # FR-004 (185) stop-block lever (verified, research/stop-block-capability-matrix.md): Codex Stop
            # decision:block creates a continuation prompt from reason (PR #14532) + built-in stop_hook_active.
            # Firing reliability (Esc-interrupt / headless exec) is a real-host dogfood concern, not a block-capability one.
            StopBlockShape          = 'decision-block'
            BootstrapDeliveryMode   = 'pointer'
            # Feature 202 T002 (DA-1, option B): this host's INPUT contract is data. The dispatcher's single
            # normalizer (ConvertTo-SpecrewTurnRecord) reads it; shared code names no host. Field paths may be
            # dotted (a.b.c). Measured payloads: tests/fixtures/hook-payloads/<host>/ (202 research.md, T001).
            InputFieldMap           = @{
                SessionId      = @('session_id')
                Prompt         = @('prompt')
                # Feature 202 iteration 002, T017 (FR-027, DA-2): the host's own ID for a typed message, the same on the
                # prompt event and on that message's transcript entry, so both recording paths key it the same way.
                MessageId      = @('turn_id')
                TranscriptPath = @('transcript_path')
                Source         = @('source')
                ToolName       = @('tool_name')
            }
            # Evaluated in order; the first matching rule decides. A prompt that matches no host-injected rule
            # is typed-user, and only typed-user can produce a receipt (FR-006). hook-output-journal: the prompt
            # equals text this dispatcher emitted (a replayed Stop reason). prompt-pattern: a host envelope or a
            # replayed Specrew directive.
            OriginRules             = @(
                # Feature 202 iteration 002, T028 (B4F-147): a sub-agent's task text reaches the prompt hook as a prompt. A payload
                # carrying a sub-agent field was not typed by the human, whatever its text, so it can never be an approval.
                # No Codex sub-agent payload has been recorded (probe/codex, 0.163.0); the rule holds the same fields as Claude's.
                @{ Origin = 'host-injected'; Rule = 'payload-field'; Fields = @('agent_id', 'parent_agent_id') },
                @{ Origin = 'host-injected'; Rule = 'hook-output-journal' },
                @{ Origin = 'host-injected'; Rule = 'prompt-pattern'; Pattern = '(?is)^\s*<(?:task-notification|hook_prompt|turn_aborted|system-reminder|environment_context)\b' },
                @{ Origin = 'host-injected'; Rule = 'prompt-pattern'; Pattern = '(?is)^\s*(?:Specrew:|Specrew\s+review\s+[-\u2014]|\[specrew-|AWAITING YOUR VERDICT:|BOUNDARY NOT READY FOR A VERDICT:|Campaign review authority\b)' },
                @{ Origin = 'typed-user'; Rule = 'prompt-present' }
            )
        }
        Registrations  = @(
            @{ Event = 'SessionStart'; DispatcherEvent = 'SessionStart'; HandlerShape = 'hooks-array'; Timeout = 30 },
            @{ Event = 'UserPromptSubmit'; DispatcherEvent = 'UserPromptSubmit'; HandlerShape = 'hooks-array'; Timeout = 30 },
            @{ Event = 'Stop'; DispatcherEvent = 'Stop'; HandlerShape = 'hooks-array'; Timeout = 30 }
        )
    }
}