项目文件夹

文件
wehub-resource-sync 4b6817381b
CI (OpenClaw E2E) / openclaw test (push) Has been cancelled
CI / coverage-report (push) Has been cancelled
CI / test-kubernetes (push) Has been cancelled
CI / should-run-thorough (push) Has been cancelled
CI / test-thorough (cloudwatch-demo) (push) Has been cancelled
CI / test-thorough (flink-ecs) (push) Has been cancelled
CI / test-thorough (upstream-lambda) (push) Has been cancelled
CI / test-thorough (prefect-ecs-fargate) (push) Has been cancelled
Release / build-binaries (zip, opensre.exe, onefile, windows-latest, windows-x64) (push) Has been cancelled
Benchmark image — build + push to ECR (any adapter) / build + push (push) Has been cancelled
CI / quality (ubuntu-latest) (push) Has been cancelled
CI / test (tools-runtime) (push) Has been cancelled
CI / test (e2e-general) (push) Has been cancelled
CI / test (cli-runtime) (push) Has been cancelled
CI / test (e2e-provider-and-openclaw) (push) Has been cancelled
CI / test (integrations-and-misc) (push) Has been cancelled
Release / verify (push) Has been cancelled
Release / build-python-dist (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-15-intel, darwin-x64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-latest, darwin-arm64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04, linux-x64) (push) Has been cancelled
Release / publish-release (push) Has been cancelled
Release / publish-main-release (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-checks (no-LLM) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-live shard ${{ matrix.shard_index }} (push) Has been cancelled
Release / prepare (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04-arm, linux-arm64) (push) Has been cancelled
Synthetic Deterministic Tests / Synthetic offline (deterministic) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:10:45 +08:00

276 行
17 KiB
Markdown

# Interactive shell package
These instructions apply to `interactive_shell/` and all of its
subdirectories. The repo-root `AGENTS.md` still applies.
## Purpose
`interactive_shell/` owns the interactive OpenSRE terminal surface: the REPL
loop, slash-command surface, local alert ingestion, shell execution, and Rich /
prompt-toolkit UI. Reusable agent session state, prompt history, grounding, and
prompt construction live under `core.agent`.
Design for a terminal user who may be in the middle of an incident: behavior
should be predictable, interruptible, explainable, and safe by default.
## Package map and ownership
| Area | Owns | Keep out |
| --- | --- | --- |
| `main.py` | process/bootstrap boundary for starting the REPL | per-turn dispatch/runtime logic |
| `controller.py` | top-level REPL wiring, alert listener lifecycle, prompt loop, background workers, and shutdown | feature-specific business logic or compatibility-only forwarding |
| `runtime/core/turn_accounting.py` | shell turn accounting (`ShellTurnAccounting`) for analytics, telemetry, recorder flush, turn persistence, and intent stamps | turn-flow control (owned by `core.agent_harness`) or tool-calling turn execution |
| `command_registry/` | slash-command definitions, argument validation, command dispatch | long-running implementation details better placed in services/runtime modules |
| `runtime/` | background task workers, lifecycle/`ReplState`, runtime context assembly, semantic shell-turn execution, and core harness adapters | prompt text, reusable session persistence, or compatibility shims |
| `tools/interactive_shell/shell/` | shell command parsing, shell execution policy, subprocess execution, and the `run_shell_command`/`run_cd`/`run_pwd` runner (next to the `shell_run` tool in `tools/interactive_shell/actions/shell.py`) | slash-command execution |
| `references/` | CLI/docs/source/AGENTS reference loading and caching | generated model prose |
| `config/` | interactive-shell config loading and tool catalog metadata | global app config unrelated to the REPL |
| `ui/` | Rich/prompt-toolkit rendering, theme, menus, streaming output, and domain views such as `incoming_alerts.py` (receiver/queue/listener lifecycle lives in `core.domain.alerts.inbox`) | business logic or network calls |
When a change crosses these boundaries, prefer extracting a small helper in the
owning area rather than adding more logic to the caller.
## Cross-cutting rules
- Treat every external input as untrusted: user prompt text, slash-command args,
alert payloads, files read into prompts, history, subprocess output, model
output, and integration metadata.
- Keep the interactive path responsive. Long-running work must be cancellable,
timeout-bounded, moved off the input path, or surfaced with clear progress.
- Preserve import-time lightness. Do not start threads, call LLMs, read large
files, or contact networks at module import time.
- Prefer explicit data models and typed helpers over loosely shaped dictionaries
when data crosses submodule boundaries.
- Keep user-visible strings intentional. Slash-command names, flags, output
labels, prompts, response bodies, and error wording are user-facing API.
- Avoid new module-level mutable globals. If global coordination is unavoidable,
provide deterministic reset/cleanup hooks and test isolation.
- Do not keep compatibility-only forwarding modules after moving code. Migrate
callers/tests to the canonical owner and remove the old import path in the
same change.
## Slash commands
- Add commands as `SlashCommand` entries in the relevant `command_registry/*`
module. Keep handlers small: parse args, call focused helpers, render result.
- **REPL + CLI parity (required):** Every command in `SLASH_COMMANDS` must have a
matching `_MCP_BY_COMMAND` entry in
`command_registry/slash_catalog.py`. That catalog feeds the LLM planner (`slash_invoke`),
planner tool specs, and compact help text. Without it, CI fails
(`test_slash_catalog_covers_all_registered_commands`).
- **New REPL-only slash command:** add `SlashCommand` in the owning
`command_registry/*` module **and** `_mcp(...)` in `slash_catalog.py` (keep
keys sorted alphabetically in `_MCP_BY_COMMAND`).
- **New CLI with REPL parity:** add the Click command under `surfaces/cli/commands/`,
register a `SlashCommand` in `command_registry/cli_parity.py` (subprocess to
`opensre …`), **and** add `_MCP_BY_COMMAND` in `slash_catalog.py` with
`llm_description`, `use_cases`, and `anti_examples` aligned to the command’s
`usage` tuple.
- **Verify before push:**
`uv run python -m pytest tests/interactive_shell/command_registry/test_slash_catalog.py -q`
- Use `validate_args` for cheap pre-policy validation so bad arguments do not
trigger confirmations or side effects.
- Send command execution through the central dispatch and execution-policy
helpers. Do not bypass `execution_policy.py` for new commands.
- **Alpha allow-all execution policy (current behavior):** the REPL runs with
**no command guardrails**. `execution_policy.py` resolves every action to
`allow` with **no confirmation prompt** — all slash/`opensre` commands,
investigations, synthetic tests, code-agent launches, LLM runtime switches, and
**all** shell commands run immediately, in any context (TTY or not, trust mode
or not). There is **no shell-command safety policy**: the
read-only/mutating/restricted classification and the `deny` floor were removed
(`shell_policy.py` deleted; parsing/policy/execution live under
`tools/shell/`). Mutating commands (`rm`/`mv`/`docker`), `restricted` commands
(`sudo`, `systemctl`, `kill`, `dd`, …), shell operators (`| && ; > <`), and
command substitution all run; the `!` prefix is honored but optional. The only
shell input still rejected is genuinely empty input (a bare `!` or whitespace).
Do **not** re-add a shell allowlist or deny floor while in alpha — see
`docs/interactive-shell-action-policy.md`. The former `ExecutionTier`
classification was removed because it gated nothing under default-allow; if an
opt-in stricter policy is reintroduced after alpha, gate it in
`execution_policy.py` (the `ask` verdict, confirmation UX, and `trust_mode` are
retained as the hook), not via a planner-stage denial.
- Non-TTY behavior under default-allow: actions no longer fail closed on
non-interactive stdin (there is nothing to confirm). The fail-closed path only
applies if a verdict is explicitly `ask`, which the default policy does not
emit.
- **CPR / exclusive-stdin registration (required for table-outputting commands):**
Under `patch_stdout(raw=True)`, the REPL runs dispatch concurrently with the
next `prompt_async()`. When a command emits Rich table output, prompt_toolkit
redraws the prompt mid-flight, sending an `ESC[6n` DSR query; the terminal's
CPR response (`ESC[<row>;<col>R`) arrives as literal keystrokes in the incoming
prompt buffer, causing garbage like `^[[60;1R` to appear.
**Any command that calls `print_repl_table` (directly or via `render_table` /
`render_integrations_table` / `render_models_table` / etc.) must be added to
`_EXCLUSIVE_STDIN_MENU_COMMANDS` in `runtime/utils/input_policy.py`.** That makes the main
loop call `await state.queue.join()`, blocking the next prompt until dispatch
completes and both drain cycles clean up stale CPR bytes before the next
`prompt_async()` starts.
- **How to check:** after adding a command, run it in the REPL and type a few
characters in the next prompt. If no `^[[…R` garbage appears, the registration
is correct.
- **Agent-selected interactive commands:** `_EXCLUSIVE_STDIN_MENU_COMMANDS`
only reserves stdin for literal `/slash` command text that
`_literal_slash_command_text` recognizes. When free text like
"remove github" is resolved by the action agent into an inline-picker
command (`/integrations remove`, `/integrations setup`, `/mcp connect`,
`/mcp disconnect`, or a bare `/integrations` / `/mcp` menu), the loop has not
reserved stdin, so `tools/interactive_shell/actions/slash.py` must NOT run the picker inline. It defers
via `session.queue_auto_command(...)`, which re-submits the command as
literal command text so the loop can reserve exclusive stdin before the
agent path runs it. New raw-stdin picker/wizard commands the action agent can emit
must be added to
`_INTERACTIVE_PICKER_MENUS` / `_INTERACTIVE_PICKER_SUBCOMMANDS` in
`tools/interactive_shell/actions/slash.py`.
## Action Selection And Execution
- **Hard boundary:** do not add regex/keyword/fuzzy intent routing for natural
language, or any deterministic mapping from non-`/`-prefixed prose to an action
(e.g. "show integrations" -> `/integrations`). Engineers have been fired before
for reintroducing intent heuristics that compete with the action agent.
- **Sanctioned exception:** input the user types as a literal `/slash` command
is dispatched deterministically (a static `slash_invoke` call in
`core/agent_harness/turns/action_driver.py`), so slash commands keep working when the
action-agent LLM is unavailable. This is an explicit-command bypass, not
intent inference — it fires only when the message *is* a `/command`, and
free-form text is still LLM-selected. See
`docs/interactive-shell-action-policy.md` ("Deterministic literal-`/slash`
dispatch").
- **No planning-stage fail-closed safeguard (v0.1 decision).** The second-phase
action agent never denies a turn. Because every terminal action is read-only,
an unmatched/ambiguous/chatty clause is not a safety risk — the agent executes
the clauses it can map and lets the rest fall through to the conversational
assistant. We removed the `denied` decision path, the `mark_unhandled` planner
tool, the `UNHANDLED:` convention, and the "I couldn't safely decide actions"
message because they caused frequent false denials (e.g. a conversational
question that embedded a quoted, list-style directive) with no safety upside.
Details and rationale live in `core/agent_harness/AGENTS.md`. If
mutating actions are ever introduced, gate them with the
execution-stage confirmation policy (`tools/shared/execution_policy.py`), not a
planner-stage denial.
- Keep deterministic command detection in `orchestration/` for terminal UI
policy only; use the action agent for slash/tool action selection.
- Send uncertainty to a safe surface: help/chat or a clarification, not direct
mutation or shell execution.
- LLM-generated text must never execute directly. Convert proposed actions into
explicit planned actions, show them to the user when appropriate, then execute
through `orchestration/` and policy gates.
- Keep action summaries human-readable and specific enough for confirmation UX
and audit logs.
- When adding a new action type, test allowed, denied, and confirmation-required
paths.
## LLM prompts, grounding, and references
- Keep prompts bounded. Enforce size caps for docs, source chunks, histories,
observations, alert text, and command output included in model context.
- Ground procedural/help answers in maintained references (`docs/`, CLI help,
AGENTS files, source snippets). If references do not support an answer, say so
rather than inventing steps.
- Do not include secrets in prompts. Redact or omit tokens, auth headers, env
values, local credentials, and raw integration config.
- Keep prompt rules reusable in `core.agent_harness.prompts` so chat/help/action
surfaces use consistent terminology and formatting.
- Reference caches should be deterministic, invalidatable when source files
change, and cheap to rebuild in tests.
## Terminal UI and rendering
- Escape user-controlled content before passing it to Rich markup
(`rich.markup.escape`): alerts, command output, file paths, integration names,
model/provider labels, errors, docs snippets, and model text that is not
already intentionally rendered as Markdown.
- Use semantic tokens from `ui/theme.py`. Do not introduce raw hex colors, Rich
named colors, or raw ANSI escapes outside `ui/theme.py` unless a narrow
prompt-toolkit compatibility path requires it.
- Keep rendering helpers as pure as practical: accept data, return/render Rich
objects, avoid reading config or mutating session state from UI modules.
- Any raw terminal-mode code must check TTY support and restore terminal state
in `finally`.
- Be careful mixing `prompt_toolkit.patch_stdout`, Rich live rendering, and
background output. Prefer append-only, paragraph-buffered, or throttled
rendering paths that do not corrupt the editable prompt.
- UI changes should handle narrow terminals, non-ASCII fallback where relevant,
long text, empty states, and non-TTY automation.
## Shell, subprocesses, and local system effects
- Shell execution changes belong under `shell/` and must preserve parsing,
quoting, timeout, redaction, and policy behavior.
- Treat subprocess output as untrusted display text; escape it before Rich
markup and cap what is retained or sent to prompts.
- Use explicit timeouts and clear cancellation behavior for subprocesses. Avoid
waits that can hang the REPL indefinitely.
- Keep allow/deny decisions explainable. If a command is blocked, return a
user-facing reason and a safe alternative when possible.
## State, history, config, and background work
- Prefer explicit `Session` fields for session state. Keep ownership clear:
runtime owns lifecycle, history owns persistence, config owns shell-specific
settings.
- Background threads/tasks/listeners must have deterministic shutdown. Tests
should stop handles and workers in fixtures or `finally` blocks.
- Protect shared queues and mutable session data with locks or single-owner
discipline. Avoid check-then-act races around queues, cancellation flags,
current tasks, and listener handles.
- History should avoid storing secrets or excessive payloads. Apply truncation
and privacy policy consistently.
- Config loading should degrade gracefully with actionable errors; do not make
the REPL unusable because an optional config or catalog source is missing.
## External input and local listener safety
- Network-ish local surfaces such as `core.domain.alerts.inbox` (started by the
REPL entrypoint) must validate cheap request metadata before blocking reads or
expensive parsing.
- Never perform unbounded request-body reads. For alert POSTs specifically,
validate `Content-Length` first, and only then read the bounded body:
- non-numeric `Content-Length` values make `int(...)` raise `ValueError`;
catch this and return `400`.
- negative lengths must return `400`; `rfile.read(-1)` reads until EOF rather
than zero bytes, which can stall the single-threaded handler.
- oversized positive lengths must return `413` without attempting to read the
advertised body.
- Preserve clean unauthorized responses for real POST bodies by draining only a
bounded body before returning `401`; this avoids close-with-unread-data resets
on some platforms without allowing oversized pre-auth reads.
- Keep request-size and malformed-header checks effective for both authenticated
and unauthenticated callers.
- Keep non-loopback listener binding protected by a token. Use constant-time
token comparison and never log bearer tokens, raw auth headers, or full alert
payloads.
## Testing expectations
- Put tests under `tests/interactive_shell/`, mirroring the package area
when useful (`orchestration/`, `ui/`, etc.). Never add tests under
source packages.
- For focused changes, run the closest tests, for example:
- `uv run python -m pytest tests/core/domain/alerts/test_inbox.py`
- `uv run python -m pytest tests/interactive_shell/<area>/`
- `uv run python -m pytest tests/interactive_shell/`
- Add regression tests for incident-prone edges: platform socket behavior,
malformed input, non-TTY execution, cancellation, policy denial, prompt-size
caps, Rich escaping, and background cleanup.
- Prefer deterministic tests over sleep-heavy tests. Use fake classifiers,
fake sessions, fake consoles, monkeypatched subprocesses, and small fixtures.
- For UI work, test pure formatting/rendering helpers where possible and keep
full REPL-loop tests minimal.
- For action-planning or execution-policy changes, test both safe fallback behavior and
the intended positive path.
## Change checklist
Before considering an interactive-shell change complete, check:
1. Is the logic in the right submodule, with import-time side effects avoided?
2. Is user-facing behavior preserved or intentionally documented?
3. Are unsafe actions sent through execution policy with the correct tier?
4. Are external inputs bounded, escaped, redacted, and timeout-protected?
5. Do background resources shut down deterministically?
6. Are focused tests added or updated under `tests/interactive_shell/`?
7. If `SLASH_COMMANDS` changed, does `slash_catalog.py` include every command
(REPL and `cli_parity`)? Run `test_slash_catalog.py`.