Skip to content

feat(select_path): inline file/folder picker for ambiguous paths#197

Open
hazemahmedx0 wants to merge 4 commits into
mainfrom
feat/inline-path-selector
Open

feat(select_path): inline file/folder picker for ambiguous paths#197
hazemahmedx0 wants to merge 4 commits into
mainfrom
feat/inline-path-selector

Conversation

@hazemahmedx0

@hazemahmedx0 hazemahmedx0 commented Jun 21, 2026

Copy link
Copy Markdown
Member

When the agent can't tell which file or folder the user meant, it had two bad options: guess and risk acting on the wrong path, or stop and ask in plain text and break its own flow. This adds a third. The agent calls a select_path tool, the user picks, and the same turn keeps going. No extra user message, no restart. It only fires when the path is genuinely ambiguous, so normal requests are untouched.

Before

image

After

Screenshot 2026-06-22 at 2 16 27 AM

https://www.loom.com/share/5495d1aff1a7488db532fbdbd0f55926

What's in here:

  • select_path tool definition and handler with two modes. Pick mode disambiguates between concrete candidates that already exist in the project. Browse mode is for when there's nothing concrete to offer, so the host opens a picker rooted at a sensible directory.
  • A small SelectionElicitor protocol (core/interaction/selection.py) so the core stays transport agnostic. It only knows "ask for a selection, get a path back" and does not care whether that's a terminal prompt or a GUI.
  • A CLI elicitor for the terminal, plus a console fallback and a graceful no-op when running headless.
  • Hosts inject their elicitor through ChatSessionConfig, and browse picks that come back relative are resolved against the request root.

This is the bottom of a three-repo stack. The picker is useless without a host to drive it, so the other two build on this:

  • cowork-server streams the request to the client and feeds the pick back into the paused turn.
  • cowork renders the card and opens the native OS picker.

Tested both modes locally through the full cowork stack.


Part of the inline path picker stack. Merge bottom up:

  1. anton: feat(select_path): inline file/folder picker for ambiguous paths #197 (this PR, the select_path tool and elicitor protocol)
  2. cowork-server: feat(selection): stream select_path requests and resolve picks mid-turn cowork-server#77 (streams the request, resolves the pick)
  3. cowork: feat(selector): inline native path picker for ambiguous paths cowork#212 (the native OS picker UI)

Related but independent: mindshub_inference #302 (https://github.com/mindsdb/mindshub_inference/pull/302) is the image side of the same multimodal and file effort. It translates image_url blocks for every provider and transcodes unsupported image types to PNG. It does not depend on this stack and this stack does not depend on it.

Adds a transport-agnostic SelectionElicitor strategy plus a select_path tool
the model calls ONLY when a path is genuinely ambiguous. The tool resolves
candidates confined to the project root (path-traversal guarded, .anton hidden),
auto-resolves a single match, reports no-match for zero, and prompts only when
>=2 candidates remain. The chosen path returns as the tool result so the agent
continues with no separate user message. CLI gets a terminal picker; embedding
hosts inject their own elicitor.
When the user references a file/folder without a resolvable path, the tool now
opens a navigable picker (browse mode) instead of forcing the model to ask for
a typed path. A ToolDef.prompt rule steers the model to the picker over asking.
Browse-mode picks are validated as any existing path of the requested kind;
pick mode (candidate disambiguation) is unchanged.
…gainst root

Lets the host pass a SelectionElicitor through ChatSessionConfig instead of
setting it on the session after construction, so cowork-server can wire its
streaming picker in one place. Browse-mode picks that come back relative are
now resolved against the request root, and the escape watcher resumes in a
finally so a tool error cannot leave it paused.
main is red (and so is every branch off it): the tests for the
procedural-memory section, conversation discipline, and skills loop landed,
but the matching prompt_builder/prompts source did not. build() still
required conversation_started while the tests call it without, so they died
with TypeError, and the discipline assertion failed.

This adds the missing half: conversation_started defaults to the live clock
when omitted, procedural memory moves after the other contexts to match
test_section_appears_after_other_contexts, and the discipline wording is made
consistent. Not picker work, but the picker branch can't go green until main
does, and this fixes both.
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.

1 participant