instar 1.3.641 → 1.3.643

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.
Files changed (54) hide show
  1. package/dist/commands/server.d.ts.map +1 -1
  2. package/dist/commands/server.js +160 -6
  3. package/dist/commands/server.js.map +1 -1
  4. package/dist/config/ConfigDefaults.d.ts.map +1 -1
  5. package/dist/config/ConfigDefaults.js +29 -0
  6. package/dist/config/ConfigDefaults.js.map +1 -1
  7. package/dist/core/InFlightSyncOpMarker.d.ts +29 -0
  8. package/dist/core/InFlightSyncOpMarker.d.ts.map +1 -0
  9. package/dist/core/InFlightSyncOpMarker.js +149 -0
  10. package/dist/core/InFlightSyncOpMarker.js.map +1 -0
  11. package/dist/core/PostUpdateMigrator.d.ts +16 -0
  12. package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
  13. package/dist/core/PostUpdateMigrator.js +91 -0
  14. package/dist/core/PostUpdateMigrator.js.map +1 -1
  15. package/dist/core/SessionManager.d.ts +146 -1
  16. package/dist/core/SessionManager.d.ts.map +1 -1
  17. package/dist/core/SessionManager.js +498 -144
  18. package/dist/core/SessionManager.js.map +1 -1
  19. package/dist/core/SleepWakeDetector.d.ts +33 -0
  20. package/dist/core/SleepWakeDetector.d.ts.map +1 -1
  21. package/dist/core/SleepWakeDetector.js +53 -0
  22. package/dist/core/SleepWakeDetector.js.map +1 -1
  23. package/dist/core/devGatedFeatures.d.ts.map +1 -1
  24. package/dist/core/devGatedFeatures.js +23 -0
  25. package/dist/core/devGatedFeatures.js.map +1 -1
  26. package/dist/core/types.d.ts +52 -0
  27. package/dist/core/types.d.ts.map +1 -1
  28. package/dist/core/types.js.map +1 -1
  29. package/dist/lifeline/ServerSupervisor.d.ts +42 -0
  30. package/dist/lifeline/ServerSupervisor.d.ts.map +1 -1
  31. package/dist/lifeline/ServerSupervisor.js +79 -4
  32. package/dist/lifeline/ServerSupervisor.js.map +1 -1
  33. package/dist/lifeline/TelegramLifeline.d.ts.map +1 -1
  34. package/dist/lifeline/TelegramLifeline.js +8 -0
  35. package/dist/lifeline/TelegramLifeline.js.map +1 -1
  36. package/dist/monitoring/DegradedTmuxGuard.d.ts +161 -0
  37. package/dist/monitoring/DegradedTmuxGuard.d.ts.map +1 -0
  38. package/dist/monitoring/DegradedTmuxGuard.js +277 -0
  39. package/dist/monitoring/DegradedTmuxGuard.js.map +1 -0
  40. package/dist/monitoring/guardManifest.d.ts.map +1 -1
  41. package/dist/monitoring/guardManifest.js +18 -0
  42. package/dist/monitoring/guardManifest.js.map +1 -1
  43. package/dist/server/routes.d.ts.map +1 -1
  44. package/dist/server/routes.js +13 -5
  45. package/dist/server/routes.js.map +1 -1
  46. package/package.json +2 -2
  47. package/scripts/lint-no-direct-destructive.js +14 -0
  48. package/scripts/lint-sync-subprocess-chokepoint.js +208 -0
  49. package/scripts/sync-subprocess-chokepoint-baseline.json +148 -0
  50. package/src/data/builtin-manifest.json +65 -65
  51. package/upgrades/1.3.642.md +46 -0
  52. package/upgrades/1.3.643.md +66 -0
  53. package/upgrades/side-effects/false-excuse-deferral-stop-guard.md +75 -0
  54. package/upgrades/side-effects/tmux-event-loop-resilience.md +86 -0
