Skip to content

Replace log-parsing detectors with a push-based coding-tool status model#120

Merged
0101 merged 42 commits into
mainfrom
extension-for-reporting
Jul 17, 2026
Merged

Replace log-parsing detectors with a push-based coding-tool status model#120
0101 merged 42 commits into
mainfrom
extension-for-reporting

Conversation

@0101

@0101 0101 commented Jul 17, 2026

Copy link
Copy Markdown
Owner

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 transient Done status that doesn't match what the CLI actually reports.

Changes

Push model (replaces the log-parsing detectors)

  • Reporting extension — a new passive Copilot CLI extension (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 with canvas-bridge and never alters the transcript.
  • Server push domain — a pure SessionActivity fold, a SQLite/WAL SessionActivityStore durable mirror, and a single-writer SessionActivityService ingestion mailbox + HTTP endpoint, wired into RefreshScheduler and WorktreeApi.
  • Deleted the three log-parsing detectors and their tests (~2,600 lines) and the inert pull-model plumbing.

Idle-only status model

  • Retire Done; add NoSession. The status dot becomes a pure four-way function: red Working / yellow WaitingForUser / blue Idle (open session) / grey NoSession.
  • Overview "Agents" gains a blue Idle group with a per-agent time-since-idle chip; an openness window separates blue (open) from grey (closed/crashed); the card footer persists while any session data remains.

Provider model

  • Collapse to a single push-capable CodingToolProvider = CopilotCli (the Claude/Copilot cases and the separate server-side PushProvider are merged into one DU), kept extensible by adding a case — the compiler then flags every provider-specific branch.

Specs

  • docs/spec/session-status-push.md and docs/spec/idle-only-status.md document the model; worktree-monitor.md / beads-overview-band.md reconciled to the new vocabulary.

Hardening (from a /review pass)

A focused-review pass over the branch produced fixes that are folded in:

  • Heartbeats are now a dedicated liveness-only report — they bump last_seen without 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_user idle suppression no longer sticks a card on WaitingForUser after completion (a single askUserOpen latch, maintained across live + replay).
  • Resume only reuses a stored session id for the configured provider; a legacy 'done' DB row no longer crashes durable-store reads; out-of-order events no longer mislabel history rows.
  • Illegal state removed — a dedicated per-session status DU makes NoSession unrepresentable per session (no runtime failwith).
  • F# modern-syntax, immutability, and duplication cleanups.

Tests

  • New unit suites: SessionActivityTests, SessionActivityStoreTests, SessionActivityServiceTests, CodingToolPushSourceTests, CodingToolSinceTests.
  • Fast suite: 1238 passing, 0 failing.
  • On-demand (E2E / Playwright / Smoke): 252 passing, 11 environment-gated skips (Azurite storage emulator; live PR data; active-session-gated smoke). E2E fixtures (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).

0101 added 30 commits July 15, 2026 11:07
…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.
0101 added 9 commits July 17, 2026 09:21
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
Copilot AI review requested due to automatic review settings July 17, 2026 10:55

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment thread src/Server/SessionActivityService.fs Outdated
Comment thread src/Server/SessionActivityService.fs
Comment thread src/Server/SessionActivityService.fs Outdated
Comment thread src/Server/SessionActivityStore.fs Outdated
Comment thread src/Server/SessionActivityStore.fs Outdated
Comment thread src/Server/WorktreeApi.fs
Comment thread src/Extension/reporting/extension.mjs
Comment thread src/Server/SessionActivityService.fs Outdated
Comment thread src/Server/CodingToolStatus.fs
Comment thread docs/spec/session-status-push.md Outdated
0101 added 3 commits July 17, 2026 13:23
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
0101 enabled auto-merge (squash) July 17, 2026 12:04
@0101
0101 merged commit 7aafa13 into main Jul 17, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants