Add a built-in NVIDIA NIM OpenAI-compatible workflow that reads NVIDIA_API_KEY, document the backend, and cover auth/profile materialization. Also make Windows shell resolution skip discovered bash executables that cannot run commands, and update the macOS Terminal backspace troubleshooting note to reflect the current fix.
Add an ohmo-only /group flow for Feishu private chats, persist managed group metadata, and route shared-chat sessions safely. Also update Feishu reply handling and gateway tests for private/group behavior.
Treat per-server initialization cancellation as a connection failure instead of aborting overall startup, and suppress cleanup-time exception groups from half-open MCP sessions.
Teammate spawn (`agent` tool, `task_create`) failed on Windows with exit
127 and "command not found" against a backslash-mangled Python path,
even though the same `bash -lc "..."` command worked interactively.
Root cause: `subprocess_backend.spawn` built a single shell command
string with a `KEY='val' python ...` env-prefix, then routed through
`bash -lc` via `asyncio.create_subprocess_exec`. Two failures stacked:
1. The env-prefix string used Python `repr()` for values, and Git
Bash's `-lc` argument parser consumed backslashes inside the
quoted segments as escape sequences (`\U` -> `U`).
2. Even with proper `shlex.quote` of every command part — keeping
backslashes intact — Git Bash launched via
`asyncio.create_subprocess_exec` could not exec a Windows-pathed
Python interpreter, returning `command not found` for the same
argv that interactive bash exec'd fine.
Fix: don't use a shell at all for teammate spawn.
* Add `argv: list[str] | None` to `TaskRecord` plus matching kwargs
on `BackgroundTaskManager.create_shell_task` /
`create_agent_task`. Either `command` (shell) or `argv` (direct
exec) must be supplied; both is rejected.
* `_start_process` runs the argv path via
`asyncio.create_subprocess_exec(*argv, env=...)` directly, with no
bash wrapper.
* `subprocess_backend.spawn` builds an argv list (via
`get_teammate_command()` plus inherited CLI flags) and hands it to
`create_agent_task(argv=..., env=...)`. Inherited env vars now
flow through `env=` rather than being embedded in the command
string.
* The legacy shell-evaluated `command=` path is preserved verbatim
for callers (e.g. `BashTool`) that legitimately want shell
semantics — only teammate spawn migrates to direct exec.
Tests: 5 new unit tests in `tests/test_swarm/test_subprocess_backend.py`
and 3 in `tests/test_tasks/test_manager.py` lock in the contract — env
plumbed via `env=` kwarg, argv list preserves Windows backslashed
paths, command/argv mutual exclusion, direct-exec round-trip.
Validated end-to-end on Windows 11 against a real CEO/Lead workflow:
CEO successfully calls `agent` tool, Lead spawns at depth=1 and
issues 29 tool calls. Same path was producing exit-127 task logs
before this change.
PR #96 fixed the user-skill loader to use yaml.safe_load instead of
naive line-by-line splitting, so YAML block scalars (`>`, `|`),
quoted values, and other standard constructs parse correctly. The
bundled skill loader still used the old parser, so a bundled skill
with a folded description like
---
name: my-skill
description: >
A long folded description
that spans multiple lines.
---
would emit `description='>'` (just the marker character) instead of
the expanded text.
Extract the YAML-aware parser into `openharness.skills._frontmatter`
and have both `skills/loader.py::_parse_skill_markdown` and
`skills/bundled/__init__.py::_parse_frontmatter` delegate to it. The
bundled loader keeps its `Bundled skill: <name>` fallback prefix via
the `fallback_template` argument.
Add five regression tests in `tests/test_skills/test_loader.py`
covering folded scalars, literal scalars, inline descriptions, the
"Bundled skill:" fallback, and the heading-plus-paragraph fallback
on the bundled side.
The ohmo gateway process management (start, find, stop) only worked on
Unix-like systems. On Windows, all three operations fail:
- start_gateway_process: uses start_new_session=True (no equivalent on
Windows; also lacks stdin=DEVNULL which causes the subprocess to
inherit the parent's console input)
- _pid_is_running: uses os.kill(pid, 0) which raises PermissionError
for processes the caller doesn't own on Windows
- _iter_workspace_gateway_pids: shells out to `ps`, which doesn't exist
on Windows
- stop_gateway_process: uses os.kill(pid, SIGTERM), which is not
supported on Windows
Replace each with a Windows-compatible path guarded by sys.platform:
- Start: CREATE_NEW_PROCESS_GROUP | DETACHED_PROCESS, stdin=DEVNULL
- PID check: OpenProcess/GetExitCodeProcess via ctypes (STILL_ACTIVE)
- PID listing: wmic process where commandline like ... get processid
- Stop: taskkill /F /T /PID
The Unix paths remain unchanged.
Co-authored-by: ancietyding <ancietyding@tencent.com>
Support absolute glob patterns without crashing and retry ohmo channel messages without ImageBlocks when a provider rejects image input.\n\nFixes #225\nFixes #226
Add /model list/add/remove/clear so a single provider profile can expose multiple switchable models in the TUI selector. Also add regression coverage for invalid grep regexes in the Python fallback.\n\nFixes #222\nRefs #218
DeviceCodeFlow._try_open_browser previously called
``subprocess.Popen(["start", "", url], shell=True)`` on Windows. Because
``shell=True`` routes through cmd.exe, a URL returned by the GitHub
device-flow endpoint (or a user-configured enterprise endpoint) that
contained ``&``/``|``/``^`` would have its trailing tokens interpreted as
command separators — e.g. ``https://x.com&calc.exe`` would launch
calc.exe alongside the browser.
Replace the Windows branch with ``os.startfile``, which calls
ShellExecuteW directly and hands the full URL to the registered
URL handler verbatim. Also reject any non-http(s) scheme up front so
``file:`` / ``javascript:`` / bare executable tokens cannot reach any
platform launcher. macOS and Linux/WSL paths are unchanged (they were
already shell=False).
Adds tests/test_auth/test_flows.py covering the four platform branches
plus the scheme guard, including an explicit regression assertion that
shell=True is never set on the Popen calls.
Co-authored-by: José Maia <glitch-ux@users.noreply.github.com>