Repository navigation
Merge dev → main (automated) - #2085
Merged
Merged
Conversation
added 30 commits
August 16, 2026 14:56
Add dim '▼ more' / '▲ more' markers to the herdr worklist selection list (createListRenderer, list mode) so users can tell when items are hidden below the fold or above the current scroll position: - ▼ more rendered after the last visible item when scrollOffset + visible.length < flatItems.length (items below the fold). - ▲ more rendered before the first item/group separator when scrollOffset > 0. - Indicator rows are reserved in the render budget (effectiveBudget) before the visible window is trimmed, and counted in the fill-blank used rows, so the rows - 1 line-count invariant (WL-0MSAAON63003N6LO) holds with indicators active. - Edge case handled in a single pass: reserving the bottom indicator row may trim the window so the list fully fits — the indicator is then omitted. - Indicators are display-only rows: no navigation state, selection index, or hierarchy scroll offset is consumed (getVisibleItems/_adjustScroll unchanged). Tests (worklist.test.ts): indicator below fold, ▲ more when scrolled, both indicators, no indicators when the list fits, rows-1 invariant with indicators (plain and grouped), dim non-selectable display rows, and getVisibleItems semantics unchanged. README updated (feature bullet, fold indicators section, design decisions).
Regenerate the auto-generated version constant to match package.json (v1.0.11). The v1.0.11 bump (eebe41e) missed the regeneration, leaving a stale version.ts that breaks tests/cli/init.test.ts ('should create semaphore when config exists but semaphore does not') which asserts result.version === getPackageVersion() via tsx src/cli.ts. Fixes WL-0MSVUMPLX0087HWE (pre-existing, fails on clean dev too; CI masked it by building first).
…ection getNextAuditCandidate now passes --root-only to wl list so only parent items in completed/in_review are audit candidates; children are never dispatched for /skill:audit independently. Updates code comments and the README downtime section; adds unit + CLI e2e coverage (AC1-AC5).
parseLlamaStatus coerces numeric slot_id (proxy contract: int) to string, clamps negatives to 0, preserves the zero-length string rejection; regression tests mirror the live proxy payload at parse level and end-to-end through the worker tick (poll → idle → dispatch). Fixes the Aug 15-16 zero-dispatch regression where every poll fail-closed to busy.
install-herdr.sh runs npm ci (fallback npm install) in packages/herdr before linking the plugin, so marked and @worklog/shared are present on a fresh clone or after a node_modules cleanup. Fixes the prefix+l worklist crash (ERR_MODULE_NOT_FOUND on marked from md-viewer.ts). Still warn-only and idempotent; link and keybinding behavior unchanged.
- buildMetaRows prefixes all icon-bearing metadata values (Status, Stage, Priority, Type, Risk, Effort, Audit, Reviewed) with the same icon helpers used by list rows, as 'icon + text label' (e.g. 🔄 in_progress, ⭐ high). - Stage row uses new shared stageDisplayIcon helper for audit-aware in_review logic (✅/❌/❓ fresh, ⏳ stale-passed, 🔍 otherwise), ensuring the list prefix and metadata Stage row never diverge. - Type row shows epic icon (⊙) for epic items only. - Audit/Reviewed rows pair icons with text labels (ready to close, not ready, unknown, needs review, reviewed). - With icons disabled, metadata falls back to plain text — no emoji, no [BRACKET] fallbacks (unlike list rows). - 7 new test suites covering icon+text values, audit-aware stage row, epic Type icon, plain-text fallback, and panel/detail rendering. - stageDisplayIcon unit tests added to icons.test.ts. - README updated with icon+text metadata documentation.
… TUI (WL-0MSI7278X0008XGM, WL-0MSI72EYW00050N2) - ANSI.mouseEnable/mouseDisable lifecycle sequences emitted on raw-mode entry / cleanup (AC1). - Pure SGR parser (parseMouseEvent), split-chunk buffering (consumeMouseChunk). - mapMouseToAction row mapping (header/fold/separator/footer/panel aware), double-click window (400ms inclusive), wheel/touch scroll (list + detail), filter-stage taps, drag-motion guard (AC2-AC6). - Clears the 41 pre-existing failing tests in worklist-mouse.test.ts (TDD red phase committed without implementation) — resolves WL-0MSWJBBHT002IA6Z and unblocks WL-0MSGIXHHI009KFW9 review. - Test-suite gate excludes only the environmental tests/skill-path-conventions.test.ts (global ~/.pi/agent/skills/plan/SKILL.md legacy reference, triaged in WL-0MSX3H7L20009QTH).
… (best-effort frequency config)
- selectImplementCandidate accepts risk <= Medium (was Low only) and effort <= Medium (was Small/Extra Small only). - createDowntimeDeps wl next flags updated to --risk medium --effort medium. - Test expectations updated to match new thresholds.
…ortcut creates a pane Selection-list dispatches now pass --no-focus to both pane launchers so the worklist keeps keyboard focus while the new pane opens in the background: - src/index.ts: buildSendToPiArgs emits --no-focus for every agent-route dispatch (mirrors buildDowntimePaneArgs); new buildRunInPaneArgs helper used by the !! / ! pane route and the plain-shell stdout route. - scripts/run-in-pane.sh: gains a --no-focus option parsed at the head of argv alongside --cwd; the final pane zoom is skipped when set. Backward compatible: without the flag the zoom still runs. - Tests: index.test.ts extends buildSendToPiArgs assertions and adds a buildRunInPaneArgs group (AC1-AC3); test_run_in_pane.sh adds a mock-herdr main-mode section covering skip-zoom / keep-zoom / command-still-runs (AC4). - README.md: Command output / Pi agent dispatch features and Design decisions document the no-focus behavior; outage scope (downtime worker, open-pi-agent, open.sh/toggle.sh) noted as unchanged (AC5/AC6).
Add the missing onData dispatch wiring for mouse events: - Add clickState variable for double-click detection (400ms window) - Add dispatchMouse() helper that: - Consumes SGR mouse events via consumeMouseChunk() BEFORE keyboard path - Maps events to actions via mapMouseToAction() - Dispatches actions (select-row, open-detail, back, wheel, scroll-detail, filter-stage) - Returns false for non-mouse input (falls through to keys) - Mouse input is inert during modal states (code-freeze, form, ship-it dialog) - Mouse sequences fully consumed before keypress parser; unknown sequences are inert (AC6). Drag-motion and release events are safely ignored (AC6). - Keyboard navigation and chord shortcuts unaffected. Build: green (0 TS errors). Tests: 42 mouse + 229 worklist = 271 passing.
…port - Update footer hints: add [click] select, [dbl-click] open, [wheel] scroll - Update README: add mouse and touch navigation section - Update docs/dev/pause-when-hidden.md: add mouse/SGR reporting section - Build: green (0 TS errors, 271 worklist+mouse tests passing) - Full suite: 4746 tests passing (3 pre-existing failures in tests/herdr/index.test.ts unrelated to this change — pre-existing --no-focus assertion failure)
…ting Root cause: dispatchMouse updated clickState to the CURRENT event before mapMouseToAction ran. mapMouseToAction then checked the window against lastClick (now - at === 0 always <= 400ms), so EVERY left press self-matched as a double-click and returned 'open-detail' instead of 'select-row' — a single click behaved like Enter instead of like an arrow. Fix: extract the mouse input wiring into an exported handleMouseInput(state, key, termSize, clickState) pure-ish dispatch function. It parses with consumeMouseChunk, maps with mapMouseToAction, and advances the clickState tracker AFTER the double-click check (only on left-button presses — releases, wheel, and motion never prime the window). dispatchMouse now delegates to it. Behavior: - single click/tap selects a row (moves selection like arrow keys), mode stays 'list' - second click on the SAME row within 400ms opens detail (Enter-equivalent) - second click on a DIFFERENT row selects that row, never opens detail - wheel/motion/release events never prime the double-click window - partial SGR prefixes are consumed (never leak into keyboard path) - plain keys/foreign escapes (arrows) fall through untouched (AC6) Tests: 7 new regression tests in worklist-mouse.test.ts (49 total). Full suite: 4753 passing (+7), 3 pre-existing unrelated failures.
Add install and install:plugin scripts to package.json, creating a single-command install for all integrations. Changes: - package.json: add 'install' and 'install:plugin' scripts (install chains install:plugin then install:pi-extension) - tests/scripts/package-scripts.test.ts: 8 tests for script wiring - README.md: document npm run install in Pi agent plugin section - packages/herdr/README.md: mention npm run install as alternative postbuild and install:pi-extension remain unchanged. Relates to WL-0MSHZR0J2000Q9SW
…ing active window status Event-driven herdr window status (agent icons + pane focus/visibility), keeping polling (herdr agent list / tab get) as a fail-open fallback. Covers children F1-F7: - F1 (WL-0MSIG3A06008HKBB): mock herdr socket fixture + subscriber contract tests - F2 (WL-0MSIG3G2X007RAEL): events.ts HerdrEventSubscriber (connect, subscribe, typed dispatch, reconnect/backoff, fail-open, add/removePaneSubscription, close on exit) - F3 (WL-0MSIG3IO7009RL03): pane_focused event path — PollGate.setVisibleFromEvent, immediate panePaused update + doRefresh on hidden→visible; resume-poll becomes fallback-only (gated by events-health flag) - F4 (WL-0MSIG3LWG00590AG): agent-status event-path tests (red → green) - F5 (WL-0MSIG3OTB005V1CL): AgentTracker event-applied path (applyAgentStatusChanged / applyAgentDetected / applyPaneGone, snapshotStates, mergeAgentStatesCached) with per-render-cycle coalescing - F6 (WL-0MSIG3RL1006AQID): index.ts/worklist.ts wiring — one subscriber per plugin instance, events-health flag, close() on TUI exit; min_herdr_version stays 0.7.0 (runtime degradation for pre-0.7.5) - F7 (WL-0MSIG3U7W006GH2R): README + shared protocol docs (events.subscribe shape, pushed frames, per-pane lifecycle, session-scope limitation) AC1-AC5 satisfied: event-driven agent status within 1s, event-driven focus transition, cross-instance coverage via pane_agent_detected + shared agent-panes.json re-read, fail-open fallback with no behavioral regression, initial state via one-time snapshot. Full packages/herdr suite green (1188).
…28AP80002F5S) Root cause: formatDetailView sliced at tocLines.length, dropping the 4-line header and duplicating the ToC. Total output exceeded viewportHeight, causing the first lines to scroll off-screen. Fix: - Compute pinnedHeight = 4 (header) + tocLines.length; render [...allLines.slice(0, pinnedHeight), ...bodyLines.slice(offset, offset+bodyViewport)] - Add DETAIL_HEADER_LINES = 4 constant for clarity - Update detailScrollDown to use bodyViewport for consistent clamping - Update existing tests to reflect pinned header behavior Tests added: line-count invariant, header visibility, ToC non-duplication, no-ToC regression check.
The buildSendToPiArgs function now prepends --no-focus (WL-0MSHIA53D009DJOT) to prevent stealing focus from the selection list. Three tests needed updating to include --no-focus in expected arg arrays.
…xes prefix+l worklist pane crash) The HerdrEventSubscriber removed its only socket 'error' listener once the initial events.subscribe succeeded. A later socket reset (ECONNRESET) from herdr then emitted an unhandled 'error' event, crashing the whole plugin process (exit code 1) and killing the worklist pane within ~3 seconds of `prefix+l`. Keep the error listener attached post-subscribe so errors route through handleSocketError() (fail-open reconnect with backoff) instead of crashing. Only the connect-phase timeout listener is removed once the connection settles. Adds a regression test that injects an ECONNRESET on the socket after subscribe and asserts the subscriber stays alive and calls onError (the test fails with the pre-fix code).
Add a per-instance in-memory enabled override to createDowntimeWorker so a shortcut can flip dispatch on/off for one herdr instance independently of the global settings. Effective enabled = override ?? cfg.enabled, honored by both the enabled getter and tick()'s early return. Backward compatible: default null follows the global setting exactly as before.
…[Downtime Off] header Add the d chord (/downtime toggle) to bundled shortcuts.json, route it as an internal TUI action through dispatchChordCommand/executeResolvedCommand (optional onDowntimeToggle callback — no pane, no stdout), and create the downtime worker unconditionally in index.ts so the toggle can force dispatch on for one pane when the global setting is off. renderDowntimeStatus now shows [Downtime Off] instead of [downtime disabled] for all disabled states. Tests cover chord routing, header rendering, per-instance isolation and force-on-when-global-off.
…ntime Off] Document the d shortcut (toggle downtime dispatch for the current pane, per-instance in-memory) in the shortcut keys section and add a Per-pane toggle subsection in the downtime worker documentation; replace [downtime disabled] with [Downtime Off] in the header-status list.
…ests) Test-first suite for the downtime dispatcher round-robin feature (parent WL-0MSSRED76008LGB6): - createRoundRobinRegistry: shared durable cursor persisted to .worklog/downtime-round-robin.json, fail-open on missing/corrupt file, per-tier cursors (audit/implement/plan/intake), atomic temp+rename writes - getEffectivePollInterval: probe jitter ±50% (factor 0.5 + rng, clamped to [0.5, 1.5]), injectable RNG for deterministic tests - 16 unit tests: cursor round-trip persistence, fail-open corrupt/empty files, rotation cycling, cross-instance divergence, per-tier isolation, jitter bounds and clamping herdr suite: 1229 tests pass; tsc clean; full project suite green.
Wire the round-robin registry (WL-0MSW6CACJ0035UGK) into selection: - selectWithRotation: groups candidates by priority level, rotates within the tied top-priority group via the shared durable cursor; fail-open (no registry / no priority → sortIndex order, pre-rotation behaviour) - priority field parsed in parseNextCandidatesOutput / parseImplementCandidatesOutput / parseAuditCandidatesOutput - selectAuditCandidate / selectImplementCandidate / selectNextCandidate accept an optional registry and delegate to selectWithRotation - 8 new tests: rotation cycling, priority-first preservation, single-member top group, fail-open paths, and per-tier wiring herdr suite: 1239 tests pass; tsc clean; full project suite green.
Dispatch wiring end-to-end (parent WL-0MSSRED76008LGB6): - createDowntimeDeps caches a shared round-robin registry per worklog root and passes it to selectNextCandidate / selectAuditCandidate / selectImplementCandidate, so all three tiers rotate within tied-priority groups via the durable cursor file - createDowntimeWorker accepts an optional registry; expose jitterPollIntervalMs() computing the jittered ±50% probe interval - TaskScheduler gains a getIntervalMs hook so a task recomputes a FRESH interval on every reschedule; the downtime scheduler task uses the worker's jitterPollIntervalMs to deserialize probes across instances - 7 new tests: scheduler per-schedule jitter, worker jitter fallback + cross-RNG divergence, deps rotation via cursor file (deep-equality + persisted advance) herdr suite: 1246 tests pass; tsc clean; full project suite green.
Docs for the downtime dispatcher round-robin + jitter feature (parent WL-0MSSRED76008LGB6): - README: new 'Round-robin tie-break' and 'Probe jitter' subsections documenting the shared durable cursor (.worklog/downtime-round-robin.json), fail-open conventions, dispatched-marker guard, and the ±50% jittered probe interval - downtime-worker.ts module header: document selectWithRotation and jitterPollIntervalMs Absorption of WL-0MSMHX6IR007F3H6 is already recorded (deleted with 'Absorbed by WL-0MSSRED76008LGB6'). ARCHITECTURE.md has no downtime content to update. herdr suite: 1246 tests pass; tsc clean; full project suite green.
…s->10s - DEFAULT_DOWNTIME_IDLE_THRESHOLD_MS 240_000 -> 60_000 (floor 1s unchanged) - DEFAULT_DOWNTIME_POLL_INTERVAL_MS 30_000 -> 10_000 (existing floor) - Update tests/herdr/settings.test.ts defaults to 60000/10000 - Update packages/herdr/README.md documented defaults - AC4: deployed config untouched (no downtime keys => defaults apply)
…ing mouse support
Replace all hardcoded DEFAULT_DOWNTIME_IDLE_THRESHOLD_MS (240_000) and DEFAULT_DOWNTIME_POLL_INTERVAL_MS (30_000) values in tests with the exported constants. Adjusts timing assertions to match the new 60s threshold and 10s poll interval. Fixes AC3: idle-threshold timing tests now use DEFAULT_DOWNTIME_IDLE_THRESHOLD_MS; no test asserts old 240s/30s values. (WL-0MSOMV6CW003OXTK)
… scrolls (WL-0MSQ44MDX008U69J) Flip the space priority in the worklist pane: the selection list may use up to the full available pane height (header + items + separators + indicators + footer), and the metadata panel fills whatever space remains — expanding when the list is short, sitting at the bottom (min 3 rows) when the list is long. The metadata panel scrolls independently (m/M) via metaScrollOffset. - Add computeDynamicLayout() shared by the renderer and the main TUI loop (panel height is passed into metaScrollDown for correct clamping) - createListRenderer computes panelHeight from content need instead of the fixed 20-40% reservation (computeMetadataPanelHeight is now a legacy fallback used only when no dynamic height is supplied) - Initial view starts at the top of the selection list (scroll offset 0); long lists no longer scroll the header off the top - Preserve the rows - 1 line-count invariant (WL-0MSAAON63003N6LO) and the m/M scroll indicator; update mouse click-row mapping for the new layout - Update unit tests (list-height/content-need, metadata expansion, reveal-by-scroll, scroll clamping, line-count invariant) and docs
added 28 commits
August 23, 2026 08:43
…NHE0048HW3) Add comprehensive investigation report for snapd udev monitor goroutine hang that caused system hang on 2026-08-20. Key findings: - Root cause: udev monitor goroutine stuck in select on netlink socket (udevmon.go:147) - Cascading effect: watchdog SIGABRT → snapd won't restart → forced reboot - snapd 2.76.3 available with netlink parser fixes (90% phased) - Current system stable for 2+ days post-incident Mitigation plan: - Primary: upgrade to snapd 2.76.3 (includes netlink parser fixes) - Fallback: disable systemd watchdog (override file provided) Includes: - Full investigation report (docs/dev/snapd-investigation.md) - Systemd override drop-in (docs/systemd/snapd-watchdog-overrides.conf) - Monitoring plan and verification steps
Handle verbose agent-produced risk/effort descriptions by extracting leading keywords before delimiters. - riskOrdinal: split on delimiters (-, :, –, —, whitespace), take first token, then switch-match. 'Medium — NVIDIA driver changes…' → 2. - effortOrdinal: exact match on stripped string first, then word-boundary regex fallback for keywords anywhere in the string. '1–4 hours — Small. Investigation…' → 2. Mirrors the herdr client-side fix in downtime-worker.ts.
Test riskOrdinal, effortOrdinal, and matchesRiskEffort with verbose
agent-produced descriptions and regression coverage for:
- Verbose risk ("Medium — ..." → ordinal 2)
- Verbose effort ("1–4 hours — Small. ..." → ordinal 2)
- Short spellings (xs/s/m/l/xl) and long forms
- Fail-closed for unknown/empty values
- high/severe risk excluded by at-most medium filter
…fig (WL-0MT1KJNHE0048HW3) - Comprehensive investigation report with root cause analysis - Goroutine stack dump analysis showing udev monitor select hang - Upstream snapd research and version availability - Systemd watchdog override configuration (WatchdogSec=0) - Monitoring plan and recommended actions
…tax errors Defect 1 — FTS index never updated incrementally: - import() re-upserts FTS rows for all imported items after the clear (clearWorkItems/clearComments now also purge worklog_fts) - upsertItems() upserts FTS per saved item - reconcileBlockedStatus / reconcileDependentStatus upsert FTS after status changes so status-filtered search reflects the change - upsertComments()/importComments() re-index affected items (comment text lives in the parent item's FTS row) Defect 2 — malformed FTS5 queries failed silently: - searchFts no longer swallows errors; WorklogDatabase.search() retries the query as a quoted phrase and emits a visible warning when auto-quoting succeeds; surfaces when it fails - CLI (wl search) prints the warning (human + --json field) and exits non-zero with a clear message on hard query errors Tests: fts-freshness.test.ts (freshness + error surfacing + AC1/AC3), cli/search-fts-freshness.test.ts (e2e). Docs: CLI.md, docs/dev/fts.md. WL-0MSLW8UCP001771K
…false→null The old cycle allowed pressing `d` twice to force-enable dispatch (override = true), which silently overrode the global disable setting and any sibling-pane disable. This violated the operator expectation that pressing `d` disables dispatch until explicitly re-enabled. New cycle: null → false (disable) → null (follow settings). No force-enable path. The second press returns to following the global setting without requiring explicit confirmation. Tests updated: existing toggle/override tests adapted to new cycle; new tests added for 60-minute no-dispatch guarantee (AC1), no force-enable on second press (AC3), three-press cycle, tick short-circuit after disable, and tick behavior after second press. WL-0MT58VKOW0066XJ6
The d-shortcut disable was in-memory only, so a pane/plugin restart silently re-enabled dispatch (reported incident: audits dispatched while the operator believed dispatch was disabled). This adds a small per-worklog-root marker file that survives restarts: - toggle() -> disable writes <cwd>/.herdr-downtime-disabled - toggle() -> follow-settings removes it (no-op when absent) - createDowntimeWorker reads the marker at init: present => override = false (disabled) unless an explicit opts.override is passed (explicit wins, marker is the fallback) - All marker IO is best-effort/non-fatal: toggle() never crashes on fs errors (e.g. read-only FS, missing cwd) New module downtime-disable-marker.ts (existsSync/writeFileSync/rmSync sync helpers). Tests: 10 new tests covering creation, no-rewrite, removal, init-restore, explicit-override precedence, per-root scoping, and zero-dispatch across idle+cooldown windows (AC1/AC6). Full suite: 5351 passed, 0 failures. WL-0MT58WX3X0092PTK, WL-0MT5SFP990001FNW
…ment - DowntimeWorker gains restoredFromMarker: true only when the disable was restored from the persisted marker at construction (survived a restart); false for live d-presses and cleared on explicit re-enable - renderDowntimeStatus renders "[Downtime Off (restored)]" for a marker-restored disable so a restored-off pane is never silent (parent AC2/AC4); all other states unchanged and always agree with the effective gate (override ?? settings) - Live toggle() writes the marker but is NOT flagged restored; the second press removes the marker and clears the flag Tests (worklist.test.ts): 6-state effective-gate matrix (disabled pane never renders the enabled-idle string), Off regardless of global setting, restored notice renders/clears, integration tests with a real marker on disk (restore → notice → re-enable → notice gone) and live-press not flagged restored. Full suite: 5362 passed, 0 failures. WL-0MT5SFX1S002YUYZ, WL-0MT5SG0VU005ARUR
…WL-0MSXH3UF5000HJUS) Implements file-lock-based leader election for the downtime dispatcher refactor (parent WL-0MST3OJ8S0001ROL): - O_CREAT|O_EXCL atomic lock acquisition at .worklog/downtime-leader.lock - Lease file (.worklog/downtime-leader-lease.json) with 5-min TTL, refreshed on each proxy-poll cycle via refreshLease() - detectStaleLeader() + cleanupStaleElection() for expiry detection and re-election after a crashed/idle leader - runElectionWithRetry() with exponential backoff (1-2s) for non-leaders - close() preserves the winning lease (crash-recovery: lease expiry, not explicit deletion, triggers takeover) - Fail-safe: missing/unreadable files degrade to no-dispatch Note: lock location uses .worklog/ (matching the parent AC's downtime-coordination.json location and existing dispatch-log/round-robin convention) rather than the child's .worklock/ mention. 30 unit tests covering lock acquisition, lease expiry, stale detection, re-election, and graceful degradation.
…8151F)
Manages <worklog-root>/.worklog/downtime-coordination.json for the
downtime dispatcher refactor (parent WL-0MST3OJ8S0001ROL):
- One entry per instance: {instanceId, workItemId, directory, assignedAt,
lastUpdated}
- Safe concurrent access: O_CREAT|O_EXCL exclusive lock (flock-equivalent
for the single-machine v1 scope) + atomic tmp+rename writes — no partial
JSON, no interleaved read-modify-write loss
- upsertEntry / removeEntry / getEntry / mergeEntries / pruneStaleEntries
- Leader-side pruning of entries older than the 5-minute lease bound
- Fail-safe: missing/corrupt file or lock contention → operation skipped,
never throws, never touches another instance's entry
21 unit tests covering format, upsert/update/remove, lock serialization,
stale pruning, batch merge, and fail-safe paths.
…st (WL-0MSXHA1B0005G3E5, WL-0MSXHA7LP000QNGP) Refactors the downtime dispatcher (parent WL-0MST3OJ8S0001ROL) so only the elected leader dispatches work, from the shared coordination file: - dispatchFromCoordination: leader re-fetches each entry's item and classifies it into its tier (audit → implement → plan → intake), applies the code-freeze + spare-capacity slot minimums, dispatches the highest-priority item via the existing CAS-claim/marker/spawn pipeline (AC5 guards preserved) and removes the entry for its owner to re-queue. - classifyItemForDispatch: pure tier classifier (audit recency window, implement risk/effort caps, open-state guards — fail-closed). - computeMostImportantItem / runCoordinationCheckIn: every instance's 30-min check-in computes its most-important item and upserts/verifies its coordination entry (first check-in on startup; re-offers after dispatch; removes a dead entry on a genuine empty backlog; fail-open on wl errors). - Worker tick: leader refreshes the 5-min lease per poll and polls the proxy; non-leaders skip proxy polling and dispatch entirely, running only the cheap lease-takeover check + the 30-min check-in (AC1/AC2/AC4). - index.ts: wires coordinationDir + the fetchItem dep (with auditedAt enrichment for the audit tier). - Legacy fallback: workers without a coordinationDir run the pre-refactor direct tier chain (fail-open; existing tests unchanged). Also fixes the pre-existing herdr build breakage from the WL-0MSJ4BT4Z002HH9B icon refactor (private _clampSelection access + DisplayRow union narrowing) — triaged as WL-0MT5YFJ8A0059SDJ. 28 new tests (coordination-dispatch.test.ts) + hasLease coverage; the full 49-file herdr suite passes (1652 tests) and the package builds clean.
…67VAL) - downtime-log.ts: new bounded rolling .worklog/downtime-coordination.log (separate from the dispatch log so marker readers never see coordination records) with appendCoordinationLogEntry (fail-closed, never throws) - runCoordinationCheckIn logs check-ins (offered item / emptied entry); the worker logs leadership wins/takeovers; dispatchFromCoordination logs stale-entry pruning - dispatchFromCoordination now prunes stale entries (lastUpdated older than the lease TTL) at the start of each leader dispatch cycle (parent AC3 risk mitigation) - README: documents the leader-election + shared coordination list architecture (election, lease, check-in, leader dispatch, fail-safe) 2 new coordination-log tests; full 49-file suite passes (1654 tests).
…low (WL-0MSXHAKDT005H6VQ) End-to-end coverage of the refactored downtime dispatcher (parent WL-0MST3OJ8S0001ROL): - full flow: two instances → one elected leader → both check in → leader detects idle → dispatches the highest-priority offer into the OFFERING instance's worklog directory → entry removed → owner re-queues its next item at the next 30-min check-in - lease expiry + takeover by a new instance (AC2) - CAS regression: an already-in_progress offer is never re-dispatched - stale-entry pruning by the leader dispatch cycle Also fixes two implementation bugs surfaced by the tests: - leader-election lock/lease writes now mkdir the coordination dir (fresh worklogs may lack it) - dispatchFromCoordination re-reads the coordination file after pruning so a pruned stale entry can never classify or dispatch the same cycle 4 integration tests; full 50-file herdr suite passes (1658 tests); the repo's full suite (tests/cli + tests/unit) passes after a fresh build.
- README `d` shortcut + Per-pane toggle section: scope is per-pane only (sibling panes / other roots unaffected); disable now persists across plugin restarts via the .herdr-downtime-disabled marker with a visible [Downtime Off (restored)] header notice; re-enable by pressing `d` again or deleting the marker; the toggle cycle is d=disable / d=follow-settings with NO force-enable state - shortcuts.json d-shortcut description updated to match - renderDowntimeStatus JSDoc documents the restored notice and the effective-gate agreement guarantee Full suite: 5362 passed, 0 failures. WL-0MT5SMS6Z0072P2L
…AC6) Confirms H1 + H3 jointly explain the reported incident (audit WL-0MSKSMRFP001Q3XU dispatched 11:05:23Z while dispatch believed disabled, plus the 11:05-11:47Z multi-root wave): - H1 (override lost/cycled): the old toggle force-enabled on a second press (null→false→true→null) and was in-memory only — worklist panes were (re)opened 10:25-10:28, silently reverting any earlier press to default-enabled; both variants now removed by the marker + new cycle - H2 (in-flight dispatch): rejected — the dispatch log entry and pane spawn timestamps show a NEW dispatch, not a completion - H3 (scope mismatch): confirmed contributor — no leader election, so every root dispatched concurrently by default; per-pane `d` scope now documented Open questions Q1-Q3 closed/acknowledged. README links the RCA. WL-0MT5SMVXS004T3BK
…lot mode (WL-0MT65T14L002HTWB) The global idle tracker (idleSince) drives the title bar idle indicator, but in per-slot mode it was fed the relaxed per-slot global gate (perSlotGlobalIdleChecks: server-up + no model-switch only) which deliberately ignores per-slot query activity. The displayed idle counter therefore showed '[⏳ downtime idle Xs]' while the llama UI was actively processing in a slot. Fix (display-only): the tracker's record() call in the per-slot tick branch now also requires that NO slot reports is_processing=true — when any slot is processing (including the operator's own session), the title bar shows '[downtime busy]'. Dispatch logic is unchanged: the free-slot count and spare-capacity relaxation still come from perSlotTracker, not tracker.idleSince. Tests: updated two per-slot routing tests whose idleSince expectations encoded the buggy display behavior; added four display tests covering busy-while-processing, idle-only-when-all-free, reset/restart of the idle counter, and global-busy reset.
…WL-0MSS1Q5ER007QDKX) The coordination (leader-election) dispatch path checked the scheduled- prompts tier only in legacy dispatchDowntimeWork; dispatchFromCoordination never consulted getDueScheduledPrompt, so scheduled prompts never fired in the default leader configuration (audit AC3/AC6 partial). - Add the scheduled-prompts stage to dispatchFromCoordination as the FIRST dispatch stage (freeze-gated, same fail-closed semantics as the legacy path): a due prompt dispatches via dispatchScheduledPrompt (persist lastTriggeredAt → kind:scheduled marker → spawn) before any coordination-tier work, so it never triggers the no-candidate cooldown. - Add coordination-path tests covering first-stage dispatch, persist+marker ordering, fail-closed marker aborts, freeze gating, and tier fall-through. - Document the leader-path scheduled check in packages/herdr/README.md.
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.
Automated release created by ship skill.\n\nIncludes CHANGELOG.md with work-item summaries from this release.