instar 1.3.648 → 1.3.650
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dashboard/index.html +14 -14
- package/dist/commands/server.d.ts.map +1 -1
- package/dist/commands/server.js +3 -0
- package/dist/commands/server.js.map +1 -1
- package/dist/core/AutoUpdater.d.ts.map +1 -1
- package/dist/core/AutoUpdater.js +23 -3
- package/dist/core/AutoUpdater.js.map +1 -1
- package/dist/core/UpdateChecker.d.ts +12 -0
- package/dist/core/UpdateChecker.d.ts.map +1 -1
- package/dist/core/UpdateChecker.js +22 -0
- package/dist/core/UpdateChecker.js.map +1 -1
- package/dist/core/types.d.ts +10 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/monitoring/DegradationReporter.d.ts +18 -0
- package/dist/monitoring/DegradationReporter.d.ts.map +1 -1
- package/dist/monitoring/DegradationReporter.js +24 -0
- package/dist/monitoring/DegradationReporter.js.map +1 -1
- package/dist/monitoring/SystemReviewer.d.ts +22 -0
- package/dist/monitoring/SystemReviewer.d.ts.map +1 -1
- package/dist/monitoring/SystemReviewer.js +37 -0
- package/dist/monitoring/SystemReviewer.js.map +1 -1
- package/dist/monitoring/WorktreeMonitor.d.ts +13 -6
- package/dist/monitoring/WorktreeMonitor.d.ts.map +1 -1
- package/dist/monitoring/WorktreeMonitor.js +50 -33
- package/dist/monitoring/WorktreeMonitor.js.map +1 -1
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +8 -3
- package/dist/server/routes.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +48 -48
- package/upgrades/1.3.649.md +30 -0
- package/upgrades/1.3.650.md +21 -0
- package/upgrades/eli16/autoupdate-strand-detector.md +22 -0
- package/upgrades/eli16/dashboard-health-signal-polish.md +26 -0
- package/upgrades/eli16/systemreview-onboot-refresh.md +24 -0
- package/upgrades/eli16/worktree-monitor-async-git.md +24 -0
- package/upgrades/side-effects/autoupdate-strand-detector.md +36 -0
- package/upgrades/side-effects/dashboard-health-signal-polish.md +59 -0
- package/upgrades/side-effects/systemreview-onboot-refresh.md +39 -0
- package/upgrades/side-effects/worktree-monitor-async-git.md +39 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Side-Effects Review — Dashboard health-signal polish (3 pre-existing fixes)
|
|
2
|
+
|
|
3
|
+
**Version / slug:** `dashboard-health-signal-polish`
|
|
4
|
+
**Date:** `2026-06-23`
|
|
5
|
+
**Author:** Echo (autonomous)
|
|
6
|
+
**Tier:** 1 (three contained, low-risk fixes — two client-side dashboard guards + one health-status windowing on an endpoint whose `status` field does not gate restarts on this agent)
|
|
7
|
+
**Second-pass reviewer:** not-required (Tier 1; small contained fixes, the server-side change covered by new unit tests)
|
|
8
|
+
|
|
9
|
+
## Summary of the change
|
|
10
|
+
|
|
11
|
+
Three minor, pre-existing dashboard issues found while verifying the dashboard-freeze fix (#1253). None affect the dashboard connection; all are noise/cosmetic.
|
|
12
|
+
|
|
13
|
+
1. **WhatsApp QR poll spam.** `pollWaQr()` polls `/whatsapp/qr` every 3s. When WhatsApp isn't configured the route returns 503, but the poll loop was never stopped — so the browser console accumulated ~20 503s/min for the whole session. Fix: call `stopWaPolling()` on the 503 (adapter absent).
|
|
14
|
+
2. **Null-deref TypeError on load.** A parse-time block attached a `'message'` listener to `ws`, guarded by `if (typeof ws !== 'undefined')`. `ws` is a declared `let` that is **null** at parse time (`connectWebSocket()` runs later in a `.then()`), and `typeof null === 'object' !== 'undefined'`, so the guard passed and `ws.onmessage` threw `Cannot read properties of null (reading 'onmessage')` on every load. The block was the only place paste WebSocket events refreshed the drop-zone — so the feature was also effectively dead. Fix: fold `paste_delivered` / `paste_acknowledged` into the main `handleMessage` switch (where it survives reconnects) and remove the broken parse-time block.
|
|
15
|
+
3. **Stale "Degraded" badge.** The vital-signs badge renders `/health`'s `status`, which is `degraded` whenever `DegradationReporter.getEvents().length > 0`. Degradation events are append-only and never self-clear, so a single transient/benign fallback pins the badge red for the entire process lifetime even after recovery. Fix: `/health` now uses a new `DegradationReporter.getRecentEvents()` (30-min window) for status + the reported count/summary — a persistent problem keeps re-reporting so it stays visible; a one-off ages out.
|
|
16
|
+
|
|
17
|
+
## The change
|
|
18
|
+
|
|
19
|
+
- `dashboard/index.html` — (1) `stopWaPolling()` on 503; (2) paste cases added to `handleMessage`, broken parse-time block removed.
|
|
20
|
+
- `src/monitoring/DegradationReporter.ts` — new `getRecentEvents(windowMs = HEALTH_WINDOW_MS, now = Date.now())` + `static HEALTH_WINDOW_MS = 30min`. Filters by event `timestamp`; an unparseable timestamp is KEPT (fail-safe: surface, never hide). `getEvents()` (full log) is unchanged.
|
|
21
|
+
- `src/server/routes.ts` — `/health` calls `getRecentEvents()` instead of `getEvents()` for the `degradations` list that feeds `status` + `degradations` count + `degradationSummary`.
|
|
22
|
+
- `tests/unit/degradation-reporter.test.ts` — 3 new tests (recent kept / old aged out / unparseable kept).
|
|
23
|
+
|
|
24
|
+
## Side effects & risk
|
|
25
|
+
|
|
26
|
+
- **`/health` status semantics.** `status` flips back to `ok` once degradations age past 30 min without recurring. Reviewed consumers: the **Telegram lifeline** derives `serverHealthy` from the supervisor's process status, NOT from `/health.status`, so this never affects restart behavior on a Telegram agent (Echo). `SlackLifeline` keys on `status === 'ok'` only to detect the unhealthy→healthy *recovery* transition (a log/notify), not to trigger restarts. The change makes status MORE truthful (reflects current health), and is the safe direction (a real, recurring problem keeps re-reporting and stays visible).
|
|
27
|
+
- **The full degradation log is untouched.** `getEvents()`, `getUnreportedEvents()`, mark-reported, and persistence all still see every event — only the /health *status view* is windowed.
|
|
28
|
+
- **Client-side fixes are isolated** to the dashboard static asset; no server behavior changes; the dashboard isn't in the typecheck/test suite, so both were syntax-checked with `node --check`.
|
|
29
|
+
- **Risk:** low. No new config, no new route, no migration (the dashboard asset + core source ship with the package on update). Reversible (revert the three edits).
|
|
30
|
+
|
|
31
|
+
## 6b. Operator-surface quality (Operator-Surface Quality standard)
|
|
32
|
+
|
|
33
|
+
This change touches `dashboard/index.html`. It alters **background behavior**, not layout or primary actions — so it adds no new operator-facing controls or content. Against the four criteria:
|
|
34
|
+
|
|
35
|
+
1. **Leads with the primary action?** Unchanged — the dashboard still leads with the session list + vital-signs strip. No new primary action is introduced; nothing is moved, collapsed, or pushed below the fold.
|
|
36
|
+
2. **Zero raw internals as primary content?** Yes — the change adds NO displayed content. It REMOVES noise (the WhatsApp console 503 spam) and an on-load TypeError, and makes the existing plain-language health badge ("Healthy" / uptime vs "Degraded") accurate. No JSON/UUID/fingerprint/hash is shown.
|
|
37
|
+
3. **Destructive actions de-emphasized?** N/A — no destructive control is added or touched.
|
|
38
|
+
4. **Plain language + phone width?** Yes — the only user-visible behavioral change is the top-of-dashboard badge now correctly reads "Healthy" when the box is healthy (it was stuck on "Degraded"). Plain wording, same layout, same width — no new elements, so no phone-width regression.
|
|
39
|
+
|
|
40
|
+
(The dashboard's pre-existing raw-input field — the PIN/token unlock — already carries the co-located `operator-surface-power-user` marker and is untouched here.)
|
|
41
|
+
|
|
42
|
+
## 7. Multi-machine posture (Cross-Machine Coherence)
|
|
43
|
+
|
|
44
|
+
**machine-local BY DESIGN.** The vital-signs badge reads THIS machine's `/health`; each machine's `DegradationReporter` and health state are local, per-machine observability (a degradation on machine A is machine A's concern). The dashboard already exposes pool-wide state through separate `?scope=pool` surfaces (sessions, guards, attention, …), which this change does not touch. The WhatsApp poll and the paste WS handler are pure client-side, per-browser-tab.
|
|
45
|
+
|
|
46
|
+
- **User-facing notices?** None emitted — no one-voice gating concern.
|
|
47
|
+
- **Durable state strand on topic transfer?** No durable state added; nothing strands.
|
|
48
|
+
- **Generated URLs?** None.
|
|
49
|
+
|
|
50
|
+
## Verification
|
|
51
|
+
|
|
52
|
+
- `tsc --noEmit`: 0 errors.
|
|
53
|
+
- `tests/unit/degradation-reporter.test.ts`: 20/20 (incl. 3 new windowing tests).
|
|
54
|
+
- `tests/unit/{routes-degradations-mark-reported,degradation-never-silent,degradation-reporter-reentrancy-wedge}.test.ts`: 14/14 (no regression on the mark-reported / never-silent paths).
|
|
55
|
+
- `dashboard/index.html` embedded script: `node --check` passes.
|
|
56
|
+
|
|
57
|
+
## Rollout
|
|
58
|
+
|
|
59
|
+
No flag, no migration. Three additive fixes that surface previously-hidden console errors and make the health badge truthful. The genuinely-stale `systemReview` *panel* (last ran 2026-06-20, never re-runs) is a SEPARATE, more-involved concern (re-running 16 probes on a cadence is cost-bearing) — explicitly NOT bundled here; tracked for follow-up.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Side-Effects Review — SystemReviewer on-boot refresh (fix stale "Degraded" panel)
|
|
2
|
+
|
|
3
|
+
**Version / slug:** `systemreview-onboot-refresh`
|
|
4
|
+
**Date:** `2026-06-23`
|
|
5
|
+
**Author:** Echo (autonomous)
|
|
6
|
+
**Tier:** 1 (one added on-boot review timer on a self-scheduling monitor; behavior-additive, behind a default-on flag, covered by 4 new unit tests)
|
|
7
|
+
**Second-pass reviewer:** not-required (Tier 1)
|
|
8
|
+
|
|
9
|
+
## Summary of the change
|
|
10
|
+
|
|
11
|
+
The SystemReviewer (the probe-health "system review" surfaced in `/health`, rendered as the dashboard health panel) runs a full review every `scheduleMs` (default 6h). But `start()` only set up the interval — it ran NO review on boot. On an agent that restarts more often than 6h (updates, recovery bounces, the dashboard-freeze restarts), the 6h timer resets on every boot and never fires, so the displayed review stays frozen at the last pre-restart boot. Observed live: the systemReview in `/health` was stuck at `2026-06-20T11:51` (the meltdown) — days stale, showing "critical / 11-of-16 probes passed" long after the box recovered.
|
|
12
|
+
|
|
13
|
+
## The change
|
|
14
|
+
|
|
15
|
+
- `src/monitoring/SystemReviewer.ts` — `start()` now schedules ONE on-boot review (`setTimeout`, default 30s delay, unref'd) in addition to the interval, gated by `shouldRunInitialReview()`: it runs only when the last persisted review is absent or older than `initialReviewStaleAfterMs` (default 1h), so a restart loop doesn't pile up reviews. An unparseable timestamp is treated as stale (run it — fail toward fresh). New config: `reviewOnStart` (default true), `initialReviewDelayMs` (30s), `initialReviewStaleAfterMs` (1h). `stop()` clears the new timer.
|
|
16
|
+
- `src/commands/server.ts` — forwards the three new config fields to the SystemReviewer so `reviewOnStart: false` is honored as an off-switch.
|
|
17
|
+
- `src/core/types.ts` — the three new optional fields on `monitoring.systemReview`.
|
|
18
|
+
- `tests/unit/SystemReviewer.test.ts` — 4 new tests (no-prior→runs, stale→runs, fresh→skips, reviewOnStart:false→never).
|
|
19
|
+
|
|
20
|
+
## Side effects & risk
|
|
21
|
+
|
|
22
|
+
- **Cost: one extra review per boot, only when stale.** The probes are LOCAL checks (no LLM, no network — verified by reading the probe sources), so a review is cheap. The freshness guard means even a rapid restart loop triggers at most one review per `initialReviewStaleAfterMs` window, not one per boot.
|
|
23
|
+
- **Boot is not slowed.** The review is on a 30s `setTimeout`, unref'd, and `review()` is already async — it never blocks startup.
|
|
24
|
+
- **Default-on, with an off-switch.** `monitoring.systemReview.reviewOnStart: false` disables it. Existing agents get the default (true) via the constructor's DEFAULT config — no migration needed (a code-side default, not a persisted-config requirement).
|
|
25
|
+
- **Failure is contained.** An on-boot review error goes to the existing dead-letter path (the same `writeDeadLetter` the interval uses), independent of DegradationReporter by design.
|
|
26
|
+
- **Risk:** low. Additive timer on a self-scheduling monitor; reversible (flag or revert); covered by tests.
|
|
27
|
+
|
|
28
|
+
## 7. Multi-machine posture (Cross-Machine Coherence)
|
|
29
|
+
|
|
30
|
+
**machine-local BY DESIGN.** Each machine runs its own SystemReviewer over its own probes and persists to its own `review-history.jsonl`; the `/health` systemReview reflects THIS machine's health. The on-boot review is per-machine local behavior — no cross-machine state, no notice emitted, no URL generated. A pool already surfaces each machine's health via that machine's own `/health`.
|
|
31
|
+
|
|
32
|
+
## Verification
|
|
33
|
+
|
|
34
|
+
- `tsc --noEmit`: 0 errors.
|
|
35
|
+
- `tests/unit/SystemReviewer.test.ts`: 128/128 (124 existing + 4 new on-start tests covering both sides of the staleness boundary).
|
|
36
|
+
|
|
37
|
+
## Rollout
|
|
38
|
+
|
|
39
|
+
No migration. Default-on (the stale panel is a clear bug); off-switch via `monitoring.systemReview.reviewOnStart: false`.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Side-Effects Review — WorktreeMonitor async git scans
|
|
2
|
+
|
|
3
|
+
**Version / slug:** `worktree-monitor-async-git`
|
|
4
|
+
**Date:** `2026-06-23`
|
|
5
|
+
**Author:** Echo (autonomous)
|
|
6
|
+
**Tier:** 1 (behavior-preserving async refactor — no new feature, no decision points, no dark-gate, no migration)
|
|
7
|
+
**Second-pass reviewer:** not-required (Tier 1; small, low-risk, fully covered by existing unit tests)
|
|
8
|
+
|
|
9
|
+
## Summary of the change
|
|
10
|
+
|
|
11
|
+
`WorktreeMonitor.scanWorktrees()` issues several git commands — `git worktree list --porcelain` plus per-worktree `rev-list`/`diff` — through a private `gitCommand()` that used **`spawnSync('/bin/sh', ['-c', 'git …'])`**. On a repository with many worktrees (Echo accumulated ~282) `git worktree list` alone takes seconds, and the scan ran on BOTH a 5-minute periodic timer AND on every `sessionComplete` event. Each scan therefore blocked the **server event loop** synchronously for seconds.
|
|
12
|
+
|
|
13
|
+
That freeze was invisible on `localhost` (the dashboard websocket reconnects instantly), but over the Cloudflare tunnel (`echo.dawn-tunnel.dev`) a 1–2s freeze times out in-flight requests, dropping the dashboard websocket and failing its polls → the user-visible "Disconnected / 0 sessions / Mem 0%". This is the residual event-loop-block cause behind the dashboard "disconnected" reports, after the sync-READ fixes in #1247–#1251.
|
|
14
|
+
|
|
15
|
+
## The change
|
|
16
|
+
|
|
17
|
+
Convert `gitCommand` from `spawnSync` to async `execFile` (promisified), and propagate `await` through the scan call graph so the event loop stays responsive while git runs in the background.
|
|
18
|
+
|
|
19
|
+
Files modified:
|
|
20
|
+
- `src/monitoring/WorktreeMonitor.ts` — `gitCommand` → `async` (execFile, stdout-on-nonzero-exit/timeout semantics preserved to exactly match the old `spawnSync result.stdout ?? ''`); `listWorktrees`, `checkUnmergedWork`, `findOrphanBranches`, `getDefaultBranch`, `getWorktreeAge`, `scanWorktrees` → `async`; `periodicScan` resolves worktree ages with `Promise.all` before filtering; `formatPeriodicAlert` → `async`.
|
|
21
|
+
- `src/server/routes.ts` — `GET /hooks/worktrees` handler → `async`, awaits `scanWorktrees()`.
|
|
22
|
+
- `tests/unit/worktree-monitor.test.ts` — the 13 tests that called the now-async methods updated to `await` them.
|
|
23
|
+
|
|
24
|
+
## Side effects & risk
|
|
25
|
+
|
|
26
|
+
- **Behavior preserved.** The scans still run on the same triggers and the same 5-minute interval; only the blocking changed to non-blocking. `gitCommand`'s return contract (stdout even on a non-zero exit / timeout) is kept identically, so callers see the same strings.
|
|
27
|
+
- **No external surface change.** `GET /hooks/worktrees` returns the same JSON; the periodic + post-session alert paths are unchanged.
|
|
28
|
+
- **Concurrency.** Scans are serialized per the existing event flow (no new parallelism introduced beyond `Promise.all` over `getWorktreeAge`, which is read-only `git show` per worktree).
|
|
29
|
+
- **Risk:** low. A behavior-preserving async conversion, fully covered by the existing 22 unit tests (updated to await) + the serendipity-routes integration tests, both green.
|
|
30
|
+
|
|
31
|
+
## Verification
|
|
32
|
+
|
|
33
|
+
- `tsc --noEmit`: 0 errors.
|
|
34
|
+
- `tests/unit/worktree-monitor.test.ts`: 22/22 pass.
|
|
35
|
+
- `tests/integration/serendipity-routes.test.ts`: 22/22 pass.
|
|
36
|
+
|
|
37
|
+
## Rollout
|
|
38
|
+
|
|
39
|
+
No flag, no migration — the fix is strictly better behavior on the existing code path and ships on the next release. (A shadow-dist hotpatch — disabling both scan triggers — relieved the freeze immediately on the affected agent; this PR is the durable replacement that keeps the feature working without blocking.)
|