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>
web
The web UI for omnigent server --agent <agent>. SPA built with Vite + React + TypeScript +
Tailwind v4 + shadcn/ui. Talks to the current Omnigent API surface
(/v1/agents, /v1/sessions, session-scoped
/v1/sessions/{id}/resources/files).
Develop
In one terminal, start the omnigent server (default port 6767). Use
--agent to pre-register one or more agents at startup (accepts a YAML file or
an agent-image directory; can be repeated):
.venv/bin/omnigent server --agent examples/hello_world.yaml
In another terminal, start the Vite dev server (port 5173):
cd web
pnpm install
pnpm run dev
The Vite dev server proxies /v1 and /api to http://localhost:6767. Set
OMNIGENT_URL to override the proxy target:
OMNIGENT_URL=http://localhost:9000 pnpm run dev
To develop against a Databricks workspace-hosted server, point OMNIGENT_URL
at the bare workspace origin — the dev proxy fills in the /api/2.0/omnigent
mount and authenticates with your databricks auth login token automatically:
OMNIGENT_URL=https://my-workspace.databricks.com pnpm run dev
Additional omnigent server options:
| Flag | Default | Description |
|---|---|---|
--host |
127.0.0.1 |
Host to bind to |
-p / --port |
6767 |
Port to listen on |
--database-uri |
<data-dir>/chat.db |
Database URI for stores |
--artifact-location |
<data-dir>/artifacts |
Path for artifact storage |
-c / --config |
(none) | Path to YAML config file |
--execution-timeout |
7200 |
Max wall-clock seconds per execution |
--agent |
(none) | Pre-register an agent (repeatable) |
Build + serve from the Omnigent server
cd web
pnpm run build
Vite writes the bundle to ../omnigent/server/static/web-ui/ (configured in
vite.config.ts). When that directory exists and contains index.html, the
FastAPI app in omnigent/server/app.py mounts it at /. After a build:
.venv/bin/omnigent server --agent examples/hello_world.yaml
# open http://localhost:6767/
Lint + format
pnpm run lint # oxlint .
pnpm run lint:fix # oxlint --fix .
pnpm run format # prettier --write .
pnpm run format:check # prettier --check .
pnpm run type-check # tsc -b
pnpm run type-check runs in CI as part of the Pre-commit checks
job (.github/workflows/lint.yml) and gates merge. Run it locally
before committing any change under web/.
Test
pnpm run test # vitest run
pnpm run test:watch # vitest in watch mode
Reducer parity
The TypeScript reducer at src/lib/blockStream.ts is a hand-mirror of
the Python reducer at
sdks/python-client/omnigent_client/_stream.py. Same for:
| TS file | Mirrors |
|---|---|
src/lib/blocks.ts |
omnigent_client/_blocks.py |
src/lib/events.ts |
omnigent_client/_events.py |
src/lib/types.ts |
minimal subset of omnigent_client/_types.py |
src/lib/sse.ts |
omnigent_client/_sse.py |
src/lib/blockStream.ts |
omnigent_client/_stream.py |
src/lib/blockStream.test.ts |
tests/frontends/sdk/test_stream.py |
There is no cross-language CI gate today. When _stream.py
changes for a real bug (e.g. new harness quirk, dedup edge case), the
TypeScript port can lag — drift surfaces only when someone next runs
pnpm run test after a behavioral change. Workflow when _stream.py
changes:
- Read the diff to
_stream.py(or_blocks.py/_events.py). - Update
blockStream.ts(orblocks.ts/events.ts) to match. - Add or update a case in
blockStream.test.tsthat pins the new behavior — same shape astest_stream.py. pnpm run test→ green.
If we ever decide cross-language fixture parity is worth the
maintenance burden, we'd port the captured-fixture approach used
for test_stream.py.
web-only divergences
web carries a few constructs the Python SDK doesn't, on purpose. They're listed here so a future maintainer doesn't try to "restore parity" by mirroring them across.
UserMessageBlock(inblocks.ts) — surfaces persisted user message items as blocks so the bubble walker sees a single flat list. The SDK'sBlockStream.stream()never emits user messages (its consumers receive the user input as the caller's own argument, not back through the stream).BlockContext.responseId+BlockContext.itemId— populated by the TS reducer from the SSE wire format (response.created.response.idandevent.item.id/event.item.response_idonoutput_item.done) so each block knows its server origin. The TS eventsToolCall/ToolResult/MessageDone/NativeToolCallcarryitemId+responseIdto thread the values through.- Flat block storage in
chatStore.blocks, grouped at render time bybuildBubbleskeyed onctx.responseId. The SDK has no equivalent — its consumers iterate the block stream procedurally without a stateful store.
When _stream.py / _events.py / _blocks.py change for a
substantive reason (new event type, new dedup edge case), continue to
mirror the behavioral changes here; just leave the divergences above
alone.
Stack
- Vite + React 19 + TypeScript
- Tailwind v4 (
@import "tailwindcss", no config file) - shadcn/ui (
radix-novapreset, neutral base, CSS variables) - TanStack Query, Zustand, React Router v7
- streamdown (+
@streamdown/code,@streamdown/math,@streamdown/mermaid), shiki, framer-motion, cmdk, react-hotkeys-hook, use-stick-to-bottom, next-themes, react-hook-form, zod - Lint: oxlint. Format: prettier.