Bugfixes batches#69
Merged
Merged
Conversation
…warm-up (mitto-8qp)
Resolves RPC context-deadline storms (~157/day, sonnet-5) caused by an
aggressive shrinking retry budget that timed out during large-context
model warm-up. The prior attempt-1 timeout (12s) was smaller than the
observed warm-up latency of large-context models (e.g.
claude-sonnet-5-0-500k, >12s), so every attempt of the shrinking
{12s,8s,5s} schedule timed out even though most of the outer 90s
setModelAsyncCallerBudget sat unused.
Widen the per-attempt schedule to {20s,15s,8s} (~44s total per caller),
which still stays within the contention bound covered by
setModelAsyncCallerBudget at up to 4 concurrent callers. Update all
mirrored comments/docs describing the old 25s math, and add a
cross-package mirrored-constants-drift convention note so future
schedule changes remember to update internal/conversation's local
mirror.
Add TestSetModelSchedule_LargeContextModelSucceedsWithinOuterBudget to
internal/acpproc/acp_process_manager_test.go.
… cline (mitto-llr) Audit of vendor-specific MCP server discovery scripts found suspicious config paths causing missing MCP servers in agent discovery. Corrects claude-code (now reads ~/.claude.json) and fixes codex and cline discovery scripts. Add TestMCPList_RealConfigPaths_mitto_llr table-driven regression test in internal/agents/manager_test.go.
…saturation poll (mitto-tfb) The saturation-detection infrastructure from mitto-13ck.2 tracked consecutive session/new + LoadSession + set_model context-deadlines but only ever failed fast — it never recycled the degraded shared process, so a saturated process could hang around indefinitely. Add a public, non-mutating IsSaturated() accessor on SharedACPProcess (distinct from the private isSaturated(), which self-clears to probe mode) so the GC's health tier can poll saturation state without perturbing the state machine, and wire it into the GC to proactively recycle degraded idle shared processes. Add TestGCHealthTier_RecyclesSaturatedIdleProcess.
…-consumed Add config.DefaultModelProfiles() as the canonical, hardcoded set of 7 model-capability profiles (Claude/Opus/Sonnet/Haiku/GPT-5/GPT-4/ Gemini), kept in sync with config/config.default.yaml's `models:` block by `make check-model-tags`. Unlike config.default.yaml, which only seeds settings.json on first run, these profiles are always available via the new (*Config).EffectiveModelProfiles(), which unions settings.json's Models with the canonical defaults (user profiles win on name collision). Route all tag/name resolution (ModelProfileByName, ModelProfilesByTag, ResolveModelTags) through EffectiveModelProfiles so a prompt's preferredModels (modelTag/modelName) resolves even when settings.json predates or omits `models:` — previously this silently no-opped to the baseline model. Add config.CanonicalModelTags() (sorted, de-duped tag set) and wire it into GET /api/config as model_tags so SettingsDialog.js can suggest canonical tags via a <datalist> when editing a profile's tags. Add `make check-model-tags` target validating builtin prompt modelTag references and Go/YAML drift.
Per-folder/global shortcut toolbars (conversation, beads-issue, tasks-list) can carry several shortcut buttons that overflow the toolbar on phone-width viewports. Keep only the first shortcut visible below 640px via a new .mitto-shortcut-extra utility applied to buttons at index > 0. Also document the un-loop/re-loop LoopStore persistence symmetry requirement and note that a concurrent loop conversation sharing the same repo can stash/reset the working directory mid-task, silently dropping a prior turn's uncommitted edits.
…model ERROR (mitto-5q8)
Part A: add JS-heap-OOM stderr patterns to stderrCrashPatterns so
StartStderrMonitor fires onCrashDetected immediately on agent Node/V8
heap exhaustion, speeding proactive recycle instead of waiting for the
60s control-request timeout.
Part B: in applyConfigOptionWithOpts, downgrade the set-model failure
log to WARN ("Skipping model change; agent process is restarting")
when IsACPConnectionError(err) is true (dead/restarting shared ACP
process), since this is a transient restart-gap condition rather than
a genuine model-selection failure. Genuine failures on a live process
still log ERROR. Control flow and the wrapped error return are
unchanged.
…err (mitto-9o6) execRunner.Run discarded stdout on failure, so when bd exits non-zero during dolt backend warm-up but writes its diagnostic to stdout (or nothing) instead of stderr, the logged error carried stderr="" and the real cause was invisible. Add diagnosticOutput(): use stderr when bd wrote to it, otherwise fall back to trimmed stdout, rune-safe length-capped at 2000 chars to avoid flooding logs. IsNotFound is unaffected since bd's not-found message always goes to stderr. Graceful degradation (the other half of mitto-9o6) was already covered by the existing runBeadsRead bounded-retry logic.
Loop conversations auto-archived due to broken ACP (ArchiveReasonACPFailures) previously stayed archived forever unless a human noticed and manually unarchived them. Add LoopRunner.checkAutoUnarchiveRecovery(), polled once per minute from RunOnce(): - Eligibility: archived with ArchiveReasonACPFailures and a loop config still exists (store.Loop(id).Get() succeeds). Manual/inactivity archives and non-loop ACP failures are excluded. - Retry cadence: ~1h per conversation, anchored on Metadata.AutoUnarchiveLastAttemptAt (or ArchivedAt) so it survives restarts. - Anti-storm stagger: 10m global minimum gap between attempts; at most the single most-overdue session is retried per poll. - A retry performs the same steps as a manual unarchive: clear archive fields, resume the ACP process, broadcast state changes, and reuse handlers.Handlers.RestoreLoopOnUnarchive (now exported) to re-enable the loop. - Persist the attempt timestamp before invoking the callback so it survives a crash mid-resume; clear it only on success. Adds the AutoUnarchiveLastAttemptAt metadata field, wiring in server.go, a full test suite in loop_runner_test.go, and doc updates.
Error toasts previously auto-dismissed after 10s like warnings, risking a user missing a critical message. error-style toasts now never auto-dismiss; they persist until the user closes them via the dismiss button.
'auggie mcp list' resolves <workspace> to the git toplevel, not the Mitto workspace's working_dir. A workspace whose working_dir is a git subdirectory sees servers registered in <git-root>/.augment/settings.local.json instead of its own file, so the MCP tab can show servers the running agent never actually loads. Document the divergence and workarounds (move to git-root config, register at user scope, or point working_dir at the git root).
…ix, and investigate prompt - Replace the single AGENTS.md 'How to answer customer questions' section with a per-channel knowledge file (.mitto/slack-support-<channel-id>.md), so each Slack channel keeps its own repos/docs/runbooks/escalation guidance instead of sharing one project-wide note. - Fix the need-info state machine: once the customer replies to a need-info bead, it now transitions to awaiting-us instead of staying stranded in need-info. - Fix the mitto_ui_textbox 'Result' field name from the non-existent 'full' to 'text' across all support prompts. - Add 'Support: investigate' — a new prompt that digs for an answer using the channel knowledge file, records findings on the bead, and produces a draft reply without ever posting to Slack. - 'Support: reply to user' now reuses an existing DRAFT comment instead of regenerating it, persists the draft to the bead before opening the review dialog, and correctly branches on submit/timeout/abort so a draft is never lost when the reviewer is away.
…e busy (mitto-1h0) The mitto-tfb proactive recycle (GC Tier 5) only recycles a saturated shared ACP process when it is fully idle. During the 2026-07-06 15:53-15:55 recurrence, session/new and set_model timed out at full RPC deadline while those very RPCs were in flight (ActiveRPCs>0), so Tier 5's idle gate skipped the process and it stayed wedged, starving user and auxiliary sessions. Add GC Tier 6: recycles a CONFIRMED-degraded process (saturationLevel >= 2, i.e. it tripped saturation, served its cooldown, and its post-cooldown probe also timed out) even while busy, dropping the ActiveRPCs()/IsPrompting idle gate. Guarded by a mandatory no-streamed-progress check (SessionInfo's new LastStreamActivityAt) so a legitimately slow-but-progressing prompt is never killed. Tier 5 is unchanged. - shared_acp_process.go: non-mutating IsConfirmedDegraded()/SaturationLevel() getters (saturationMu-guarded, never perturb the probe state machine). - session_info.go / background_session.go / session_manager.go: plumb LastStreamActivityAt from BackgroundSession into SessionInfo. - acp_process_gc.go: Tier 6 block after Tier 5, same recycle action (MarkGCSuspended + sessionClose + StopProcess). - acp_process_gc_test.go: recycle-busy-degraded, skip-progressing-degraded, skip-level1-busy. - docs/devel/acp.md: document Tier 5 and Tier 6.
…d of bare 500 bd refuses to auto-apply pending schema migrations to a remote-backed database (only one designated clone may migrate it), so every read against that store previously failed and surfaced to the frontend as a generic 500 with no indication of what was wrong or how to fix it. - internal/beads/beads.go: IsSchemaSkew() detects the skew signature in a command's stderr; SchemaSkewDBPath() best-effort parses the offending database path out of the same stderr for the remediation message. - internal/web/handlers/beads.go: writeBeadsError() now distinguishes a schema skew from a genuine internal error, responding HTTP 409 with a remediation hint (db path + migrate/bootstrap instructions) instead of a bare 500. - internal/web/handlers/helpers.go: new errCodeBeadsSchemaSkew error code. - web/static/components/BeadsView.js: renders a distinct actionable error card (schemaSkew state) when the list load fails with that error code, instead of the plain error text. - Unit tests for IsSchemaSkew/SchemaSkewDBPath and the handler's 409 path.
…odified (mitto-tf9) GET /api/workspace-prompts derived its Last-Modified header (and the If-Modified-Since 304 short-circuit) solely from the workspace .mittorc mtime, even though the returned prompt list is merged from several other sources whose files can change independently: the global prompts dir (MITTO_DIR/prompts, including builtins deployed by `mitto prompts update-builtin`), the settings file, additional configured prompts_dirs, and the workspace's own .mitto/prompts. A prompt change that didn't touch .mittorc kept getting 304'd until an unrelated .mittorc mtime bump, so the frontend never saw it without a hard reload. computeWorkspacePromptsLastModified() now takes the max mtime across every contributing source, so any of them advancing the header correctly invalidates the client's cached list. Adds a regression test (TestWorkspacePrompts_BuiltinDeployAdvancesLastModified) deploying a builtin prompt without touching .mittorc and asserting the conditional GET returns 200 with the new prompt.
…mitto-8ul.1) cgw-managed-tools conversations failed to start because Auggie blocks servicing session/new until its MCP servers finish initializing (~225s), while SharedACPProcess used a fixed 25s per-attempt / 75s total budget. The RPC timed out, tripped the saturation circuit breaker after 3 consecutive failures, and left the conversation stuck in is_prompting=true with no useful feedback. - SessionConfig.McpInitTimeout (default 240s, configurable/disableable) in internal/config/settings.go. - SharedACPProcess.coldMCPBudget()/beginMCPInitWindow() widen the session/new and session/load per-attempt and total budgets to MCPInitTimeout only for the first call on a cold process; once one session RPC succeeds (mcpInitDone) subsequent calls use the normal 25s budget again. - Saturation guard: a DeadlineExceeded during the extended cold window is expected agent-side latency, not evidence of a hung process, and no longer counts toward the saturation threshold. - Stderr-signature detection (bgsession_acp_process.go) recognizes when the agent reports MCP-init progress or an internal MCP-init timeout; the latter aborts the pending RPC immediately via mcpInitTimeoutCh instead of waiting out the full budget. - New mcp_initializing / mcp_init_timed_out WebSocket notifications (server.go, ws_messages.go) drive frontend toasts (useBackgroundNotifications.js, useWebSocket.js) so the UI shows "Starting MCP servers..." instead of an indefinite spinner. - RecommendedLoadTimeout() lets shared_session_handshaker.go widen its outer 30s session/load timeout so it doesn't truncate the extended budget. - Mock ACP server updated to simulate delayed/timed-out MCP init for the new integration tests. Tests: mcp_init_budget_test.go (5 unit tests on budget selection), tests/integration/inprocess/mcp_init_timeout_test.go (TestMCPInitTimeout_FailsFastOnStderrSignal, TestMCPInitDelay_ExtendedBudgetAllowsSuccess). Full integration suite 143/0, gofmt/vet clean.
…mitto-mqe) LoadPromptsFromDir silently skipped any .prompt.yaml file that failed to parse, with only a code comment noting "In production, this would use a logger" — so a broken prompt file vanished with no diagnostic signal anywhere. - LoadPromptsFromDirWithErrors (prompts.go) is the new entry point: returns both the successfully-loaded prompts and a PromptLoadError per failing file, and logs each failure at WARN. LoadPromptsFromDir is kept as a thin wrapper for existing callers. - PromptsCache.reload() collects load errors across all scanned prompt directories; PromptsCache.LoadErrors() exposes the most recent set. - handleGlobalEventsWS (events_ws.go) sends a persistent error-style toast to newly-connected clients for each outstanding load error, covering the case where a prompt file was already broken at server startup (not just edit-time). Tests: TestLoadPromptsFromDirWithErrors_ReportsBadFileAndKeepsGood, TestPromptsCache_LoadErrors_ReportsBadFile. gofmt/build/vet/config tests green.
…+budget-gated storms (mitto-5eq)
… metadata.yaml (mitto-k6h)
…ation signal (mitto-k6h)
… only slow workspaces (mitto-mw0) Replace eager pinning of all workspaces with a self-selecting model: warm a keepalive session, probe its health (session/new latency + MCP readiness), and pin only workspaces that are slow or broken. Healthy workspaces cost nothing — GC reaps them normally. Config: - Add PrewarmConfig at Config.Prewarm with thresholds session_new_fast=10s, mcp_ready=10s, healthy_probes_to_unpin=3, max_pin_duration=30m, max_pinned_workspaces=5 (yaml + nil-safe parse accessors). Pin state: - Add Pinned/PinReason/PinExpiry to SessionInfo. - Pin state machine on ACPProcessManager: PinWorkspace, UnpinWorkspace, IsPinned, RecordPrewarmProbeResult, ExpirePinsAndAlert, FirePrewarmPinAlert. Probe + controller: - probePrewarmHealth times the PurposeKeepAlive session/new and reads MCPInitDone/MCPInitTimedOut to compute a health verdict with reason codes (session_new_failed, mcp_timeout, mcp_not_ready, slow_session_new). - Hysteresis: unpin only after N consecutive healthy probes. - MaxPinDuration cap folded into EnsureMCPBackoffRetry via PrewarmPinReevaluator (no extra timer). GC: - Tier-1 (recycle) and Tier-2 (shutdown) pin exemptions, expiry-aware so the max-pin-duration cap self-heals a stuck pin. Observability: - SetOnPrewarmPinAlert -> BroadcastPrewarmPinAlert (prewarm_pin_alert WS message) fired at-most-once per pin for MCP-timeout pins and on cap expiry. Tests: 8 prewarm_pin tests + 3 GC pin tests + 2 config tests, all green. Follow-up: mitto-yns (frontend toast handler for prewarm_pin_alert).
…tto-29q) coldMCPBudget's mcpInitDone latch only granted the extended MCPInitTimeout budget for the FIRST successful session/new on a shared process. Agents like Auggie re-run the full MCP initialize handshake on EVERY session/new, so every subsequent conversation or unarchive on the same warm process fell back to the normal 25s RPC deadline and failed with context deadline exceeded while the agent was still waiting on MCP servers. Edge-detect the false->true transition of mcpInitInProgress in onMCPInitProgress (CompareAndSwap) so it fires once per handshake episode instead of once per process lifetime, and let coldMCPBudget / RecommendedLoadTimeout re-grant the extended budget whenever mcpInitInProgress is true, even after mcpInitDone has latched. Adds regression coverage for budget re-grant after a prior success (TestColdMCPBudget_ReinitAfterFirstSuccessReExtends) and for reverting to the normal budget once idle (TestColdMCPBudget_WarmIdleNotInProgressRevertsToNormal).
…tto-f51) PromptWithMeta's deferred session/new handshake ran with the session's base context (no deadline) while isPrompting was already latched true. On a cold-start MCP handshake this could sit for up to ~240s per retry attempt with no watchdog armed and no streamed activity, so a wedged handshake left the conversation stuck in is_prompting forever with no recovery short of archive/unarchive or a manual ForceReset. Add pdRecommendedHandshakeDeadline() to read the shared process's own RecommendedLoadTimeout hint, and bound each completeDeferredHandshake attempt in a goroutine watched by runHandshakeWithWatchdog against that deadline plus a margin (falling back to handshakeWatchdogFallback when the process reports no recommendation). A watchdog trip surfaces a friendly "still starting up" message, resets prompting state, and does not retry (the orphaned goroutine still holds the shared process, so retrying would just re-hang). Adds regression coverage for the watchdog firing on a wedged handshake and for the fallback deadline being used when the dependency reports zero.
mitto-mw0's adaptive ACP/MCP pre-warming broadcasts a prewarm_pin_alert WebSocket message when a workspace is pinned due to a slow MCP-init, or when the max-pin-duration cap force-releases a still-broken pin, but there was no frontend consumer so the warning never reached the user. useWebSocket.js's handleGlobalEvent dispatch re-emits the message as a mitto:prewarm_pin_alert CustomEvent; useBackgroundNotifications.js listens for it and shows a warning toast naming the workspace (workspace_name, falling back to the basename of working_dir), with expired-aware copy distinguishing "pin released after max-pin-duration cap" from "workspace pinned due to slow MCP", and appending the reason when present.
…(mitto-54k.1)
On cold start, ~N sessions in a workspace reconnect their WebSockets
nearly simultaneously; each spawned its own unbounded goroutine calling
sessionManager.ResumeSession, saturating the Mitto process and starving
the agent's inbound HTTP initialize/tools/list on :5757/mcp (timeouts up
to 560s blocking the first prompt).
Introduce a small counting semaphore (resumeSemaphore) on *Server that
bounds concurrent interactive ResumeSession calls to a configurable
limit (default 3). Acquire INSIDE the spawned resume goroutine so the
WebSocket handler is never blocked; the frontend still receives
'connected' immediately with is_running=false. Release after
ResumeSession returns (success or failure), preserving all existing
post-resume side effects (negative-cache invalidation,
tryAttachToSession, BroadcastACPStarted, TriggerFollowUpSuggestions).
The user-focused ensure_resumed foreground path (session_ws.go:~1994)
intentionally BYPASSES this bound so the session the user is actively
looking at resumes first, even when the cold-start fan-out has
saturated the interactive pool.
Config knob 'startup_resume_concurrency' mirrors the existing
'startup_stagger_ms' pattern (config.SessionConfig field +
Default/Get accessor, threaded through internal/config/config.go
raw->typed). Wired in server.go next to the loop-runner stagger.
Tests: TestResumeSemaphore_{BoundsConcurrency,ForegroundBypass,
ClampsNonPositiveCapacity,NilReceiverIsNoop} in
internal/web/resume_semaphore_test.go +
TestSessionConfig_GetStartupResumeConcurrency in
internal/config/settings_test.go.
…n (mitto-54k.2) Fix B for mitto-54k (Cold-start MCP starvation): independent safety net alongside Fix A's resume-storm bound (mitto-54k.1). Audit of internal/mcpserver/server.go confirms Mitto's own inbound /mcp initialize + tools/list are served entirely by the go-sdk's mcp.NewStreamableHTTPHandler from the statically-registered tool table (built at NewServer time via mcp.AddTool). The handshake path never acquires Mitto's internal locks (s.mu, s.sessionsMu) nor calls any blocking helper (WaitForPendingRequest, correlation waits, store lookups); no receiving/sending middlewares are registered. Add TestFastPath_InboundInitAndToolsListStayBoundedUnderLoad as a regression guard: starts a real MCP server, applies 32-way concurrent initialize+tools/list round-trips (each a fresh MCP client session over StreamableClientTransport), and asserts every round-trip completes under a 2 s budget with a stable, non-empty tool set. Observed max round-trip on this machine: ~35ms under load with race detector. Document the warm-up fast-path in docs/devel/mcp-tool-discovery.md, cross-referencing Fix A as the source-side fix. - internal/mcpserver/server_fastpath_test.go (new) - docs/devel/mcp-tool-discovery.md
The project-memory note in .augment/rules/42-mcpserver-development.md and CLAUDE.md was written while the gap was open. Update it now that both fixes landed on this branch: - mitto-54k.1 (eea071d): bounds the interactive resume storm at the source via a per-Server resume semaphore. - mitto-54k.2 (9cbf025): confirmed the inbound /mcp initialize+tools/list handshake is already lock-free (SDK static tool table), backed by a regression test.
Harden the existing Support prompt suite so a draft reply is never lost to a review-dialog timeout/abort or left only in the chat transcript: - Save the proposed message to the bead as a DRAFT [OUTBOUND] comment BEFORE opening the mitto_ui_textbox review dialog (support-gather-info, support-investigate). - Explicit on-submit / on-timeout / on-abort branching for the review result; never post to Slack unless the user actually submits. - 'Never dump the draft as chat text, never ask a free-text approve/hold/post question' hand-off rules once state:drafting is set. - support-watch-channel: allow a short blocking mitto_ui_form/textbox ask ONLY for the per-channel knowledge-file gate, even during a silent scheduled run, since the run cannot proceed at all without that guidance; falls back to a non-blocking notify + skip-drafting-this-run on timeout. - support-continue-conversation: updated description to reflect the review-before-post flow.
New prompt that periodically sweeps all tracked support-question beads: refreshes each from its linked Slack thread, closes or flags stale/irrelevant tickets (with user confirmation), and re-evaluates priority/status/next-steps for everything that stays open. Read-only on Slack — never posts a reply.
…ld set_model + harden warm-once barrier (mitto-54k.5, mitto-54k.3) The warm-once barrier (mitto-54k.3) fixed the session/new MCP-init wedge, but the cold-start bottleneck moved one RPC downstream to session/set_model: the interactive prompt goroutine called applyModelPreference synchronously before the Prompt RPC, so a slow set_model on a cold agent (~85s observed in cgw, Auggie Opus) blocked the first "Say hello" until "Agent slow during prompt". Fix E (mitto-54k.5): make the interactive model switch best-effort/async. applyModelPreference now runs SetSessionModel in a background goroutine bounded by modelSwitchAsyncBudget (90s, session-ctx), and waits only modelSwitchSyncGrace (3s) for it to land. A warm switch applies to THIS turn; a cold/slow switch is deferred to the background (applying to the NEXT turn) and the prompt dispatches immediately on the current model. Override-pill/baseline semantics preserved via finalizeOverride (happens-before via close(done)); setModelSem serialisation and the mitto-29q re-arm are unchanged (switch still goes through pdSetActiveModelOnly). Barrier hardening (mitto-54k.3): shared_acp_process.go NewSession/LoadSession now do a post-acquire warmth re-check — a caller that finds mcpInitDone=true after waiting on the gate releases it immediately and recomputes warm budgets instead of holding it through its RPC. Entry uses the raw cold predicate so mitto-29q warm per-session re-handshakes still bypass the barrier. Tests: prompt_dispatcher_test.go asserts the cold path logs "Deferring model switch to background" and dispatches without blocking; mcp_init_budget_test.go adds TestWarmOnceBarrier_OnlyOneHolderThroughWarmup. go vet + unit tests green for both packages.
… (mitto-nnu)
The test built Server{} with zero-valued config, so
computeWorkspacePromptsLastModified (broadened by mitto-tf9 to fold in ALL
prompt-source mtimes) still consulted appdir.PromptsDir() and
appdir.SettingsPath(), which resolve to the developer's real Mitto data dir.
Those files survive the .mittorc deletion, so Last-Modified stayed non-zero and
the assertion "no Last-Modified after deletion" failed.
mitto-tf9's fold-all-sources behavior is correct; this was a test-isolation
gap. Isolate MITTO_DIR to an empty temp dir using the codebase's established
pattern (t.Setenv + appdir.ResetCache + t.Cleanup) so all folded sources return
zero after deletion. Test-only change; no production code affected.
Extracts the C3 (Mobile Resilience) cluster from useWebSocket.js into a dedicated sub-hook at web/static/hooks/useWSMobileResilience.js. Moved (verbatim): * lastHiddenTimeRef (useRef) * staleRecoveryCooldownRef (useRef) * isMobileDevice (useMemo) * visibilitychange effect (async handler: hidden-time tracking, checkAuthWithRetry retry cadence, fetchStoredSessions, reconnectAllSessionsStaggered w/ 300ms settle delay). Placement: sub-hook is called after reconnectAllSessionsStaggered / fetchStoredSessions / switchSession are declared (they are passed as props). staleRecoveryCooldownRef and isMobileDevice are destructured at composer scope so pre-existing bare references in handleSessionMessage (L1671, L1960, L3077) and in INITIAL_ACK_TIMEOUT_MS resolve to the sub-hook's bindings via lexical closure. Bindings are initialised before any effect body or event handler runs (post-render), so TDZ is not hit. Composer imports trimmed: cleanupExpiredPrompts, checkAuthWithRetry, STALE_THRESHOLD_MS were only used by the moved effect. useWebSocket.js: 5823 -> 5711 LOC (-112) useWSMobileResilience.js: new, 211 LOC (verbatim effect body dominates) Public return bag unchanged (diffed against HEAD).
…t to hooks/useWSConnection.js (mitto-90f.6.2)
…seWebSocket to hooks/useWSDeliveryVerification.js (mitto-90f.6.3)
secureFetch is exported from web/static/utils/csrf.js, not utils/api.js; the previous imports resolved to a non-existent named export and would break at runtime as soon as the create/comments code paths ran.
…esDialog shell useWorkspacesData.loadData reads prevSelectedWorkspaceKeyRef, but useWorkspaceEdits (which previously owned the ref via useRef) needs setWorkspaces from useWorkspacesData — a circular dependency between the two hooks. Hoist the ref up into the WorkspacesDialog shell and pass it into both hooks, so useWorkspacesData can be invoked before useWorkspaceEdits without either hook depending on the other.
… badges Restructure task and conversation rows into a consistent two-line shape (line 1: workspace badge + agent/bd id/priority chips; line 2: title, truncating full-width) so panels on the same page share row height and titles are no longer squeezed by side chips. Tighten row padding via a shared COMPACT_ROW_STYLE inline constant (the precompiled tailwind.css does not include arbitrary padding utilities like p-[.5rem]). Upgrade workspaceBadge to a folder-icon chip on a distinct surface for easier scanning. Match the Dashboard nav button in SessionList with px-2 py-1 so it sits flush with the new row density.
…upport sweep prompts Introduce a new terminal state 'out-of-scope' (not our area / not something we will pursue) for support tickets, with a polite hand-off via 'Support: reply to user' before closing so waiting customers are not silently dropped. Protect closed beads (resolved / stale / out-of-scope) from being reopened by later Slack activity: watch-channel now treats a matched thread whose bead is already closed as done and never creates a duplicate; housekeeping never flips a closed bead back to an active state. Mandate a per-run Slack thread re-read for every open ticket — in particular those parked in 'awaiting-customer' and 'need-info' — so a new customer reply always flips the bead back to 'awaiting-us' instead of being silently missed.
…ile loop) Restructure the reconcile step of 'Support: watch channel' and 'Support: housekeeping' from a two-phase batch (list all beads -> fetch all threads -> reconcile) into a per-ticket loop: for each open ticket, load the bead, fetch that one thread, reconcile it, then move to the next. Interleaving keeps only one thread's context in scope at a time so the agent reasons about each thread while it is fresh. The auditable read gate is preserved (still N thread-fetch calls for N open tickets, one per ticket) but reframed as the natural outcome of the loop rather than an up-front batch requirement.
The global Dashboard aggregates beads across all workspaces but was only refreshing on its 15s poll — bd CLI changes, other agents, or git pulls left the In-progress / Ready / Recently modified lists stale between polls. Lift the inline fetchOnce into a useCallback (fetchDashboard) and add a useEffect subscribing to the window 'mitto:beads_changed' event, calling fetchDashboard unconditionally (no working_dir filter — Dashboard is cross-workspace, unlike BeadsView which scopes to its own workspace). The 15s poll stays as a safety net for the WS-disconnected case. Mirrors the BeadsView.js:984-993 pattern. Backend pipeline (fsnotify watcher → Server.OnBeadsChanged → useWebSocket.js) already in place; no backend changes.
…idation (mitto-is2.3)
…lidation (mitto-is2.4)
…ntextFromConfig Both internal/cmd/mcp.go and internal/web/server.go had a private buildMigrationContext(cfg) helper doing the same thing. Move it into internal/session as MigrationContextFromConfig and call from both sites. Net -12 lines, no behavior change.
…ailures Loop conversations hitting the Augment API HTTP 413 augmentTooLarge error (Conversation context too large for model) were treated as generic transient failures and kept retrying with exponential backoff forever, wasting resources against a context that will only grow. Detect IsContextTooLargeError in LoopRunner.deliverPrompt's scheduled error branch, bump a per-session counter, and after MaxLoopContextWindowFailures (=3) consecutive hits call LoopStore.MarkStopped with the new StoppedReasonContextWindowExceeded. Counter is cleared on any successful delivery so an intervening manual trim/summarize keeps the loop alive. Frontend surfaces the new reason in LOOP_STOPPED_LABELS as 'Stopped: context too large'. Fixes mitto-7jn
The read-cache decorator has shipped through mitto-is2.1-.4 with all acceptance criteria met. Flipping the CLI flag default from false to true so the cache is on by default. --beads-cache=false remains as an off-switch for at least one release cycle per epic acceptance criteria.
The prior flip in internal/cmd/web.go only affected the 'mitto web' CLI command. cmd/mitto-app/main.go constructs its own web.Config literal at line 1224 that bypasses the webCmd flag machinery, so BeadsCache took Go's zero value (false) and the read cache was silently disabled in the macOS app (which is what users actually run). Set BeadsCache: true explicitly in the app's web.Config to match the flipped CLI default.
Live metrics from the just-shipped mitto-is2 cache showed hit_rate=0.40
and a user report that repeated ticket-open (open A → open B → re-open A)
was still slow. Diagnosis: CachingClient.Show was an explicit pass-through
per the original design ("per-id invalidation is not worth the complexity").
That comment is falsified by production evidence — the beads viewer's
per-ticket load path (HandleBeadsShow → beadsClient().Show) is the
dominant miss for the viewer UX, and BeadsView.js re-fetches on every
issue open (useEffect deps: workingDir, currentIssueId, refreshNonce).
Fix: route Show through the existing doJSON helper with tag "show:"+id.
The per-id entries live in the same per-dir map slot, so evictDir() drops
them for free on any writer/watcher event — no per-id invalidation logic
needed. Zero additional complexity, purely a code-path re-route.
Also updates fakeClient.Show in cache_test.go to return an id-tagged
payload so the per-id keying test can distinguish A from B.
New unit tests (all pass):
- TestCachingClient_ShowCachedSameID: 2x Show(dir,id) → 1 inner call
- TestCachingClient_ShowInvalidatedByWrite: Show → SetStatus → Show → 2 inner calls
- TestCachingClient_ShowKeyedPerID: Show(A), Show(B), Show(A), Show(B) → 2 inner calls, id-specific payloads
All 19 CachingClient tests pass; integration test TestCachingClient_WriteReadThrough still green.
… (mitto-3gu) The Tier 4 memory-recycle GC logs a single aggregated RSS number for the shared ACP process tree, which conflates agent-side growth with MCP-child growth. Under mitto-3gu we saw multi-GB RSS climbs on long-running loop conversations and could not attribute them without a live ps probe. Add SharedACPProcess.RSSBytesDetailed() returning (parent, descendants, descendantCount) and wire it through a rssBreakdownSampler seam on ACPProcessManager so both Tier 4 recycle log lines carry parent_rss_bytes, descendant_rss_bytes, and descendant_count fields. Tests inject a synthetic split via the sampler.
… retry (mitto-54k.11) getOrCreateAuxiliarySession wraps SharedACPProcess.NewSession in a fresh context with budget auxSessionCreateBudget (was hardcoded 30 s). NewSession itself runs a bounded-retry loop where each attempt has sessionCreateAttemptTimeout=25 s and the total sequence is capped by sessionCreateTotalBudget=60 s. When attempt 1 hits the agent's own internal ~25 s deadline (evidence: `rpc_ms≈25000, error="context deadline exceeded"` in aux logs) the remaining wall-clock in the wrapper ctx is ~5 s; shouldFailFastCreateAttempt then bails at the attempt-2 boundary because remaining (5 s) < perAttemptBudget (25 s). The retry loop is regressed to a single attempt on the aux path even though the underlying budget (60 s) would fund a retry — every title-gen / mcp-check / follow-up dispatch that lands during the wedge fails. Set auxSessionCreateBudget to sessionCreateTotalBudget for parity with the underlying NewSession retry sequence. Pinned by TestAuxSessionCreateBudgetFundsRetry (invariant: budget ≥ 2×perAttempt AND fail-fast predicate does not bail at the attempt-2 boundary after one full drain).
…CP agents (mitto-54k.10) The default V8 old-space limit (~1.5 GB) is a common source of hard aborts in long-running loop conversations: once the heap reaches the mark-compact threshold V8 kills the Node process, tearing down every session sharing that shared ACP process. Set NODE_OPTIONS=--max-old-space-size=12288 in defaults.env for all builtin Node-based agents (augment, claude-code, cline, codex, gemini, github-copilot, kilo, qwen-code). Guarded by TestBuiltinAgents_NodeAgentsRaiseV8HeapLimit which asserts every npx-launched builtin agent carries the option so we don't silently regress when a new agent lands.
Field logs (mitto-54k.9) show ~8-minute handshake wedges on stale sessions. The starvation exception in the shared-cap path (mitto-1ut) releases the shared handshake capability when the load probe times out, letting the probe (45s) stack with the fallback session/new (up to 240s) for ~285s worst-case; two of those in a row match the observed wedge. The 45s value dates from an earlier era where session/load could legitimately run long; observed MCP-init abort windows in current builds are 11-25s, so capping the stale-load probe at 25s aligns it with the agent's own internal deadline without cutting healthy loads short. TestHandshaker_StaleLoadProbeTimeout_CeilingCap pins the ≤25s ceiling.
…ation_new (mitto-dj9) When an orchestrator LLM composes multiple mitto_conversation_new calls in one turn, it can short-circuit repeated initial_prompt arguments to a bracketed self-reference like "[Same driver body]". Without a guard the server enqueues that literal string as the loop child's seed and the loop re-fires an unactionable prompt on every tick. Add looksLikePlaceholderLoopSeed() and gate mitto_conversation_new's free-text initial_prompt path (PromptName == "") when the call is creating a loop child (LoopPrompt != "" or LoopTrigger != ""). Rejects trimmed strings under 512 bytes matching a bracketed pattern with a self-reference trigger word (same/see/above/prior/driver/body/prompt) and returns an actionable error advising to pass the full body or use prompt_name. The server-expanded prompt_name path is unaffected — it cannot be truncated this way.
… unconditional Done (mitto-dj9/i5k/fko/6am)
The four beads-issue-loop orchestrators (fixing-bug{,s},
implementing-feature{,s}) suffered four related defects that regressed
loop-driver correctness:
- mitto-dj9: the outer orchestrator seeded child loops by inlining the
driver body as a free-text initial_prompt/loop_prompt. LLMs
short-circuited the repeated argument to "[Same driver body]"; the
server-side placeholder guard now rejects that shape, so the drivers
must instead pass prompt_name/loop_prompt_name and let the server
expand the body at dispatch.
- mitto-i5k: the per-issue Done branch was gated on the child having
reported success, which caused the outer loop to livelock on children
that closed themselves without reporting. Made the Done branch
unconditional (the child owns its own state; the orchestrator only
observes reports).
- mitto-fko: added a recently-closed-parent filter so a bead re-appearing
in the ready set moments after its parent orchestrator closed doesn't
immediately re-spawn.
- mitto-6am: Step 3f (fan-out to worker) now requires a named worker
prompt — free-text worker seeds are refused for the same
short-circuiting reason as mitto-dj9.
Guarded by config/beads_loop_prompts_defects_test.go, which walks each
driver YAML and asserts the four defective patterns are absent (inline
free-text loop seed, conditional Done, missing recently-closed filter,
free-text worker fan-out).
…rt sweep tables Both support-sweep prompts (support-housekeeping and support-watch-channel) render a per-bead summary table whose "What's next" cell previously showed a bare state phrase. Turn that cell into a markdown link to the bead's `slack_url` (with a permalink fallback in watch-channel where the ticket may not yet have a stored URL), so the sweep summary jumps straight to the originating thread instead of forcing an extra `bd show` lookup.
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.
No description provided.