Skip to content

[Bug]: Web Studio bot does not track the user-selected directory — currentUri is dropped at the AgentPanel boundary #2883

Description

@Ussrmby

Bug Description

Summary

In the Web Studio playground, the bot/agent has no way to know which directory the
user has selected in the left-side resource tree. The tree selection
(currentUri, mirrored into the URL as ?uri=viking://...) is dropped at the
AgentPanel boundary
— it is never sent to the bot chat endpoint, so the agent
always answers from the knowledge-base root (or the per-session sandbox cwd)
instead of the directory the user is currently looking at.

Defects (verified against main)

The full request path carries no directory field end-to-end:

  1. Frontend builds the request without the directory.
    web-studio/src/routes/playground/route.tsx holds currentUri in state and
    passes it to TerminalPanel (L842), but not to AgentPanel (L851):

    {activePanel === 'terminal' ? (
      <TerminalPanel currentUri={currentUri} ... />
    ) : (
      <AgentPanel
        initialSessionId={sessionId}
        onOpenResource={onOpenResource}
        onSessionChange={onSessionChange}
      />
    )}

    AgentPanel.send (agent-panel.tsx:169) only forwards the message text:

    const send = useCallback((message: string) => {
      void chat.send(message)
    }, ...)
  2. The chat request body has no directory field.
    web-studio/types/ov-server/bot/v1/chat.ts:

    export type BotChatRequest = {
      channel_id?: string
      message: string
      need_reply?: boolean
      session_id?: string
      stream?: boolean
      user_id?: string
    }

    No selectedDir / currentUri / targetUri / cwd field exists.

  3. Server proxy injects identity but not the directory.
    openviking/server/routers/bot.py, _attach_openviking_connection attaches
    account_id / user_id / agent_id / role / api_key / server_url, but nothing
    about the user-selected directory.

  4. Backend ChatRequest / OpenVikingConnection have no directory field.
    bot/vikingbot/channels/openapi_models.pyOpenVikingConnection only
    carries identity fields; ChatRequest likewise has none.

  5. Agent context injects only the sandbox cwd, not the selected directory.
    bot/vikingbot/agent/loop.py builds ContextBuilder from
    sandbox_manager.get_workspace_path(session_key); build_system_prompt
    (bot/vikingbot/agent/context.py) injects ## Sandbox Environment with
    sandbox_cwd — a per-session FUSE mount path derived from session_key, which
    has no relation to the tree node the user clicked.

Impact

  • The agent and the resource tree behave as two disconnected modules. Asking
    "list the current directory" / "summarize this directory" after selecting a
    node does not ground the answer in that directory; the LLM often picks the
    wrong tool (list_dir over the sandbox) or lists viking:// root.
  • The empty-state copy in the Agent panel promises "Agent actions sync with the
    tree on the left"
    (and the reverse direction is tracked separately in
    bug-report-agent-directory-sync.md), but neither direction is implemented.
    This issue covers the tree → agent direction.

Repro

1. Open the Studio playground (e.g. http://<host>:1933/studio/playground).
2. In the left resource tree, click a directory, e.g.
   viking://resources/xxxxxxxxxx/
   (the URL becomes /studio/playground?...&uri=viking://resources/...%2F).
3. Switch to the Agent panel and send: "list the current directory".
4. Observe the agent does NOT call openviking_list on the selected directory.
Image

Steps to Reproduce

  1. Start the server with --with-bot and open the Studio playground.
  2. In the left resource tree, select a directory node. The browser URL updates to
    /studio/playground?...&uri=viking://resources/<dir>/.
  3. Switch to the Agent panel and send a directory-scoped question, e.g.
    list the current directory or summarize this directory.
  4. Observe the agent's tool calls: it does not pass the selected URI to
    openviking_list. It either calls list_dir (sandbox filesystem) or
    openviking_list with the default viking:// root.

Expected Behavior

When the user has selected a directory in the tree, the agent should be aware of
it and ground directory-scoped requests on that URI:

  • Selected: viking://resources/5th-gen-amd-epyc-processor-architecture-w_ae3f95e9/
  • User sends: list the current directory
  • Expected: agent calls openviking_list({"uri": "viking://resources/5th-gen-amd-epyc-processor-architecture-w_ae3f95e9/"})
  • Actual: agent calls list_dir (sandbox) or openviking_list({"uri": "viking://"}) (root)

When the request is unrelated to the selected directory, the agent should ignore
it and answer normally.

Actual Behavior

The agent never receives the selected directory. The /bot/v1/chat/stream
request body is {message, session_id, user_id, stream} — no directory field —
so the LLM has no signal about which tree node the user is viewing and defaults
to the sandbox cwd or the knowledge-base root.

Root causes (see Defects above):

  1. route.tsx does not pass currentUri to AgentPanel.
  2. BotChatRequest has no directory field, so sendChatStream cannot send one.
  3. _attach_openviking_connection injects only identity.
  4. OpenVikingConnection / ChatRequest have no directory field.
  5. build_system_prompt injects only sandbox_cwd, not the selected directory.

Minimal Reproducible Example

# The bot chat request body that the Studio playground actually sends
# (captured from /bot/v1/chat/stream). There is no field for the selected dir.
import json
body = {
    "message": "list the current directory",
    "session_id": "<sid>",
    "user_id": "<uid>",
    "stream": True,
}
print(json.dumps(body, indent=2))
# {"message": "...", "session_id": "...", "user_id": "...", "stream": true}
# ^ no selectedDir / currentUri / targetUri anywhere



# Backend: the connection forwarded to vikingbot also lacks any directory field.
# openviking/server/routers/bot.py :: _build_openviking_connection
connection = {
    "account_id": ctx.user.account_id,
    "user_id": ctx.user.user_id,
    "agent_id": "web-playground",
    "role": ...,
    "api_key_type": ...,
    "server_url": ...,
    "namespace_policy": ...,
}
# ^ no current_dir / selected_dir

Error Logs

No exception is raised. The agent simply does not ground on the selected
directory — a silent functional gap, not a crash.

Evidence (after a user clicked viking://resources/<dir>/ and sent a message):
- /bot/v1/chat/stream request body: {message, session_id, user_id, stream}
  (no directory field)
- vikingbot.log shows:
  [TOOL_CALL]: openviking_list({"uri": "viking://"})   # root, not the selected dir

OpenViking Version

0.4.5 (also reproduced on main @ 2846bb6, 2026-06-26)

Python Version

3.10

Operating System

Linux

Model Backend

Other

Additional Context

Suggested fix (minimal, no frontend rebuild needed)

The Studio playground already mirrors the selected directory into the URL query
(?uri=viking://..., via syncSearch in route.tsx). For same-origin chat
requests the browser sends that full URL in the Referer header, so the server
proxy can recover the selected directory without any frontend change:

  1. openviking/server/routers/bot.py: in _attach_openviking_connection, parse
    the uri= query from request.headers["referer"] and attach it to the
    forwarded openviking_connection (e.g. as current_dir).
  2. bot/vikingbot/channels/openapi_models.py: add current_dir: Optional[str]
    to OpenVikingConnection.
  3. bot/vikingbot/agent/loop.py: read current_dir from the connection and pass
    it to ContextBuilder.
  4. bot/vikingbot/agent/context.py: in build_system_prompt, inject a
    ## User-Selected Directory section that tells the LLM to prefer that URI as
    the openviking_list / openviking_search argument when the request
    concerns it, and to ignore it otherwise.

This is the reverse direction of the empty-state promise tracked in
bug-report-agent-directory-sync.md (that one is agent → tree; this one is
tree → agent). A proper frontend fix (passing currentUri to AgentPanel and
adding a field to BotChatRequest) is more correct long-term, but the
Referer-based approach above works today with backend-only changes.

Reference implementation

A working, verified implementation of the fix above is attached for the
maintainers' reference. It is additive only — 4 files, +71 lines, 0 deletions,
no existing logic touched — so it is low-risk to review and can serve as the
base for a PR.

Changed files (additive, against v0.4.5):

File +lines Change
openviking/server/routers/bot.py +30 _extract_current_dir + _stamp_current_dir, inject into openviking_connection
vikingbot/channels/openapi_models.py +6 current_dir: Optional[str] field on OpenVikingConnection
vikingbot/agent/loop.py +16 read current_dir from connection, pass to ContextBuilder
vikingbot/agent/context.py +19 ContextBuilder accepts current_dir; build_system_prompt injects ## User-Selected Directory

Unified diff (source_diff.patch, git a/ b/ style, applies cleanly on
v0.4.5 with patch -p1):

--- a/vikingbot/agent/context.py
+++ b/vikingbot/agent/context.py
@@ -38,6 +38,7 @@
         is_group_chat: bool = False,
         eval: bool = False,
         openviking_connection: dict[str, Any] | None = None,
+        current_dir: str | None = None,
     ):
         self.workspace = workspace
         self._templates_ensured = False
@@ -49,6 +50,7 @@
         self._is_group_chat = is_group_chat
         self._eval = eval
         self._openviking_connection = openviking_connection
+        self._current_dir = (current_dir or "").strip() or None
         self.latest_relevant_memories: str | None = None
@@ -125,6 +127,24 @@
                 f"## Sandbox Environment\n\n...`{sandbox_cwd}`..."
             )

+        # User-selected directory (forwarded by the Studio proxy from the tree
+        # node the user is currently viewing). Strong guidance: when the user's
+        # intent involves that directory, prefer it as the target URI for
+        # openviking_list / openviking_search so answers are grounded in what
+        # the user is looking at.
+        if self._current_dir:
+            parts.append(
+                f"## User-Selected Directory\n\n"
+                f"The user is currently viewing this directory in the Studio tree:\n"
+                f"`{self._current_dir}`\n\n"
+                f"When the user's request concerns this directory (e.g. listing, "
+                f"summarizing, or finding documents under it), prefer passing this "
+                f"URI as the `uri` argument to `openviking_list`, or as the "
+                f"`target_uri` for `openviking_search`, instead of defaulting to the "
+                f"knowledge-base root. If the request is unrelated to this directory, "
+                f"ignore it and answer normally."
+            )
+
         # Bootstrap files
--- a/vikingbot/agent/loop.py
+++ b/vikingbot/agent/loop.py
@@ -931,6 +947,8 @@
             if not isinstance(openviking_connection, dict):
                 openviking_connection = None
             msg.openviking_connection = openviking_connection
+            # User-selected directory forwarded by the Studio proxy (if any).
+            current_dir = openviking_connection.get("current_dir") if openviking_connection else None
             profile_user_list = []
@@ -1082,6 +1100,7 @@
                 is_group_chat=is_group_chat,
                 eval=self._eval,
                 openviking_connection=openviking_connection,
+                current_dir=current_dir,
             )
--- a/openviking/server/routers/bot.py
+++ b/openviking/server/routers/bot.py
@@ -93,6 +93,30 @@
+def _extract_current_dir(request: Request) -> str:
+    """Extract the user-selected directory URI from the browser request.
+
+    The Studio playground mirrors its selected tree node into the URL query
+    (``?uri=viking://resources/.../``). The browser sends that full URL in the
+    ``Referer`` header for same-origin chat requests, so we can recover the
+    directory the user is currently looking at — without any frontend change —
+    and forward it to vikingbot so the agent can act on it.
+
+    Returns the URI string when the selected entry is a directory, else ``""``.
+    """
+    from urllib.parse import urlparse, parse_qs
+
+    for src in (request.headers.get("referer"), str(request.url)):
+        if not src or "uri=" not in src:
+            continue
+        uri = parse_qs(urlparse(src).query).get("uri", [""])[0]
+        # Only forward directory selections (trailing slash). File URIs are
+        # ignored so the agent is not biased toward a single file.
+        if uri and uri.endswith("/"):
+            return uri
+    return ""
+
+
 def _attach_openviking_connection(
     ...
+def _stamp_current_dir(enriched: dict, current_dir: str) -> None:
+    """Attach the user-selected directory to the forwarded connection dict."""
+    if not current_dir:
+        return
+    conn = enriched.get("openviking_connection")
+    if isinstance(conn, dict):
+        conn["current_dir"] = current_dir
@@ -131,9 +158,19 @@
     enriched = dict(body)
+    current_dir = _extract_current_dir(request)
     api_key = _extract_forward_api_key(request)
     ...
     # _stamp_current_dir(enriched, current_dir) called before each return path
+    enriched["openviking_connection"] = _build_openviking_connection(...)
+    _stamp_current_dir(enriched, current_dir)
+    return enriched
--- a/vikingbot/channels/openapi_models.py
+++ b/vikingbot/channels/openapi_models.py
@@ -62,6 +62,12 @@
     server_url: Optional[str] = Field(default=None, description="OpenViking server URL")
+    current_dir: Optional[str] = Field(
+        default=None,
+        description="User-selected directory URI (viking://...) forwarded by the "
+        "Studio proxy so the agent can ground its answers on the tree node the "
+        "user is currently viewing.",
+    )

The full source_diff.patch and the four patched source files (preserving the
repo's directory structure under patched_sources/) are included in the
attached archive bot_dir_sync_patch.zip for convenience.

Verified working

The approach above was implemented and verified locally on v0.4.5: after
selecting a directory and sending "list the current directory", the agent
correctly calls openviking_list with the selected URI; unrelated questions
("what memories do you have") ignore the directory and answer normally.

Related

  • Reverse direction (agent → tree, viking:// link rendering): separate issue.

bot_dir_sync_patch.zip

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    Status
    In progress

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions