Add interactive askUserQuestion and configurable swarm spawn mode#209
Open
Zephyr709 wants to merge 34 commits into
Open
Add interactive askUserQuestion and configurable swarm spawn mode#209Zephyr709 wants to merge 34 commits into
Zephyr709 wants to merge 34 commits into
Conversation
Adds a real ToolSearch implementation that lets Anthropic OAuth sessions discover and unlock registry tools at runtime, including the previously unreachable askUserQuestion. - New tool: src/tool/tool_search.rs (15-entry curated deferred registry, scored matching, per-session unlock state) - Registry::definitions_for_names: resolve specific tools by registry key - Agent::tool_definitions appends unlocked tools (works around the locked_tools cache so unlocks mid-session reach the API) - AnthropicProvider::format_tools OAuth branch now passes through any unlocked tools alongside the 10 hardcoded Claude Code tools, with ephemeral cache_control moved to the actual last tool Tests: 5 tool_search unit tests + existing ask_user_question tests pass.
Replace the side-panel markdown rendering with a real blocking overlay: - Arrow keys / j-k to navigate options, Enter to pick - Recommended option pre-selected for fast Enter-to-confirm - Synthetic "Other (type custom answer)" row switches into free-form text input mode where the user can type any reply - Space toggles options in multi-select mode - Letter shortcuts (A, B, ...) jump to the matching option id - Esc cancels and the tool returns a Canceled outcome - 1h timeout fallback if no answer arrives Architecture: - New crate::ask_user module exposes a global pending-request registry keyed by request_id with a oneshot sender/receiver pair, mirroring the existing StdinInputRequest pattern. - BusEvent::AskUserQuestionOpened lets the tool publish a question for the TUI to render without holding direct App state. - src/tui/ask_user_modal.rs is the ratatui overlay widget; centered, uses Clear for full opacity, drawn last in draw() so it sits on top of chat + side panel. - src/tui/app/ask_user_modal_app.rs is the App glue (open / dispatch keys / submit answer). - handle_modal_key short-circuits to the modal when visible so the user gets full input focus. Side-panel close: Alt+M already toggles visibility (documented in help). Mermaid render: gated behind JCODE_ENABLE_MERMAID=1 and the mermaid-renderer feature in jcode-tui-markdown which is not enabled by default. Not addressed in this commit; tracked separately. 17 tests pass: 9 modal unit tests + 4 tool integration tests with unique session ids + 3 ask_user registry tests + 1 tool_search test.
The askUserQuestion tool runs inside the shared server process while the TUI is a remote-client. Bus events do not cross the IPC boundary, so the previous commit's modal would never open for remote-mode clients. This commit adds the missing wire round-trip: Server -> Client (push the question): - New jcode_protocol::AskUserQuestion/Option/Answer payload types shared by both ends as the wire schema and by crate::ask_user as the in-process bus payload (one source of truth via type aliases). - New ServerEvent::AskUserQuestionOpened variant. - client_lifecycle forwards BusEvent::AskUserQuestionOpened to the active client when session_id matches, mirroring SidePanelUpdated. - remote/server_events.rs dispatches the event by opening the modal in-place via App::open_ask_user_modal. Client -> Server (return the answer): - New Request::SubmitAskUserAnswer variant (tagged lightweight so it cuts through processing-state queues). - RemoteConnection::submit_ask_user_answer uses the detached send path so the synchronous key handler does not have to await. - client_lifecycle dispatches SubmitAskUserAnswer to crate::ask_user::submit_answer to wake the pending oneshot. Modal-side glue: - AskUserModalApp now enqueues each completed answer into a new App::pending_ask_user_answers vec in addition to calling local submit_answer (so in-process / test paths still work uniformly). - remote::handle_tick drains the queue every tick and dispatches each answer via remote.submit_ask_user_answer. Result: the modal opens in the remote TUI and the picked answer reaches the agent's tool task across the socket. Verified end-to-end against the live shared-server build (4s reload).
Introduces [provider.model_allowlist] in config.toml so users can restrict the model picker and `/model` command to a chosen subset for each built-in provider. Previously the allowlist field on [providers.<profile>.models] only applied to custom openai-compatible profiles, so OAuth providers (claude/openai/gemini/antigravity) always exposed the full vendor catalog. The new field is a BTreeMap<String, Vec<String>> keyed by the provider canonical name (anthropic/openai/gemini/antigravity, with "claude" as an alias for anthropic). Substring + case-insensitive matching keeps patterns short. Filtering is applied at every visible surface that lists models: - Agent::available_models_display() and Agent::model_routes() - CLI `jcode model list` - TUI slash-command suggestion list - TUI inline model picker route fetch Round-trip is covered by new tests in jcode-config-types.
Match Claude Code's pattern: the modal takes over the chat input slot instead of floating as a centered popup. Cleaner focus and clearly communicates that the agent is blocked on the user. - new AskUserModal::render_inline(frame, area) draws into a host-supplied Rect - AskUserModal::desired_height() reports needed rows - ui.rs reserves the input chunk for the modal when visible and skips drawing the input box / inline UI / idle animation
The openrouter provider impl backs every named openai-compatible profile (opencode-go, ollama-cloud, deepseek, ...) and always reports provider.name() == "openrouter". Extend provider_model_allowlist_patterns() so it also accepts the active profile id, which is the natural key users expect in `[provider.model_allowlist]` (e.g. `opencode-go = [\"deepseek-v4-flash\"]`). Adds active_openai_compatible_profile_id() helper alongside the existing active_openai_compatible_display_name(). The lookup tries the canonical provider name, the `claude`/`anthropic` alias pair, and finally the active openai-compatible profile id.
Previously filter_model_routes_by_allowlist applied a single allowlist key to every route in the list. When the agent surfaces the aggregated MultiProvider routes, that meant only the current provider's allowlist was respected and other providers' routes were never filtered. Resolve the allowlist key from each route's (provider, api_method) pair so cross-provider model pickers honour every configured allowlist: - Anthropic / OpenAI / Gemini / Antigravity / Copilot / Cursor map to their respective allowlist keys. - openai-compatible:<profile-id> routes (opencode-go, ollama-cloud, deepseek, ...) map to the embedded profile id. Providers without a matching allowlist remain unrestricted.
- docker-compose.agent-db.yml: postgres:16-alpine on port 5432, well-known localhost creds - db-execute tool: agents execute scoped SQL via docker exec psql into their session schema - Schema-per-session isolation (agent_<session_suffix> convention) - Session-id sanitization: lowercase alphanumeric with leading-letter guarantee - Compiles clean, passes end-to-end smoke test with CRUD + cross-schema namespace isolation Part of goal: autonomous-local-neon-postgres-analytics-substrate-for-agents
When JCODE_PROVIDER_PROFILE_ACTIVE is not set (no CLI override), clear any lingering JCODE_NAMED_PROVIDER_PROFILE, JCODE_ACTIVE_PROVIDER, JCODE_RUNTIME_PROVIDER, and JCODE_OPENROUTER_CACHE_NAMESPACE env vars before the default_provider guard runs. Without this, stale env from a previous jcode session's shell environment blocks config.toml's default_provider from taking effect, causing the model picker and session provider key to resolve to the wrong provider. Regression tests cover both the stale-clear case and the explicit CLI override preservation case.
This was referenced May 21, 2026
TypeScript/QuickJS extension runtime for user-defined tools, commands, and hooks
quangdang46/jcode#3
Open
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This branch collects the current agent/tooling UX work:
askUserQuestiontool support and an interactive TUI modalspawn_mode = visible | headless | autoNotable new swarm behavior
swarm spawnand spawn-backed assignment actions can now request:visible: launch a terminal windowheadless: create an in-process/headless worker, avoiding Ghostty focus stealingauto: preserve historical visible-then-headless fallback behaviorValidation
cargo fmt --checkgit diff --checkcargo check -p jcode-protocolcargo check -p jcodecargo check -p jcode-desktopcargo test -p jcode-protocol test_comm_spawn_roundtrip_with_optional_nonce -- --nocapturecargo test -p jcode-protocol test_comm_assign_next_roundtrip -- --nocapturecargo test -p jcode --lib assign_next_prefers_worker_with_dependency_context -- --nocapturecargo test -p jcode --lib assign_next_prefers_worker_with_matching_subsystem_metadata -- --nocaptureFollow-ups
headlessglobally.