Replace log-parsing detectors with a push-based coding-tool status model#120
Merged
Conversation
…tests) New src/Server/SessionActivity.fs: SessionId/EventId/PushProvider value types, 7-case SessionEvent DU, SessionActivityReport, SessionStatus, pure fold/foldMany, stalenessTimeout/idleWindow constants, freshnessAdjusted crash-net, pickActive collapse. Registered after FileUtils.fs. 24 ported+extended tests in src/Tests/SessionActivityTests.fs. Build clean (0 warn/0 err); 24/24 tests pass.
New src/Server/SessionActivityStore.fs (Microsoft.Data.Sqlite): session_status + activity_events schema with WAL/synchronous=NORMAL/busy_timeout pragmas; UpsertStatus last-write-wins, AppendEvent INSERT OR IGNORE dedupe, LoadLiveStatuses idle-window rebuild, PruneOld, QueryWindow. Package + Compile refs; store contract added to spec. Tests in src/Tests/SessionActivityStoreTests.fs.
…dpoint + wiring New SessionActivityService.fs: single-writer MailboxProcessor (fold -> live Map -> persist -> feed RefreshScheduler) and POST /api/session/activity handler mirroring canvasRegisterHandler. RefreshScheduler gains UpdateSessionStatus StateMsg + SessionStatuses map. Program.fs wires instance-specific SQLite path, service start/dispose, and the csrf-guarded route. Retention timer prunes old rows hourly.
…i-session collapse + resume Repoint CodingToolStatus/WorktreeApi card coding-tool fields (status/skill/last-user/last-assistant) and getLastSessionId onto the SessionActivity push live state via pickActive collapse (drop-Idle, most-recent-active wins, all fields from one winner). Resume reads most-recent-any from the store. Adds fromPushSessions, collapseByWorktree, idlePushResult. Tests in CodingToolPushSourceTests.fs.
…log-parse scheduling Deleted Copilot/Claude/VsCodeCopilot detectors + their tests, removed the three-surface resolution scaffolding from CodingToolStatus.fs, stopped scheduling RefreshCodingTool in RefreshScheduler, updated .fsproj Compile entries. Push-only build; kept the pure fold in SessionActivity.fs. UpdateCodingTool/CodingToolData retained-but-unfed (out of scope).
…c4) in spec Record decision to defer removal of residual inert pull-model plumbing (UpdateCodingTool/CodingToolData + resolveProvider repoint) to follow-up cc4.
…exists with canvas-bridge) Add passive treemon-reporting extension (src/Extension/reporting/reporting.mjs + @treemon/reporting package.json): subscribes to the SDK event stream, drops sub-agent (agentId) events and <skill-context> injections, maps the 7 relevant SDK event types to the wire-contract kinds, fans out POST /api/session/activity to TREEMON_PORTS|TREEMON_PORT|5000, replays getEvents() on join (newest-wins guard), 60s heartbeat re-asserts working/waiting, pendingAskUser suppresses went_idle. No canvas, no tools, no session.send. treemon.ps1: Install-ReportingExtension installs alongside canvas-bridge from Deploy-Frontend. Spec updated.
…umbing (UpdateCodingTool/CodingToolData) + repoint resolveProvider Delete WorktreeApi.resolveProvider and repoint 5 launch sites to CodingToolStatus.readConfiguredProvider (per-worktree .treemon.json). Drop CodingToolData field, UpdateCodingTool StateMsg case, and the always-false hasCodingToolActivity branch (+codingToolActivityThreshold) from RefreshScheduler. Remove the 6 coding-tool-override tests + helper from IdleDetectionTests. Update spec Decisions (cc4 concretion).
…ssionId fallback drops all status reports Replace the report-dropping blank-id fallback with a real-id guard: trim session.sessionId/session.id and, when absent, log and process.exit(0) (mirrors the joinSession-failure bail) so a blank sessionId is never POSTed (server parseReport rejects it as missing sessionId). Correct the misleading anonymous-fold comment and drop the now-impossible anonymous log fallback.
…timestamps, breaks staleness decay Add clampFutureTimestamp + futureSkewAllowance (5 min) in SessionActivityService; parseReport now takes 'now' and clamps a future occurredAt down to now before the fold, so last_seen can't go future and freshnessAdjusted decays to Idle normally. tryAcceptReport passes DateTimeOffset.UtcNow. Documented in spec Decisions. Tests: far-future clamped + within-skew kept (31/31 SessionActivityServiceTests pass).
… after restart for sessions older than 2h window Add SessionActivityStore.StatusesForWorktree (durable session_status query by worktree, no idle-window filter). WorktreeApi.resumeSession feeds getLastSessionId from that durable store instead of the 2h-filtered live cache and drops its GetState fetch. Store threaded through Program.fs as a SessionActivityStore option shared with the ingestion service. New StatusesForWorktreeTests prove >2h-stale sessions return.
…uilt on rejoin; live idle downgrades waiting session Guard the live went_idle suppression on (pendingAskUser || currentStatus === 'waiting') so a rejoin/replay (live-only pendingAskUser starts false but currentStatus is rebuilt to 'waiting' from a replayed awaiting_user_input) no longer lets a live session.idle downgrade a genuinely-waiting session to Idle. Matches the heartbeat's source of truth. Documented in docs/spec/session-status-push.md 'ask_user exactness'.
…N comparator on malformed timestamps Replace subtractive getEvents() replay comparator (Date.parse(a)-Date.parse(b), NaN on malformed timestamps -> inconsistent sort) with a replayMs() helper that normalizes unparseable timestamps to -Infinity (oldest-first) and compares via </> for a stable total-order comparator. Follows the Number.isNaN guard used in updateStatus().
…-only, unbounded in-memory growth Add internal evictStaleStatuses in RefreshScheduler.fs (idle-window eviction measured against the newest LastSeen, replay-safe) and apply it on every UpdateSessionStatus so SessionStatuses mirrors the store's live cache instead of growing append-only. Tests: SessionStatusEvictionTests (5 cases). Decision recorded in docs/spec/session-status-push.md.
…x length on message/skill text Add maxTextLength (8192) hard cap + null-safe capText helper in SessionActivityService; apply in parseMessage (user_prompt/assistant_message), awaiting_user_input question, and skill_invoked skillName. Defence-in-depth storage/memory bound independent of the client's 2000-char cap. Tests + spec decision added.
… null, and duplication cleanup F4: _.Prop >> Ctor.value shorthand in CodingToolStatus.fs + SessionActivity*Tests. F8: pruneTimer Timer option = None (Some on start, Option.iter on dispose). F11: byWt.[..] -> byWt[..] indexer syntax (5 sites). F12: removed change-history comment in IdleDetectionTests. F13: extracted ts/msg into Tests.TestUtils; deduped three test files. F9: not applicable (line 225 vs 479 are in separate top-level fns, no shared scope); left unchanged.
…to extension.mjs so the CLI discovers/loads it Renamed src/Extension/reporting/reporting.mjs -> extension.mjs; package.json main=extension.mjs; treemon.ps1 Install-ReportingExtension now copies extension.mjs (mirrors canvas-bridge Install-Extension); spec + SessionActivityService.fs producer comments updated to extension.mjs. Build 0/0, node --check OK, ps1 parses OK.
…ation.* so ask_user maps to WaitingForUser Subscribe extension.mjs to elicitation.requested/elicitation.completed (the events ask_user emits in Copilot CLI 1.0.71+), mapping elicitation.requested to the awaiting_user_input wire kind reading data.message (fallback data.question), and set/clear pendingAskUser accordingly. user_input.* handlers kept for back-compat. Spec updated to document the elicitation.* event names.
…tegory) Resolved 5 conflicts toward the push model (took HEAD per hunk), preserving cc4's removal of the pull-model coding-tool plumbing (CodingToolData / effectiveActivity override / UpdateCodingTool) and the 9k8 detector deletion, while keeping main's non-conflict work (#111 skill selector, #118 last-known-good worktrees). Absorbed main's new CodingToolResult.LastActivity field: idlePushResult=None, fromPushSessions=winning session's last-seen. Kept #119's WorktreeStatus.CodingToolSince field + client Overview rendering; the server sets it None for now - wiring time-since-state onto the push model is the next feature (idle-only tracking). Build 0 warn/0 err; Fast suite 1225 passed/0 failed. Copilot-Session: 2b6845cd-2c94-4bc6-b5cc-6dac41a47b77
Plan for feature tm-extension-for-reporting-h70: retire Done, map turn_ended/went_idle to Idle, blue dot for idle-with-open-session, grey for no open session, and surface idle agents in the Overview with time-since-idle. Copilot-Session: 2b6845cd-2c94-4bc6-b5cc-6dac41a47b77
The footer (last user prompt + last agent message + skill) must stay whenever we have the data; decouple footer data (most-recent-any session) from the status-dot open-session collapse so going Idle/NoSession no longer blanks it. Folded into task azk + verify step in c85. Copilot-Session: 2b6845cd-2c94-4bc6-b5cc-6dac41a47b77
…nd (domain + card dots + Overview Idle group + demo) Reshape CodingToolStatus to Working|WaitingForUser|Idle|NoSession (drop Done). TurnEnded/WentIdle -> Idle; NoSession is a worktree-level collapse result. Card dots: Idle=blue (#89b4fa), NoSession=grey (#585b70). Overview Stopped group -> Idle group. idlePushResult -> noSessionPushResult. Demo fixture, E2E fixtures, and all tests updated together to stay build-green. Build green (0 warn/0 err); Fast 1226 passed.
…enness signal) Extend heartbeatTick to re-assert resting (done/idle) sessions as a status-preserving went_idle every HEARTBEAT_INTERVAL_MS, so an OPEN idle session keeps last_seen fresh (blue) while a closed one decays (grey), alongside the existing working/waiting heartbeats. Waiting still maps to awaiting_user_input so the went_idle suppression invariant holds. Document the deferred SIGTERM/SIGINT close-ping (Decision 3) in the spec.
…-since-idle wiring Openness window (openWindow=3min) drives the status dot: open-active -> Working/Waiting, open-all-idle -> Idle (blue), no open session -> NoSession (grey). Footer decoupled from the dot (active winner else most-recent-any) so Idle/NoSession worktrees keep their last messages + skill. CodingToolSinceByWorktree stamps/freezes/clears time-since-idle, fed to WorktreeStatus.CodingToolSince guarded to Idle. Tests + spec Decisions 7-8 added.
…rows crash durable-store reads after upgrade
Add backward-compat parseStatus read arm ('done' -> Idle) plus an idempotent
construction-time migration rewriting legacy status='done' rows to 'idle' in
session_status and activity_events. Adds LegacyDoneStatusTests covering the read
arm, LoadLiveStatuses rehydrate, and in-place migration.
…SinceByWorktree lifecycle + restart stamping F10/C-13: prune the global CodingToolSinceByWorktree stamp on RemoveWorktree and UpdateWorktreeList (it lives on DashboardState, not PerRepoState) so a reused path gets a fresh idle stamp instead of the pre-removal frozen one. F11/C-14: new batch SeedSessionStatuses scheduler message (posted by SessionActivityService.Start) stamps each worktree's time-since-idle from its NEWEST session rather than the oldest-replayed row, avoiding an overstated post-restart chip. Tests: CodingToolSincePruningTests (3) + SeedSessionStatusesTests (3); SchedulerTests 89/89.
…tale 'Done' status refs in in-scope specs Retired stale CodingToolStatus.Done references: beads-overview-band.md Unattended inactive-agent set (Done/Idle -> Idle/NoSession) and agent-group mapping (Done=>Stopped -> Idle=>Idle); resume-last-session.md resume condition (Idle/Done -> Idle/NoSession).
…12): reconcile stale 'Done' vocab in worktree-monitor.md Card dot (F4) and Decisions parent/subagent line (F12) updated to the live model (Working/WaitingForUser/Idle/NoSession; upgrade Idle to Working). Dead pull-model 'Coding Tool Detection' section marked Legacy (superseded by push model) with Done->Idle historical framing for F5/F6/F7 rather than a blind swap. Rationale + F5 divergence (per-session status is never NoSession) recorded as Decision #11 in idle-only-status.md.
…eeds 1000-LOC file-size limit Extract the four CodingToolSince/time-since-idle fixtures (StampIdleSinceTests, CodingToolSinceByWorktreeTests, CodingToolSincePruningTests, SeedSessionStatusesTests) plus wtA/storedWt helpers into src/Tests/CodingToolSinceTests.fs, registered before SchedulerTests.fs in Tests.fsproj. SchedulerTests.fs 1604->1376 lines; the diff no longer grows it so the file-size-limit rule no longer fires. Rationale in spec Decision #12. Build green, 104 CodingToolSince+Scheduler tests pass.
…atus refs in beads-overview-band.md Renamed the retired 'Stopped (Done)' agent group to 'Idle' throughout the Overview Agents band (idle-state circles, Idle #89b4fa palette + hue list, working/waiting/idle circles), corrected the TaskBucketKind RequireQualifiedAccess rationale to cite BeadsSummary/DU case-name collisions, and clarified idle sessions form a distinct blue Idle group. Legitimate Done TASK bucket (Sigma closed issues) preserved.
Under the push model the CodingToolRefresh category ('Agent' row) had no poll to log, so it sat permanently 'pending'. Feed it from the push path instead: every accepted extension push (UpdateSessionStatus) and the restart seed (SeedSessionStatuses) now stamp the row with the pushing worktree and the push instant as a green success, so a growing 'X ago' signals pushes have stopped. Duration is blank (a push has no server-side work). Demo fixture updated to push wording; tests in CodingToolSinceTests.
The cyclic arrow implied a poll; this row is now fed by extension pushes. Swap it for a plain up-arrow (push up), mirroring the down-arrow used by the PR/Git fetch (pull) rows. Updated the matching E2E display-name assertions.
Addresses the confirmed findings from the branch review (F4 left as-is: the mutable pruneTimer is the correct lifecycle idiom; rule-quality note filed). - F1 docs/spec/idle-only-status.md: trim ephemeral per-task Decisions #5-12, keep durable architecture - F2 src/Server/CodingToolStatus.fs: drop ASCII section-divider decoration, keep the design comment - F3 src/Server/RefreshScheduler.fs: F# 9 _.Value.LastSeen shorthand - F5 src/Server/SessionActivityService.fs: document the mutable pruneTimer lifecycle field - F6 src/Server/SessionActivityService.fs: F# 9 _.Dispose() shorthand - F7 src/Server/SessionActivityStore.fs: remove unreachable "done" -> Idle parse arm (migration is the single source of truth); drop the now-dead test - F8 src/Server/WorktreeApi.fs: resume only reuses a stored session ID when the configured provider is Copilot, so a Claude worktree never gets a Copilot ID - F9 src/Server/WorktreeApi.fs: F# 9 _.StatusesForWorktree(...) shorthand - F10 src/Tests/SessionActivityStoreTests.fs: F# 9 _.Status.Status shorthand - F11 treemon.ps1: extract shared Install-CopilotExtension helper Copilot-Session: 699a4326-5ec0-4d83-98f0-ed55c612245c
src/Extension/reporting/extension.mjs: `session.idle` arriving after an ask_user completion could be suppressed forever, pinning the card on WaitingForUser. The old guard OR'd a live-only `pendingAskUser` flag with `currentStatus === "waiting"`, but a `*.completed` event cleared only the flag, never `currentStatus`, so the stale "waiting" kept suppressing idle. Collapse both signals into a single `askUserOpen` latch maintained in BOTH the live and replay paths (the ask_user *.completed events are subscribed and returned by getEvents()), and suppress `went_idle` only while it is open. A completed ask_user — live or replayed — now releases the latch so a real idle settles the card on Idle, while a genuinely-open ask_user still survives a rejoin. Net reduction in logic: drops the buggy currentStatus fallback and the now-dead `isLive` handle() parameter. Copilot-Session: 699a4326-5ec0-4d83-98f0-ed55c612245c
…table Per-session fold state (SessionStatus / ActivityEventRow) typed its status as the four-case CodingToolStatus, but the fold only ever produces three of them — NoSession is a worktree-level collapse result. The type admitted a value the fold can never reach, forcing runtime failwith guards in the store's statusText/parseStatus. Introduce a dedicated three-case per-session DU and widen only at the collapse boundary: - SessionActivity.fs: add [<RequireQualifiedAccess>] SessionLevelStatus = Working | WaitingForUser | Idle, plus a total toCodingToolStatus widening. SessionStatus.Status, the fold, emptyStatus, freshnessAdjusted and pickActive now use it. - SessionActivityStore.fs: ActivityEventRow.Status uses SessionLevelStatus; statusText becomes total (drops the NoSession -> failwith arm); parseStatus returns SessionLevelStatus. - CodingToolStatus.fs: fromPushSessions widens the winning session's status via toCodingToolStatus, so NoSession is produced only by the collapse. NoSession is now unrepresentable per session (caught at compile time rather than a runtime crash), and statusText no longer needs a failwith. Tests updated to the per-session DU where they build/assert session state; the worktree-level (CodingToolStatus) assertions and stampIdleSince args are unchanged. Full fast suite green (1249 passed). Copilot-Session: 699a4326-5ec0-4d83-98f0-ed55c612245c
Heartbeats were sent as synthetic status events (turn_started /
awaiting_user_input / went_idle) and folded through the same
ordering/append path as real events. That one conflation caused three
findings:
- F20: a heartbeat stamped with the client wall clock could overtake a
slightly-earlier real event, and the last-write-wins guard then dropped
the real message from live state.
- F14: every 60s heartbeat appended a synthetic row to activity_events,
inflating the history stream for the full 14-day retention.
- F19 (partial): heartbeat-origin out-of-order rows were mislabeled with
the newest fold status.
Make the heartbeat a dedicated liveness-only report (kind "heartbeat"):
- extension.mjs: heartbeatTick sends kind "heartbeat" once a status is
established, dropping the synthetic-kind mapping.
- SessionActivity.fs: add the Heartbeat event (never bears on status).
- SessionActivityService.apply: heartbeats take a touch path that bumps
last_seen only (in memory, store, and the scheduler) without moving
updated_at, re-folding, or appending. A heartbeat for an unknown session
is ignored.
- SessionActivityStore.TouchLastSeen: forward-only last_seen bump.
Also close the remaining F19 case for genuine real-event reordering: an
out-of-order (older) event's history row now records the event's own direct
effect (fold onto empty) instead of the current newest live status.
Adds three service tests (heartbeat touch, ignored-when-unknown, out-of-order
row attribution). Full fast suite green (1252 passed). Spec updated.
Copilot-Session: 699a4326-5ec0-4d83-98f0-ed55c612245c
The system modeled and launched two providers (Claude | Copilot) while push status ingestion was Copilot-only (PushProvider = CopilotCli), so a Claude-configured worktree could launch but never show live status (F18). The collapse also hardcoded the provider, discarding a multi-provider domain that did not really exist (F16). Claude can no longer push, so drop it and make the model honest — a single DU of one, kept open for the next push-capable tool (e.g. a GitHub App) by adding a case: - Shared: CodingToolProvider = CopilotCli (Claude/Copilot cases removed); the separate server-side PushProvider DU is merged into it. - CodingToolCli / SyncEngine / CodingToolStatus / CardViews / WorktreeApi: every provider match collapses to the single CopilotCli arm, kept as an exhaustive match so the compiler flags each site when a provider is added. - readConfiguredProvider: only "copilot" is recognized; "claude" (or any unknown value) logs and falls back to the default. - fromPushSessions: the card provider is the single CopilotCli (no separate PushProvider bridge); a future provider threads its own value here. - Ingestion (SessionActivity/Store/Service) carries CodingToolProvider on the wire/rows — the extensibility hook — instead of PushProvider. Tests updated to the single provider; the Claude-specific command-builder cases are removed; the demo fixture's provider values move to CopilotCli. Full fast suite green (1238 passed). Copilot-Session: 699a4326-5ec0-4d83-98f0-ed55c612245c
Follow-up to the provider collapse (2c15e48): the CodingToolProvider DU is serialized by case name, so E2E fixtures and DOM assertions still carrying the old "Claude"/"Copilot" names broke their fixture servers (union decode: "key 'X' not present in the dictionary") or asserted stale tooltips. - fixtures/overview-band.json: 10 provider values "Copilot" -> "CopilotCli". - DashboardTests: the two sync-button E2E tests now expect the "Copilot is active" tooltip and Copilot-labelled fixture cards. (fixtures/worktrees.json + DemoModeTests were already updated in 2c15e48.) Non-Fast suite green after the fix (OverviewBandE2E 6/6). Copilot-Session: 699a4326-5ec0-4d83-98f0-ed55c612245c
The live-data smoke test that self-skips when no active session cards are present still referred to "Claude" sessions in its name, log lines, and Assert.Ignore messages. It is provider-agnostic (it checks .user-prompt rendering, not the provider), so reword "Claude" -> "coding-tool" now that Claude is no longer a provider. No behaviour change. Copilot-Session: 699a4326-5ec0-4d83-98f0-ed55c612245c
There was a problem hiding this comment.
Pull request overview
Replaces provider-specific log polling with a durable, push-based Copilot CLI session-status pipeline and introduces the Idle/NoSession model.
Changes:
- Adds reporting extension, HTTP ingestion, SQLite persistence, and scheduler integration.
- Removes Claude/Copilot/VS Code log detectors and consolidates provider handling.
- Updates UI, CLI, fixtures, tests, and specifications for Idle/NoSession semantics.
Show a summary per file
| File | Description |
|---|---|
treemon.ps1 |
Installs the reporting extension. |
src/Client/CardViews.fs |
Maps new statuses and provider labels. |
src/Client/index.html |
Updates status and activity colors. |
src/Client/OverviewBand.fs |
Replaces Stopped with Idle. |
src/Client/OverviewData.fs |
Aggregates open idle agents. |
src/Client/OverviewViews.fs |
Marks agent status as push-driven. |
src/Cli/Program.fs |
Formats NoSession status. |
src/Extension/reporting/extension.mjs |
Pushes SDK session events and heartbeats. |
src/Extension/reporting/package.json |
Defines reporting extension package. |
src/Server/ClaudeDetector.fs |
Removes Claude log detector. |
src/Server/CodingToolCli.fs |
Consolidates Copilot CLI commands. |
src/Server/CodingToolStatus.fs |
Collapses pushed sessions into card state. |
src/Server/CopilotDetector.fs |
Removes Copilot log detector. |
src/Server/DemoFixture.fs |
Updates demo statuses and provider. |
src/Server/Program.fs |
Wires persistence, service, and route. |
src/Server/RefreshScheduler.fs |
Stores pushed statuses and idle timestamps. |
src/Server/Server.fsproj |
Updates modules and SQLite dependency. |
src/Server/SessionActivity.fs |
Defines the pure session state machine. |
src/Server/SessionActivityService.fs |
Implements ingestion and lifecycle management. |
src/Server/SessionActivityStore.fs |
Adds SQLite/WAL persistence. |
src/Server/SyncEngine.fs |
Uses consolidated provider handling. |
src/Server/VsCodeCopilotDetector.fs |
Removes VS Code log detector. |
src/Server/WorktreeApi.fs |
Sources cards and resume IDs from push state. |
src/Shared/Types.fs |
Defines new status/provider unions. |
src/Tests/ActionLaunchSpawnTests.fs |
Updates launch tests for Copilot CLI. |
src/Tests/ClaudeDetectorTests.fs |
Removes obsolete detector tests. |
src/Tests/ClaudeSessionReplayTests.fs |
Removes obsolete replay tests. |
src/Tests/CodingToolOrchestratorTests.fs |
Updates provider configuration tests. |
src/Tests/CodingToolPushSourceTests.fs |
Tests worktree-level push collapse. |
src/Tests/CodingToolSinceTests.fs |
Tests idle timestamps and push rows. |
src/Tests/CommandBuilderTests.fs |
Updates Copilot-only command expectations. |
src/Tests/CopilotDetectorTests.fs |
Removes obsolete detector tests. |
src/Tests/DashboardTests.fs |
Updates status, color, and provider assertions. |
src/Tests/DemoModeTests.fs |
Updates demo provider and transitions. |
src/Tests/IdleDetectionTests.fs |
Removes detector-driven activity behavior. |
src/Tests/OverviewBandE2ETests.fs |
Verifies the new Idle group. |
src/Tests/OverviewDataTests.fs |
Tests Idle/NoSession aggregation. |
src/Tests/SchedulerTests.fs |
Tests live-status eviction. |
src/Tests/ServerParsingTests.fs |
Removes Claude path tests. |
src/Tests/SessionActivityServiceTests.fs |
Tests parsing, ingestion, and restart recovery. |
src/Tests/SessionActivityStoreTests.fs |
Tests persistence, migration, and retention. |
src/Tests/SessionActivityTests.fs |
Tests the session fold and freshness. |
src/Tests/SmokeTests.fs |
Uses provider-neutral terminology. |
src/Tests/TestUtils.fs |
Adds session-event fixture helpers. |
src/Tests/Tests.fsproj |
Registers new suites and removes old ones. |
src/Tests/VsCodeCopilotDetectorTests.fs |
Removes obsolete detector tests. |
src/Tests/WorktreePathResolutionTests.fs |
Supplies the new API dependency. |
src/Tests/fixtures/overview-band.json |
Updates overview provider/status fixtures. |
src/Tests/fixtures/worktrees.json |
Updates dashboard provider/status fixtures. |
docs/spec/beads-overview-band.md |
Documents the Idle agent group. |
docs/spec/idle-only-status.md |
Specifies Idle/NoSession semantics. |
docs/spec/resume-last-session.md |
Updates resume eligibility vocabulary. |
docs/spec/session-status-push.md |
Documents the push architecture. |
docs/spec/worktree-monitor.md |
Reconciles monitoring documentation. |
Review details
- Files reviewed: 54/54 changed files
- Comments generated: 11
- Review effort level: Medium
idle-only-status.md's Done->Idle / NoSession changes are already merged, so fold it into session-status-push.md: rewrite that spec to the current 4-way status model (Working/WaitingForUser/Idle/NoSession, openness blue-vs-grey, decoupled footer, time-since-idle) and trim its as-built changelog and migration narrative. Delete idle-only-status.md. Trim worktree-monitor.md's legacy log-parsing-detector section (removed detectors) to a concise pointer and fix downstream stale references (burst task, Key Files, fixtures, Decisions).
The claude/copilot/vscode session fixtures were only consumed by the log-parsing detector tests (ClaudeDetectorTests, ClaudeSessionReplayTests, CopilotDetectorTests, VsCodeCopilotDetectorTests), which were deleted when status moved to the push model. No remaining test or source references them; fixtures are glob-included in Tests.fsproj so no project edit is needed.
Address PR #120 reviewer feedback: atomic append+upsert in one transaction with a store-failure-tolerant ingestion mailbox; immutable retention Timer (no let mutable); recursive reader accumulators instead of while-loop list builds; durable retained footer/resume metadata so the resume button stays reachable for sessions aged out of the live window; and monotonic LastSeen so a delayed event can't grey an open card. Copilot-Session: 9e28b430-a1f1-469c-a45a-beadf70c0599
0101
enabled auto-merge (squash)
July 17, 2026 12:04
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.
Problem
Each worktree card's coding-tool status (the dot, the footer, the Overview "Agents" band) was derived by polling and parsing CLI log files via per-provider detectors (
ClaudeDetector,CopilotDetector,VsCodeCopilotDetector). That approach was fragile and provider-specific, could only infer state the logs happened to expose, and modeled a transientDonestatus that doesn't match what the CLI actually reports.Changes
Push model (replaces the log-parsing detectors)
src/Extension/reporting/extension.mjs) subscribes to the SDK session event stream and POSTs a tiny status contract to/api/session/activity. It registers no tools and no canvas, so it coexists withcanvas-bridgeand never alters the transcript.SessionActivityfold, a SQLite/WALSessionActivityStoredurable mirror, and a single-writerSessionActivityServiceingestion mailbox + HTTP endpoint, wired intoRefreshSchedulerandWorktreeApi.Idle-only status model
Done; addNoSession. The status dot becomes a pure four-way function: red Working / yellow WaitingForUser / blue Idle (open session) / grey NoSession.Provider model
CodingToolProvider = CopilotCli(theClaude/Copilotcases and the separate server-sidePushProviderare merged into one DU), kept extensible by adding a case — the compiler then flags every provider-specific branch.Specs
docs/spec/session-status-push.mdanddocs/spec/idle-only-status.mddocument the model;worktree-monitor.md/beads-overview-band.mdreconciled to the new vocabulary.Hardening (from a
/reviewpass)A focused-review pass over the branch produced fixes that are folded in:
last_seenwithout moving the last-write-wins clock or appending history, so a heartbeat can't drop a slightly-earlier real event and doesn't inflate the event stream.ask_useridle suppression no longer sticks a card on WaitingForUser after completion (a singleaskUserOpenlatch, maintained across live + replay).'done'DB row no longer crashes durable-store reads; out-of-order events no longer mislabel history rows.NoSessionunrepresentable per session (no runtimefailwith).Tests
SessionActivityTests,SessionActivityStoreTests,SessionActivityServiceTests,CodingToolPushSourceTests,CodingToolSinceTests.worktrees.json,overview-band.json) updated to the new provider case.Diff: 54 files, +4,416 / −4,073 (a large architectural swap — the detectors out, the push model in).