* fix(files): browse outside the workspace behind a slash-merging proxy
The file panel showed "No files in workspace" for any directory above or
outside the working folder when the app is served through the Databricks
Apps front door, even though the picker dropdown listed those very
directories.
Absolute browse locations were marked by a leading %2F in the filesystem
route's {path} segment. That %2F decodes to a boundary // which the front
door merges back to a single / (//health 301-redirects to /health), so
/Users/me arrived at the server as a workspace-relative Users/me and listed
a nonexistent path under the workspace. The picker uses the host filesystem
endpoint, which already sends bare slashes, so it was unaffected -- hence
paths in the dropdown but an empty tree.
Follow the host endpoint's own convention instead: send the path with
literal slashes and no leading %2F (nothing for a proxy to merge) and name
an absolute location out of band with ?base=host. The server re-adds the
leading slash, applies the same owner gate (an absolute path is owner-only,
so base=host is not a way around it), and re-encodes %2F only on the
internal server->runner leg, which is not proxied. A genuine leading slash
still works for a direct, unproxied client.
Covers list, lazy directory expand, search, file read, and write; the
filesystem root (base=host with an empty segment) is handled on the no-path
route.
Test plan:
- e2e_ui test_absolute_browse_survives_a_slash_merging_proxy reproduces the
front door's %2F-decode + //-merge on the session's filesystem/search
requests and asserts the tree lists an outside directory. Reverting the
web change makes it fail with "No files in workspace".
- collaboration test asserts the owner gate applies to the base=host form
(collaborator 403, owner 200), so it is not an escape hatch.
- route tests pin base=host -> absolute (%2F) forwarding, a bare path
without base staying workspace-relative, and base=host root -> "/".
Co-authored-by: Isaac
Signed-off-by: dbczumar <corey.zumar@databricks.com>
* refactor(files): reuse the shared browse-location helpers for file read/write
Address Polly's non-blocking notes on PR #5174. fetchFileContent and
writeFileContent reimplemented the "strip leading slash + ?base=host" wire
rule inline; route them through the exported browseLocationSegment /
browseLocationBase so the slash-merge-safe contract lives in one place.
Behavior is identical (verified: same URL for relative and absolute paths).
Also note in the list_environment_root base=host branch that the delegate
(read_or_list_environment_path) runs its own owner-gated _validate_session,
so the delegation is authorized there.
Co-authored-by: Isaac
Signed-off-by: dbczumar <corey.zumar@databricks.com>
* fix(files): open a file at an absolute browse location by its absolute path
A follow-on to the browse-outside-workspace fix: navigating to a directory
outside the workspace (e.g. /tmp) now lists files, but OPENING one 404'd.
The tree names files by their bare name relative to the browsed location, and
openTreeFile re-attached that name only for workspace-relative locations --
for an absolute location it handed the viewer the bare name, which the viewer
then looked up under the workspace root (nonexistent) → 404.
Always re-attach the browsed location: for an in-workspace location that
yields a workspace-relative path, and for one outside the workspace the file's
absolute path, which fetchFileContent already requests host-absolutely
(base=host, per the earlier consolidation).
Tests: FilesPanel unit test (open a file at an absolute location → the
absolute path reaches onFileSelect; mutation-verified against the old
bare-name form) and a useFileContent test pinning the absolute wire form
(bare path + base=host).
Co-authored-by: Isaac
Signed-off-by: dbczumar <corey.zumar@databricks.com>
* fix(files): keep the browsed directory when the file viewer closes
Opening a file swaps the files panel for the FileViewer in the rail's
content slot, unmounting the panel -- and the browse location was plain
component state, so closing the viewer remounted the panel at the
workspace root instead of the directory the file was opened from
(browse to /tmp, open a log, go back -> dumped at the workspace root).
Remember the location in a module-level per-conversation cache, the same
pattern FolderTree already uses for its expanded-folders set across the
identical unmount/remount. Seeded at mount, written on every navigation
(cleared when returning to the workspace root), and re-read on an
in-place conversation switch so another session still opens at its own
root -- the cache is keyed by conversation, so no location ever leaks
across sessions.
Tests: FilesPanel unit tests for the unmount/remount round trip and the
per-conversation scoping (round trip mutation-verified), and the
outside-workspace e2e now opens a file at the browsed directory, closes
the viewer via its tab, and asserts the header still names that
directory (also mutation-verified: against a pre-fix build it fails
with the header back at the workspace path).
Co-authored-by: Isaac
Signed-off-by: dbczumar <corey.zumar@databricks.com>
---------
Signed-off-by: dbczumar <corey.zumar@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.