The Docker backend currently turns any non-empty allowed_domains list into
unrestricted bridge networking even though no per-domain enforcement exists.
Keep Docker networking disabled and emit a warning until the backend can
actually honor domain policies.
Constraint: Preserve a small, reviewable fix instead of designing a full Docker egress filter
Rejected: Keep bridge networking with docs-only clarification | leaves silently overbroad behavior in place
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: If Docker domain policies are re-enabled later, add true enforcement before widening network access
Tested: PYTHONPATH=src pytest -q tests/test_sandbox/test_docker_backend.py tests/test_sandbox/test_adapter.py
Tested: PYTHONPATH=src ruff check src tests
Not-tested: Full pytest suite in this environment (collection fails because optional pyperclip dependency is missing)
Related: #150
Personalization currently captures full `export NAME=value` payloads and
re-injects them through local rules into future system prompts. Narrow the
environment-variable extraction to the variable name so the feature can still
remember environment hints without persisting secret material.
Constraint: Keep personalization's environment-hint workflow intact
Rejected: Remove env_var extraction entirely | larger behavior change than needed for a first fix
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Do not persist raw env var values without a separate sensitivity model and tests
Tested: PYTHONPATH=src pytest -q tests/test_personalization/test_extractor.py tests/test_prompts/test_claudemd.py
Tested: PYTHONPATH=src ruff check src tests
Not-tested: Full pytest suite in this environment (collection fails because optional pyperclip dependency is missing)
Related: #149
Both built-in agents previously set model="haiku", which caused
subprocess spawning to fail for users running any non-Anthropic provider
(OpenAI, Bedrock, custom base URLs, etc.) because the literal string
"haiku" is not a valid model on those APIs.
Changes:
- Explore and claude-code-guide now use model="inherit", consistent
with the existing Plan and verification built-in agents.
- build_inherited_cli_flags now skips the --model flag when the value
is "inherit", so the subprocess inherits the parent model via the
OPENHARNESS_MODEL env var that build_inherited_env_vars already
forwards. Previously "inherit" was passed verbatim as a model name,
which would have also broken Plan and verification agents.
Tests added:
- build_inherited_cli_flags: model="inherit" and model=None produce no
--model flag; a real model name is included as expected.
- Builtin agent definitions: Explore and claude-code-guide must not
reference Anthropic-only model aliases; Plan, verification, Explore,
and claude-code-guide must all use None or "inherit".
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Replace "OpenHarness-new" with "OpenHarness" in hero title
- Shorten description to "Kanban for OpenHarness self-evolution"
- Group 13 status columns into 4 vibe-kanban style: To Do, In Progress,
In Review, Done — each card still shows its original status badge
- Redesign PipelineAnimation: orbital layout with 5 color-coded stage
nodes, central hub, background grid, ambient particles, dual traveling
packets, spoke data pulses, and scan line — fills the space properly
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the inline HTML template with a Vite + React + TypeScript app
in autopilot-dashboard/. The new design follows the AnyFS dark theme
aesthetic with animated SVG hero background (data streams, constellation
nodes, binary rain, scanline), pipeline visualization, glass cards with
status-colored glow effects, and JetBrains Mono typography throughout.
- autopilot-dashboard/: new Vite+React project, builds to docs/autopilot/
- HeroBackground.tsx: multi-layer SMIL SVG animation
- PipelineAnimation.tsx: QUEUE→PREP→RUN→CHECK→MERGE traveling glow
- CI workflow updated to build React app before deploying to Pages
- Python _render_dashboard_html simplified to a minimal fallback page
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add built-in `minimax` profile to `default_provider_profiles()` with
`MiniMax-M2.7` as default model and `https://api.minimax.io/v1` base URL
- Add `minimax_api_key` / `MINIMAX_API_KEY` auth source support to
`auth_source_provider_name()`, `default_auth_source_for_provider()`,
and `resolve_auth()` env-var lookup
- Add MiniMax to `_KNOWN_PROVIDERS`, `_AUTH_SOURCES`, `_PROFILE_BY_PROVIDER`,
and auth-status check in `AuthManager`
- Add `minimax` and `minimax_api_key` to CLI provider/auth-source label maps
and `oh auth login` provider list; update default model for
`minimax-anthropic` interactive flow from `minimax-m1` to `MiniMax-M2.7`
- Add `MiniMax-M2.7` / `MiniMax-M2.7-highspeed` model suggestions to the
React UI model picker
- Add unit tests for MiniMax profile, auth-source mapping, env-var resolution,
and profile materialisation
Co-authored-by: octo-patch <octo-patch@github.com>
ANTHROPIC_BASE_URL and ANTHROPIC_MODEL were applied unconditionally after
profile materialization, replacing explicit profile settings like Ollama's
base_url with unrelated endpoints. Now OPENHARNESS_* env vars always
override, while ANTHROPIC_*/OPENAI_* vars only apply when the active
profile doesn't explicitly configure the corresponding field.
Also adds Ollama setup instructions to README.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The Windows PowerShell installer (scripts/install.ps1) hard-coded openh.exe
as the expected binary and printed "Launch (PowerShell): openh" in the
final instructions. The `openh` console-script alias was added in ce84a6a,
which landed after the v0.1.6 tag — so `pip install openharness-ai`
(the default install path) fetches a wheel whose entry_points.txt only
declares `oh`, `ohmo`, and `openharness`. openh.exe is never created, and
users follow the install banner straight into a "not recognized" error
(issue #144).
Instead of requiring a new PyPI release before the installer becomes
correct, detect which launcher the wheel actually produced and guide the
user to it. Preference order: openh (no shell collisions) -> openharness
(no collisions, always present) -> oh (collides with PowerShell's
Out-Host alias unless invoked as oh.exe).
Verification now succeeds against whichever launcher exists, and the
final "Next steps" block recommends the matching command along with the
relevant Out-Host caveat.
Co-authored-by: José Maia <glitch-ux@users.noreply.github.com>
`asyncio.gather` was called without `return_exceptions=True`, so a single
escaping exception from one parallel tool propagated out of the gather,
abandoned its sibling coroutines, and left the assistant turn with one or
more `tool_use` blocks that never received a matching `tool_result`. The
Anthropic Messages API rejects the next request on the session with a 400
in that state, so any unhandled tool exception during a multi-tool turn
effectively bricks the session.
Pass `return_exceptions=True` and synthesize an `is_error=True`
`ToolResultBlock` for each raised exception, matched to the originating
`tool_use_id`. The exception is logged via `log.exception` so the failure
is still observable.
Co-authored-by: José Maia <glitch-ux@users.noreply.github.com>
* fix(tools): todo_write updates existing items in-place instead of duplicating
When an agent marks a todo item done by calling todo_write with
checked=True, the tool previously always appended a new [x] line,
leaving the original [ ] line intact. This produced duplicate entries.
The tool now performs an upsert:
- If the item exists as [ ], replace it with [x] in-place.
- If it is already in the target state, return a no-op result.
- Otherwise, append as before.
Adds test_todo_write_upsert to cover the new behaviour.
Co-authored-by: Copilot
* fix(tools): simplify todo_write tool description for LLM clarity
Co-authored-by: Copilot
---------
Co-authored-by: JiangYulin <yulin@JiangYulindeMacBook-Pro.local>
* fix(tui): write newline on exit so shell prompt starts on fresh line
Ink hides the cursor during rendering and restores it on exit, but does
not emit a trailing newline. When the TUI process ends, the terminal
cursor sits at the end of the last rendered line, causing the shell
prompt to appear directly concatenated with the TUI output.
Rename restoreCursor → restoreTerminal and append '\n' alongside the
cursor-show escape sequence so the parent shell always receives the
prompt on a clean new line.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* test(tui): add regression guard for exit newline; update changelog
- Add tests/test_ui/test_tui_exit_sequence.py with two tests:
* test_tui_exit_handler_writes_newline: asserts the cleanup write
includes the trailing \n alongside \x1B[?25h
* test_tui_exit_handler_registered_for_all_signals: asserts cleanup
is registered for 'exit', SIGINT, and SIGTERM
- Add entry to CHANGELOG.md [Unreleased] Fixed section
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(tests): update ohmo cli test inputs for allow_remote_admin_commands prompt
Commit dd1d235 added a new 'Allow explicitly listed administrative slash
commands from remote channels?' confirmation step to the gateway config
wizard, but the four interactive test cases in test_ohmo/test_cli.py were
not updated to provide an answer for it. This caused stdin to be exhausted
and click to emit Abort(), making all four tests fail with exit_code=1.
Add 'n' as the answer for allow_remote_admin_commands in each affected
test, and move the existing 'restart gateway' answer after it in
test_ohmo_config_interactive_can_restart_gateway.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The Feishu channel did not support replying inside group topic threads.
Every bot response created a new top-level message, making conversations
in topic-enabled groups fragmented.
Three changes:
1. feishu.py: include thread_id/root_id in inbound metadata so that
router.py can create per-topic session keys for group chats.
2. feishu.py: add reply_in_thread support to _send_message_sync using
the lark-oapi ReplyMessageRequest with .reply_in_thread(True).
3. bridge.py: forward inbound message_id to OutboundMessage metadata
so the Feishu channel can use it for thread replies.
Files under ~/.openharness/ — credentials, settings, session snapshots,
cron registry, memory index — were written with `Path.write_text()` in
truncating mode. A crash, SIGKILL, power loss, or out-of-disk error
during the write leaves a truncated file on disk; concurrent writers
clobber each other's updates; and the credentials file spent a brief
window at the default umask mode (commonly 0o644) before chmod-to-0600
ran.
Introduce `openharness.utils.fs.atomic_write_text` / `atomic_write_bytes`
which write to a same-directory temp file, fsync, apply the target mode
while the file is still private, and `os.replace` into place. Thread
them through all persistence writers. For read-modify-write on shared
files (credentials, settings, cron, memory index), pair atomic writes
with the existing `exclusive_file_lock` primitive so two `oh` processes
no longer race.
The generic lock helper moves from `openharness.swarm.lockfile` to
`openharness.utils.file_lock`. `swarm.lockfile` is retained as a thin
re-export so existing callers keep working.
Co-authored-by: José Maia <glitch-ux@users.noreply.github.com>
The existing srt/bubblewrap sandbox only wraps shell commands — file I/O
tools bypass it entirely via direct Python calls. This adds Docker as a
second sandbox backend that provides container-level isolation for tool
execution while keeping the engine and API keys on the host.
- Add DockerSandboxSettings with image, resource limits, and env config
- Add backend field to SandboxSettings ("srt" or "docker")
- Implement DockerSandboxSession: long-running container per session
- Route shell, glob, and grep subprocess calls through docker exec
- Add path validation for file tools when Docker sandbox is active
- Auto-build default sandbox image (python:3.11-slim + ripgrep + git)
- Network isolation: --network=none by default, bridge when domains allowed
- Lifecycle integration: container starts/stops with the session
- atexit safety net for cleanup on unexpected exit
- 34 unit tests, 19 E2E tests (require Docker daemon)
Co-authored-by: José Maia <glitch-ux@users.noreply.github.com>