feat(swarm): lineage, working_dir, require_reply; session-scoped busy state, pinnable sidebar; config/glob/tool-call fixes - #29
Open
hbruceweaver wants to merge 25 commits into
Conversation
A `**` glob rooted at a broad directory (e.g. a session whose workspace is $HOME) walked iCloud/Library/go/pkg/mod with Follow=true and no cancellation, stalling review/agent sub-sessions for 15+ minutes. The fastwalk fallback now checks ctx per entry and returns partial matches, no longer follows symlinks (rg -L dropped to match), and the glob tool applies a 30s budget that reports partial results instead of hanging the turn.
…failing the call Models under load drop bash's required description or emit truncated argument JSON, and fantasy rejected the whole call. description is now optional (first line of the command by default) and a deterministic RepairToolCall hook fixes malformed JSON and fills omitted descriptive params; anything semantic is still left for the model to retry.
Providers() ignored its *Config argument and always returned the full embedded catalog, so options.disable_default_providers only took effect in setupProviders. Every catalog consumer — most visibly the TUI model picker — still listed all ~38 embedded providers. Selecting a model from one of them (e.g. anthropic/claude-fable-5 rather than a configured provider's copy) then prompted for an API key the user never intended to supply, with no indication that a same-named model was available from a working provider. Honor the flag in Providers() so the catalog matches provider loading. Nil config and nil Options fall back to the embedded catalog.
The model dialog and 'crush run -m' both build a SelectedModel from just provider+model, while configureSelectedModels' default path seeds MaxTokens and ReasoningEffort from the catalog. Switching models therefore dropped both. Dropping ReasoningEffort is not merely a lost default: shouldSetEffort gates on the selection's effort being one of the model's reasoning_levels, so an empty effort means Crush never sends reasoning_effort, the provider is never asked to reason, and thinking traces silently vanish after a model switch. UpdatePreferredModel now fills unset fields from the model's catalog entry. Only unset fields are touched, so an explicit choice still wins. An effort is seeded only when the catalog advertises it in reasoning_levels — an unrecognized effort would fail the same membership check anyway, and is a hard request error on some providers. Verified live: 'crush run -m dp-gpt/...' now persists reasoning_effort and stores reasoning parts; the default path is unchanged.
…red result metadata, pinned-dir turns
…before its turn ends
… instead of a corrupted assistant turn
…s stop blocking the event loop on RPCs
Nesting spawned workers under their spawner pulled busy workers down under idle spawners, so the running session was no longer near the top of its group. Order top-level subtrees by their most urgent member (active > pending > busy > unread > newest), re-rank immediately when a prompt becomes pending, and tint busy/pending titles with the status color so a running row reads as running beyond its one-cell dot.
The per-workspace cap sliced the nested order blindly, so a busy worker (or the spawner it nests under) could land in the "… N more" overflow row. Treat the cap as a soft budget: pending/busy rows and their ancestor chain are always shown first, the remaining slots fill in layout order, and if the must-show rows alone exceed the cap they are all shown anyway.
The navigator only scrolled by moving the cursor, so a long list could not be browsed with the wheel. Wheel events over the sidebar rect now move its viewport (without taking focus); the cursor is pulled onto the nearest visible row so Render's keep-cursor-visible clamp cannot undo the scroll.
Co-authored-by: Cursor <cursoragent@cursor.com>
hbruceweaver
force-pushed
the
port/upstream-feature
branch
from
September 8, 2026 18:35
0db3589 to
83db543
Compare
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
Ports the rest of my
local/dailywork onto the post-#27ModelRefdesign. Everything that #27 superseded (orchestrator role,Session.Model, per-session picker,run -mstamp) is dropped; what's left is:working_dir—sessions.spawned_by_session_id/spawned_by_workspace_id(migration20260904010000), stamped from the trusted sender onswarm new(never from model input).working_dirparam onswarm new, defaulting topath, validated to resolve to the target workspace (subdir or linked git worktree). Swarm-driven turns with no client cwd run in the session's pinned dir, so a worker spawned into a sibling worktree actually works there. StructuredSwarmResponseMetadataon every swarm result. Sidebar nests workers under their spawner; picker showsby <color-animal>.require_replyon the swarm tool — the target owes the sender a reply before its turn can end: told up front via a trailer, nudged with a continuation turn (max 2), then its final message (or error) is auto-forwarded. Tracked byswarm.ReplyTracker, coordinator-owned.fix(config)×2 —Providers()honorsdisable_default_providers(the picker still listed all embedded providers);UpdatePreferredModelbackfillsmax_tokens/reasoning_effortfrom the catalog so switching models doesn't silently drop reasoning. Both have regression tests.isAgentBusy()asked the workspace-wide flag, so with swarm workers streaming in the same workspace an idle session showedesc cancel(with nothing to cancel), the working placeholder, and refused/continue, New Session, Summarize and the external editor. Now scoped to the open session;isWorkspaceBusy()kept for the two genuinely workspace-wide uses (LSP stop-all, deferred model reload).sessionAgent.Runreturned early on non-cancel errors (e.g. a gateway dropping the stream mid tool-call) withoutdispatchNextQueued, so prompts queued behind the failed turn sat there until the next send.alt+s(orpwhile focused, or the palette) pins the navigator so it survives session switches;ctrl+sthen toggles focus, esc/hrelease focus,ctrl+fre-shows it on leaving fullscreen. Persisted asoptions.tui.sessions_sidebar_pinned. Enter on the "…N more" overflow row now expands that workspace in place ("show less" collapses) instead of opening the picker./fork [message]— fork the open conversation from the prompt:/forkalone forks at the latest user message; typing/forkopens a completion list of the session's user messages (newest first, ordinal + preview) so an earlier point can be chosen like/modelchooses a model. Accepts an id prefix,#N, orlast. Also a "Fork Conversation" palette entry. The existingshift+F-on-a-message path and the fork dialog (title, worktree toggle) are unchanged.alt+z/ctrl+shift+z(or palette) parks the drafted prompt + attachments so another message can go first; press again to restore, or swap when both exist. Also: todo/queue pills are hidden while the chat is previewing another session from the navigator (they describe the committed session and were following the preview around), andctrl+shift+sis accepted as a pin alias on Kitty-protocol terminals.permissions.sysadmin— config option to start every workspace with sysadmin mode on (was palette-toggle only).isAgentBusy()(ready + session-busy) plusAgentQueuedPromptsare called several times perUpdateand perView, each a blocking HTTP round trip. During a streaming turn (tens of message events/sec) that is hundreds of RPCs/sec on the Bubble Tea goroutine, and keypresses stall until the stream ends — worst when scrolled to the bottom (follow mode re-renders every delta).ClientWorkspacenow memoizes those answers (1s TTL backstop) and drops them eagerly on agent/run-complete/attention/message/session events and on local send/cancel/clear-queue, so the hot path makes no RPCs.tool_use(tool-input-start) that never completes, fantasy's copy of the step is missing a block the model produced. Sending it back is fatal under Anthropic extended thinking (400 thinking blocks in the latest assistant message cannot be modified) and silently lossy elsewhere.OnStepFinishnow records such steps andPrepareStepreplays them as a single user message (assistant text + completed calls with results + which calls were lost) so the turn continues instead of dying.20260904000000migration (the spawned-by columns under the id upstream now uses formodel_ref) no longer failsALTER … duplicate column nameon open. The spawned-by migration is idempotent,20260906000000_ensure_session_model_ref.sqlbackfills the column, andsessions_columns.goadds any still-missing session columns after migrate. Regression test inconnect_test.go.WaitForIdlewake). If fix(backend): report accepted-but-not-yet-active runs as busy in session listings #28 lands first this rebases down to 10 commits.ModelRef adaptations
session.CreateOptions{ModelRef, SpawnedBy*, WorkingDir}withCreateWithModelRefkept as a thin wrapper so existing call sites don't churn; all stamps go into theCreateSessionINSERT (sqlc regenerated).SwarmSpawnOptions/SwarmNewOptionscarryModelRef string; model-ref validation against the target workspace is unchanged and runs before any insert. Swagger regenerated once at the end.Test plan
go build ./...,go veton touched packagesgo testgreen on agent, agent/tools, backend, session, db, proto, message, swarm, config, fsext, app, ui/dialog, ui/modelfork_slash_test.go,stash_test.go,agent_state_cache_test.go,incomplete_step_test.go,TestIsAgentBusy_ScopedToOpenSession,sessionsidebar_pin_test.go,TestSidebar_ExpandOverflowInPlace,TestConfig_PermissionsSysadmin,TestCreateWithOptionsStampsLineageAndWorkingDir,swarm_lineage_test.go(sibling-worktree pin, working_dir validation, trusted spawner),swarm_test.go(metadata, working_dir param),swarm_reply_test.go×2,reply_test.go,model_defaults_test.go,TestProviders_DisableDefaultProvidersSuppressesCataloginternal/server TestE2E_ShutdownCallbackFiresWhenLastClientLeaves,internal/workspace TestClientWorkspace_ConnectionDropIsReportedAndRetried(both fail identically on cleanfeature)