|
| None | register_active_agent ("object | None" agent) |
| None | _announce_interrupt ("object | None" agent, str stage) |
| | _o3_sigint_handler (signum, frame) |
| str | _active_sessions_dir () |
| str | _read_startup_input (prompt_driver, str prompt_text, str default, *, presenter) |
| None | _show_serving_host (presenter, client) |
| ChatSession | _load_session_by_name_for_startup (str name, presenter, list|None tool_schemas, str sessions_dir) |
| ChatSession|None | _maybe_autoreload_crashed_session (Agent agent, presenter, prompt_driver, str sessions_dir) |
| bool | _recover_incomplete_session_tail (ChatSession session, presenter, prompt_driver) |
| bool | _show_config_validation (presenter, result) |
| "object" | _select_presenter_once (args) |
| None | _safe_presenter_close (presenter) |
| None | _assert_stdin_is_tty_or_input_mode (args) |
| str | _anchor_banner_line (object root, str source, "str | None" branch) |
| None | _emit_startup_anchor_banner (presenter, args) |
| None | main (list[str]|None argv=None) |
Main entry point for the OC Chat REPL — wiring shell only.
Purpose
-------
Parse argv, initialise diagnostics, select a presenter, run the
storage migration, drive the startup session selection menu, build
an :class:`~oc_chat.core.agent.Agent` and a slash-command
:class:`~oc_chat.core.commands.Context`, and run the read-eval loop
that ferries user input into ``agent.run_turn(...)`` and slash
commands into :func:`dispatch`. All the per-turn ReAct logic, tool
dispatch, registry construction, startup wizard, CHANGELOG updates,
and presentation formatting now live in their own modules; this
file is the orchestration shell that wires them together.
Responsibilities
----------------
- :func:`main` — the wiring sequence above.
- Slash-command loop body: dispatch slash commands, otherwise
drive ``agent.run_turn(user_input)``; on ``/goal`` completion,
trigger :func:`append_session_log` against the agent's session.
The decomposition lives at:
- :mod:`oc_chat.core.react_loop` (G.6.A) — ``run_turn`` + tool dispatch.
- :mod:`oc_chat.core.tool_registry` (G.6.A) — ``build_tool_registry``.
- :mod:`oc_chat.core.startup` (G.6.B) — ``startup_session_prompt``.
- :mod:`oc_chat.core.session_log` (G.6.B) — ``append_session_log``.
- :mod:`oc_ui.terminal.formatting` (G.6.B) — presentation formatters.
- :mod:`oc_chat.cli` (G.6.C) — argparse, presenter selection,
terminal clear.
- :mod:`oc_chat.core._version` (G.6.C) — version-string resolver.
- :mod:`oc_chat.core.agent` (G.6.C) — :class:`Agent` and
:class:`AgentConfig`, the runtime building block this shell wires.
Diagnostics
-----------
Domain: OC_CHAT.MAIN
Levels:
L2 — REPL lifecycle events.
L3 — semantic details.
L4 — deep tracing (currently unused).
Contracts
---------
- ``oc_diagnostics.init(intercept_streams=False,
intercept_exceptions=False)`` runs once at the top of ``main()``.
Stream intercept is disabled so the REPL keeps clean stdout/stderr.
Dependencies
------------
- oc_diagnostics (structured debug logging via _dbg)
- oc_chat.cli (argparse, presenter selection, terminal clear)
- oc_chat.core.agent (Agent + AgentConfig — runtime building block)
- oc_chat.core.session_log (post-goal SESSION_LOG.md writer)
- oc_chat.core.commands (slash-command dispatch)
- oc_chat.core.persistence (migrate_legacy_layout)
- oc_chat.core.provider_plugin (ProviderPlugin ABC and registry singleton)
- oc_chat.core.startup (startup_session_prompt — interactive bootstrap)
- oc_chat.core._version (resolve_version — banner/CHANGELOG)
- providers.deepseek (DeepSeek provider plugin — default backend)
- oc_ui.terminal.input (PromptDriver, TurnStats, prompt_prefix builder)
- oc_chat.core.config_validation (validate_runtime_config and format_config_issue - the startup configuration check and its messages)
- oc_chat.core.session (ChatSession, TokenStats and the metadata and system-prompt helpers - rebuilding a session for the crash auto-reload)
- oc_chat.core.session_transition (IterationStatsSink and the turn-phase marks - the process-owned stats sink and the deferred Ctrl+C)
- oc_chat.tools.ask_user (bind_interaction - binds the ask_user tool to the live prompt driver)
- providers.ollama (Ollama provider plugin - registered on import)
- providers.openai_compat (the seven OCON lane providers - registered on import)
- oc_chat.core.batch_runner (lazy; run_batch_turn - the headless --input / --output turn)
- oc_chat.core.cancellation (lazy; signal_tool_cancel - stops in-flight tools on the second Ctrl+C)
- oc_chat.core.deployment_shape (lazy; check_deployment_shape and DeploymentShapeError - refuses a sandbox that overlaps an importable root)
- oc_chat.core.endpoint_registry (lazy; EndpointRegistry - restores the session's last-used endpoint)
- oc_chat.core.event_bootstrap (lazy; attach_default_writer - the NDJSON event writer on the default bus)
- oc_chat.core.external_content_gate (lazy; blast_radius_banner - the permissive profile's startup banner)
- oc_chat.core.generation_options (lazy; GenerationOptions - restores the session's persisted generation options)
- oc_chat.core.lane_switch (lazy; switch_lane - applies a lane chosen in the opening menu to the Agent)
- oc_chat.core.lane_tree (lazy; serving_host_of - names the host that served a turn)
- oc_chat.core.presenter_events (lazy; attach_presenter_bridge - renders the approval and sub-agent events in the terminal)
- oc_chat.core.prompt_layers (lazy; _git_branch - the branch in the startup anchor banner)
- oc_chat.core.risk_profile (lazy; resolve_risk_profile - the active risk profile for the deployment-shape check)
- oc_chat.core.session_mode (lazy; the --auto launch flag's switch into auto mode)
- oc_chat.core.settings (lazy; init_runtime_settings - resolves the composition mode once from the settings file and the CLI)
- oc_chat.tools.filesystem (lazy; _sandbox_root and _sandbox_root_info - the sandbox root for the anchor banner and the deployment-shape check)
- oc_ui.presenter_base (lazy; INTERRUPT_NOTICES - the stderr fallback for a Ctrl+C notice)
| None oc_chat.main._announce_interrupt |
( |
"object | None" | agent, |
|
|
str | stage ) |
|
protected |
Announce a Ctrl+C *stage* through the presenter, falling back to stderr.
Plan 188 T5. The handler used to write this notice inline, which meant the
component that OWNS the terminal — the presenter, possibly mid-``Live`` —
had no say in how or where it landed. It now goes through
:meth:`~oc_ui.presenter_base.Presenter.show_interrupt_notice`, whose
default is the same stderr write, so a presenter that does not override it
behaves exactly as before.
Two properties matter because this runs inside a signal handler:
* **it never raises** — a broken presenter must not turn Ctrl+C into a
crash, so any failure degrades to the stderr notice and the
cancellation below still proceeds;
* **it stays one short write** — no prompting, no blocking, no
renderable pipeline (see the hook's signal-safety contract).
Definition at line 169 of file main.py.
| oc_chat.main._o3_sigint_handler |
( |
| signum, |
|
|
| frame ) |
|
protected |
Two-stage cooperative Ctrl+C (Phase O3e + TUI-polish).
During an active turn the Agent owns a ``CancellationToken``:
* **1st Ctrl+C** → request a cooperative interrupt and NOTHING ELSE
(operator decision 2026-08-12): the in-flight tool keeps running, and the
turn is asked "Stop the current action? (y/N)" at its next safe
checkpoint — which arrives after that tool returns. "n" continues with
nothing lost; nothing was cancelled before the answer. A one-line notice
says the current tool keeps running and a second press force-stops.
* **2nd Ctrl+C** (an interrupt is already pending, or a hard cancel is set)
→ force: flip the hard-cancel flag, signal the process-wide tool-cancel
flag (the ``[cancelled by user]`` / returncode −2 machinery in
``tools/_proc.py`` — so the child dies cleanly, not orphaned), and raise
``KeyboardInterrupt`` so blocking I/O unwinds immediately.
**Plan 188 T5.** Both notices go through :func:`_announce_interrupt` →
the active presenter's ``show_interrupt_notice``, instead of the raw
``stderr`` write this handler used to do inline. The mechanism above is
unchanged; what changed is that the component owning the terminal (Rich,
possibly mid-``Live``) now chooses how and where the message lands, with
the previous stderr write as the base-class default and the fallback.
Between turns (no active token) Ctrl+C raises ``KeyboardInterrupt`` as before
— the prompt's own handler cancels the input line without exiting OC Chat.
**Transition deferral (plan 152 R09-01).** While a session transition is
committing, the interrupt is RECORDED and the handler returns without
raising. That is what makes the commit block genuinely uninterruptible:
``dispatch`` is covered by no ``KeyboardInterrupt`` guard, and
``KeyboardInterrupt`` is a ``BaseException`` an ``except Exception`` wrapper
misses, so a press landing between two "no-fail" installs would otherwise
strand a mixed tuple with no recovery. Repeated presses coalesce to one
pending flag; it is consumed exactly once at the next prompt boundary or
turn launch.
Definition at line 204 of file main.py.