Commit Graph

2681 Commits

Author SHA1 Message Date
harry-yao_data c5d6d06d94 perf(runtime): stop pulling the server router into every client import
`RuntimeCaps.routing_settings` defaulted to
`field(default_factory=_default_routing_settings)`, and that factory
imports `omnigent.server.smart_routing`. Because `runtime/_globals.py`
builds a module-level `RuntimeCaps()`, the factory ran on every
`import omnigent.runtime` — so any CLI client that touched the runtime
paid for the server's routing stack (and, transitively,
`model_fallbacks` -> `onboarding.provider_config` -> `spec.parser`)
before doing any work.

Default the field to `None` instead. Every read already goes through
`smart_routing.routing_settings(caps)`, which substitutes
`RoutingSettings()` for anything that is not a `RoutingSettings` — so an
unconfigured deployment resolves the same defaults as before, just at
first use instead of at import. Nothing reads the attribute directly.

`import omnigent.runtime`: 0.23s -> 0.04s (-190ms, -83%), and
`omnigent.server.smart_routing` no longer loads in client processes.

Co-authored-by: Isaac <no-reply@databricks.com>
2026-08-22 03:06:27 +00:00
Jeremiah lu b551f669d6 fix(openai-agents): wrap string assistant content for the chat converter (#4824)
Resuming a conversation whose history contained an assistant message with
plain-string content crashed before reaching the model:

    TypeError: string indices must be integers, not 'str'
      chatcmpl_converter.py:625 in items_to_messages

A string is legal Responses-API content, but items_to_messages iterates an
assistant message's content expecting blocks. Given a string it walks the text
character by character and indexes each character, so the very first one raises.
User strings are unaffected — they reach extract_text_content, which accepts
them, and callers depend on them staying strings.

Because history is replayed on every turn, one such item ends the conversation
permanently: each retry fails identically before the model is reached, and the
only escape is to abandon the conversation.

Only the assistant branch is normalized, and only when content is a string, so
block content and user strings pass through untouched.

Co-authored-by: Tomu Hirata <tomu.hirata@gmail.com>
2026-08-20 05:41:00 +09:00
Yuan Tang ddc5f6ee60 chore(k8s): Address ArgoCD overlay review follow-ups and PR #4744 comments (#4982)
* Address ArgoCD overlay review follow-ups (#4977) and PR #4744 comments

Add CI validation of kustomize overlays, clarify the ignoreDifferences
/data vs /stringData ArgoCD normalization, separate sync-completes from
app-healthy in the Ingress wave comment, add TODO(v0.29) to the
bare-Pod fallback in terminate(), and update the sandbox-runners README
to reflect the bare-Pod → Job migration.

Signed-off-by: Yuan Tang <terrytangyuan@gmail.com>

* fix(ci): install kustomize via official script instead of third-party action

The pinned SHA for imranismail/setup-kustomize was unresolvable.

Signed-off-by: Yuan Tang <terrytangyuan@gmail.com>

* fix(docs): correct backoffLimit value in sandbox-runners README

The README stated `backoffLimit: 0` but the actual code uses
`_JOB_BACKOFF_LIMIT = 6` — fix the doc to match.

---------

Signed-off-by: Yuan Tang <terrytangyuan@gmail.com>
2026-08-19 12:38:39 -07:00
Dhruv Gupta 473941e322 fix(acp): resolve the tool identity on a bare permission request (#5050)
An ACP agent may ask permission carrying only a `toolCallId` — no `title`,
`kind`, or `rawInput`. Devin does. `_extract_tool_call` then resolved the name to
the literal string "tool" with empty arguments, so:

- the approval card asked the user to approve "Devin wants to use **tool**",
  preview `tool({})`, with no command shown; and
- the TOOL_CALL policy was evaluated as `{"name": "tool", "arguments": {}}`,
  which no builtin rule can match — rules gate on the tool name before reading
  `arguments["command"]`, so a "deny `rm -rf`" policy sat silent.

The originating `tool_call` update carries the real name and command and always
arrives first, and the executor already caches `toolCallId -> name` there to
close the right tool card. Cache the `rawInput` beside it and fall back to both
when the request omits them; values the request does carry still win. The
correlation is the protocol's own id, so no vendor `_meta` key is read.

Both caches are released when the call closes, as the name cache already was.

Co-authored-by: Isaac

Signed-off-by: Dhruv Gupta <dhruv.gupta@databricks.com>
2026-08-19 12:31:39 -07:00
Corey Zumar 1f575ba8de fix(docker): honor configured execution timeout (#5016)
Signed-off-by: dbczumar <corey.zumar@databricks.com>
2026-08-19 11:27:43 -07:00
Corey Zumar 8f63f3271b fix: preserve managed host logs on relaunch (#5042)
Signed-off-by: dbczumar <corey.zumar@databricks.com>
2026-08-19 11:17:01 -07:00
Edwin He 9df23f7aeb chore(ucode): pin OSS ucode to a fixed commit (#5043)
Omnigent's uvx setup path resolved ucode from the mutable `main` branch, so
setup could silently pick up a new ucode commit between runs and break
unexpectedly. Pin `_UCODE_GIT_REF` to a fixed, known-good commit
(94271a78c7139220b7333bcae91e522f95ef3af3) so setup is reproducible.

A full SHA is immutable, so uvx caches the built wheel by ref and reuses it
across runs; drop the `--refresh-package ucode` that existed only to defeat
the mutable branch's stale cache.

Co-authored-by: Isaac

Signed-off-by: Edwin He <41037314+Edwinhe03@users.noreply.github.com>
2026-08-19 18:08:48 +00:00
Mark Tai c6d1f7d5e0 feat(web): resume imported / host-less sessions from the web (#4905)
An imported or otherwise unbound session (no host, no runner) couldn't run from
the web: it read as reachable (so the first message dropped against a runner
that can't start) or dead-ended on the terminal reconnect path.

The fix is mostly server-side liveness. An imported transcript is a
native-harness session that only runs in a runner on a host, never in-process,
so report it as runner_online=false via a new `imported` connectivity marker
(keyed on the omnigent.import.source label — the sibling of the existing fork
`needs_workspace` marker, computed in the same query). With that, the open view
routes to the EXISTING host picker (ResumeWithDirectoryDialog) instead of the
dead end. That picker — the same one forks and new-chat use — binds the session
to an online host + workspace (defaulting to the caller's current host) and
launches a runner via the existing POST /v1/hosts/{id}/runners path. No new
host-selection UI, no new launch route.

The picker is offered only when the resume will actually work
(unboundSessionResumableInApp): the caller must OWN the session (launch_runner
requires owner — a shared non-owner 404s), and for imports the harness must
reconstruct context from the omnigent transcript so it carries onto a chosen
host. Kimi has no resume path, and kiro/qwen resume only from a local recording
that lives on the original machine, so those route to the terminal reconnect
path instead of a picker that would start blank.

Also:
- Skip the cold-boot startup grace for imports so the picker shows at once.
- Generalize ResumeWithDirectoryDialog to prefill from the session's own fields
  when there is no fork source.
- `omnigent import` prints the session's browser URL instead of the bare id.

Co-authored-by: Isaac

Signed-off-by: Mark Tai <mark.tai@databricks.com>
Co-authored-by: Mark Tai <mark.tai@databricks.com>
2026-08-19 10:16:18 -07:00
Corey Zumar 05eaa253d1 fix(host): retry Databricks auth refresh (#5014)
Signed-off-by: dbczumar <corey.zumar@databricks.com>
2026-08-19 10:12:39 -07:00
Evelyn Hur 699809de6e Show actual server target in host-daemon conflict error (#4821)
The "A host daemon is already running for this server" error suggested
`omnigent host stop --server ...`, where the literal `...` hid the fact
that `--server` needs an argument and left users guessing which value to
pass. Build the hint via the existing `_host_stop_command` helper from
the conflicting record, so the message prints a ready-to-run command:
the real URL for a remote daemon, or `--server ""` (the empty-string
alias) for a local daemon, matching the `host --background` hint.

Co-authored-by: Isaac

Signed-off-by: Evelyn Hur <122575337+evelyn-hur@users.noreply.github.com>
Co-authored-by: Pat Sukprasert <pattara.sk127@gmail.com>
2026-08-20 01:11:47 +08:00
Hubert 4f05fbd0ac [OMNI-2359] [OMNI-2350] Show the task tracker inside the chat (#5036)
* Move tasks to chat box

Signed-off-by: Hubert Zub <hubert.zub@gmail.com>

* Remove tasks from tab

Signed-off-by: Hubert Zub <hubert.zub@gmail.com>

* padding

Signed-off-by: Hubert Zub <hubert.zub@gmail.com>

* test(web): e2e test for the in-chat Plan tracker

Drives the real chat store (mocked todos) through ChatPlanAccordion:
collapsed by default, expands to the task list, tracks a live
completion-count update, and self-hides when the list is cleared.

Co-authored-by: Isaac
Signed-off-by: Hubert Zub <hubert.zub@gmail.com>

* test(server): cover per-item todo validation filter; fix e2e-test lint

Adds a pytest case proving _handle_external_session_todos drops
malformed todo items (bad status / non-str content / non-str
activeForm / non-dict) while keeping well-formed ones, on both the
session.todos SSE channel and the cached snapshot — the one todos-
pipeline path the existing tests didn't exercise.

Also switch the ChatPlanAccordion e2e test's Todo `type` to an
`interface` to satisfy oxlint (consistent-type-definitions).

Co-authored-by: Isaac
Signed-off-by: Hubert Zub <hubert.zub@gmail.com>

* test(e2e_ui): browser e2e for the in-chat Plan tracker

Move the tracker's e2e coverage into the Playwright suite where it can
exercise the real UI: tests/e2e_ui/chat/test_plan_tracker.py seeds the
session.todos contract through the events route (the forwarders' path),
then asserts the pinned Plan card seeds from the snapshot on load, stays
collapsed by default, expands to the task list on click, tracks a live
completion count, and disappears when the list clears. Mirrors
test_mcp_startup_indicator.py's seed-then-republish pattern.

Adds a data-testid="plan-tracker" hook to ChatPlanAccordion, and drops
the jsdom vitest e2e (web/.../ChatPlanAccordion.e2e.test.tsx) it
supersedes; the ChatPlanAccordion unit test stays.

Co-authored-by: Isaac
Signed-off-by: Hubert Zub <hubert.zub@gmail.com>

* docs(web): align Plan accordion max-height comment with code

The comment said "Cap the expanded list at 100px" while the class is
max-h-[150px]; sync the number (flagged by Polly review). Comment-only.

Co-authored-by: Isaac
Signed-off-by: Hubert Zub <hubert.zub@gmail.com>

---------

Signed-off-by: Hubert Zub <hubert.zub@gmail.com>
2026-08-19 17:11:45 +02:00
Hubert 537620909c [OMNI-3751] Change document view mode toggle to dropdown (#5015)
Signed-off-by: Hubert Zub <hubert.zub@databricks.com>
Co-authored-by: Hubert Zub <hubert.zub@databricks.com>
2026-08-19 14:47:33 +02:00
Corey Zumar ed9369474f fix(web): stop rendering single-dollar spans as LaTeX math (#5013)
A single $ is prose far more often than a math delimiter — currency,
rates like $/PR and $/session, shell variables — and single-dollar math
paired any two of them up, rendering everything in between as
letter-by-letter math soup. Require $$ to open math and drop the
currency/env-var escaping heuristics that tried to guess prose apart from
math. Explicit TeX delimiters now normalize to $$ so \(x\) still
renders as inline math.

Signed-off-by: dbczumar <corey.zumar@databricks.com>
2026-08-19 01:52:35 -07:00
omnigent-ci[bot] 1fceeeb754 Bump version to 0.11.0.dev0 (#4989)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-08-19 05:44:29 +00:00
omnigent-ci[bot] 71a3437168 docs(changelog): record v0.10.0 (#4991)
* docs(changelog): record v0.10.0

* docs(changelog): fix truncated and malformed entries in v0.10.0

Complete 18 truncated entries, add proper [Bug fix / Test/CI] tags to
#4508 and #4509 (which had bare `*` bullets), and drop the internal-only
[Docs] N/A entry (#4925).

Signed-off-by: Tomu Hirata <tomu.hirata@gmail.com>

---------

Signed-off-by: Tomu Hirata <tomu.hirata@gmail.com>
Co-authored-by: omnigent-ci[bot] <294685417+omnigent-ci[bot]@users.noreply.github.com>
Co-authored-by: Tomu Hirata <tomu.hirata@gmail.com>
2026-08-19 04:33:43 +00:00
Daniel Lok 86726e0ab5 fix(web): keep the Working shimmer lit while background tasks run (#4906)
* fix(web): keep the Working shimmer lit while background tasks run

The background-tasks pill (#4893) introduced a shared `isBackgroundTasksOnly`
predicate that gated all three busy surfaces off `bgCount > 0` alone, without
checking whether the agent's turn was still active. So any live turn that
coincided with a background task — notably `waiting`, where the parent is
parked on its async-work drain of sub-agents / background shells — had its
"Working…" shimmer suppressed and replaced by the pill, misreading an active
turn as finished.

Make the shimmer and the pill independent surfaces:

- `isBackgroundTasksOnly` now also requires the turn to be inactive
  (`!agentWorking`), so the shimmer yields only once the turn has genuinely
  ended (`idle`) with tasks lingering.
- `BackgroundTaskPill` shows on `bgCount > 0` alone, decoupled from the shimmer,
  so both appear together while the turn is active.
- `workingIndicatorLabel` no longer emits the background count (the pill owns
  it); the shimmer just rotates its working messages or shows "Blocked on: …".

Co-authored-by: Isaac

* fix(web): keep the background-task pill lit while the turn works

The pill vanished the moment the "Working…" shimmer appeared, so the two
surfaces were still effectively mutually exclusive. The cause was the
background-shell tally being zeroed on every new turn: the server's
_publish_status popped the cache on a `running` edge, the client's
session_status reducer zeroed it on `running`, and the optimistic send path
cleared it synchronously. All three date from the single-surface design, where
the count was a LABEL on the shimmer ("N background tasks still running") that a
new turn should replace with "Working…".

Now that the pill is a separate surface, background shells outlive turn
boundaries and the tally must persist across the turn so the pill stays lit
beside the shimmer. Stop clearing on `running` in all three places; keep
clearing only on an authoritative Stop-hook `0` (shell finished) and on
`failed` (a dead session may never post another count). The next Stop hook
re-reports the count authoritatively.

Also note the server normalizes a claude-native turn-end `waiting`+count to
`idle` (see _background_task_delivery_status), so the client's real
"working + shell" state is `running` with a preserved count — reflected in the
reworked e2e coverage.

Co-authored-by: Isaac

* fix(web): remove the scroll-pinned Working tab

The pinned "Working…" tab (shown while scrolled up) was designed to merge its
flat bottom edge into the composer, but the background-task pill now sits
between them — so the tab reads as a stray rounded card floating above the
pill. Remove the sticky tab entirely (WorkingStatusPin); the inline shimmer at
the end of the thread is the working cue.

Move the tab's one non-visual job — the sole aria-live region announcing the
working state — onto the inline WorkingIndicator: a stable "Working…" in a
role=status region, with the rotating visible label kept aria-hidden so it
never re-announces. Screen readers still get one announcement per turn.

Co-authored-by: Isaac
2026-08-19 08:18:47 +08:00
Zeyi (Rice) Fan 03c7907966 fix(host): keep capability probes out of tunnel handshake (#4769)
## Related issue

Closes [OMNI-2964](https://linear.app/omnigent/issue/OMNI-2964/fix-host-tunnel-connection-issue-when-it-fails-to-detect-hanress)

## Summary

- Move harness and gateway capability discovery out of reconnect handshakes, bound startup discovery, and degrade probe failures to visible warnings with unknown metadata.
- Add a backward-compatible `host.connection_error` frame so accepted tunnels can surface server-side setup failures with their stage and retryability.
- Make background startup wait for the existing server-side host status before reporting success and retain reconnect regression coverage.

ELI5: checking which agent CLIs are installed is optional setup information. A broken CLI should not prevent the host from introducing itself to the server, so the host now connects with that information marked unknown and refreshes it later.

```text
host startup ── capability probe ──┬─ success → cached metadata
                                  └─ failure/timeout → warning + unknown
                                                    │
                                                    ▼
WebSocket upgrade → host.hello → connected receive loop
                                      ▲
server setup failure → host.connection_error
```

## Test Plan

- `uv run pytest tests/host/test_frames.py tests/server/integration/test_host_tunnel_route.py tests/host/test_connect.py tests/host/test_cli_host.py -q`
- `uv run pytest tests/host/test_connect.py::test_silent_connect_streak_escalates_and_slows_reconnects tests/host/test_connect.py::test_inbound_frame_resets_silent_connect_streak -q`
- `uv run ruff check` on all changed Python and test files.
- `uv run pyrefly check omnigent/host/connect.py omnigent/host/frames.py omnigent/server/routes/host_tunnel.py omnigent/cli.py`

## Demo

N/A — backend/CLI reliability change with no visual UI.

## Type of change

- [x] Bug fix
- [ ] Feature
- [ ] UI / frontend change
- [ ] Refactor / chore
- [ ] Docs
- [ ] Test / CI
- [ ] Breaking change

## Test coverage

- [x] Unit tests added / updated
- [x] Integration tests added / updated
- [ ] E2E tests added / updated
- [ ] Manual verification completed
- [ ] Existing tests cover this change
- [ ] Not applicable

## Coverage notes

Automated coverage exercises capability exceptions and timeouts, server error propagation, background registration checks, retryability, and silent reconnect backoff.

## Changelog

`omnigent host` now stays connected when optional harness detection fails and surfaces server-side tunnel setup errors.

Signed-off-by: Zeyi (Rice) Fan <zeyi.f@databricks.com>
2026-08-18 17:18:32 -07:00
Tomu Hirata adcf83ccb6 feat(pi): Add searchable model picker for new sessions with Databricks Unity AI Gateway OAuth (#4961)
* feat(pi): add searchable start model picker

Signed-off-by: Anthony Ivan <anthony.ivan@databricks.com>

* fix(pi): harden model picker compatibility

Signed-off-by: Anthony Ivan <anthony.ivan@databricks.com>

* refactor(pi): simplify model picker filtering

Signed-off-by: Anthony Ivan <anthony.ivan@databricks.com>

---------

Signed-off-by: Anthony Ivan <anthony.ivan@databricks.com>
Signed-off-by: Tomu Hirata <tomu.hirata@gmail.com>
Co-authored-by: Anthony Ivan <anthony.ivan@databricks.com>
2026-08-19 00:02:35 +00:00
Mark Tai 70ee54bdba feat(cli): gate naked omni invocations behind a wrapper guard (#4766)
* feat(cli): gate naked omni invocations behind a wrapper guard

Operators who front the CLI with a wrapper (e.g. `isaac omni`) can set OMNIGENT_REQUIRE_WRAPPER to refuse direct `omni`/`omnigent` calls. The wrapper sets OMNIGENT_WRAPPER_BYPASS around its own invocation to pass through, and OMNIGENT_WRAPPER_COMMAND names the command to suggest in the block message. The guard runs at the top of main() before any work, and is covered by unit tests on the message logic plus subprocess e2e tests for the block and bypass paths.

Co-authored-by: Isaac
Signed-off-by: Mark Tai <mark.tai@databricks.com>

* style(cli): drop stray blank line left by the main merge

Co-authored-by: Isaac
Signed-off-by: Mark Tai <mark.tai@databricks.com>

---------

Signed-off-by: Mark Tai <mark.tai@databricks.com>
Co-authored-by: Mark Tai <mark.tai@databricks.com>
2026-08-18 15:38:07 -07:00
Dhruv Gupta 036e0b9d29 fix(electron): trigger sign-in from "Run on this machine" instead of looping on "No hosts" (#4972)
Clicking "Run on this machine" looped back to a "No hosts" error whenever
the desktop's stored Databricks OAuth grant had expired, forcing the user
to run `omni` in a terminal to complete the browser sign-in.

Root cause: serverAuthed() treated any Databricks pointer record as
authed without checking token freshness, so ensureServerAuth skipped
`omnigent login`. The spawned `omnigent host` (no TTY) then hit the
non-interactive auth guard and exited pre-connect, and connectThisMachine
returned silently — stranding the user on "No hosts".

Fix (contained to the desktop shell + web UI; no shared CLI change):

- ensureServerAuth now decides "auth needed?" with a GET /v1/me probe
  (probeServerAuth) — the same signal the CLI's own pre-flight trusts —
  instead of the stale on-disk token file. When not authed it runs the
  idempotent `omnigent login`, which silently refreshes a live grant with
  no browser and only opens the browser for a genuine re-auth.
- Spawn `omnigent host --non-interactive` so any residual auth gap fails
  loudly with a classifiable authError rather than hanging on a missing
  TTY. (No Python change — the flag already exists.)
- Surface the failure in the New Chat dialog with a "Try again"
  affordance instead of returning silently; auth failures get
  sign-in-flavored copy. Threads authError through the host-control IPC
  result and HostActionResult.
- Raise the login timeout 180s -> 305s so a human completing the browser
  sign-in isn't SIGKILLed mid-flow (the CLI's own OIDC deadline governs).

Tests: probeServerAuth (status/redirect/token branches), ensureServerAuth
(loopback/authed/unreachable/login-success/login-failure), and the New
Chat dialog's error surfacing + retry.

Co-authored-by: Isaac

Signed-off-by: Dhruv Gupta <dhruv.gupta@databricks.com>
2026-08-18 12:03:36 -07:00
Dhruv Gupta fb579783ce fix(web): make the Working… status pin opaque in dark mode (#4962)
The pinned "Working…/Tinkering…" tab (WorkingStatusPin) used `bg-card`,
which in dark mode is a translucent glass surface: `--card` is
rgba(31, 39, 45, 0.6) and the global `.dark .bg-card` rule adds a
backdrop-blur. Over the transcript the tab read as a see-through frosted
pill floating above the composer — most visible on mobile.

Switch the tab to `bg-card-solid`, the opaque `--card` variant the
composer itself uses in dark mode. This makes it opaque and, by not
matching the `.dark .bg-card` glass rule, lets its `border-b-0` actually
merge flush into the composer instead of the glass rule re-adding a
bottom edge. Light mode is unchanged (`--card` and `--card-solid` are
both #fff).

Co-authored-by: Isaac

Signed-off-by: Dhruv Gupta <dhruv.gupta@databricks.com>
2026-08-18 11:23:22 -07:00
Yuan Tang fabdc7d25b feat(deploy): add ArgoCD overlay for kubernetes sandbox provider (#4788)
* feat(deploy): add ArgoCD overlay for kubernetes sandbox provider

Add a Kustomize overlay that layers sync-wave annotations onto the
sandbox-runners overlay so ArgoCD deploys resources in dependency order
(namespaces → RBAC → config → Deployment). Includes a sample Application
CR and documentation for quick-start, out-of-band credential management,
and multi-environment setups via ApplicationSet.

Signed-off-by: Yuan Tang <terrytangyuan@gmail.com>

* fix(deploy): address ArgoCD overlay review feedback

- Remove over-engineered sync waves; ArgoCD's built-in kind ordering
  already sequences Namespace → SA → Role → ConfigMap → Deployment.
  Waves added health gates that caused PVC deadlock (WaitForFirstConsumer
  blocks until a consumer Pod is scheduled) and Ingress stall (no
  controller → Progressing forever).
- Switch from 13 name-pinned strategic merge patches (which fail silently
  into wave 0 on a rename) to 3 kind-regex JSON patches (31 lines vs 151).
- Add Prune=false on Namespaces and PVC to prevent accidental cascade on
  Application deletion or stale targetRevision.
- Add ignoreDifferences for omnigent-secrets (selfHeal was reverting
  operator credentials to the checked-in placeholder) and PVC storage
  (API server mutations cause perpetual SyncFailed).
- Fix syncOptions: remove inert CreateNamespace=true (destination.namespace
  is unset), correct RespectIgnoreDifferences comment to reference the
  actual ignoreDifferences block.
- Restructure README quick start around fork-and-push (local edits have no
  effect when ArgoCD reads from Git), add namespace wait between Application
  apply and Secret creation, document auth prerequisite (accounts provider
  403s on managed runner dial-back), fix "delete Ingress" advice to use
  $patch: delete instead of removing base/ingress.yaml (which breaks all
  overlays), fix postgres composition advice (direct resource causes
  duplicate-base error), document deletion cascade and selfHeal behavior.

Signed-off-by: Yuan Tang <terrytangyuan@gmail.com>

---------

Signed-off-by: Yuan Tang <terrytangyuan@gmail.com>
2026-08-18 10:59:21 -07:00
Hubert ddfa872809 OMNI-3743: Add session name and project information to chat title bar, fix sizing (#4940)
* OMNI-3743: Add session name and project information to chat title bar

Signed-off-by: Hubert Zub <hubert.zub@gmail.com>

* Improvement

Signed-off-by: Hubert Zub <hubert.zub@gmail.com>

* Another fix

Signed-off-by: Hubert Zub <hubert.zub@gmail.com>

* Native app fixes

Signed-off-by: Hubert Zub <hubert.zub@gmail.com>

* test(e2e-ui): regenerate visual baselines

* test fixes

Signed-off-by: Hubert Zub <hubert.zub@gmail.com>

* Post-review fixes

Signed-off-by: Hubert Zub <hubert.zub@gmail.com>

* restore native back

Signed-off-by: Hubert Zub <hubert.zub@gmail.com>

---------

Signed-off-by: Hubert Zub <hubert.zub@gmail.com>
Co-authored-by: omnigent-ci[bot] <294685417+omnigent-ci[bot]@users.noreply.github.com>
2026-08-18 17:43:18 +00:00
Anton Nekipelov 21f71ddfdd fix(runner): retry tunnel 401/403 on an already-connected runner (#4957)
#3943 replaced the unconditionally-fatal 403 with a retry streak, which
is strictly better than exiting on the first rejection but has no
ever_connected condition — so a runner that already completed an upgrade
still dies once three rejections land consecutively. Because delay_s is
reset to the base delay on every rejection, those three attempts land
within a few seconds, so a brief connectivity blip is enough to exhaust
the streak: healthy tunnel to dead process in 8 seconds.

Dropping off a VPN reproduces it — an intermediary answers the WS upgrade
with 403 before the request reaches the server. The same runner survives
or dies depending purely on whether the token refresh wins the race
against the streak, and the error text tells the user to re-authenticate
when the credentials were valid the whole time. The exit takes down every
conversation on the runner, not just the active one, and being ungraceful
it leaks detached terminal tmux servers until a later runner's
reap_orphaned_terminals() sweep.

The host tunnel already got this treatment in #4025: a tunnel that
completed an upgrade proved its credentials, so a later 401/403 is a
network-path artifact and retries indefinitely rather than forcing a
manual restart. The runner path was one surface behind; this applies the
same posture:

- The fatal streak now applies only before the first successful upgrade.
  A never-connected runner still fails loud after three rejections, so
  a genuinely-forbidden runner does not busy-reconnect forever.
- An already-connected runner keeps the escalating backoff instead of
  resetting to the base delay, so a sustained outage retries at the 10 s
  cap rather than hammering the rejecting proxy every ~0.5 s.
- The retry logs at WARNING and names VPN/network as the likely cause,
  so a genuinely revoked credential is not silent to an operator.

Token invalidation still runs on every rejection, so a plain mid-session
expiry recovers on the next attempt as before.

Continues #3516, which identified this fix before #3943 landed and went
stale against it. That PR's ever_connected guard is reapplied here on
top of #3943's streak structure, and its host-bootstrap-bearer test is
carried over; the rest of its diff was superseded upstream.

Tests: an already-connected runner survives a rejection streak well past
the fatal bound and escalates 0.5→10 s; a 403-rejected host bootstrap
bearer is swapped for the runner's own refreshable token. The existing
never-connected fatal tests are unchanged and still pass.



Co-authored-by: Isaac

Signed-off-by: Anton Nekipelov <226657+anton-107@users.noreply.github.com>
Co-authored-by: Bryan Qiu <bryan.qiu@databricks.com>
2026-08-18 17:34:46 +00:00
Dhruv Gupta 1fc4b283b8 test(host): close the cancel race in the midspawn leak test (#4971)
test_launch_cancelled_midspawn_does_not_leak_untracked_runner signals
spawn_started after Popen returns, then cancels the launch task. On a
loaded machine the event loop is descheduled in that gap, _handle_launch
runs to completion, and the cancel arrives after the window it is meant
to exercise, so the test fails with "DID NOT RAISE CancelledError"
instead of catching a leak.

Hold the spawn thread inside the shielded call until the test has issued
its cancel, so the cancel lands in the leak window regardless of
scheduling. The assertions are unchanged, and the test still exercises
the real post-spawn/pre-register window it was written for.

Reproduced by inserting a 0.2s sleep between the spawn signal and the
cancel, which fails identically to CI; with this change the same
insertion passes.

Signed-off-by: Dhruv Gupta <dhruv0811@gmail.com>
Co-authored-by: Isaac <no-reply@databricks.com>
2026-08-18 10:34:33 -07:00
Edwin He 83b7ff409f fix(web): back off silent sticky-apply PATCHes when the backend errors (#4777)
* fix(web): back off silent sticky-apply PATCHes when the backend errors

The sticky model/effort applies in bindStream and
refetchRunnerBackedSessionState fire on every bind/switch while the
session's server-side override is still null. When the backend is
erroring the PATCH never persists, so the null-override guard never
closes and the applies re-fire on every rebind. During an outage that
becomes a self-sustaining PATCH storm with no backpressure: the failures
are swallowed (fire-and-forget .catch), so nothing slows down.

Add a failure-scoped, auto-clearing client backoff. A backend-unhealthy
failure (5xx / network / timeout) pauses the silent applies for a
cooldown; a 404 parks that gone session; the next success clears the
cooldown so stickiness resumes the moment the backend recovers. A
successful send-path bind also clears it, and it feeds a failing bind
into the same backoff. Normal operation is unchanged — the PATCH
succeeds on the first try and nothing ever arms.

Refs OMNI-2513.

Co-authored-by: Isaac
Signed-off-by: Edwin He <41037314+Edwinhe03@users.noreply.github.com>

* fix(web): let a 404-parked sticky-apply recover on the next successful bind

The silent sticky-apply parks a session on a 404 so its failure doesn't
pause the others. But nothing lifted that park except a page reload: the
sticky applies that would clear it are themselves gated by the park, so a
parked session could never re-apply.

A sticky PATCH only runs after a successful snapshot GET, so a 404 there is
a transient mid-bind race rather than a durable "gone". Lift the park when
bindStream's snapshot GET next succeeds (proof the session exists); if it is
genuinely gone that GET 404s and bindStream bails before any PATCH, so there
is no storm either way. Also treat 410 Gone like 404.

Refs OMNI-2513.

Co-authored-by: Isaac
Signed-off-by: Edwin He <41037314+Edwinhe03@users.noreply.github.com>

* fix(web): treat a sticky-apply 404 as a transient backend failure

A 404 on the silent sticky-apply PATCH does not mean the session is gone:
here it means the permission check didn't succeed (a flaky permission
service), which is backend-wide and transient — the same root cause as the
5xx errors seen in the same outage. So a 404 must pause every session's
applies via the global cooldown, exactly like a 5xx, rather than parking
the one session that happened to 404.

Collapse the per-session gone-set into the single global cooldown: every
failure (4xx incl. 404, 5xx, network) arms it; the next success clears it.
This removes the recovery machinery the per-session park needed — the
cooldown is inherently self-clearing — and suppresses more of the storm
during a real outage (the first failure pauses all sessions instead of
letting each fire once before parking).

Refs OMNI-2513.

Co-authored-by: Isaac
Signed-off-by: Edwin He <41037314+Edwinhe03@users.noreply.github.com>

* fix(web): reopen the sticky-apply cooldown by time, not on a success

During the outage ~90% of requests failed, so ~10% still succeeded. With
the cooldown clearing on any success, each of those lucky successes would
reopen the gate and let the next (still-likely-failing) sticky apply fire —
a flap that leaks a fresh apply on every success rather than holding.

Arm the cooldown on failure only and reopen it purely by elapsed time; a
success no longer clears it, so the successful fraction mid-outage can't
flap the gate. This also drops the send-path from the cooldown entirely
(it fails loudly on its own) and removes the success bookkeeping. Recovery
is the window elapsing (≤30s), which is fine for a cosmetic sticky apply.

Refs OMNI-2513.

Co-authored-by: Isaac
Signed-off-by: Edwin He <41037314+Edwinhe03@users.noreply.github.com>

* fix(web): keep the /model readout honest while sticky-apply is cooling down

The sticky-model apply is skipped during the cooldown, but the readout
still computed effectiveSessionOverride from the sticky model, so the
/model picker briefly claimed an override the server never persisted —
the inverse of the honesty this change is about.

Fold the cooldown check into willApplyStickyModel so the readout and the
PATCH decision share one condition: while blocked, we neither apply nor
claim the override, and effectiveSessionOverride stays null to match the
un-persisted server truth.

Refs OMNI-2513.

Co-authored-by: Isaac
Signed-off-by: Edwin He <41037314+Edwinhe03@users.noreply.github.com>

---------

Signed-off-by: Edwin He <41037314+Edwinhe03@users.noreply.github.com>
2026-08-18 10:16:26 -07:00
Yuan Tang a447db22cb feat(k8s): replace bare Pods with Jobs for automatic failover (#4744)
* feat(k8s): replace bare Pods with Jobs for automatic failover

The Kubernetes sandbox launcher previously created bare Pods with
restartPolicy: Never. A crashed host container was a dead end until
a human retried. This change wraps the Pod template in a batch/v1 Job
with restartPolicy: OnFailure and a configurable backoffLimit (default 3),
so the kubelet automatically restarts a crashed host container with
exponential backoff — providing automatic failover without a custom
scheduler or work queue.

Key changes:
- build_pod_manifest() → build_job_manifest(): wraps the Pod spec in a
  Job with backoffLimit, activeDeadlineSeconds, and a liveness probe
  (pgrep -f "omnigent host") to detect stuck processes.
- KubernetesSandboxLauncher now uses BatchV1Api alongside CoreV1Api.
- start_host() creates a Job; _wait_for_pod_running() discovers the
  Job's child Pod via the job-name label selector.
- terminate() deletes the Job with propagationPolicy: Foreground,
  cascading to its child Pods.
- RBAC Role updated: added batch/v1 Jobs (create/get/delete), changed
  Pods from create/get/delete to list/get (Pod lifecycle is now managed
  by the Job controller).

The host's existing WebSocket reconnect logic re-registers the tunnel
automatically after a container restart, and the runner's durable
conversation checkpointing recovers incomplete turns on session re-init.

Signed-off-by: Yuan Tang <terrytangyuan@gmail.com>

* fix: ruff format + unused variable lint

Signed-off-by: Yuan Tang <terrytangyuan@gmail.com>

* fix(k8s): address reviewer feedback on Job migration

- RBAC: retain pods create/delete for one-release upgrade overlap window
- Drop ineffective liveness probe (pgrep matches reaper's own argv)
- Add bare-Pod delete fallback in terminate/best-effort for pre-migration
  sandboxes (Job 404 → try deleting the old bare Pod)
- Restore dropped inline comments explaining security decisions

Signed-off-by: Yuan Tang <terrytangyuan@gmail.com>

* fix(k8s): address reviewer blocking feedback on Job migration

1. **Stale Pod references**: update module docstring, `_new_pod_name`,
   `provision`, role.yaml header to reflect Job model. Rename
   `_POD_DELETE_*` → `_DELETE_*`. Add version to TODO(v0.29).

2. **`_terminal_failure` reworked for OnFailure**: init container non-zero
   exit is no longer terminal unless Pod phase is `Failed` (backoffLimit
   exhausted). CrashLoopBackOff on the host container is detected even
   though the Pod stays in phase `Running`. `_wait_for_pod_running` now
   checks `_terminal_failure` BEFORE accepting `Running`.

3. **terminate no longer leaks Secrets**: each delete is independently
   try/caught so a 403 on Job delete still cleans up the Secret. The
   first error is re-raised after all deletes run.

4. **Child-Pod discovery hardened**: `_find_job_pod` re-raises 401/403
   (surfaces RBAC immediately), filters out Pods with deletionTimestamp,
   prefers Running phase. `_wait_for_pod_running` re-discovers on 404
   instead of treating it as terminal (supports Pod replacement under
   eviction/drain). 403 hint updated to include `jobs`.

5. **backoffLimit raised to 6**: comment clarifies it is a lifetime
   budget shared with init containers; 6 leaves headroom for init
   retries while still surfacing persistent crashes.

68 tests (62 updated + 6 new).

Signed-off-by: Yuan Tang <terrytangyuan@gmail.com>

---------

Signed-off-by: Yuan Tang <terrytangyuan@gmail.com>
2026-08-18 16:09:12 +00:00
Dhruv Gupta ce00d35f0d fix(harnesses): let a configured acp: agent win over a same-slug builtin row (#4927)
A configured agent named "Devin" slugifies onto `devin`, which is also an
`ACP_CLI_HARNESSES` row id, so both sources describe the same harness by the
same name. They failed in opposite directions:

- the web picker showed one row, silently the builtin — both seed the same
  `builtin_agent_id`, and the row seeded second overwrote the user's entry,
  dropping the `--model` their command carried;
- `omni setup` showed two identically labeled "Devin" rows, one per source.

The configured agent wins in both: it names the exact command, which a row's
fixed argv cannot express. `shadowed_builtin_acp_rows` states the rule once and
both surfaces read it, matching row ids only — an alias-shaped name ("Grok
Build" -> `grok-build`) is a separate harness id and does not shadow `grok`.

Listing only. `--harness devin` and `harness: devin` specs still resolve to the
row, and removing the config entry brings the row straight back.

Co-authored-by: Isaac

Signed-off-by: Dhruv Gupta <dhruv.gupta@databricks.com>
2026-08-18 08:41:56 -07:00
Jackson Zheng 8aaf72c91a feat(web): restyle chat error banner as a centered pill (#4931) 2026-08-18 07:33:23 +00:00
Edwin He 65021dc1e8 fix(databricks): resolve harness launch models from the workspace (#4915)
* fix(databricks): resolve harness launch models from the workspace

The Databricks AI Gateway has retired the legacy `databricks-*` model
namespace (`501 NOT_IMPLEMENTED ... Use Unity Catalog model services (v3)`).
Several managed harnesses take their launch model from the bundled MLflow
provider catalog, whose Databricks ids carry exactly that retired spelling,
so every gateway turn fails. `claude-native` was migrated to live Unity
Catalog discovery in July; its siblings were left behind.

- codex-native: `_resolve_databricks_codex_model` resolves through the live
  UC model-services listing (ids are `system.ai.` by construction), then
  ucode's cached copy, then the bundled catalog as a documented last resort.
  An explicit legacy `model_override` is matched against the servable ids on
  the bare id, so it recovers instead of failing forever; a model the
  workspace does not serve passes through untouched.
- claude-sdk (Polly, Debby): resolve the launch model from the live listing
  using the family precedence claude-native itself falls back to. And on a
  real Databricks AI Gateway, negotiate betas (`CLAUDE_CODE_USE_GATEWAY`)
  instead of setting `CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS`, which made
  Claude Code strip `interleaved-thinking` and the gateway reject the blocks
  with `400 ... Expected 'thinking'`. Unset an inherited disable flag around
  the spawn, scoped to gateway launches; a non-Databricks/mock gateway keeps
  the original workaround.
- pi-native: resolve the launch model from the live listing.
- model_catalog.fetch_databricks_model_service_entries: scope the UC listing
  to `schemas/system.ai` and paginate. Unscoped and unpaged it walked the
  whole metastore and returned one page of whatever schemas sorted first, so
  a workspace serving 53 models reported 2 and zero Claude entries. A repeated
  page token returns the pages collected so far (a partial `system.ai` list
  still launches) rather than raising, since callers treat an exception as
  "no listing" and fall back to the retired `databricks-` catalog.

Signed-off-by: Edwin He <41037314+Edwinhe03@users.noreply.github.com>

* test(databricks): keep codex build test offline

build_codex_native_server now resolves the launch model through live
Unity Catalog discovery, so a build with a profile makes a real
model-services call. test_build_codex_native_server_uses_profile_host_without_static_token
passed only on a machine with ambient Databricks credentials and crashed
the CI worker on the network call. Stub discovery offline; the test
asserts the profile-host base URL + auth command, not model resolution.

Signed-off-by: Edwin He <41037314+Edwinhe03@users.noreply.github.com>

---------

Signed-off-by: Edwin He <41037314+Edwinhe03@users.noreply.github.com>
2026-08-18 04:32:05 +00:00
Dhruv Gupta 6d277c4fc0 docs(acp): correct the env-var claim on builtin ACP CLI rows (#4925)
The Devin row's comment and auth hint both implied an environment variable can
configure or authenticate the agent (`DEVIN_MODEL`, "or set a Devin API key").
It cannot: the generic ACP spawn env is deny-by-default with no allowed prefixes,
and a catalog row has no `env_passthrough` of its own — only a user-configured
`acp:<slug>` agent can declare one. Verified against the real builder:

    builtin row                      -> DEVIN_MODEL forwarded: False
    acp: agent declaring it          -> DEVIN_MODEL forwarded: True

Devin is unaffected in practice because `devin auth login` writes a credential
file it reads back at spawn, so state the file-based path instead and point a
per-model setup at an `acp:<slug>` agent carrying `--model`.

Also record the constraint once in the module docstring, since it decides whether
a future vendor can be a row at all: env-var-only vendors need a user-configured
agent, disk-credential vendors work as rows.

Co-authored-by: Isaac

Signed-off-by: Dhruv Gupta <dhruv.gupta@databricks.com>
2026-08-17 17:36:01 -07:00
Dhruv Gupta 5de71a1877 feat(acp): Devin as a builtin harness + catalog-derived picker identity (#4920)
* feat(harness): add Devin as a builtin ACP CLI harness

Devin (Cognition's `devin` CLI) speaks ACP on stdio via `devin acp`, so it is
one catalog row — like Grok Build. This makes Devin a first-class harness: it
shows in `omni setup` (own auth, `devin auth login`), launches via
`--harness devin`, and — with this PR's picker seeding — seeds into the web New
Chat picker once the `devin` binary is on PATH, with no user `acp:` config
needed. It runs Devin's account-default model; set DEVIN_MODEL to pin one.

The setup overview now has two builtin ACP CLI rows (Devin, then Grok Build,
sorted by id), shifting the numbered rows below; the scripted-stdin ordering /
dispatch / openclaw tests are updated. Per-row catalog wiring is auto-covered by
the parametrized tests in test_acp_cli_harnesses.py.

Co-authored-by: Isaac
Signed-off-by: Dhruv Gupta <dhruv.gupta@databricks.com>

* fix(web): group the builtin `devin` harness under Harnesses, not Agents

This PR adds `devin` to the backend ACP CLI catalog, so a seeded Devin
agent carries `harness: "devin"` (a bare builtin id, not `acp:devin`).
The picker's harness/agent split calls isAcpHarnessAgent, which matches
`acp:*` or an id in ACP_CLI_HARNESS_IDS — a frontend mirror of
ACP_CLI_HARNESSES that still listed only `grok`. So the builtin Devin
fell into the "Agents" group instead of "Harnesses ▸ More".

Add `devin` to ACP_CLI_HARNESS_IDS so it groups with the harnesses,
beside Grok / OpenCode / Cursor, and extend the test.

Co-authored-by: Isaac
Signed-off-by: Dhruv Gupta <dhruv.gupta@databricks.com>

* feat(web): derive ACP harness identity from the server catalog, not a frontend list

Adding a builtin ACP harness took a frontend edit: the picker recognized ACP
agents via a hardcoded id set mirroring ACP_CLI_HARNESSES, and rendered their
name by capitalizing the agent slug. So a new row landed under "Agents" instead
of "Harnesses" until someone remembered the mirror, and even a known row showed
the wrong name — Grok Build as "Grok", a user's "My Devin Agent" as
"My-devin-agent".

Both facts already exist server-side and the frontend already fetches them: the
harness catalog reports `capabilities.integration_mode == "acp-subprocess"` for
builtin ACP rows AND user-configured `acp:<slug>` agents, plus a `label` (the
vendor's for a builtin, the user's own for a configured agent). The catalog
fetch just dropped both.

Read them: useAvailableAgents stamps `acpHarness` and the catalog label onto
each agent, isAcpHarnessAgent prefers that flag, and the id set stays only as a
fallback for servers that don't report capabilities. A new builtin ACP harness
is now one row in acp_cli_harnesses.py — the picker groups and names it with no
frontend change, which is what this PR's Devin row should have needed.

The catalog read is gated on the picker's own `enabled` so a disabled picker
still issues no request, and the label is applied only to ACP-family harnesses,
so a composed agent keeps its own name (Polly stays "Polly", not "Claude SDK").

Co-authored-by: Isaac
Signed-off-by: Dhruv Gupta <dhruv.gupta@databricks.com>

---------

Signed-off-by: Dhruv Gupta <dhruv.gupta@databricks.com>
2026-08-17 17:17:29 -07:00
Dhruv Gupta fcf5a902af feat(server): seed configured ACP agents into the New Chat picker (#4909)
* feat(server): seed configured ACP agents into the New Chat picker

The web New Chat picker lists AGENTS from GET /v1/agents, and native
harnesses appear only because _ensure_default_native_agents seeds a
<harness>-ui agent for each. Nothing seeded ACP agents, so a configured
acp:<slug> agent (Devin, ...) or an installed builtin ACP CLI harness
(grok) never showed in the picker on its own — the ACP sibling of the
`omni setup` discovery gap.

Seed a picker built-in per ACP harness set up on the server's host: one
per user-configured acp:<slug> agent (in config == set up, matching
harness_is_configured), and one per builtin ACP CLI harness whose binary
is on PATH. On a host with no ACP setup (the common remote-server case)
this seeds nothing.

Two things the naive version got wrong, fixed here:

- Name, not label. Agent names must be [a-zA-Z0-9_-]+, so a display label
  like "Grok Build" / "Gemini CLI" fails spec validation at load ("agent
  name ... must match ..."). Seed by the slug (agent.slug / the catalog
  id); the web picker capitalizes it for display (devin -> "Devin").
- Grouping. GET /v1/agents already returns a `builtin` flag
  (session-scope-NULL + deterministic id), but partitionAgentsByKind
  grouped by a hardcoded name allowlist, so dynamically-seeded ACP agents
  fell under "Custom agents". Group by the `builtin` flag, falling back to
  the allowlist only for older servers — so seeded ACP agents sit with the
  harnesses.

Purely additive: only adds picker rows, never touches native seeding; a
malformed acp: block is logged and skipped, never fatal to startup.

Verified against a real machine config (Devin + kilocode + grok all seed)
and with the web unit test for partitionAgentsByKind.

Co-authored-by: Isaac
Signed-off-by: Dhruv Gupta <dhruv.gupta@databricks.com>

* fix(web): group generic-ACP harness agents under "Harnesses", not "Agents"

The New Chat picker builds its "Harnesses" section from
agentList.filter(isNativeCodingAgent), so the ACP agents this PR seeds (Grok,
and configured acp:<slug> agents like Devin / Kilocode) fell through to the
"Agents" group beside Polly / Debby instead of sitting with the native CLIs.

Add isAcpHarnessAgent (harness `acp:*`, or a builtin ACP CLI id like `grok`)
and widen the picker's harness/agent split to include it, so these
harness-backed picks fold into "Harnesses > More" next to OpenCode / Cursor.

Grouping-only: selection is unchanged (both sections render through the same
renderEntry, whose onSelect launches by agent id), and ACP entries show no
readiness badge (they are not not-ready host entries). Composed built-ins
(Polly / Debby) still stay under "Agents".

Co-authored-by: Isaac
Signed-off-by: Dhruv Gupta <dhruv.gupta@databricks.com>

---------

Signed-off-by: Dhruv Gupta <dhruv.gupta@databricks.com>
2026-08-17 21:11:36 +00:00
Hubert ba3692130d [OMNI-2843 fix] Fix overlaying toolbar icons (#4897)
Signed-off-by: Hubert Zub <hubert.zub@gmail.com>
2026-08-17 14:13:06 +02:00
Hubert 57df36a1ee feat(web): show background tasks as a composer pill, not the working shimmer (#4893)
* feat(web): show background tasks as a composer pill, not the working shimmer

Once a turn ends but background shells/sub-agents outlive it, the "Working…"
shimmer misreads as the agent still thinking. Route that state to a dedicated
BackgroundTaskPill above the composer instead: a shared isBackgroundTasksOnly
predicate gates both shimmer surfaces off and the pill on. A parked dialog
(blockedOn) still wins the shimmer, since it needs an action.

Co-authored-by: Isaac
Signed-off-by: Hubert Zub <hubert.zub@gmail.com>

* e2e tests

Signed-off-by: Hubert Zub <hubert.zub@gmail.com>

---------

Signed-off-by: Hubert Zub <hubert.zub@gmail.com>
2026-08-17 11:55:02 +02:00
Tomu Hirata fc0e2e99c6 perf: eliminate redundant DB queries in session create and host wake paths (#4809)
- Replace 4x set_labels + get_conversation pairs in _create_session_from_existing_agent
  with in-memory conv.labels.update() — saves 4 round-trips per session creation
- _record_create_route_prompt: apply label in-memory instead of refetching the row
- _stamp_routing_decision_label caller: apply ROUTING_DECISION_LABEL_KEY in-memory
- _maybe_relaunch_managed_sandbox: replace host_store.is_online() (which calls get_host
  internally) with host_is_live(host) using the already-fetched host object
- _maybe_wake_stale_resumable_managed_sandbox: same host_is_live fix
- Update test_concurrent_relaunch_messages_kick_a_single_launch to give its dead_host
  SimpleNamespace the status/updated_at fields that host_is_live reads

Each query is slower on managed infra, so removing these redundant reads reduces
per-request latency on the hot session-creation and message-dispatch paths.

Closes OMNI-3243

Signed-off-by: Tomu Hirata <tomu.hirata@gmail.com>
2026-08-17 01:36:13 +00:00
Tomu Hirata c2439c6fd7 fix(windows): pass Windows process essentials through harness env filters (#4886)
On native Windows, `agent_env.BASE_ALLOW_EXACT` (the shared deny-by-default
env filter used by all harness executors) did not include SYSTEMROOT, COMSPEC,
USERPROFILE, or the other Windows-mandatory constants. Any harness CLI spawned
via `clean_agent_env` (codex, pi, claude-sdk, antigravity, …) died instantly
on spawn because Winsock/crypto cannot initialise without SYSTEMROOT — the
subprocess exited before reading stdin, causing the executor to await a
JSON-RPC response that never arrived and silently idle to the 600s watchdog.

The constant set already existed as `WINDOWS_ENV_PASSTHROUGH` in `_platform.py`
and was already wired into `os_env._DEFAULT_ENV_PASSTHROUGH` and
`connect._RUNNER_ENV_ALLOWLIST`. This commit adds it to `BASE_ALLOW_EXACT` so
every harness executor inherits it automatically, matching the pattern used
elsewhere.

Also fixes three related Windows issues surfaced in omnigent-ai/omnigent#4851:

- `PYTHONUTF8` was not forwarded through `_RUNNER_ENV_ALLOWLIST`, so the host
  daemon / runner subprocess printed Unicode status chars (✓ ↑) on the Windows
  ANSI code page (cp1252), raising `UnicodeEncodeError` and killing the host
  tunnel in an infinite reconnect loop.

- `_session_create_validation.validate_existing_host_workspace` and
  `_workspace_validation.validate_workspace` required `workspace.startswith("/")`,
  rejecting every Windows drive-letter path (C:\…) from a connected Windows host.
  Windows absolute paths matching `^[A-Za-z]:[/\\]` are now accepted.

- `harness_install._harness_cli_version_satisfies` returned `False` on
  `packaging.version.InvalidVersion`, so pre-release versions like
  `0.146.0-alpha.9.2` (newer than the declared floor) were reported as
  too-old and the harness was refused at the version gate. The fix extracts
  the leading X.Y.Z segment as a fallback for non-PEP-440 strings.

Signed-off-by: Tomu Hirata <tomu.hirata@gmail.com>
2026-08-17 01:23:12 +00:00
aminekaabachi 901aa8d12a feat(web): white-label UI branding via config.yaml (#2857) 2026-08-15 15:35:11 -07:00
Tomu Hirata 2aba5079d4 feat(bench): add CLI startup latency benchmark (#4793)
* feat(bench): add CLI startup latency benchmark

Measures wall-clock time from omnigent claude --server invocation to the
Claude terminal being ready (signalled by 'Claude terminal ready.' spinner
message, emitted just before tmux attach).

Unlike the HTTP/API benchmarks in run.py, this drives the real CLI binary
end-to-end against a remote server — auth, daemon tunnel, session create,
runner launch, terminal boot — via pexpect.

Usage:
  uv run --no-sync dev/benchmarks/omnigent/cli_startup.py
  uv run --no-sync dev/benchmarks/omnigent/cli_startup.py --also-isaac-omni --runs 10
  uv run --no-sync dev/benchmarks/omnigent/cli_startup.py --output startup.json
  uv run --no-sync dev/benchmarks/omnigent/cli_startup.py --max-p50-ms 12000

JSON output is compatible with the existing benchmark schema.

Signed-off-by: Tomu Hirata <tomu.hirata@gmail.com>

* ci(bench): add cli-startup job to benchmark workflow

Adds a new 'CLI startup latency' job that runs cli_startup.py against
the ai-devtools managed workspace (OMNIGENT_REMOTE_AUTH_TOKEN secret).

- Runs on nightly schedule (when secret is configured) and on
  workflow_dispatch with cli_startup_runs input (default 5, 0 = skip)
- Skips gracefully when OMNIGENT_REMOTE_AUTH_TOKEN secret is absent
- Uploads benchmark-results-cli-startup-{run_id}.json as an artifact
  for the Databricks trend dashboard (same schema as the HTTP benchmarks)
- Renders a job summary table via report_markdown.py

Signed-off-by: Tomu Hirata <tomu.hirata@gmail.com>

* feat(bench): align cli_startup with existing journey schema

- Use RunResult/aggregate/print_results/check_thresholds/build_report
  from the existing framework instead of custom stats/output code
- Each run is now a RunResult with all latency samples (matching the
  HTTP/API journey shape), not one run-per-sample
- Journey names are cli_startup and isaac_omni (snake_case, no spaces)
- Output table uses the same renderer as run.py
- Add cli_startup_runs dispatch input and cli-startup job to benchmark.yml

Signed-off-by: Tomu Hirata <tomu.hirata@gmail.com>

* refactor(bench): move cli_startup into journeys.py; use local bench server

The cli_startup journey now lives in journeys.py alongside the other
journeys, using env.base_url (the local bench server) instead of a
remote Databricks URL. This aligns it with the existing pattern:
needs_host=True boots the host daemon, and omnigent claude --server
<local-url> connects to it for the full startup sequence.

cli_startup.py becomes a thin shim that calls run.py --journeys cli_startup.

benchmark.yml cli-startup job now uses run.py directly — no
OMNIGENT_REMOTE_AUTH_TOKEN secret needed, just pexpect + claude CLI.

Signed-off-by: Tomu Hirata <tomu.hirata@gmail.com>

* ci(bench): fold claude CLI install into Install dependencies step

Signed-off-by: Tomu Hirata <tomu.hirata@gmail.com>

* ci(bench): merge cli_startup into existing benchmark job (sqlite leg only)

Signed-off-by: Tomu Hirata <tomu.hirata@gmail.com>

* remove cli_startup.py shim — use run.py --journeys cli_startup directly

Signed-off-by: Tomu Hirata <tomu.hirata@gmail.com>

* ci(bench): run cli_startup on all matrix backends

Signed-off-by: Tomu Hirata <tomu.hirata@gmail.com>

* ci(bench): install pexpect+claude before Run benchmark so cli_startup does not skip

Signed-off-by: Tomu Hirata <tomu.hirata@gmail.com>

* fix(bench): fix policy_evaluate setup (POST /v1/agents → /v1/sessions bundle); add needs_runner to cli_startup

- policy_evaluate setup was calling POST /v1/agents which is GET-only.
  Fix: use POST /v1/sessions multipart bundle upload (same as ensure_agent),
  with executor fields added to pass spec validation, and read session_id
  from the correct response key.

- cli_startup: add needs_runner=True so the test_runner_journeys_are_capped
  invariant passes (needs_host implies needs_runner in BenchEnvironment but
  not on the Journey dataclass itself).

Signed-off-by: Tomu Hirata <tomu.hirata@gmail.com>

* ci(bench): add pexpect+claude install to benchmark-pr.yml

cli_startup is in ALL_JOURNEYS so it runs in the benchmark-pr regression
check too. Without pexpect and claude installed, every iteration fails
with RuntimeError.

Signed-off-by: Tomu Hirata <tomu.hirata@gmail.com>

* fix(bench): replace test fixture function ref in policy_evaluate with self-contained one

tests.runtime.policies.conftest._always_allow is a test fixture that may
not be importable in the server subprocess's PYTHONPATH in CI, causing
HTTP 500 on every evaluate call. Replace with _bench_policy_allow defined
directly in journeys.py, which is always importable since dev/ is on
PYTHONPATH in the benchmark environment.

Signed-off-by: Tomu Hirata <tomu.hirata@gmail.com>

* fix(bench): gate cli_startup on OMNIGENT_BENCH_SERVER; skip gracefully when not set

cli_startup conflicts with the bench environment's host daemon when run
against the local bench server — omnigent claude spawns its own daemon
which hits a 'host on another replica' error. Gate on OMNIGENT_BENCH_SERVER
env var instead: skip with a clear RuntimeError when unset, use the remote
server when set.

- Remove needs_runner/needs_host (no local server contact)
- Reduce max_iterations from 5 to 3 (each is ~10s)
- Set OMNIGENT_BENCH_SERVER in benchmark.yml and benchmark-pr.yml
- Relax test_runner_journeys_are_capped to allow non-runner journeys to cap

Signed-off-by: Tomu Hirata <tomu.hirata@gmail.com>

* ci(bench): remove hardcoded OMNIGENT_BENCH_SERVER from workflows

cli_startup skips gracefully in CI (no OMNIGENT_BENCH_SERVER set).
Run it manually: OMNIGENT_BENCH_SERVER=<url> uv run --no-sync dev/benchmarks/omnigent/run.py --journeys cli_startup

Signed-off-by: Tomu Hirata <tomu.hirata@gmail.com>

* fix(bench): run cli_startup against local bench server; drop OMNIGENT_BENCH_SERVER

The daemon conflict was caused by needs_host=True booting a bench daemon
alongside the CLI's own daemon. With needs_host=False the bench environment
starts only the server; omnigent claude spawns its own daemon freely — no
conflict.

Result: 5.3s local vs 11s remote. CI runs it as part of the default suite
with no remote credentials needed.

Signed-off-by: Tomu Hirata <tomu.hirata@gmail.com>

* fix(bench): use omnigent polly instead of omnigent claude for cli_startup

claude-native requires the external claude CLI binary which:
- Takes too long to boot on CI (90s timeout → job gets stuck)
- Requires npm install of @anthropic-ai/claude-code

polly (omnigent run with the bundled openai-agents harness) exercises the
same startup path (daemon, session create, runner launch, runner connect)
without any external binary dependency. Signal: 'Launching your agent'
with a 30s timeout instead of 90s.

Remove @anthropic-ai/claude-code install from both benchmark workflows.

Signed-off-by: Tomu Hirata <tomu.hirata@gmail.com>

* fix(bench): move cli_startup to OPT_IN_JOURNEYS; exclude from default run

cli_startup against the local bench server hangs in CI — the polly runner
can't complete its startup within 30s, burning 19 min (39 attempts × 30s
including warmup) before failing.

Move it to OPT_IN_JOURNEYS: excluded from the default set, must be run
explicitly via --journeys cli_startup. resolve_journeys() looks in both
registries so it still works when named. Remove pexpect install from CI
workflows since it's no longer needed for the default benchmark run.

Signed-off-by: Tomu Hirata <tomu.hirata@gmail.com>

* fix(bench): cli_startup back in ALL_JOURNEYS; add skip_warmup flag; 60s timeout

- Move cli_startup back to ALL_JOURNEYS (not needs_host; spawns its own daemon)
- Add Journey.skip_warmup: when True, run_latency skips the warmup phase
  regardless of --warmup. Avoids 10x60s = 10min of wasted warmup hangs.
- Increase timeout from 30s to 60s (CI runner is slower than local Mac)
- Restore pexpect install in both benchmark workflows

With skip_warmup=True and max_iterations=3: 3 runs x 3 = 9 iterations max,
no warmup hangs. Worst case: 9 x 60s = 9min if all timeout (shouldn't happen).

Signed-off-by: Tomu Hirata <tomu.hirata@gmail.com>

* debug(bench): include RuntimeError message in failure breakdown for CI visibility

Signed-off-by: Tomu Hirata <tomu.hirata@gmail.com>

* fix(bench): stop stale daemons before each cli_startup iteration

A leftover host daemon from the previous iteration causes the next
omnigent polly to fail with 'runner tunnel rejection' or 'host is on
another replica'. Run omnigent stop before spawning polly to ensure
a clean slate each time.

Signed-off-by: Tomu Hirata <tomu.hirata@gmail.com>

* fix(bench): move omnigent stop to prepare hook so it's outside the latency timer

Signed-off-by: Tomu Hirata <tomu.hirata@gmail.com>

---------

Signed-off-by: Tomu Hirata <tomu.hirata@gmail.com>
2026-08-15 12:16:06 +00:00
Jackson Zheng dc10a22147 OMNI-3247: Update in-chat error patterns (#4787) 2026-08-14 21:50:12 -07:00
Zeyi (Rice) Fan 08b20956f2 fix(cli): honor isolated Omnigent state directories (#4822)
## Related issue

[OMNI-3489](https://linear.app/omnigent/issue/OMNI-3489/honor-omnidev-state-and-config-directories-in-omnigent-cli-paths)

## Summary

- Prevent `omnidev omnigent …` commands from leaking auth tokens, session logs, host daemon records, and native harness launch state into the developer's real `~/.omnigent` directory.
- Make runtime state honor `OMNIGENT_DATA_DIR` while configuration independently honors `OMNIGENT_CONFIG_HOME`; harness-specific native-state overrides still take precedence.
- Keep the real `HOME` and `XDG_*` environment intact so harness credentials and caches remain available, and update REPL E2E setup to seed its theme in the effective config without clobbering mock auth.

**ELI5:** omnidev already gives each development pod its own labeled storage boxes, but some Omnigent code still put files in the user's shared box. Those paths now use the pod's boxes without moving the user's home directory.

```text
omnidev omnigent
       |
       +-- OMNIGENT_DATA_DIR ------> tokens, logs, host/native state
       +-- OMNIGENT_CONFIG_HOME ---> config.yaml
       +-- HOME / XDG_* ------------> unchanged credentials and caches
```

## Test Plan

- `uv run --frozen pytest tests/frontends/sdk/test_user_config.py`
- `uv run --frozen pytest tests/test_native_state_legacy_dirs.py`
- `uv run --frozen pytest tests/host/test_cli_host.py::test_host_pid_path_honors_data_dir_at_import`
- `uv run --frozen pytest tests/e2e/omnigent/test_pexpect_harness.py`
- `uv run --frozen pytest tests/e2e/omnigent/test_repl_smoke.py::test_repl_smoke_single_prompt`
- `cargo test --manifest-path dev/omnidev/Cargo.toml omnigent_cmd::tests`
- `uv run --frozen ruff check omnigent/claude_native_state.py omnigent/cli.py omnigent/cli_auth.py omnigent/codex_native_state.py omnigent/opencode_native_state.py omnigent/repl/_session_log.py sdks/ui/omnigent_ui_sdk/terminal/_config.py tests/frontends/sdk/test_user_config.py tests/host/test_cli_host.py tests/test_native_state_legacy_dirs.py tests/e2e/omnigent/_pexpect_harness.py tests/e2e/omnigent/test_pexpect_harness.py`
- `cargo fmt --manifest-path dev/omnidev/Cargo.toml --check`

## Demo

N/A — non-visual CLI state-isolation fix.

## Type of change

- [x] Bug fix
- [ ] Feature
- [ ] UI / frontend change
- [ ] Refactor / chore
- [ ] Docs
- [ ] Test / CI
- [ ] Breaking change

## Test coverage

- [x] Unit tests added / updated
- [ ] Integration tests added / updated
- [x] E2E tests added / updated
- [ ] Manual verification completed
- [ ] Existing tests cover this change
- [ ] Not applicable

## Coverage notes

Regression tests cover pod environment wiring, data/config override precedence, HOME fallbacks, host pidfile placement, native harness state roots, and REPL startup with an isolated config home.

## Changelog

`omnidev omnigent` commands now keep runtime state and configuration inside their development pod.

Signed-off-by: Zeyi (Rice) Fan <zeyi.f@databricks.com>
2026-08-15 00:00:14 +00:00
Edwin He 39c986cb06 feat(desktop): build macOS app for Intel + Apple Silicon (#4772)
Set build.mac.target to build both x64 and arm64 for dmg + zip so the
macOS desktop build stops shipping only the build host's architecture.
mac.artifactName already templates ${arch}, so the two arches produce
distinct files. Config only — electron-builder reads mac.target the same
way for the manual signed release build (pnpm run build:mac:release).

Closes #842

Co-authored-by: Isaac

Signed-off-by: Edwin He <41037314+Edwinhe03@users.noreply.github.com>
2026-08-14 16:56:02 -07:00
Edwin He c8c4f81826 ci(electron): remove the redundant manual Electron Build workflow (#4771)
This dispatch-only workflow only produced unsigned, throwaway desktop
installers as workflow artifacts — it never published a release. Nothing
depends on it: it is workflow_dispatch-only (not a reusable workflow), no
other workflow or action references it, and the secure release repo builds
Windows + Linux itself (it merely models this workflow's steps). Rather than
maintain a second, drift-prone desktop-build definition, remove it. The
macOS multi-arch change lives independently in web/electron/package.json.

Co-authored-by: Isaac

Signed-off-by: Edwin He <41037314+Edwinhe03@users.noreply.github.com>
2026-08-14 15:47:32 -07:00
Zeyi (Rice) Fan 6c2daae4a9 fix(codex): pin shadowed config provider on resume (#4818)
## Related issue

N/A — reported and reproduced locally.

## Summary

- Pin Codex's detected `config.toml` provider when an explicit, non-default same-name Omnigent entry shadows ambient default synthesis.
- Resolve the provider once during native launch so rollout metadata, app-server, and remote TUI use the same immutable selection.
- Preserve spec, explicit-default, global-auth, subscription, and dismissed-provider precedence.

ELI5: if Codex is configured to use a gateway but Omnigent's matching provider entry is not marked default, a resumed conversation now follows Codex's actual gateway instead of falling back to unauthenticated OpenAI.

```text
Codex config detection ──► resolved native launch ──► resume rollout/TUI
      Databricks                  Databricks                 Databricks
```

## Test Plan

- `uv run --frozen pytest tests/test_native_codex_provider.py -k 'config_provider_shadowed_by_nondefault_explicit_entry_still_pins or shadowed_config_detection_uses_active_profile_provider or resolve_native_codex_launch_undismissed_config_provider_routes_via_pin or resolve_native_codex_launch_dismissed_config_provider_pins_openai'`
- `uv run --frozen pytest tests/test_codex_native.py -k 'resolve_native_codex_launch_no_provider_sets_login_fallback_summary or resolve_native_codex_launch_databricks_provider_sets_summary'`
- `uv run --frozen ruff check omnigent/codex_native_app_server.py tests/test_native_codex_provider.py tests/test_codex_native.py`
- `uv run --frozen ruff format --check omnigent/codex_native_app_server.py tests/test_native_codex_provider.py tests/test_codex_native.py`
- `git diff --check`

## Demo

N/A — non-visual backend fix.

## Type of change

- [x] Bug fix
- [ ] Feature
- [ ] UI / frontend change
- [ ] Refactor / chore
- [ ] Docs
- [ ] Test / CI
- [ ] Breaking change

## Test coverage

- [x] Unit tests added / updated
- [ ] Integration tests added / updated
- [ ] E2E tests added / updated
- [ ] Manual verification completed
- [x] Existing tests cover this change
- [ ] Not applicable

## Coverage notes

The new tests reproduce the shadowed non-default provider state and verify active Codex profile selection. Existing tests cover dismissed providers, ordinary detected providers, explicit defaults, and no-provider summaries.

## Changelog

Resumed Codex conversations now keep using the provider selected in Codex configuration.

Signed-off-by: Zeyi (Rice) Fan <zeyi.f@databricks.com>
2026-08-14 22:17:47 +00:00
Zeyi (Rice) Fan 73abf26b8e fix(web): quote server URLs in generated commands (#4817)
## Related issue

https://linear.app/omnigent/issue/OMNI-3481/quote-server-urls-in-ui-connection-commands

## Summary

- Prevent shells from interpreting query strings and other metacharacters in server URLs shown by the web UI.
- Quote server URLs as single POSIX shell arguments across host, Lakebox, reconnect, and resume commands.
- Cover command rendering and embedded quote escaping with focused tests.

## Test Plan

- `cd web && npm test -- src/lib/shell.test.ts src/shell/ReconnectSessionDialog.test.tsx`
- `cd web && npm test -- src/shell/NewChatDialog.test.tsx -t "quotes server URLs"`
- `cd web && npm run type-check`
- `cd web && ./node_modules/.bin/oxlint --deny-warnings --report-unused-disable-directives src/lib/shell.ts src/lib/shell.test.ts src/shell/NewChatDialog.tsx src/shell/NewChatDialog.test.tsx src/shell/ReconnectSessionDialog.tsx src/shell/ReconnectSessionDialog.test.tsx`
- `cd web && npm exec -- prettier --check src/lib/shell.ts src/lib/shell.test.ts src/shell/NewChatDialog.tsx src/shell/NewChatDialog.test.tsx src/shell/ReconnectSessionDialog.tsx src/shell/ReconnectSessionDialog.test.tsx`

## Demo

Before:

```sh
omni host --server https://example.com/api?profile=dev&glob=*
```

After:

```sh
omni host --server 'https://example.com/api?profile=dev&glob=*'
```

## Type of change

- [x] Bug fix
- [ ] Feature
- [x] UI / frontend change
- [ ] Refactor / chore
- [ ] Docs
- [ ] Test / CI
- [ ] Breaking change

## Test coverage

- [x] Unit tests added / updated
- [x] Integration tests added / updated
- [ ] E2E tests added / updated
- [ ] Manual verification completed
- [ ] Existing tests cover this change
- [ ] Not applicable

## Coverage notes

Unit coverage verifies shell quoting directly and rendering through both connection-command UI paths.

## Changelog

Server URLs in copyable connection and reconnect commands are now safely quoted.

Signed-off-by: Zeyi (Rice) Fan <zeyi.f@databricks.com>
2026-08-14 21:05:27 +00:00
Zeyi (Rice) Fan b9d53f0a96 feat(server): add deployment-wide release feature flags (#4775)
## Related issue

Follow-up to #4673.

## Summary

- Add a typed, default-off release-feature registry driven by one comma-separated `OMNIGENT_FEATURES` environment variable, with strict validation and lifecycle metadata.
- Gate the web Usage route/navigation and page-only report enrichment while preserving the existing `GET /v1/usage` CLI API.
- Migrate web-driven harness installation to the same immutable startup snapshot and wire rollout configuration across Docker, Kubernetes, Render, Railway, and Databricks.

ELI5: the server reads one list of enabled features when it starts, enforces that same list on backend routes, and tells the web app which controls and pages to show.

```text
OMNIGENT_FEATURES
        |
        v
  FeatureFlags snapshot
     /             \
backend gates    GET /v1/info
                       |
                       v
                 frontend gates
```

## Test Plan

- `uv run pytest tests/server/test_feature_flags.py tests/host/test_local_server.py tests/server/integration/test_utility_endpoints.py tests/server/integration/test_hosts_install_harness.py tests/server/integration/test_hosts_store_credential.py tests/server/routes/test_usage_report.py tests/server/test_openapi_drift.py -q`
- `cd web && pnpm vitest run src/lib/capabilities.test.ts src/lib/harnessSetup.test.ts src/App.test.tsx src/shell/Sidebar.test.tsx`
- `uv run pytest tests/e2e_ui/sessions/test_usage_page_feature.py -q`
- `uv run python scripts/dump_openapi.py --check`
- `pre-commit run --files <changed files>`
- Verified default-off and enabled Usage route/sidebar behavior, strict unknown-feature rejection, legacy CLI usage compatibility, and harness route enforcement.

## Demo

- Default off: the updated visual baselines show the original sidebar without the Usage row.
- Enabled Usage page: https://github.com/user-attachments/assets/8385d4f0-47ad-430f-bf2c-06c35af6c499

## Type of change

- [ ] Bug fix
- [x] Feature
- [x] UI / frontend change
- [ ] Refactor / chore
- [x] Docs
- [ ] Test / CI
- [ ] Breaking change

## Test coverage

- [x] Unit tests added / updated
- [x] Integration tests added / updated
- [x] E2E tests added / updated
- [x] Manual verification completed
- [x] Existing tests cover this change
- [ ] Not applicable

## Coverage notes

Manually reviewed the default-off visual output and verified that the Usage route is absent while the capability is disabled. Targeted backend and frontend tests cover both flag states, capability parsing, startup snapshots, and harness enforcement.

## Changelog

Usage and web-driven harness setup can now be enabled per deployment with `OMNIGENT_FEATURES`.

Signed-off-by: Zeyi (Rice) Fan <zeyi.f@databricks.com>
2026-08-14 10:57:20 -07:00
Zeyi (Rice) Fan 8f194d6f9d test(antigravity): wait for quiescence poll progress (#4778)
## Related issue

N/A — test-only reliability fix.

## Summary

- Prevent the quiescence backoff regression test from exhausting an event-loop iteration budget while its polls are still completing in worker threads.
- Signal the async test when the target poll count is reached and always cancel its mirror task during cleanup.

## Test Plan

- `uv run pytest tests/test_antigravity_native_reader.py::test_the_quiescence_recheck_backs_off_after_agy_vetoes_a_close -q`
- `uv run ruff check tests/test_antigravity_native_reader.py`

## Demo

N/A — non-visual test-only change.

## Type of change

- [ ] Bug fix
- [ ] Feature
- [ ] UI / frontend change
- [ ] Refactor / chore
- [ ] Docs
- [x] Test / CI
- [ ] Breaking change

## Test coverage

- [x] Unit tests added / updated
- [ ] Integration tests added / updated
- [ ] E2E tests added / updated
- [ ] Manual verification completed
- [ ] Existing tests cover this change
- [ ] Not applicable

## Coverage notes

The updated unit test exercises the existing quiescence recheck backoff behavior with deterministic cross-thread synchronization.

Signed-off-by: Zeyi (Rice) Fan <zeyi.f@databricks.com>
2026-08-14 10:42:43 -07:00
Tomu Hirata 204e99d5c6 fix(deps): resolve protobuf gencode/runtime mismatch in antigravity extra (#4795)
google-antigravity ships proto files compiled against protobuf 7.x
(gencode version 7.35.x). With the prior `protobuf>=6,<7` core pin the
runtime was always 6.x, causing:

  Detected incompatible Protobuf Gencode/Runtime versions when loading
  google/antigravity/proto/localharness.proto: gencode 7.35.0 runtime
  6.33.6. Runtime version cannot be older than the linked gencode version.

Fixes #4774.

Changes:
- Widen core `protobuf` constraint from `>=6,<7` to `>=6,<8` so the
  resolver can pick 7.x when needed.
- Pin `protobuf>=7,<8` in the `antigravity` extra so installing
  `omnigent[antigravity]` always selects a 7.x runtime; the protobuf
  cross-version guarantee lets a 7.x runtime load our 6.x gencode.
- Declare `[tool.uv] conflicts` for extra/group pairs that are mutually
  exclusive (antigravity vs cwsandbox/modal; lint vs cwsandbox/modal)
  so uv can resolve them in independent forks without a lockfile error.
- Bump `grpcio-tools` floor to `>=1.83` (first release that bundles
  libprotoc 35.1 / protobuf 7.x gencode) and regenerate
  `omnigent/api/routing/v1/routing_pb2.py` so the `routing-pb2-fresh`
  pre-commit hook continues to pass.

Signed-off-by: Tomu Hirata <tomu.hirata@gmail.com>
2026-08-14 09:25:20 +00:00
Yuan Tang d41f491e37 feat(runner): auto-assign structured names to subagents (#4489)
* feat(runner): auto-assign structured names to subagents

Subagents are now automatically assigned meaningful structured names
(e.g. "researcher-1", "coder-2") at spawn time instead of relying on
LLM-chosen titles. A background display-name generator also produces
human-readable task-derived labels (e.g. "Investigate auth token
refresh") that the UI prefers when available.

The LLM's `title` argument to sys_session_send becomes optional — it
is stored as a hint label for display-name generation but is no longer
the spawn-or-continue key. The structured name is returned in the
response handle; the LLM uses it (or session_id) to continue sessions.

Changes span the full stack:
- Entity/DB: new display_name column on conversation metadata
- Runner: per-parent ordinal counter with restart recovery
- Tool dispatch: auto-generate structured names, make title optional
- Server: expose display_name on ChildSessionSummary, schedule
  background display-name generation for child sessions
- Web UI: prefer display_name in graph/panel labels

Signed-off-by: Yuan Tang <terrytangyuan@gmail.com>
2026-08-14 06:48:21 +00:00
Jackson Zheng 4e13ff5b82 fix(web): keep chat above growing composer (#4767) 2026-08-13 22:33:11 -07:00