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.
- package/dist/commands/server.d.ts.map +1 -1
- package/dist/commands/server.js +160 -6
- package/dist/commands/server.js.map +1 -1
- package/dist/config/ConfigDefaults.d.ts.map +1 -1
- package/dist/config/ConfigDefaults.js +29 -0
- package/dist/config/ConfigDefaults.js.map +1 -1
- package/dist/core/InFlightSyncOpMarker.d.ts +29 -0
- package/dist/core/InFlightSyncOpMarker.d.ts.map +1 -0
- package/dist/core/InFlightSyncOpMarker.js +149 -0
- package/dist/core/InFlightSyncOpMarker.js.map +1 -0
- package/dist/core/PostUpdateMigrator.d.ts +16 -0
- package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
- package/dist/core/PostUpdateMigrator.js +91 -0
- package/dist/core/PostUpdateMigrator.js.map +1 -1
- package/dist/core/SessionManager.d.ts +146 -1
- package/dist/core/SessionManager.d.ts.map +1 -1
- package/dist/core/SessionManager.js +498 -144
- package/dist/core/SessionManager.js.map +1 -1
- package/dist/core/SleepWakeDetector.d.ts +33 -0
- package/dist/core/SleepWakeDetector.d.ts.map +1 -1
- package/dist/core/SleepWakeDetector.js +53 -0
- package/dist/core/SleepWakeDetector.js.map +1 -1
- package/dist/core/devGatedFeatures.d.ts.map +1 -1
- package/dist/core/devGatedFeatures.js +23 -0
- package/dist/core/devGatedFeatures.js.map +1 -1
- package/dist/core/types.d.ts +52 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/lifeline/ServerSupervisor.d.ts +42 -0
- package/dist/lifeline/ServerSupervisor.d.ts.map +1 -1
- package/dist/lifeline/ServerSupervisor.js +79 -4
- package/dist/lifeline/ServerSupervisor.js.map +1 -1
- package/dist/lifeline/TelegramLifeline.d.ts.map +1 -1
- package/dist/lifeline/TelegramLifeline.js +8 -0
- package/dist/lifeline/TelegramLifeline.js.map +1 -1
- package/dist/monitoring/DegradedTmuxGuard.d.ts +161 -0
- package/dist/monitoring/DegradedTmuxGuard.d.ts.map +1 -0
- package/dist/monitoring/DegradedTmuxGuard.js +277 -0
- package/dist/monitoring/DegradedTmuxGuard.js.map +1 -0
- package/dist/monitoring/guardManifest.d.ts.map +1 -1
- package/dist/monitoring/guardManifest.js +18 -0
- package/dist/monitoring/guardManifest.js.map +1 -1
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +13 -5
- package/dist/server/routes.js.map +1 -1
- package/package.json +2 -2
- package/scripts/lint-no-direct-destructive.js +14 -0
- package/scripts/lint-sync-subprocess-chokepoint.js +208 -0
- package/scripts/sync-subprocess-chokepoint-baseline.json +148 -0
- package/src/data/builtin-manifest.json +65 -65
- package/upgrades/1.3.642.md +46 -0
- package/upgrades/1.3.643.md +66 -0
- package/upgrades/side-effects/false-excuse-deferral-stop-guard.md +75 -0
- package/upgrades/side-effects/tmux-event-loop-resilience.md +86 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/commands/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/commands/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAwCH,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAU3D,OAAO,EAAE,eAAe,EAAiC,MAAM,iCAAiC,CAAC;AAwBjG,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAGvD,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAsH7D,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AA+EtD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,gBAAgB,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAC1C,OAAO,CAUT;AAyID,UAAU,YAAY;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;2DACuD;IACvD,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAy4CD,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,eAAe,EACzB,cAAc,EAAE,cAAc,EAC9B,YAAY,CAAC,EAAE,YAAY,EAC3B,WAAW,CAAC,EAAE,WAAW,EACzB,WAAW,CAAC,EAAE,WAAW,EACzB,iBAAiB,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,EAGvE,UAAU,CAAC,EAAE,MAAM,OAAO,8BAA8B,EAAE,WAAW,GAAG,IAAI,EAK5E,qBAAqB,CAAC,EAAE,MAAM,OAAO,gCAAgC,EAAE,kBAAkB,GAAG,IAAI,EAKhG,mBAAmB,CAAC,EAAE,MAAM,MAAM,GAAG,IAAI,GAAG,SAAS,GACpD,IAAI,CA8eN;AA2lBD,wBAAsB,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAmzftE;AAED,wBAAsB,UAAU,CAAC,OAAO,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAsDzE;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,aAAa,CAAC,OAAO,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAuD5E"}
|
package/dist/commands/server.js
CHANGED
|
@@ -7,13 +7,17 @@
|
|
|
7
7
|
* When Telegram is configured, wires up message routing:
|
|
8
8
|
* topic message → find/spawn session → inject message → session replies via [telegram:N]
|
|
9
9
|
*/
|
|
10
|
-
import { execFileSync } from 'node:child_process';
|
|
10
|
+
import { execFile, execFileSync } from 'node:child_process';
|
|
11
11
|
import fs from 'node:fs';
|
|
12
12
|
import os from 'node:os';
|
|
13
13
|
import path from 'node:path';
|
|
14
14
|
import { fileURLToPath } from 'node:url';
|
|
15
|
+
import { promisify } from 'node:util';
|
|
15
16
|
import pc from 'picocolors';
|
|
16
17
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
18
|
+
// (A.5) wake-handler amplifier guard — the post-wake tmux re-validation is bounded
|
|
19
|
+
// async (9s + SIGKILL) instead of a sync execFileSync that can wedge the event loop.
|
|
20
|
+
const execFileAsync = promisify(execFile);
|
|
17
21
|
import { loadConfig, ensureStateDir, detectTmuxPath, detectGeminiPath } from '../core/Config.js';
|
|
18
22
|
import { handleProcessLevelError } from '../core/uncaughtExceptionPolicy.js';
|
|
19
23
|
import { resolveDevAgentGate, resolveStateSyncStores } from '../core/devAgentGate.js';
|
|
@@ -30,6 +34,7 @@ import { gapBEligibleForTopic, recentUserMessageFromHistory, resolveGapBInjectio
|
|
|
30
34
|
import { TopicProfileTransferCarrier, createTopicProfilePullHandler } from '../core/TopicProfileTransferCarrier.js';
|
|
31
35
|
import { closeAllSqlite } from '../core/SqliteRegistry.js';
|
|
32
36
|
import { SessionManager } from '../core/SessionManager.js';
|
|
37
|
+
import { configureSyncOpMarker, defaultInflightMarkerReader } from '../core/InFlightSyncOpMarker.js';
|
|
33
38
|
import { StateManager } from '../core/StateManager.js';
|
|
34
39
|
import { StuckInputSentinel } from '../core/StuckInputSentinel.js';
|
|
35
40
|
import { SessionRecoveryChannel } from '../core/SessionRecoveryChannel.js';
|
|
@@ -4241,7 +4246,51 @@ export async function startServer(options) {
|
|
|
4241
4246
|
? { subscriptionReroutedLifetimeMinutes: subscriptionPathCfg.maxReroutedLifetimeMinutes }
|
|
4242
4247
|
: {}),
|
|
4243
4248
|
};
|
|
4244
|
-
|
|
4249
|
+
// ── (A)/(C) tmux event-loop resilience wiring (threaded through SessionManager opts) ──
|
|
4250
|
+
// SessionManager construction (here) PRECEDES the telegram/guardRegistry/sleepWakeDetector
|
|
4251
|
+
// wiring the (C) guard's notify path needs, and SessionManager takes the guard as a
|
|
4252
|
+
// readonly constructor arg — so the guard is built HERE with a LATE-BOUND raiseAttention
|
|
4253
|
+
// (a closure over a mutable holder), then the holder + the stall listener + the guard-
|
|
4254
|
+
// registry getter are wired later (the §C block before sleepWakeDetector.start()). When
|
|
4255
|
+
// the dev-gate resolves off, tmuxAsyncEnabled is false ⇒ SessionManager's tmux callsites
|
|
4256
|
+
// stay on today's sync byte-for-byte path (D6) and the guard never fires.
|
|
4257
|
+
const _tmuxAsyncEnabled = resolveDevAgentGate(config.monitoring?.tmuxResilience?.asyncHotPath?.enabled, config);
|
|
4258
|
+
const _degradedTmuxEnabled = resolveDevAgentGate(config.monitoring?.degradedTmuxGuard?.enabled, config);
|
|
4259
|
+
// Wire the cross-process marker MIRROR (the WRITER side, owned by this server process).
|
|
4260
|
+
// The stateDir here is byte-identical to the one TelegramLifeline passes to ServerSupervisor
|
|
4261
|
+
// (both resolve from loadConfig(projectDir).stateDir), so writer and cross-process reader
|
|
4262
|
+
// agree on <stateDir>/state/tmux-inflight-sync-op.json. This un-inerts BOTH amplifier #2
|
|
4263
|
+
// (ServerSupervisor.deferRestartForInflightSyncOp) AND the wake-handler marker short-circuit:
|
|
4264
|
+
// without it mirrorStateDir stays null, the mirror file is never written, and every
|
|
4265
|
+
// cross-process read returns null. callTimeoutMs keeps the marker stale-TTL tracking the
|
|
4266
|
+
// same per-call timeout SessionManager uses (so a tuned timeout doesn't make the marker
|
|
4267
|
+
// self-heal early). The mirror is signal-only and harmless when no flag reads it.
|
|
4268
|
+
configureSyncOpMarker({
|
|
4269
|
+
stateDir: config.stateDir,
|
|
4270
|
+
callTimeoutMs: config.monitoring?.tmuxResilience?.asyncHotPath?.timeoutMs,
|
|
4271
|
+
});
|
|
4272
|
+
// The late-bound notify sink — set in the §C wiring block once telegram exists. Until
|
|
4273
|
+
// then (and on a non-telegram install) it is a no-op, so an early episode is dropped
|
|
4274
|
+
// rather than thrown (signal-only).
|
|
4275
|
+
let _degradedTmuxRaise = null;
|
|
4276
|
+
const { DegradedTmuxGuard: _DegradedTmuxGuardCtor } = await import('../monitoring/DegradedTmuxGuard.js');
|
|
4277
|
+
// Cache the core count ONCE (stable for the process lifetime) — the guard's loadPerCore
|
|
4278
|
+
// provider is called on every (non-settle-window) evaluate() i.e. potentially N+ times per
|
|
4279
|
+
// monitor tick, and os.cpus() allocates a per-core array each call (a throughput smell on
|
|
4280
|
+
// the busy multi-agent host this guard targets). os.loadavg() is a cheap syscall; read only
|
|
4281
|
+
// loadavg[0]/cachedCores per call, matching sampleHostPressureInputs' loadPerCore exactly.
|
|
4282
|
+
const _degradedTmuxCores = os.cpus()?.length ?? 0;
|
|
4283
|
+
const degradedTmuxGuard = new _DegradedTmuxGuardCtor({ ...config.monitoring?.degradedTmuxGuard, enabled: _degradedTmuxEnabled }, {
|
|
4284
|
+
raiseAttention: (ep) => _degradedTmuxRaise?.(ep),
|
|
4285
|
+
loadPerCore: () => (_degradedTmuxCores > 0 ? os.loadavg()[0] / _degradedTmuxCores : 0),
|
|
4286
|
+
now: () => Date.now(),
|
|
4287
|
+
});
|
|
4288
|
+
const sessionManager = new SessionManager(sessionManagerConfig, state, {
|
|
4289
|
+
tmuxAsyncEnabled: _tmuxAsyncEnabled,
|
|
4290
|
+
degradedTmuxGuard,
|
|
4291
|
+
tmuxCallTimeoutMs: config.monitoring?.tmuxResilience?.asyncHotPath?.timeoutMs,
|
|
4292
|
+
tmuxMaxInFlight: config.monitoring?.tmuxResilience?.asyncHotPath?.maxInFlight,
|
|
4293
|
+
});
|
|
4245
4294
|
// Wire the SAME TTL-cached SDK-credit reader PR1's routing policy uses, so
|
|
4246
4295
|
// the reroute 'auto' decision and the intelligence-funnel routing share one
|
|
4247
4296
|
// credit source and can't drift (june15-headless-spawn-reroute, PR2). Only
|
|
@@ -11286,6 +11335,11 @@ export async function startServer(options) {
|
|
|
11286
11335
|
minWakeIntervalMs: sleepWakeCfg?.minWakeIntervalMs,
|
|
11287
11336
|
recentDriftWindowMs: sleepWakeCfg?.recentDriftWindowMs,
|
|
11288
11337
|
recentDriftLoadFloor: sleepWakeCfg?.recentDriftLoadFloor,
|
|
11338
|
+
// (B) in-flight-sync-op marker — when on, a ~0-CPU drift while a sync subprocess
|
|
11339
|
+
// op is in flight (depth>0, not stale) classifies as an event-loop BLOCK (emits
|
|
11340
|
+
// `stall`, suppresses the false wake) instead of a wake. developmentAgent gate:
|
|
11341
|
+
// LIVE on a dev agent / DARK on the fleet. Off ⇒ today's behavior byte-for-byte (D6).
|
|
11342
|
+
inFlightMarkerEnabled: resolveDevAgentGate(config.monitoring?.tmuxResilience?.inFlightMarker?.enabled, config),
|
|
11289
11343
|
});
|
|
11290
11344
|
// §P0 #9 (SE-8): give the scheduler's wake-reaper a cumulative-sleep view
|
|
11291
11345
|
// (not the single last sleep event) so a job that spanned multiple sleeps
|
|
@@ -11293,8 +11347,33 @@ export async function startServer(options) {
|
|
|
11293
11347
|
if (scheduler) {
|
|
11294
11348
|
scheduler.setCumulativeSleepProvider((a, b) => sleepWakeDetector.getCumulativeSleepMsBetween(a, b));
|
|
11295
11349
|
}
|
|
11350
|
+
// (A.5) wake-handler amplifier guard — resolved once. When LIVE, a spurious wake
|
|
11351
|
+
// produced by an in-flight sync subprocess op (an event-loop BLOCK, not a suspend)
|
|
11352
|
+
// is short-circuited at entry, and a low-confidence wake skips the heavy
|
|
11353
|
+
// tunnel/tmux recovery cascade — the recovery storm that piled more load on the host
|
|
11354
|
+
// and re-wedged it. developmentAgent gate: LIVE on a dev agent / DARK on the fleet;
|
|
11355
|
+
// when off the handler is byte-for-byte today's behavior (D6).
|
|
11356
|
+
const _tmuxAsyncHotPathWakeGuard = resolveDevAgentGate(config.monitoring?.tmuxResilience?.asyncHotPath?.enabled, config);
|
|
11296
11357
|
sleepWakeDetector.on('wake', async (event) => {
|
|
11297
11358
|
console.log(`[SleepWake] Wake detected after ~${event.sleepDurationSeconds}s sleep`);
|
|
11359
|
+
// (A.5) Marker short-circuit (residual guard): a sync subprocess op is in flight
|
|
11360
|
+
// RIGHT NOW (the cross-process mirror reports inFlight, not stale) → this "wake" is
|
|
11361
|
+
// an event-loop BLOCK, not a real suspend. (B) already classifies a depth>0 gap as a
|
|
11362
|
+
// `stall` (never a `wake`), so this is the belt-and-suspenders for the moderate-
|
|
11363
|
+
// confidence band: do the minimum and return before any recovery cascade.
|
|
11364
|
+
if (_tmuxAsyncHotPathWakeGuard) {
|
|
11365
|
+
try {
|
|
11366
|
+
const marker = defaultInflightMarkerReader(config.stateDir)();
|
|
11367
|
+
if (marker?.inFlight) {
|
|
11368
|
+
console.log(`[SleepWake] Wake fired while a sync subprocess op is in flight (ageMs=${marker.ageMs}) — ` +
|
|
11369
|
+
`event-loop BLOCK, not a suspend. Skipping recovery cascade.`);
|
|
11370
|
+
return;
|
|
11371
|
+
}
|
|
11372
|
+
}
|
|
11373
|
+
catch {
|
|
11374
|
+
/* @silent-fallback-ok — a marker-read failure falls through to the normal handler */
|
|
11375
|
+
}
|
|
11376
|
+
}
|
|
11298
11377
|
// Checkpoint SQLite WAL files to flush stale locks from pre-sleep connections
|
|
11299
11378
|
try {
|
|
11300
11379
|
topicMemory?.checkpoint();
|
|
@@ -11311,13 +11390,30 @@ export async function startServer(options) {
|
|
|
11311
11390
|
void _inboundQueue?.onWake(event.sleepDurationSeconds * 1000, event.lowConfidence ? 'low' : 'high');
|
|
11312
11391
|
}
|
|
11313
11392
|
catch { /* the backstop tick covers a missed wake trigger */ }
|
|
11314
|
-
// Re-validate tmux sessions
|
|
11393
|
+
// Re-validate tmux sessions — bounded async (9s + SIGKILL) under the amplifier
|
|
11394
|
+
// guard so a wedged tmux can't block the event loop here; informational only, so a
|
|
11395
|
+
// timeout/reject is swallowed.
|
|
11396
|
+
// NOTE: a low-confidence-wake producer (which would let this skip the heavy
|
|
11397
|
+
// tunnel/tmux recovery cascade) is a FUTURE increment — Increment 1 covers the
|
|
11398
|
+
// event-loop-block case via the in-flight marker short-circuit above, so the dead
|
|
11399
|
+
// `event.lowConfidence` cascade-skip branch (no producer in SleepWakeDetector) was
|
|
11400
|
+
// removed rather than ship a guard that can never fire.
|
|
11315
11401
|
try {
|
|
11316
11402
|
const tmuxPath = detectTmuxPath();
|
|
11317
11403
|
if (tmuxPath) {
|
|
11318
|
-
|
|
11319
|
-
|
|
11320
|
-
|
|
11404
|
+
if (_tmuxAsyncHotPathWakeGuard) {
|
|
11405
|
+
const { stdout } = await execFileAsync(tmuxPath, ['list-sessions'], {
|
|
11406
|
+
encoding: 'utf-8',
|
|
11407
|
+
timeout: 9000,
|
|
11408
|
+
killSignal: 'SIGKILL',
|
|
11409
|
+
});
|
|
11410
|
+
console.log(`[SleepWake] tmux sessions after wake: ${stdout.trim().split('\n').length}`);
|
|
11411
|
+
}
|
|
11412
|
+
else {
|
|
11413
|
+
const { execFileSync } = await import('child_process');
|
|
11414
|
+
const result = execFileSync(tmuxPath, ['list-sessions'], { encoding: 'utf-8', timeout: 5000 }).trim();
|
|
11415
|
+
console.log(`[SleepWake] tmux sessions after wake: ${result.split('\n').length}`);
|
|
11416
|
+
}
|
|
11321
11417
|
}
|
|
11322
11418
|
}
|
|
11323
11419
|
catch {
|
|
@@ -11403,6 +11499,64 @@ export async function startServer(options) {
|
|
|
11403
11499
|
notify('DIGEST', 'system', `Machine woke up after ${mins > 60 ? `${Math.round(mins / 60)}h` : `${mins}m`} of sleep.${reapNote} Everything's reconnected and running.`);
|
|
11404
11500
|
}
|
|
11405
11501
|
});
|
|
11502
|
+
// ── (C) DegradedTmuxGuard — signal-only degraded-shared-tmux watcher (wiring) ──
|
|
11503
|
+
// The guard was CONSTRUCTED at the SessionManager site (it is a readonly constructor
|
|
11504
|
+
// dep of SessionManager, which precedes this block) with a late-bound raiseAttention.
|
|
11505
|
+
// Here we finish the wiring now that telegram/guardRegistry/sleepWakeDetector exist:
|
|
11506
|
+
// set the notify sink, attach the (B) `stall` listener, and register the runtime getter.
|
|
11507
|
+
// SIGNAL-ONLY: any actual tmux refresh is the operator's explicit Y/N; the guard NEVER
|
|
11508
|
+
// kills the shared socket (a kill-server would restart EVERY agent on the machine).
|
|
11509
|
+
// developmentAgent gate: LIVE on a dev agent / DARK on the fleet. Non-fatal wiring.
|
|
11510
|
+
try {
|
|
11511
|
+
// Register the runtime getter FIRST — before the notify-sink bind / stall-listener
|
|
11512
|
+
// attach — so a throw in that later wiring can't strip the getter and leave an
|
|
11513
|
+
// enabled-but-unregistered expectRuntime:true guard reporting phantom `missing` on
|
|
11514
|
+
// /guards. The getter only needs `degradedTmuxGuard`, which already exists from the
|
|
11515
|
+
// SessionManager-site construction.
|
|
11516
|
+
guardRegistry.register('monitoring.degradedTmuxGuard.enabled', () => degradedTmuxGuard.guardStatus());
|
|
11517
|
+
const _degradedTmuxMid = _meshSelfId ?? coordinator.identity?.machineId ?? 'local';
|
|
11518
|
+
// Bind the notify sink (was a no-op until telegram existed).
|
|
11519
|
+
_degradedTmuxRaise = telegram
|
|
11520
|
+
? (ep) => {
|
|
11521
|
+
// Resolve the machine nickname from the pool registry (single-machine ⇒ the id).
|
|
11522
|
+
const nick = (_listPoolMachines?.() ?? []).find((m) => m.machineId === _degradedTmuxMid)?.nickname ??
|
|
11523
|
+
_degradedTmuxMid;
|
|
11524
|
+
const ageMin = Math.round(ep.ageMs / 60_000);
|
|
11525
|
+
void telegram.createAttentionItem({
|
|
11526
|
+
id: `degraded-tmux:${_degradedTmuxMid}:${ep.id}`,
|
|
11527
|
+
title: `Slow terminal server on "${nick}"`,
|
|
11528
|
+
summary: 'The shared terminal server has been slow to respond — sessions may feel sluggish. ' +
|
|
11529
|
+
'Refreshing it (your call) usually clears it.',
|
|
11530
|
+
description: `The shared tmux server on ${nick} has been responding slowly ` +
|
|
11531
|
+
`(~${ep.ewmaMs}ms average over ${ep.consecutiveSlowCycles} corroborating cycles` +
|
|
11532
|
+
`${ageMin > 0 ? `, persisting ~${ageMin}m` : ''}). This can make every session feel sluggish. ` +
|
|
11533
|
+
'Restarting the tmux server (an operator action) usually clears it — I never do that automatically ' +
|
|
11534
|
+
'because it would restart every agent on this machine.',
|
|
11535
|
+
category: 'agent-health',
|
|
11536
|
+
priority: 'NORMAL',
|
|
11537
|
+
lane: 'agent-health',
|
|
11538
|
+
healthKey: `degraded-tmux:${_degradedTmuxMid}`,
|
|
11539
|
+
sourceContext: `degraded-tmux:${_degradedTmuxMid}`,
|
|
11540
|
+
});
|
|
11541
|
+
}
|
|
11542
|
+
: null;
|
|
11543
|
+
// (B) feed — a `stall` is one corroborating slow cycle. Attach BEFORE start() so no
|
|
11544
|
+
// emit is missed. Swallow any throw — a guard fault must never crash the detector.
|
|
11545
|
+
sleepWakeDetector.on('stall', (e) => {
|
|
11546
|
+
try {
|
|
11547
|
+
degradedTmuxGuard.onStall(e);
|
|
11548
|
+
}
|
|
11549
|
+
catch {
|
|
11550
|
+
/* @silent-fallback-ok — signal-only; a guard fault never crashes the detector */
|
|
11551
|
+
}
|
|
11552
|
+
});
|
|
11553
|
+
if (_degradedTmuxEnabled) {
|
|
11554
|
+
console.log(pc.green(' DegradedTmuxGuard enabled (degraded-shared-tmux watcher — signal-only)'));
|
|
11555
|
+
}
|
|
11556
|
+
}
|
|
11557
|
+
catch (e) {
|
|
11558
|
+
console.error(' DegradedTmuxGuard wiring failed (non-fatal):', e instanceof Error ? e.message : String(e));
|
|
11559
|
+
}
|
|
11406
11560
|
sleepWakeDetector.start();
|
|
11407
11561
|
// Project Map + Coherence Gate — spatial awareness and pre-action verification
|
|
11408
11562
|
const projectMapper = new ProjectMapper({ projectDir: config.projectDir, stateDir: config.stateDir });
|