Dictate with live partial results: the composer mic streams 16 kHz PCM over a
WebSocket to Octop, which signs and proxies the audio to Tencent's realtime ASR
V2 endpoint. Credentials never leave the server; the browser only learns a
`stt_realtime` boolean from `/api/voice/active`.
- settings: add a "realtime speech recognition" switch plus an AppID field to
the Tencent Cloud voice drawer. Both live in `voice_providers.extra_json`, so
no schema migration is needed.
- interaction: with realtime on, the mic becomes press-and-hold (start on
pointerdown, finish on release/leave/cancel) and stays available while a reply
is streaming, so dictation can be queued. With the switch off the previous
click-to-record one-shot upload path is unchanged.
- protocol: realtime ASR V2 answers with `result` (`slice_type`, `index`,
`voice_text_str`) while the docs describe `sentences`; both are normalized in
`infra/voice/realtime.py`.
- errors: new `VOICE_REALTIME_UNAVAILABLE` code plus localized copy for the
Tencent realtime error codes (4002-4009, 5000-5002).
Verified end-to-end against Tencent with real speech (edge-tts -> 16 kHz PCM
over the live WebSocket) and by `make all`.
* fix: schedule proactive care for agents that never saved config
Proactive care defaults to on, but the scheduler only listed persisted
enabled rows, so most experts never entered the push loop.
* fix: attach gateway connector tools without rebuilding the agent
prepare_chat_mcp rebuilt the agent whenever a builtin MCP server was
missing from the live runtime. For gateway-mode connectors that rebuild
is pure loss: they carry no HTTP transport, their tools are built
in-process from stored credentials, and _post_start_agent runs the very
same injection at the end anyway. Meanwhile the rebuild drops the
harness instance and with it the checkpointer pool an in-flight turn is
still writing to.
Split the missing names on connector mode: gateway ones refresh their
credentials and inject into the running agent, the rest keep the
existing reload path.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix: await cancellation on scheduler shutdown, and keep its sleeps out of tests
ProactiveCareScheduler.shutdown() cancelled each task and returned
without awaiting it, so a loop parked in a multi-hour sleep could
outlive the shutdown that was supposed to end it. It now gathers the
cancelled tasks before returning.
Proactive care defaults to on, so creating an agent starts one of those
sleeps. pytest-asyncio waits for leftover tasks before fixture teardown,
which hangs the suite. octop_client shuts the scheduler down and calls
the new suspend() so nothing reschedules; an autouse fixture covers
tests that boot OctopServer another way. The scheduler's own unit tests
opt out.
suspend() exists for the tests. The alternative — every test remembering
to tear the scheduler down — is the arrangement that produced the hang.
* refactor: drive websockets on one event loop and drop the cross-loop workaround
starlette's sync TestClient runs the app on its own anyio portal loop.
That left the process with two loops over one OctopServer, so primitives
bound to a loop and shared by both — AgentManager._lock — deadlocked or
raised "bound to a different event loop". The tests worked around it by
running the ws session in a worker thread.
tests.support.http now speaks ASGI directly, so the handler, the gateway
workers and the test all share one loop, the way uvicorn runs it in
production.
That removes the reason the production handlers marshalled every
outbound frame across loops with run_coroutine_threadsafe + wrap_future.
The comment on that code named the cause outright — "this handler may
run on a different loop (e.g. starlette's TestClient portal)" — so it
was test-shaped machinery sitting on the path of every frame the
dashboard receives. Both handlers now await the send directly.
* update harness-agent & harness-memory
---------
CI:
- Drop Python 3.11 from the test matrix (package requires >=3.12); the
3.11 jobs failed at `uv sync`. Linux and Windows now test Python 3.12.
- Rename tests/unit/infra/setup/tls/test_store.py -> test_tls_store.py to
fix the pytest import collision (duplicate basename) that aborted
collection on the 3.12 job.
Source bugs uncovered by the suite:
- Move ACTOR_SYSTEM to a runtime import in infra/cron/job.py (was wrongly
placed under TYPE_CHECKING, causing NameError at runtime).
- Add missing users-table columns (login_failed_count, login_locked_until,
preferences_json) in infra/db/migrate.py for legacy schema repair.
- Make auth.update_me honor explicit nulls via model_dump(exclude_unset=True).
- Relax skills hub endpoints to existence/ownership checks (require_agent_row)
instead of requiring the agent to be running.
- Marshal WebSocket frames onto the owning event loop in chat/ws.py so the
starlette TestClient portal does not deadlock.
- Include error details in the browser session 503 response.
Tests:
- Update tests to match current source APIs (harness async create/remove/
rebuild, FakeHarnessAgent skill methods, workspace nested layout, JWT
middleware, CLI surface, proactive config defaults, general-assistant
template manifest).
- Migrate WebSocket tests to starlette TestClient(...).websocket_connect
(httpx 0.28 removed AsyncClient.websocket_connect).
- Skip memory API tests when the optional harness_memory dependency is absent
via pytest.importorskip.
- Remove the invalid diagnostic test tests/integration/test_diag_e2e.py
(no assertions; inspected private internals only).
Also includes the pre-existing uncommitted orca -> Octop rename across docs,
README, LICENSE, Dockerfile and docker assets, as requested.