Skip to content

feat: reach cross-workspace session search from Telegram - #311

Merged
saucam merged 1 commit into
mainfrom
feat/telegram-cross-workspace-search
Aug 30, 2026
Merged

feat: reach cross-workspace session search from Telegram#311
saucam merged 1 commit into
mainfrom
feat/telegram-cross-workspace-search

Conversation

@saucam

@saucam saucam commented Aug 29, 2026

Copy link
Copy Markdown
Collaborator

Follow-up to #309, which gave the TUI a Tab toggle between this-workspace and all-workspaces search. Telegram was left on the old path, so the capability was still unreachable there.

Two problems, not one.

The scope was wrong even when it worked

/search sent scope:"workspace" with no workdir, so the daemon fell through to #guessCallerWorkdir — which returns the workdir of the most recently created session by that user.

That's unrelated to what the Telegram user is attached to. You could be attached to alpha, ask about alpha, and silently search beta because beta was started later. The handler now anchors on the attached session's workdir.

With nothing attached there's no workspace worth scoping to, so the search runs cross-workspace rather than scoping to an arbitrary repo — the same rule the TUI modal uses for its initial scope.

No global path

Telegram has no Tab, so the widen control is an inline-keyboard button on the results: 🌐 Search all workspaces. Offered on zero hits too, which is exactly when widening is most useful.

callback_data caps at 64 bytes and queries are free text, so the query is parked in #pendingSearches keyed ${userId}:${short} — the same shape as approvals and provider dialogs — with oldest-first eviction at 200 entries and single-use tokens.

Cross-workspace hits show the repo name, as in the TUI: two repos can each have a "fix auth" session, so a bare session name is ambiguous once results span workspaces.

Behaviour

Before After
Attached workspace of the newest session (arbitrary) workspace of the attached session
Not attached arbitrary workspace cross-workspace
Cross-workspace unreachable 🌐 button

basename is now total

A hit whose session is no longer live can carry an empty workdir — the daemon already renders those as (unknown) — and a missing repo label must not break the result list. The TUI copy from #309 had the same latent hazard and is fixed alongside; the existing chunked-output test caught it.

Verification

  • bun run typecheck clean
  • bun run lint clean (360 files)
  • bun test — 2401 pass, 19 skip, 0 fail
  • Three new tests: attached anchors on the attached workspace, unattached goes global, and the widen button re-runs the same query in the other scope (including single-use token behaviour)

Co-authored-by: Claude Opus 5 (1M context) noreply@anthropic.com

🤖 Generated with Claude Code

#309 gave the TUI a Tab toggle between this-workspace and all-workspaces
search. Telegram was left on the old path, so the same capability was still
unreachable there. Two problems, not one.

The scope was wrong even when it worked. `/search` sent scope:"workspace"
with no workdir, so the daemon fell through to `#guessCallerWorkdir`, which
returns the workdir of the most recently CREATED session by that user. That
is unrelated to what the Telegram user is attached to — you could be attached
to alpha, ask about alpha, and silently search beta because beta was started
later. The handler now anchors on the attached session's workdir.

With nothing attached there is no workspace worth scoping to, so the search
runs cross-workspace instead of scoping to an arbitrary repo. Same rule the
TUI modal uses for its initial scope.

Telegram has no Tab, so the widen control is an inline-keyboard button on the
results: "🌐 Search all workspaces". It is offered on zero hits too, which is
exactly when widening is most useful. callback_data caps at 64 bytes and
queries are free text, so the query is parked in `#pendingSearches` keyed
`${userId}:${short}` — the same shape as approvals and provider dialogs —
with oldest-first eviction at 200 entries and single-use tokens.

Cross-workspace hits show the repo name, as in the TUI: two repos can each
have a "fix auth" session, so a bare session name is ambiguous once results
span workspaces.

`basename` is total in both frontends. A hit whose session is no longer live
can carry an empty workdir — the daemon already renders those as "(unknown)"
— and a missing repo label must not break the result list. The TUI copy had
the same latent hazard and is fixed alongside.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
@saucam
saucam merged commit 029e7f9 into main Aug 30, 2026
4 checks passed
@saucam
saucam deleted the feat/telegram-cross-workspace-search branch August 30, 2026 01:47
saucam added a commit that referenced this pull request Sep 1, 2026
Third and last surface. #309 gave the TUI a Tab toggle and #311 gave
Telegram a button; the web UI still had no way to ask for a cross-workspace
search while a session was focused.

Its anchoring was already right -- it passed the focused session's workdir,
which is what Telegram had to be fixed to do -- but the scope was derived
and never overridable:

    ...(focusedSession()?.workdir
      ? { workdir: focusedSession()!.workdir, scope: "workspace" }
      : { scope: "all" })

So global search was reachable only by accident, when nothing happened to be
focused. With a session open you searched one directory, and the 73%
precision@1 path the feature exists for was unreachable.

A segmented control in the modal header now switches This workspace / All
workspaces. It is an override on top of the derived default rather than a
replacement for it, so focusing a session later does not silently undo a
choice the user made. The control is hidden entirely when nothing is
focused, because there is no workspace to scope to and a toggle with one
meaningful position is noise.

The debounce effect now depends on scope as well as query, so flipping
re-runs the current query instead of leaving stale hits from the other
regime on screen. Zero hits under workspace scope also offers a widen
button inline -- the same reasoning as the Telegram keyboard, since an
empty scoped result is exactly when widening helps.

Ctrl+K is taken for open/close, so this is a click target rather than a
key. Results already render hit.workdir, so global hits were legible
without further work.

Worth recording, since it is easy to misread: a workspace is a DIRECTORY,
not a session family. workspaceIdFromPath hashes workdir + account_id +
project_id, so every session that ever ran in that directory under the same
tenant shares the workspace, related or not -- and a fork isolated into a
git worktree gets a different path, so it lands OUTSIDE its parent's scope.
That is precisely the history cross-workspace search recovers.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants