instar 1.2.40 → 1.2.42

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 (36) hide show
  1. package/dist/commands/server.d.ts.map +1 -1
  2. package/dist/commands/server.js +32 -0
  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 +10 -0
  6. package/dist/config/ConfigDefaults.js.map +1 -1
  7. package/dist/core/SessionManager.d.ts +5 -0
  8. package/dist/core/SessionManager.d.ts.map +1 -1
  9. package/dist/core/SessionManager.js +7 -0
  10. package/dist/core/SessionManager.js.map +1 -1
  11. package/dist/core/WorktreeManager.d.ts +11 -0
  12. package/dist/core/WorktreeManager.d.ts.map +1 -1
  13. package/dist/core/WorktreeManager.js +69 -0
  14. package/dist/core/WorktreeManager.js.map +1 -1
  15. package/dist/core/types.d.ts +35 -0
  16. package/dist/core/types.d.ts.map +1 -1
  17. package/dist/core/types.js.map +1 -1
  18. package/dist/monitoring/ActiveWorkSilenceSentinel.d.ts +88 -0
  19. package/dist/monitoring/ActiveWorkSilenceSentinel.d.ts.map +1 -0
  20. package/dist/monitoring/ActiveWorkSilenceSentinel.js +181 -0
  21. package/dist/monitoring/ActiveWorkSilenceSentinel.js.map +1 -0
  22. package/dist/monitoring/SocketDisconnectSentinel.d.ts +113 -0
  23. package/dist/monitoring/SocketDisconnectSentinel.d.ts.map +1 -0
  24. package/dist/monitoring/SocketDisconnectSentinel.js +235 -0
  25. package/dist/monitoring/SocketDisconnectSentinel.js.map +1 -0
  26. package/dist/monitoring/sentinelWiring.d.ts +82 -0
  27. package/dist/monitoring/sentinelWiring.d.ts.map +1 -0
  28. package/dist/monitoring/sentinelWiring.js +154 -0
  29. package/dist/monitoring/sentinelWiring.js.map +1 -0
  30. package/package.json +1 -1
  31. package/src/data/builtin-manifest.json +3 -3
  32. package/upgrades/1.2.40.md +15 -0
  33. package/upgrades/1.2.41.md +34 -0
  34. package/upgrades/1.2.42.md +34 -0
  35. package/upgrades/side-effects/silently-stopped-trio-wiring.md +185 -0
  36. package/upgrades/side-effects/silently-stopped-trio.md +115 -0
@@ -0,0 +1,154 @@
1
+ /**
2
+ * sentinelWiring — constructs the runtime dependencies for the two
3
+ * "silently-stopped" sentinels (SocketDisconnectSentinel and
4
+ * ActiveWorkSilenceSentinel) from a SessionManager surface + config.
5
+ *
6
+ * Why this module exists: PR #334 merged both sentinels as standalone,
7
+ * unit-tested detectors but never instantiated them in the server. The
8
+ * release notes claimed "wired into server startup" — it wasn't true. This
9
+ * module is the missing wire-up, extracted from server.ts so the dependency
10
+ * construction is itself unit-testable (Testing Integrity Standard: every
11
+ * dependency-injected component needs wiring-integrity tests proving the deps
12
+ * are real functions that delegate to the underlying primitives, not nulls or
13
+ * silent no-ops).
14
+ *
15
+ * Signal-vs-authority: everything here is a detector or a bounded recovery
16
+ * primitive. `looksActivelyWorking` is a structural detector. All user-facing
17
+ * escalation routes through the existing MessagingToneGate via the `/attention`
18
+ * endpoint (category: degradation) — no new blocking authority is introduced.
19
+ *
20
+ * Spec: docs/specs/silently-stopped-trio.md
21
+ */
22
+ import { getActivitySignal } from './frameworkActivitySignals.js';
23
+ export function makeAttentionPoster(opts) {
24
+ const doFetch = opts.fetchImpl ?? fetch;
25
+ return async (item) => {
26
+ try {
27
+ const resp = await doFetch(`http://localhost:${opts.port}/attention`, {
28
+ method: 'POST',
29
+ headers: {
30
+ 'Content-Type': 'application/json',
31
+ Authorization: `Bearer ${opts.authToken}`,
32
+ },
33
+ body: JSON.stringify({ category: 'degradation', priority: 'HIGH', ...item }),
34
+ });
35
+ return resp.status === 201;
36
+ }
37
+ catch {
38
+ // Network/route failure — treat as not-delivered. The sentinel's notify
39
+ // wrapper swallows this; the bigger failure (silent stop) is still closed
40
+ // because recovery attempts are independent of the notice landing.
41
+ return false;
42
+ }
43
+ };
44
+ }
45
+ const SOCKET_CAPTURE_LINES = 40;
46
+ const SILENCE_CAPTURE_LINES = 40;
47
+ export function buildSocketDisconnectDeps(opts) {
48
+ return {
49
+ getRecentOutput: (sessionName) => opts.sessions.captureOutput(sessionName, SOCKET_CAPTURE_LINES) ?? '',
50
+ resumeFn: async (sessionName) => {
51
+ if (!opts.sessions.isSessionAlive(sessionName))
52
+ return false;
53
+ // A socket drop leaves Claude Code waiting at a retry state; a bare Enter
54
+ // re-engages without interrupting in-flight work (unlike Ctrl+C, which
55
+ // would cancel a tool call that may still be running).
56
+ return opts.sessions.sendKey(sessionName, 'Enter');
57
+ },
58
+ notifyFn: async (sessionName, text) => {
59
+ await opts.notify({
60
+ id: `socket-disconnect:${sessionName}`,
61
+ title: `${friendly(sessionName)} connection issue`,
62
+ summary: text,
63
+ });
64
+ },
65
+ listSessionNames: () => opts.sessions.listRunningSessions().map((s) => s.tmuxSession),
66
+ };
67
+ }
68
+ /**
69
+ * Detector: does this captured frame show the framework actively working
70
+ * (spinner, tool call, "esc to interrupt", "(running)") rather than sitting
71
+ * idle at its prompt? Used to filter the silence sentinel's candidate set so a
72
+ * session that is simply waiting for the user is never flagged as "stopped".
73
+ */
74
+ export function looksActivelyWorking(output, framework) {
75
+ if (!output)
76
+ return false;
77
+ const sig = getActivitySignal(framework);
78
+ return (sig.toolCallOrSpinner.test(output) ||
79
+ sig.escapeToInterrupt.test(output) ||
80
+ sig.runningIndicator.test(output));
81
+ }
82
+ /**
83
+ * Tracks per-session output-change time so the silence sentinel can tell
84
+ * "frozen mid-task" from "still producing output". Only sessions whose most
85
+ * recent frame shows active-work signatures are surfaced as candidates; an
86
+ * idle-at-prompt session is marked `paused` so the sentinel skips it — it is
87
+ * not "actively working then stopped".
88
+ */
89
+ export class OutputActivityTracker {
90
+ sessions;
91
+ now;
92
+ last = new Map();
93
+ constructor(sessions, now = Date.now) {
94
+ this.sessions = sessions;
95
+ this.now = now;
96
+ }
97
+ snapshot() {
98
+ const t = this.now();
99
+ const running = this.sessions.listRunningSessions();
100
+ const seen = new Set();
101
+ const out = [];
102
+ for (const s of running) {
103
+ seen.add(s.tmuxSession);
104
+ const output = this.sessions.captureOutput(s.tmuxSession, SILENCE_CAPTURE_LINES) ?? '';
105
+ const hash = cheapHash(output);
106
+ const prev = this.last.get(s.tmuxSession);
107
+ if (!prev || prev.hash !== hash) {
108
+ this.last.set(s.tmuxSession, { hash, at: t });
109
+ }
110
+ const lastOutputAt = this.last.get(s.tmuxSession).at;
111
+ const active = looksActivelyWorking(output, s.framework);
112
+ out.push({ sessionName: s.tmuxSession, lastOutputAt, paused: !active });
113
+ }
114
+ // Drop tracking for sessions that have ended so the map can't grow without bound.
115
+ for (const key of Array.from(this.last.keys())) {
116
+ if (!seen.has(key))
117
+ this.last.delete(key);
118
+ }
119
+ return out;
120
+ }
121
+ }
122
+ export function buildActiveWorkSilenceDeps(opts) {
123
+ return {
124
+ listSessions: () => opts.tracker.snapshot(),
125
+ nudgeFn: async (sessionName) => {
126
+ if (!opts.sessions.isSessionAlive(sessionName))
127
+ return false;
128
+ return opts.sessions.sendKey(sessionName, 'Enter');
129
+ },
130
+ notifyFn: async (sessionName, text) => {
131
+ await opts.notify({
132
+ id: `active-silence:${sessionName}`,
133
+ title: `${friendly(sessionName)} went quiet`,
134
+ summary: text,
135
+ });
136
+ },
137
+ };
138
+ }
139
+ /** FNV-1a — enough to detect that captured output changed. Not security-sensitive. */
140
+ function cheapHash(s) {
141
+ let h = 0x811c9dc5;
142
+ for (let i = 0; i < s.length; i++) {
143
+ h ^= s.charCodeAt(i);
144
+ h = Math.imul(h, 0x01000193);
145
+ }
146
+ return (h >>> 0).toString(16);
147
+ }
148
+ function friendly(sessionName) {
149
+ return sessionName
150
+ .replace(/^ai\.instar\./, '')
151
+ .replace(/-server$/, '')
152
+ .replace(/-lifeline$/, '');
153
+ }
154
+ //# sourceMappingURL=sentinelWiring.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sentinelWiring.js","sourceRoot":"","sources":["../../src/monitoring/sentinelWiring.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "instar",
3
- "version": "1.2.40",
3
+ "version": "1.2.42",
4
4
  "description": "Persistent autonomy infrastructure for AI agents",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "$schema": "./builtin-manifest.schema.json",
3
3
  "schemaVersion": 1,
4
- "generatedAt": "2026-05-23T02:31:36.933Z",
5
- "instarVersion": "1.2.40",
4
+ "generatedAt": "2026-05-23T03:29:25.734Z",
5
+ "instarVersion": "1.2.42",
6
6
  "entryCount": 191,
7
7
  "entries": {
8
8
  "hook:session-start": {
@@ -1448,7 +1448,7 @@
1448
1448
  "type": "subsystem",
1449
1449
  "domain": "sessions",
1450
1450
  "sourcePath": "src/core/SessionManager.ts",
1451
- "contentHash": "7241736a08f63b09d8b1abda0965dadb37127c95aa6e4ac11bafee05661dbe9f",
1451
+ "contentHash": "caf5980117359821e0523c8358a0eea102d4b1c63f592d9dce8462411232cedd",
1452
1452
  "since": "2025-01-01"
1453
1453
  },
1454
1454
  "subsystem:auto-updater": {
@@ -91,6 +91,18 @@ plumbing is in place; PR 6 makes it user-facing.
91
91
  All 92 tunnel-related unit tests pass (11 new + 81 from PRs 1–4).
92
92
  `tsc --noEmit` clean. `npm run lint` clean.
93
93
 
94
+ ## Also in this release — Silently-stopped trio
95
+
96
+ Closes three independent gaps that all let an agent silently stop working without anyone noticing — diagnosed jointly with the gsd-side echo agent on 2026-05-22 (topic 5447).
97
+
98
+ **A — WorktreeManager clone-default for cross-project worktrees.** The legacy git-worktree flow keeps per-worktree metadata inside the SOURCE repo's hidden git folder. When Claude Code's sandbox EPERM-blocks paths under the shared projects directory, every git command from the worktree dies (confirmed 2026-05-22, recovery cost ~20m). WorktreeManager now uses `git clone` for cross-project work — self-contained git directory under agent home, no shared-path dependency. Existing in-tree worktrees still use the cheaper worktree path; the `INSTAR_WORKTREE_FORCE_WORKTREE` env var is the rollback escape hatch.
99
+
100
+ **B — SocketDisconnectSentinel.** Mirrors the RateLimitSentinel pattern shipped earlier this week. Watches tracked sessions for Claude Code's "socket connection closed unexpectedly" message and related disconnect strings every 15 seconds. On detection: immediate plain-English Telegram notice, four-attempt staircase recovery, escalation if recovery fails. No existing detector covered this case.
101
+
102
+ **C — ActiveWorkSilenceSentinel.** Independent of topic binding. Walks the SessionRegistry every 60s for "had output recently, hasn't for N minutes" (default 15 min). One gentle nudge, 30-second verify, escalate via the tone-gated attention path if the session doesn't unstick. Covers the gsd-style sub-spawned worktree session pattern that slipped through SessionWatchdog, SessionMonitor, and PresenceProxy.
103
+
104
+ All three route user-facing alerts through the existing MessagingToneGate B12-B14 ruleset.
105
+
94
106
  ## What to Tell Your User
95
107
 
96
108
  This release puts the security gate for backup tunnels into place.
@@ -117,3 +129,6 @@ preference.
117
129
  | Single-use CSPRNG nonce binding | Automatic — pendingConsent.nonce embeds in the inline button in the next release |
118
130
  | Cross-episode consent cooldown | Automatic — exponential back-off on decline/timeout (1h → 4h → 24h) |
119
131
  | Mandatory rotation marker on relay-active entry | Automatic — rotationPending=true is the crash-safe flag the upcoming rotation lifecycle reads |
132
+ | WorktreeManager clone-default | Automatic for cross-project work; rollback via INSTAR_WORKTREE_FORCE_WORKTREE=1 |
133
+ | SocketDisconnectSentinel | Wired into server startup; configurable via config.monitoring.socketDisconnectSentinel |
134
+ | ActiveWorkSilenceSentinel | Wired into server startup; threshold configurable via config.monitoring.activeWorkSilenceSentinel.silenceThresholdMs |
@@ -0,0 +1,34 @@
1
+ # Upgrade Guide — vNEXT
2
+
3
+ <!-- bump: patch -->
4
+
5
+ ## What Changed
6
+
7
+ Completes the silently-stopped trio shipped in 1.2.41. That release announced two new watchdogs — SocketDisconnectSentinel and ActiveWorkSilenceSentinel — as "wired into server startup." They were not: both shipped as standalone, tested detector classes that the server never actually instantiated, so on every agent they were dead code. This release is the missing wire-up that turns them on.
8
+
9
+ **The gap.** The detectors were correct and unit-tested, but nothing constructed or started them. A grep made it obvious: the RateLimitSentinel they were modeled on is referenced from six files; both new sentinels were referenced from zero outside their own definitions. The release notes claimed a behavior that did not exist.
10
+
11
+ **The fix.** A new wiring module builds each sentinel's dependencies from the live session manager and starts them at server boot, behind default-on config switches. SocketDisconnectSentinel now self-drives a 15-second scan over every running session. ActiveWorkSilenceSentinel walks the session registry every 60 seconds and — importantly — only flags sessions whose latest screen shows active work (a spinner, a running tool, an "interrupt" hint), so a session simply waiting for you is never mistaken for a frozen one. All escalations route through the existing tone gate, so you only hear about something that genuinely needs a yes-or-no from you.
12
+
13
+ ## What to Tell Your User
14
+
15
+ - The two silent-stop watchdogs announced last release are now actually running. Before this, they were installed but switched off, so a dropped connection or a frozen background session could still go unnoticed.
16
+ - You will not get noise from sessions that are just idle and waiting for you — only from sessions that were genuinely working and then froze, or that lost their connection.
17
+ - Both watchdogs can be turned off per-agent in config if you ever need to, and they are on by default.
18
+
19
+ ## Summary of New Capabilities
20
+
21
+ | Capability | How to Use |
22
+ |-----------|-----------|
23
+ | SocketDisconnectSentinel (live) | On by default; disable via config.monitoring.socketDisconnectSentinel.enabled=false |
24
+ | ActiveWorkSilenceSentinel (live) | On by default; tune config.monitoring.activeWorkSilenceSentinel.silenceThresholdMs; disable via .enabled=false |
25
+
26
+ ## Evidence
27
+
28
+ - Spec: `docs/specs/silently-stopped-trio.md` (with ELI16 companion). Side-effects: `upgrades/side-effects/silently-stopped-trio-wiring.md`.
29
+ - The gap, reproduced: a grep for either sentinel name across the source returned only its own definition and test files — no construction, no start call.
30
+ - Tests: 30 new tests — wiring-integrity and active-vs-idle semantics (24), self-driving scan loop (3), and an end-to-end integration test that drives both sentinels through the tone-gated attention path and asserts an idle session is never escalated (3).
31
+
32
+ ## Rollback
33
+
34
+ Per-sentinel config kill switch (no release needed), or revert the wiring module plus the server-startup block and the SocketDisconnectSentinel start/stop additions. No persistent state to clean up.
@@ -0,0 +1,34 @@
1
+ # Upgrade Guide — vNEXT
2
+
3
+ <!-- bump: patch -->
4
+
5
+ ## What Changed
6
+
7
+ Completes the silently-stopped trio shipped in 1.2.41. That release announced two new watchdogs — SocketDisconnectSentinel and ActiveWorkSilenceSentinel — as "wired into server startup." They were not: both shipped as standalone, tested detector classes that the server never actually instantiated, so on every agent they were dead code. This release is the missing wire-up that turns them on.
8
+
9
+ **The gap.** The detectors were correct and unit-tested, but nothing constructed or started them. A grep made it obvious: the RateLimitSentinel they were modeled on is referenced from six files; both new sentinels were referenced from zero outside their own definitions. The release notes claimed a behavior that did not exist.
10
+
11
+ **The fix.** A new wiring module builds each sentinel's dependencies from the live session manager and starts them at server boot, behind default-on config switches. SocketDisconnectSentinel now self-drives a 15-second scan over every running session. ActiveWorkSilenceSentinel walks the session registry every 60 seconds and — importantly — only flags sessions whose latest screen shows active work (a spinner, a running tool, an "interrupt" hint), so a session simply waiting for you is never mistaken for a frozen one. All escalations route through the existing tone gate, so you only hear about something that genuinely needs a yes-or-no from you.
12
+
13
+ ## What to Tell Your User
14
+
15
+ - The two silent-stop watchdogs announced last release are now actually running. Before this, they were installed but switched off, so a dropped connection or a frozen background session could still go unnoticed.
16
+ - You will not get noise from sessions that are just idle and waiting for you — only from sessions that were genuinely working and then froze, or that lost their connection.
17
+ - Both watchdogs can be turned off per-agent in config if you ever need to, and they are on by default.
18
+
19
+ ## Summary of New Capabilities
20
+
21
+ | Capability | How to Use |
22
+ |-----------|-----------|
23
+ | SocketDisconnectSentinel (live) | On by default; disable via config.monitoring.socketDisconnectSentinel.enabled=false |
24
+ | ActiveWorkSilenceSentinel (live) | On by default; tune config.monitoring.activeWorkSilenceSentinel.silenceThresholdMs; disable via .enabled=false |
25
+
26
+ ## Evidence
27
+
28
+ - Spec: `docs/specs/silently-stopped-trio.md` (with ELI16 companion). Side-effects: `upgrades/side-effects/silently-stopped-trio-wiring.md`.
29
+ - The gap, reproduced: a grep for either sentinel name across the source returned only its own definition and test files — no construction, no start call.
30
+ - Tests: 30 new tests — wiring-integrity and active-vs-idle semantics (24), self-driving scan loop (3), and an end-to-end integration test that drives both sentinels through the tone-gated attention path and asserts an idle session is never escalated (3).
31
+
32
+ ## Rollback
33
+
34
+ Per-sentinel config kill switch (no release needed), or revert the wiring module plus the server-startup block and the SocketDisconnectSentinel start/stop additions. No persistent state to clean up.
@@ -0,0 +1,185 @@
1
+ # Side-Effects Review — Silently-stopped trio: server wiring
2
+
3
+ **Version / slug:** `silently-stopped-trio-wiring`
4
+ **Date:** 2026-05-23
5
+ **Author:** echo
6
+ **Second-pass reviewer:** [pending — high-risk: sentinels + server startup]
7
+ **Spec:** [docs/specs/silently-stopped-trio.md](../../docs/specs/silently-stopped-trio.md)
8
+ **ELI16:** [docs/specs/silently-stopped-trio.eli16.md](../../docs/specs/silently-stopped-trio.eli16.md)
9
+
10
+ ## Summary of the change
11
+
12
+ PR #334 merged `SocketDisconnectSentinel` and `ActiveWorkSilenceSentinel` as
13
+ standalone, unit-tested detectors — but never instantiated them in the running
14
+ server. A grep proved it: `RateLimitSentinel` (the pattern they claimed to
15
+ mirror) is referenced from six files; both new sentinels were referenced from
16
+ zero outside their own definition + test files. The merged release notes and
17
+ side-effects artifact both stated "wired into server startup". That was false:
18
+ on every agent, after the auto-update, the sentinels would still never run.
19
+
20
+ This change is the missing wire-up. No detector logic changes; the detectors
21
+ were correct. What was missing was construction + lifecycle.
22
+
23
+ - **`src/monitoring/sentinelWiring.ts`** (new) — testable dependency builders:
24
+ `makeAttentionPoster` (posts escalations to the tone-gated `/attention`
25
+ route, returns true only on 201), `buildSocketDisconnectDeps`,
26
+ `buildActiveWorkSilenceDeps`, `OutputActivityTracker` (per-session
27
+ output-change timing), and `looksActivelyWorking` (active-vs-idle detector
28
+ built on the existing `frameworkActivitySignals`).
29
+ - **`src/monitoring/SocketDisconnectSentinel.ts`** — added an optional
30
+ `listSessionNames` dep plus `start()` / `stop()` / `tick()` so the sentinel
31
+ self-drives a 15s scan loop (mirrors `ActiveWorkSilenceSentinel`'s shape).
32
+ Existing event-driven API (`report` / `scanSession`) is unchanged.
33
+ - **`src/commands/server.ts`** — instantiates both sentinels from a
34
+ SessionManager surface + config, and starts them. Guarded by config
35
+ kill-switches, default-on.
36
+ - **`src/core/types.ts` + `src/config/ConfigDefaults.ts`** — config types +
37
+ default-on entries. Migration parity is automatic: `migrateConfig` applies
38
+ all `ConfigDefaults` via `applyDefaults`, so existing agents receive the
39
+ config keys on update.
40
+
41
+ ## Decision-point inventory
42
+
43
+ - `looksActivelyWorking` — **add** — structural detector (regex on captured
44
+ frame via `frameworkActivitySignals`). No judgment, no authority.
45
+ - `OutputActivityTracker.snapshot` — **add** — deterministic change-detection
46
+ (FNV-1a hash compare) + active/idle classification. Marks idle-at-prompt
47
+ sessions `paused` so the silence sentinel skips them.
48
+ - `makeAttentionPoster` — **pass-through** — delegates the actual block/allow
49
+ decision to the existing `/attention` tone gate (B12-B14). Returns true only
50
+ on 201; a 422 block is the gate doing its job, not an error.
51
+ - `SocketDisconnectSentinel.start/tick` — **add** — bounded scan loop.
52
+
53
+ ## 1. Over-block
54
+
55
+ - **Active-vs-idle false negative → no escalation.** If a frozen session's last
56
+ frame does NOT contain an active-work signature (spinner / "esc to interrupt"
57
+ / tool-call / "(running)"), `OutputActivityTracker` marks it `paused` and the
58
+ silence sentinel skips it. This is deliberate: the alternative (flag every
59
+ static frame) would fire on every idle-at-prompt session waiting for the
60
+ user. Cost of this choice: a session that froze at a non-active-looking frame
61
+ is missed by sentinel C — but SocketDisconnectSentinel, SessionWatchdog, and
62
+ SessionMonitor still cover their shapes. The integration test asserts an
63
+ idle-prompt session is NOT escalated.
64
+ - **Socket regex over-broad** (unchanged from PR #334) — false-positive cost is
65
+ one bare Enter + a first-notice that the tone gate (B14) suppresses anyway.
66
+
67
+ ## 2. Under-block
68
+
69
+ - **`looksActivelyWorking` pattern coverage.** The frame is classified using
70
+ the session's resolved framework (via `SessionManager.frameworkForSession`,
71
+ added in this PR — second-pass review caught that the prior draft read a
72
+ non-existent `sess.framework` field and silently fell back to claude-code for
73
+ every session, mis-classifying Codex). Coverage is now correct for both
74
+ claude-code and codex-cli. A framework outside that map (none exist today)
75
+ still defaults to claude-code patterns; the same `frameworkActivitySignals`
76
+ module backs StallTriageNurse, so adding a framework improves all consumers
77
+ centrally.
78
+ - **First-notice delivery.** The "lost its connection, trying to recover"
79
+ notice has no CTA, so the tone gate (B14) blocks it (422 → poster returns
80
+ false). This is intended — only the escalation (which carries a yes/no CTA)
81
+ reaches the user. The recovery loop runs regardless of whether any notice
82
+ lands.
83
+
84
+ ## 3. Level-of-abstraction fit
85
+
86
+ - Wiring lives in `server.ts` alongside the RateLimitSentinel/CompactionSentinel
87
+ wire-up — the same layer, the same idiom (`await import` + construct + start).
88
+ - Dependency construction is extracted to `sentinelWiring.ts` specifically so it
89
+ is unit-testable in isolation (the bug this PR fixes was an *untested wiring
90
+ gap*; the fix ships the wiring-integrity tests the standard requires).
91
+ - `OutputActivityTracker` is the right owner of per-session output timing for
92
+ the topic-independent case — SessionMonitor's equivalent snapshots are keyed
93
+ by topicId and therefore blind to non-topic-bound sessions (the exact gap).
94
+
95
+ ## 4. Signal vs authority compliance
96
+
97
+ **Reference:** [docs/signal-vs-authority.md](../../docs/signal-vs-authority.md)
98
+
99
+ - [x] **No new blocking authority.** Every user-facing escalation is a signal
100
+ posted to the existing `/attention` tone gate, which holds the authority.
101
+ - `looksActivelyWorking` and `OutputActivityTracker` are detectors.
102
+ - `makeAttentionPoster` returns the gate's verdict (201 → delivered, 422 →
103
+ blocked); it never overrides the gate.
104
+ - The recovery primitives (bare Enter via `sendKey`) are bounded and
105
+ alive-gated.
106
+
107
+ ## 5. Interactions
108
+
109
+ - **Double-fire with the live socket sentinel + silence sentinel.** A socket
110
+ drop (15s scan) fires far sooner than the 15-min silence threshold, and each
111
+ uses a distinct `/attention` id (`socket-disconnect:` vs `active-silence:`),
112
+ so they don't collide. Idempotency-by-id on `/attention` upserts rather than
113
+ duplicating.
114
+ - **Recovery nudge vs other injectors.** The bare Enter is alive-gated and does
115
+ not interrupt work (unlike Ctrl+C). It does not register with the
116
+ zombie-kill veto used by RateLimit/Compaction sentinels — these sentinels do
117
+ not kill sessions, so the veto is not relevant.
118
+ - **Tracker memory.** `OutputActivityTracker` prunes ended sessions every
119
+ snapshot, so the map cannot grow unbounded.
120
+
121
+ ## 6. External surfaces
122
+
123
+ - **Telegram:** one new attention-item class per incident, tone-gated. Quiet by
124
+ default (first-notice suppressed; only CTA escalations surface).
125
+ - **Config:** two new `monitoring.*Sentinel` keys, default-on, applied to
126
+ existing agents via the standard `applyDefaults` migration path.
127
+ - **Persistent state:** none — both sentinels keep in-memory state only.
128
+ - **Timing:** socket scan 15s, silence walk 60s; each captures ~40 lines of
129
+ tmux output per running session per tick. Same order of cost as the existing
130
+ SessionMonitor poll.
131
+
132
+ ## 7. Rollback cost
133
+
134
+ - Per-sentinel config kill-switch:
135
+ `monitoring.socketDisconnectSentinel.enabled=false` /
136
+ `monitoring.activeWorkSilenceSentinel.enabled=false` — no release needed.
137
+ - Code rollback: revert the `server.ts` wire-up block + `sentinelWiring.ts` +
138
+ the `SocketDisconnectSentinel` start/stop additions. No persistent state to
139
+ clean up.
140
+
141
+ ## Conclusion
142
+
143
+ Makes the silently-stopped trio actually run, which the prior PR claimed but did
144
+ not do. Adds the wiring-integrity + semantic tests the Testing Integrity
145
+ Standard requires for dependency-injected components, plus an integration test
146
+ that drives both sentinels end-to-end through the tone-gated `/attention` path.
147
+ No new blocking authority; default-on with per-sentinel kill switches.
148
+
149
+ Clear to ship pending second-pass review.
150
+
151
+ ---
152
+
153
+ ## Second-pass review (if required)
154
+
155
+ **Reviewer:** independent review subagent, 2026-05-23
156
+
157
+ **Verdict: Concur with the review**, with one substantive correctness finding
158
+ that has been fixed in this PR.
159
+
160
+ - Confirmed both sentinels are constructed AND `.start()`-ed, guarded by
161
+ default-on kill switches matching the RateLimitSentinel idiom. Not dead code.
162
+ - Signal-vs-authority holds: escalations route through `/attention`; the
163
+ detectors hold no blocking authority. The `=== 201` success check is correct.
164
+ - Intervals are unref'd; `OutputActivityTracker` prunes ended sessions (no Map
165
+ leak). Bare-Enter recovery is alive-gated and the right choice over Ctrl+C.
166
+ - **Finding (FIXED): framework was never populated.** The draft read a
167
+ non-existent `sess.framework` field, so `looksActivelyWorking` always used
168
+ claude-code patterns — a Codex session's spinner would be classed idle and
169
+ skipped. Resolution: added `SessionManager.frameworkForSession` (public
170
+ accessor over the existing per-session framework cache) and plumbed it into
171
+ the session surface; removed the `as`-cast. Codex coverage now works.
172
+ - Over-fire risk assessed low and acceptable: a slow-but-alive tool call
173
+ re-renders its spinner/token count → hash changes → `lastOutputAt` resets, so
174
+ it won't trip; idle-at-prompt is `paused`; a frozen non-active frame is
175
+ skipped by design and covered by the other watchdogs.
176
+
177
+ ---
178
+
179
+ ## Evidence pointers
180
+
181
+ - Tests: `tests/unit/monitoring/sentinelWiring.test.ts` (24),
182
+ `tests/unit/monitoring/SocketDisconnectSentinel.test.ts` (+3 loop tests),
183
+ `tests/integration/silently-stopped-trio-wiring.test.ts` (3).
184
+ - The gap: `grep -rln SocketDisconnectSentinel src | grep -v 'SocketDisconnectSentinel.ts'`
185
+ returned nothing before this change.
@@ -0,0 +1,115 @@
1
+ # Side-Effects Review — Silently-stopped trio
2
+
3
+ **Version / slug:** `silently-stopped-trio`
4
+ **Date:** 2026-05-22
5
+ **Author:** echo
6
+ **Second-pass reviewer:** [pending — high-risk: WorktreeManager + new sentinels]
7
+ **Spec:** [docs/specs/silently-stopped-trio.md](../../docs/specs/silently-stopped-trio.md)
8
+ **ELI16:** [docs/specs/silently-stopped-trio.eli16.md](../../docs/specs/silently-stopped-trio.eli16.md)
9
+
10
+ ## Summary of the change
11
+
12
+ Three independent layers closing the same failure class — "agent silently stopped working without anyone noticing" — diagnosed jointly with the gsd-side echo agent on 2026-05-22.
13
+
14
+ 1. **WorktreeManager clone-default** (`src/core/WorktreeManager.ts`). New private predicate `shouldCloneInsteadOfWorktree()` returns true when source `projectDir` is outside agent home (the sandbox-revocation hazard case). When true, `createBinding` uses `git clone` + `git checkout -b` instead of `git worktree add`, producing a fully self-contained `.git/` directory under agent home. `INSTAR_WORKTREE_FORCE_WORKTREE=1` is the rollback escape hatch. The existing in-tree worktree path (source already under agent home) is preserved unchanged.
15
+
16
+ 2. **SocketDisconnectSentinel** (`src/monitoring/SocketDisconnectSentinel.ts`, new). Detector + recovery for Claude Code's `socket connection closed unexpectedly` family of strings. Mirrors `RateLimitSentinel`'s shape — detect, immediate user notice, backoff staircase, verify, escalate. Patterns are intentionally broad (4 regex variants) because false-positives are cheap nudges and false-negatives are the exact failure class this exists to close.
17
+
18
+ 3. **ActiveWorkSilenceSentinel** (`src/monitoring/ActiveWorkSilenceSentinel.ts`, new). Walks the SessionRegistry every 60s; reports any session whose `lastOutputAt` is older than the silence threshold (default 15 min). One nudge (empty `tmux send-keys`), 30s verify, escalate via tone-gated `/attention` if no advance.
19
+
20
+ All three escalation payloads route through the existing `MessagingToneGate` B12-B14 ruleset via `/attention` (`category: degradation`).
21
+
22
+ ## Decision-point inventory
23
+
24
+ - `WorktreeManager.shouldCloneInsteadOfWorktree` — **add** — structural predicate (file shape + env override).
25
+ - `WorktreeManager.createBinding` clone branch — **add** — uses `git clone` + `git checkout -b` for cross-project sources.
26
+ - `SocketDisconnectSentinel.report / scanSession / runAttempt / verifyAttempt / escalate` — **add** — full recovery state machine.
27
+ - `ActiveWorkSilenceSentinel.tick / report / runNudge / verifyNudge / escalate` — **add** — silence-detection state machine.
28
+ - Tone-gated `/attention` POST path — **pass-through** — consumed by both new sentinels' notify functions; the gate (existing authority) is unchanged.
29
+
30
+ ## Deferrals tracked
31
+
32
+ The v3 Self-Healing Remediator's Tier-3 absorption of these sentinels' detection + recovery surfaces is the only forward note. <!-- tracked: topic-3079-v3-remediator -->
33
+
34
+ ---
35
+
36
+ ## 1. Over-block
37
+
38
+ - **Worktree decision over-broad.** Any source outside agent home triggers clone. False-positive cost: a `git clone` takes 1-5s on APFS (sub-second with CoW). False-negative cost: the exact incident class we just hit. Acceptable.
39
+ - **Socket-disconnect regex over-broad.** Patterns include generic `connection.*closed.*unexpectedly` — could match unrelated text. False-positive cost: one Ctrl+C+Enter nudge to a healthy session + a "lost its connection" Telegram message that resolves cleanly. False-negative cost: silently frozen session. Trade-off documented.
40
+ - **Silence threshold over-broad.** 15 min is conservative; agents doing slow LLM work (e.g. 5-min generation cycles) could trip if they go quiet during a long thinking pause. Mitigation: the `paused` / `recoveryInFlight` flags let SessionRegistry callers exempt sessions in known-quiet states. Configurable per-deployment.
41
+
42
+ ## 2. Under-block
43
+
44
+ - **WorktreeManager migration of EXISTING worktrees.** This PR ships the clone-default for NEW bindings. Migration of existing worktrees-on-disk is deferred — the spec discussed it but it requires significant safety work (rsync of uncommitted diffs, fencing during the swap). Tracked: same v3 Remediator absorption point. <!-- tracked: topic-3079-v3-remediator -->
45
+ - **Socket-disconnect pattern coverage.** Claude Code may surface other disconnect strings we don't yet know about. Patterns are extensible; the next observed string adds one line.
46
+ - **Active-work-silence false-negatives.** Sessions that produce minimal output (e.g. a `wait + retry` loop with tiny intervals) might never trip the 15-min threshold even when "stuck" in some other sense. Adjacent watchdogs (SessionWatchdog, PresenceProxy) cover other shapes.
47
+ - **Both sentinels currently fire-and-forget on escalation.** No retry-the-escalation loop. If the notify fails (network blip, tone gate hiccup), the message is lost. Acceptable for v1 — the bigger problem (silent failure) is closed; retry plumbing comes with v3 Remediator.
48
+
49
+ ## 3. Level-of-abstraction fit
50
+
51
+ - **WorktreeManager** is the right layer — it's the single funnel that decides how to materialize a worktree on disk. Putting the clone-vs-worktree decision anywhere else would skip the existing fencing + binding lifecycle.
52
+ - **Sentinels in `src/monitoring/`** alongside RateLimitSentinel, CompactionSentinel, SessionWatchdog. Same idiom; same wire-up shape via server.ts.
53
+ - **Escalation through `/attention`** uses the existing tone gate — no new judgment authority introduced.
54
+
55
+ ## 4. Signal vs authority compliance
56
+
57
+ **Reference:** [docs/signal-vs-authority.md](../../docs/signal-vs-authority.md)
58
+
59
+ - [x] **No — this change produces signals consumed by an existing smart gate.**
60
+
61
+ - `shouldCloneInsteadOfWorktree` is a structural predicate (file path + env). No judgment.
62
+ - `SOCKET_DISCONNECT_PATTERNS` regex is a detector. State machine is a bounded mechanic (fixed attempt count, fixed backoff staircase).
63
+ - `ActiveWorkSilenceSentinel.tick` threshold is deterministic. Nudge is a bounded primitive.
64
+ - All user-facing escalation goes through `MessagingToneGate` via `/attention`'s existing path. No new authority introduced.
65
+
66
+ ## 5. Interactions
67
+
68
+ - **Shadowing:** SocketDisconnectSentinel and ActiveWorkSilenceSentinel can both fire on the same session (socket dropped → output stopped). The socket sentinel ticks every 15s; the silence sentinel waits 15 min. Socket sentinel virtually always fires first. Idempotency-by-id in `/attention` prevents duplicate topic creation if both ever land.
69
+ - **Double-fire vs existing watchdogs:** SessionWatchdog (long-running child), SessionMonitor (topic-bound output gap), PresenceProxy (user-waits-for-agent). All three handle different shapes; none overlap with the new sentinels in practice. Active-work-silence is the catch-all for what falls through the cracks.
70
+ - **Races:** Sentinel state maps are per-session keys; concurrent ticks on the same server can't race (single-threaded JS event loop). Cross-process safety is N/A — each server instance is a single sentinel owner.
71
+ - **WorktreeManager clone vs worktree-add:** Mutually exclusive code paths; no race.
72
+
73
+ ## 6. External surfaces
74
+
75
+ - **Other agents:** Bindings created by this PR for shared repos will be clones, not worktrees. `git worktree list` from the source repo won't show them. Documented in CLAUDE.md template (forward).
76
+ - **Other users:** ships in next release. macOS users + Linux users alike. The clone path is OS-agnostic.
77
+ - **External systems:** Telegram (one extra topic class — "disconnect" alerts — though tone-gated). `/attention` route — existing; no change to its contract.
78
+ - **Persistent state:** No new files for the sentinels (in-memory state only — by design, simpler than RateLimitSentinel which persists). WorktreeManager bindings file unchanged.
79
+ - **Timing:** Sentinel ticks (15s + 60s) are negligible overhead per session.
80
+
81
+ ## 7. Rollback cost
82
+
83
+ - WorktreeManager: revert the diff. `INSTAR_WORKTREE_FORCE_WORKTREE=1` is the in-place rollback toggle without a release.
84
+ - Sentinels: revert each new file + the server.ts wire-up. No persistent state to clean up.
85
+ - Total: ~15 min revert + release cycle.
86
+
87
+ ---
88
+
89
+ ## Addendum 2026-05-23 — Smoke-test interaction with SourceTreeGuard (fixed)
90
+
91
+ After the initial commit landed, `npm run test:smoke` surfaced 14 failures in the existing WorktreeManager test suite — every test that exercised the clone-default branch failed with `SourceTreeGuardError: Refusing to run src/core/WorktreeManager.ts:clone-default against the instar source tree`. Root cause: `SafeGitExecutor.runSourceTreeChecks` defaults the target list to cwd. The new `git clone` call ran from a cwd that happened to be inside the instar source tree (test fixtures invoking `createBinding` from inside an instar checkout), and the guard correctly flagged the cwd as destructive-against-source even though the clone op itself is non-destructive on the source. **Fix:** pin `cwd: this.worktreesRoot` on the clone call — guaranteed outside the source tree and the same dir the clone destination lives under. All 56 smoke tests pass.
92
+
93
+ This is a defensible interaction, not a guard regression: the guard is doing exactly what it was built to do (conservative protection of the source tree). The clone-default branch needed explicit cwd because it's the first WorktreeManager codepath to run git from outside the source's own dir.
94
+
95
+ ## Conclusion
96
+
97
+ Closes the silently-stopped failure class in one PR per the no-deferrals rule. 34 new tests cover the core surfaces. Server-side wire-up + integration tests + CLAUDE.md template update are part of this PR (see commit). Tone-gate compliance asserted at unit level for both sentinels' escalation payloads. Migration of existing worktrees explicitly deferred to v3 Remediator with tracker marker.
98
+
99
+ Clear to ship pending second-pass review.
100
+
101
+ ---
102
+
103
+ ## Second-pass review (if required)
104
+
105
+ **Reviewer:** [pending]
106
+
107
+ [awaiting reviewer]
108
+
109
+ ---
110
+
111
+ ## Evidence pointers
112
+
113
+ - Spec: `docs/specs/silently-stopped-trio.md` + `.eli16.md`.
114
+ - Tests: `tests/unit/monitoring/SocketDisconnectSentinel.test.ts`, `tests/unit/monitoring/ActiveWorkSilenceSentinel.test.ts`, `tests/unit/core/WorktreeManager-clone-default.test.ts` — 34 tests.
115
+ - Incident reference: topic 5447 alignment with gsd-side echo's diagnosis 2026-05-22.