@@ -0,0 +1,66 @@
1
+ # Upgrade Guide — vNEXT
2
+
3
+ <!-- assembled-by: assemble-next-md -->
4
+ <!-- bump: minor -->
5
+
6
+ ## What Changed
7
+
8
+ The instar server makes frequent calls to `tmux` (the terminal multiplexer that
9
+ holds every agent session). Those calls were SYNCHRONOUS — they blocked the
10
+ server's single event loop until tmux answered. All agents on one machine share
11
+ ONE tmux server, so when it got slow under load a single call could freeze the
12
+ whole server for ~15 seconds. Two user-visible things broke as a result: the
13
+ dashboard's live connection dropped (it showed "disconnected"), and the ~0-CPU
14
+ freeze was MISREAD as the machine going to sleep (a false sleep/wake event).
15
+
16
+ This change makes the hot path non-blocking and adds three defenses — all
17
+ dev-gated, meaning they run LIVE on development agents (where the bug was
18
+ diagnosed) and ship DARK on the fleet for a graduated rollout:
19
+
20
+ - The frequent tmux calls now run asynchronously with a hard timeout, and the
21
+ dashboard's and sessions' reads are served from a cache that can never hang on
22
+ tmux.
23
+ - An in-flight-operation marker lets the sleep detector tell a real freeze apart
24
+ from a real sleep, so a slow tmux call is no longer misreported as sleep. The
25
+ lifeline reads the same marker cross-process, so it won't restart a
26
+ busy-but-alive server.
27
+ - A signal-only "degraded tmux" guard watches for a persistently slow tmux server
28
+ and surfaces it on `/guards` — without ever killing anything.
29
+
30
+ Crucially, a tmux call that times out is treated as INDETERMINATE (keep the
31
+ session) — never as "the session is dead" — so a slow tmux can never cause a live
32
+ session to be wrongly reaped.
33
+
34
+ ## What to Tell Your User
35
+
36
+ For most agents nothing changes yet — this ships disabled on the fleet and is
37
+ being soaked on development agents first. On a development agent the visible win
38
+ is that the dashboard stops randomly showing "disconnected" and the agent stops
39
+ occasionally misreporting itself as having gone to sleep — both were caused by a
40
+ busy shared terminal manager freezing the server loop.
41
+
42
+ ## Summary of New Capabilities
43
+
44
+ - Async, timeout-bounded tmux on the server hot path; dashboard + session reads
45
+ served from a non-blocking cache that can't hang on a slow tmux.
46
+ - Tri-state tmux outcome (success / definitely-absent / indeterminate); a timeout
47
+ KEEPS the session (never a false reap under load).
48
+ - In-flight-sync-op marker as the block-vs-sleep discriminator — works
49
+ cross-process so the lifeline won't restart a busy-but-alive server.
50
+ - Signal-only `DegradedTmuxGuard` (bounded-accumulation ring, load-gated, never
51
+ kills tmux) surfacing a persistently degraded tmux server via `GET /guards`.
52
+ - All three behind dev-gated flags (`monitoring.tmuxResilience.*`,
53
+ `monitoring.degradedTmuxGuard`): live on development agents, dark on the fleet.
54
+
55
+ ## Evidence
56
+
57
+ Reproduced and guarded by a 3-tier suite (307 tests): InFlightSyncOpMarker
58
+ round-trip + TTL self-heal; SleepWakeDetector block-vs-sleep on both sides of the
59
+ boundary; SessionManager async tri-state (timeout ⇒ indeterminate KEEP, the
60
+ no-false-reap regression guard); wake-handler + ServerSupervisor amplifier guards;
61
+ DegradedTmuxGuard bounded-accumulation burst-invariant (10,000 samples, ring
62
+ length never exceeds windowSize); an integration slow-stub (a ~15s tmux stub
63
+ proves `/health` and `/sessions` never hang and zero sessions are reaped); and an
64
+ e2e dev-gate lifecycle (the three flags resolve live-on-dev / dark-on-fleet, the
65
+ guard reports alive not missing). `tsc`, `lint-sync-subprocess-chokepoint`,
66
+ `lint-guard-manifest`, and the `no-silent-fallbacks` ratchet are all green.
@@ -0,0 +1,75 @@
1
+ # Side-Effects Review — False-Excuse Deferral Stop-Guard
2
+
3
+ **Slug:** `false-excuse-deferral-stop-guard` · **Tier:** 1 (small, low-risk hook behavior addition;
4
+ operator-requested direct fix, no spec). Touches `PostUpdateMigrator.getStopGateRouterHook()` (a
5
+ hook template), so the tier signal flags `belowFloor` (a PostUpdateMigrator touch raises the floor) —
6
+ accepted: the change is a self-contained substring guard, fully reversible, no external surface.
7
+
8
+ ## Summary of the change
9
+
10
+ `stop-gate-router` hook gains `falseExcuseDeferralGuard`, mirroring the existing
11
+ `statedContinuationGuard`: a mode-independent IIFE that, on a Stop, blocks ONCE when the final
12
+ assistant message contains BOTH (a) a named piece of remaining work AND (b) a self-protective
13
+ deferral rationalization (session-length / time-of-day / made-mistakes / don't-rush /
14
+ tracked-so-it-won't-slip / next-session), re-feeding a "this excuse is false — proceed" directive.
15
+ Same change in the deployed copy is overwritten from this source on the next update (Migration
16
+ Parity — always-overwrite built-in hooks), so it reaches every agent.
17
+
18
+ ## 1. Over-block / false positive
19
+
20
+ The AND-of-both-signals requirement is the false-positive control: a self-protective phrase alone
21
+ (e.g. "it's late, but everything is done") does NOT block — `knownWork` must also match. Tested:
22
+ a genuine completion and a time-reference-with-no-pending-work both pass through (no block). Cost of
23
+ any residual false positive is bounded to ONE extra turn (the agent re-affirms and re-stops), because
24
+ the guard fires once.
25
+
26
+ ## 2. Under-block
27
+
28
+ If the agent stops with an excuse but uses wording outside the phrase lists, it slips through (a
29
+ false negative) — acceptable: the guard is a high-precision catch for the documented recurring
30
+ phrasings, not a complete classifier. The lists cover the operator-cited forms and the agent's actual
31
+ observed messages. Tunable by extending the arrays.
32
+
33
+ ## 4. Signal vs authority
34
+
35
+ The guard is a one-shot re-feed (block decision with a reminder), exactly like the
36
+ stated-continuation guard. It never silences or rewrites a message and takes no destructive action.
37
+ The `stop_hook_active` loop guard guarantees the agent is never trapped — a legitimate stop (real
38
+ external blocker / work complete / a user-only decision) re-stops cleanly on the next attempt.
39
+
40
+ ## 5. Interactions
41
+
42
+ Sits directly after `statedContinuationGuard` in the same hook, before the server round-trip — so it
43
+ works even when the server-side stop-gate is in shadow/off mode (which is exactly when these stalls
44
+ slip through). No new dependency, no network call, no state. Pure substring matching. Cannot recurse
45
+ or grow unbounded.
46
+
47
+ ## 6. External surfaces
48
+
49
+ None. No route, no egress, no spend (no LLM call — deterministic substring matching in the hook
50
+ subprocess).
51
+
52
+ ## 7. Multi-machine posture
53
+
54
+ Machine-local: the hook runs per-session on whichever machine hosts the session. No replicated state.
55
+ Each agent on each machine gets the guard via the standard hook update.
56
+
57
+ ## 8. Rollback cost
58
+
59
+ Trivial: delete the `falseExcuseDeferralGuard` IIFE from `getStopGateRouterHook()`; the next update
60
+ overwrites the deployed copy back to guard-free. No data, no migration to unwind.
61
+
62
+ ## Evidence pointers
63
+
64
+ - `tests/unit/stop-gate-false-excuse-deferral.test.ts` (6): renders valid JS containing the guard;
65
+ blocks the real-world excuse-stop (named work + self-protective excuse); blocks a
66
+ session-length/next-session deferral; does NOT block a genuine completion; does NOT block a time
67
+ reference with no deferred work (false-positive control); does NOT re-block under `stop_hook_active`.
68
+ - `tests/unit/stop-gate-stated-continuation.test.ts` + `tests/unit/generated-hooks-parse.test.ts`
69
+ stay green (the template literal still renders valid JS). Full `tsc` clean.
70
+
71
+ ## Conclusion
72
+
73
+ Delivers the operator-requested structural catch for the recurring false-excuse early-stop, as an
74
+ instar feature that ships to every agent. High-precision, loop-safe, reversible, no external surface.
75
+ Ship.
@@ -0,0 +1,86 @@
1
+ # Side-Effects Review — tmux Event-Loop Resilience (Increment 1)
2
+
3
+ **Slug:** `tmux-event-loop-resilience` · **Tier:** 2 (converged + approved spec
4
+ `docs/specs/tmux-event-loop-resilience.md`). Touches `PostUpdateMigrator` (a
5
+ config-strip migration), so the tier signal flags `belowFloor` — accepted: the
6
+ PostUpdateMigrator touch is a dev-gate cleanup strip (removes a stale persisted
7
+ `enabled:false` so the dev-gate resolves live-on-dev), fully reversible, no
8
+ external surface.
9
+
10
+ ## Summary of the change
11
+
12
+ The server's frequent synchronous `tmux` calls blocked the single event loop; a
13
+ slow shared tmux server froze the loop ~15s, dropping the dashboard websocket and
14
+ getting misread as host sleep. Increment 1: (A) an async, timeout-bounded tmux
15
+ hot path with cache-served dashboard/session reads; (B) an in-flight-sync-op
16
+ marker as the block-vs-sleep discriminator (in-process for SleepWakeDetector,
17
+ cross-process via a mirror file for ServerSupervisor); (C) a signal-only
18
+ `DegradedTmuxGuard` that surfaces a persistently slow tmux without ever killing
19
+ it. All three are dev-gated: LIVE on a development agent, DARK on the fleet.
20
+
21
+ ## 1. Over-block / false signal
22
+
23
+ `DegradedTmuxGuard` is signal-only — it raises ONE deduped attention item and
24
+ never kills, restarts, or reaps anything, so a false degradation signal costs at
25
+ most one surfaced notice. It is load-gated (suppressed when per-core load is high
26
+ — that is expected slowness, not a tmux fault) and requires N-cycle corroboration
27
+ (a single hiccup never raises an episode), with episode dedup + age-escalation.
28
+ The in-flight marker can only SUPPRESS a wake / DEFER a restart when affirmatively
29
+ present, in-flight, and non-stale — a false positive there fails toward the SAFE
30
+ direction (keep the session alive / let a genuinely-dead server restart).
31
+
32
+ ## 2. Under-block / missed signal
33
+
34
+ A real freeze the marker doesn't cover is still caught: SleepWakeDetector's
35
+ existing cpuBlockBusyRatio path (CPU-spin blocks) is unchanged, and the marker
36
+ adds the ~0-CPU I/O-wait case #1240 couldn't see. The marker self-heals via a
37
+ 2×timeout TTL so a leaked depth can never permanently blind sleep detection. The
38
+ supervisor defer is hard-capped (`starvationRestartThreshold`) so a stuck
39
+ in-flight marker can never wedge the restart path forever — it gives up loudly.
40
+
41
+ ## 3. Blast radius / fail-open
42
+
43
+ Every failure path fails toward SAFE and is non-silent (no-silent-fallbacks
44
+ ratchet enforced): a tmux call that times out maps to INDETERMINATE, never to
45
+ "absent" — `isSessionAliveAsync` returns `'indeterminate'` (never `false`) on a
46
+ timeout, so a slow tmux can NEVER cause a live session to be reaped (the
47
+ line-2352 regression the tri-state guards). The cross-process marker reader is
48
+ fail-OPEN: an absent/unparseable mirror ⇒ null ⇒ the supervisor proceeds to
49
+ restart a genuinely dead server. The mirror writer is best-effort: an unwritable
50
+ mirror never breaks a tmux call. `DegradedTmuxGuard` uses a fixed-capacity
51
+ modulo-write ring (Bounded Accumulation — 10,000-sample burst-invariant test
52
+ proves the ring length never exceeds windowSize), so it cannot grow memory under
53
+ a flood.
54
+
55
+ ## 4. Signal vs authority
56
+
57
+ The only blocking authority introduced is the supervisor restart-DEFER, and it is
58
+ bounded (hard cap + TTL + fail-open) and additive to the existing
59
+ `deferRestartForCpuStarvation` (the `||` keeps the CPU side-effect — verified by
60
+ test). The wake-handler amplifier guard short-circuits a marker-covered wake and
61
+ bounds the cascade's tmux re-validation (async 9000+SIGKILL); it never silences a
62
+ genuine recovery. `DegradedTmuxGuard` holds zero authority — it observes and
63
+ surfaces only.
64
+
65
+ ## 5. Interactions
66
+
67
+ Reuses the existing `'event-loop-block'` WakeSuppressionReason + StallEvent (no
68
+ new telemetry shape). The async hot-path twins coexist with the legacy sync
69
+ methods behind `tmuxAsyncEnabled` (off ⇒ byte-identical legacy behavior — the e2e
70
+ observable-equivalence test). `getCachedRunningSessions()` is the cache the
71
+ request routes (GET /status et al.) now read; the non-request consumers
72
+ (JobScheduler, WebSocketManager, AutoUpdater) intentionally keep the live read
73
+ (documented residual, Increment 2 territory). Per-agent socket isolation is
74
+ explicitly OUT of Increment 1 (Increment 2).
75
+
76
+ ## 6. Rollback
77
+
78
+ Each of the three flags is independently disable-able in `.instar/config.json`
79
+ (`monitoring.tmuxResilience.asyncHotPath.enabled`,
80
+ `monitoring.tmuxResilience.inFlightMarker.enabled`,
81
+ `monitoring.degradedTmuxGuard.enabled`) — set to `false` to revert that layer to
82
+ the pre-change behavior. The async hot path falls back to the sync path; the
83
+ marker branch goes inert; the guard stops observing. The migration strips only a
84
+ default-shaped persisted `false`, never an explicit operator `true`/`false`.
85
+ Config defaults OMIT `enabled` so the dev-gate (resolveDevAgentGate) governs:
86
+ fleet = dark, dev = live.