@agfpd/iapeer 0.2.39 → 0.2.41

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agfpd/iapeer",
3
- "version": "0.2.39",
3
+ "version": "0.2.41",
4
4
  "description": "Foundation core for the iapeer multi-agent ecosystem: identity, registry, storage, codec.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cli/index.ts CHANGED
@@ -1339,6 +1339,15 @@ export async function runCli(argv: string[], env: NodeJS.ProcessEnv = process.en
1339
1339
  return 1
1340
1340
  }
1341
1341
  out(`${r.woke ? 'woke + ' : ''}attaching ${r.identity}…\n`)
1342
+ // Attach-window observability (killer-hunt 11.06: «была ли смерть под
1343
+ // attach» was answerable only INDIRECTLY via wake cause=attach — i.e. only
1344
+ // when the session was dead at attach time). Bracket the operator's client
1345
+ // window in lifecycle.log: ev=attach at entry, ev=attach-end (with the tmux
1346
+ // client's exit code) when the operator detaches or the server dies under
1347
+ // the client. A death timestamp inside an attach…attach-end window now
1348
+ // reads directly off the log.
1349
+ const attachCfg = loadLifecycleConfig(env)
1350
+ appendLifecycleEvent(attachCfg.eventLogDir, { ev: 'attach', identity: r.identity, woke: String(r.woke) }, { env })
1342
1351
  // Drop the operator into the session. `env -u TMUX` so a nested attach from
1343
1352
  // inside tmux does not error ("sessions should be nested with care").
1344
1353
  const attachEnv = { ...env }
@@ -1347,6 +1356,7 @@ export async function runCli(argv: string[], env: NodeJS.ProcessEnv = process.en
1347
1356
  stdio: 'inherit',
1348
1357
  env: attachEnv as Record<string, string>,
1349
1358
  })
1359
+ appendLifecycleEvent(attachCfg.eventLogDir, { ev: 'attach-end', identity: r.identity, rc: a.status ?? -1 }, { env })
1350
1360
  return a.status ?? 0
1351
1361
  }
1352
1362
  default: {
package/src/init/index.ts CHANGED
@@ -172,6 +172,41 @@ export function writeCodexMcpConfig(daemonUrl: string, options: StorageOptions =
172
172
  return { path, added: true }
173
173
  }
174
174
 
175
+ /**
176
+ * Idempotently ensure codex's startup update-check DIALOG is disabled host-wide:
177
+ * `check_for_update_on_startup = false` as a TOP-LEVEL key in config.toml.
178
+ *
179
+ * Why (incident 11.06, codex-linus): the interactive "✨ Update available! …
180
+ * Press enter to continue" dialog is a BOOT GATE — a daemon-woken codex session
181
+ * sits on it until a human attaches. The wake reported never-became-ready after
182
+ * 4+ minutes and a notifier alert of the EMERGENCY contour (mining farm) was
183
+ * lost; the session came alive only from a manual attach. Warm-on-demand peers
184
+ * must reach ready hands-free — codex updates stay operator-driven (`codex
185
+ * update` / brew), never a boot-time question to a headless session.
186
+ *
187
+ * Mechanics: the key is a documented top-level config.toml option (present in
188
+ * the binary's own config schema; suppression PROVEN LIVE 11.06 on codex 0.138
189
+ * with the 0.139 update pending — control boot showed the dialog, the keyed
190
+ * boot went straight past it). A TOML top-level key must precede the first
191
+ * table header, so the key is PREPENDED — appending would land it inside the
192
+ * last [section]. An EXISTING `check_for_update_on_startup` of either value is
193
+ * respected (never override an explicit operator choice).
194
+ */
195
+ export function ensureCodexUpdateCheckDisabled(
196
+ options: StorageOptions = {},
197
+ ): { path: string; changed: boolean } {
198
+ const path = codexConfigPath(options)
199
+ let existing = ''
200
+ try {
201
+ existing = readFileSync(path, 'utf8')
202
+ } catch {
203
+ /* no config yet → create it */
204
+ }
205
+ if (/^\s*check_for_update_on_startup\s*=/m.test(existing)) return { path, changed: false }
206
+ writeFileAtomic(path, `check_for_update_on_startup = false\n${existing}`, 0o644)
207
+ return { path, changed: true }
208
+ }
209
+
175
210
  // ─────────────────────────────────────────────────────────────────────────────
176
211
  // Local doctrine template (<cwd>/.iapeer/IAPEER.md — personality/role; human fills)
177
212
  // ─────────────────────────────────────────────────────────────────────────────
@@ -304,6 +339,9 @@ export async function initPeer(opts: InitPeerOptions): Promise<InitPeerResult> {
304
339
  let codexMcpConfigPath: string | undefined
305
340
  if (provisioned.runtime === 'codex' || hasCodexMarker(provisioned.cwd)) {
306
341
  codexMcpConfigPath = writeCodexMcpConfig(daemonUrl, { env }).path
342
+ // Boot-gate hygiene (incident 11.06): a codex peer must never sit on the
343
+ // interactive update dialog at wake — disable the startup check host-wide.
344
+ ensureCodexUpdateCheckDisabled({ env })
307
345
  }
308
346
 
309
347
  const doctrine = ensureDoctrineTemplate(provisioned.cwd)
@@ -9,6 +9,7 @@ import {
9
9
  CODEX_BEARER_ENV_VAR,
10
10
  IAPEER_MCP_SERVER_NAME,
11
11
  codexConfigPath,
12
+ ensureCodexUpdateCheckDisabled,
12
13
  ensureDoctrineTemplate,
13
14
  initPeer,
14
15
  resolveDaemonMcpUrl,
@@ -169,3 +170,44 @@ describe('initPeer orchestration', () => {
169
170
  expect(readFileSync(join(cwd, '.iapeer', 'IAPEER.md'), 'utf8')).toBe('custom doctrine')
170
171
  })
171
172
  })
173
+
174
+ describe('ensureCodexUpdateCheckDisabled (boot-gate hygiene — incident 11.06 codex-linus)', () => {
175
+ test('PREPENDS the top-level key BEFORE the first [section] (TOML top-level scope)', () => {
176
+ const codexHome = mkdtempSync(join(tmpdir(), 'iapeer-codexhome3-'))
177
+ try {
178
+ const env: NodeJS.ProcessEnv = { ...process.env, CODEX_HOME: codexHome }
179
+ writeFileSync(codexConfigPath({ env }), 'model = "gpt-5.5"\n\n[projects."/x"]\ntrust_level = "trusted"\n')
180
+ const { changed } = ensureCodexUpdateCheckDisabled({ env })
181
+ expect(changed).toBe(true)
182
+ const toml = readFileSync(codexConfigPath({ env }), 'utf8')
183
+ expect(toml.startsWith('check_for_update_on_startup = false\n')).toBe(true)
184
+ expect(toml).toContain('[projects."/x"]') // existing config preserved
185
+ // idempotent: second run is a no-op
186
+ expect(ensureCodexUpdateCheckDisabled({ env }).changed).toBe(false)
187
+ expect((toml.match(/check_for_update_on_startup/g) ?? []).length).toBe(1)
188
+ } finally {
189
+ rmSync(codexHome, { recursive: true, force: true })
190
+ }
191
+ })
192
+ test('respects an EXPLICIT existing value (never overrides the operator)', () => {
193
+ const codexHome = mkdtempSync(join(tmpdir(), 'iapeer-codexhome4-'))
194
+ try {
195
+ const env: NodeJS.ProcessEnv = { ...process.env, CODEX_HOME: codexHome }
196
+ writeFileSync(codexConfigPath({ env }), 'check_for_update_on_startup = true\n')
197
+ expect(ensureCodexUpdateCheckDisabled({ env }).changed).toBe(false)
198
+ expect(readFileSync(codexConfigPath({ env }), 'utf8')).toContain('check_for_update_on_startup = true')
199
+ } finally {
200
+ rmSync(codexHome, { recursive: true, force: true })
201
+ }
202
+ })
203
+ test('absent config → created with just the key', () => {
204
+ const codexHome = mkdtempSync(join(tmpdir(), 'iapeer-codexhome5-'))
205
+ try {
206
+ const env: NodeJS.ProcessEnv = { ...process.env, CODEX_HOME: codexHome }
207
+ expect(ensureCodexUpdateCheckDisabled({ env }).changed).toBe(true)
208
+ expect(readFileSync(codexConfigPath({ env }), 'utf8')).toBe('check_for_update_on_startup = false\n')
209
+ } finally {
210
+ rmSync(codexHome, { recursive: true, force: true })
211
+ }
212
+ })
213
+ })
@@ -0,0 +1,94 @@
1
+ // tmux command-log (cmdlog.ts) — the killer-hunt socket-command layer.
2
+ //
3
+ // The live suite encodes boris's acceptance criterion directly: a kill-session
4
+ // issued by an EXTERNAL client must land in the -v server log WITH client
5
+ // attribution (`message: client-<pid> command: …`), file modes never wider than
6
+ // 0600, and the cap keeps volume bounded without losing the tail.
7
+
8
+ import { afterAll, beforeAll, describe, expect, test } from 'bun:test'
9
+ import { mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync, existsSync } from 'fs'
10
+ import { tmpdir } from 'os'
11
+ import { join } from 'path'
12
+ import { spawnSync } from 'child_process'
13
+ import {
14
+ CMDLOG_KEEP_BYTES,
15
+ capCmdLogs,
16
+ cmdLogDirFor,
17
+ hardenCmdLogDir,
18
+ prepareCmdLogDir,
19
+ } from './cmdlog.ts'
20
+
21
+ const tmuxAvailable = spawnSync('tmux', ['-V'], { stdio: 'ignore' }).status === 0
22
+
23
+ let root: string
24
+ beforeAll(() => {
25
+ root = mkdtempSync(join(tmpdir(), 'cmdlog-'))
26
+ })
27
+ afterAll(() => {
28
+ rmSync(root, { recursive: true, force: true })
29
+ })
30
+
31
+ describe('prepareCmdLogDir / hardenCmdLogDir / capCmdLogs (hermetic)', () => {
32
+ test('prepare WIPES the previous generation and recreates 0700', () => {
33
+ const dir = prepareCmdLogDir(root, 'claude-x')
34
+ expect(dir).toBe(cmdLogDirFor(root, 'claude-x'))
35
+ writeFileSync(join(dir!, 'tmux-server-1.log'), 'old generation')
36
+ const again = prepareCmdLogDir(root, 'claude-x')
37
+ expect(readdirSync(again!)).toEqual([]) // old log gone — one generation per dir
38
+ expect(statSync(again!).mode & 0o777).toBe(0o700)
39
+ })
40
+
41
+ test('harden clamps every file to 0600', () => {
42
+ const dir = prepareCmdLogDir(root, 'claude-h')!
43
+ writeFileSync(join(dir, 'tmux-server-2.log'), 'x', { mode: 0o644 })
44
+ hardenCmdLogDir(dir)
45
+ expect(statSync(join(dir, 'tmux-server-2.log')).mode & 0o777).toBe(0o600)
46
+ })
47
+
48
+ test('cap tail-keeps an oversized log (KEEP bytes of TAIL survive), leaves small logs alone', () => {
49
+ const dir = prepareCmdLogDir(root, 'claude-c')!
50
+ const big = join(dir, 'tmux-server-3.log')
51
+ const small = join(dir, 'tmux-server-4.log')
52
+ // 2 MB of filler ending in a recognizable tail — cap to 1 KB keep for the test
53
+ writeFileSync(big, Buffer.concat([Buffer.alloc(2 * 1024 * 1024, 0x61), Buffer.from('THE-DEATH-TAIL')]))
54
+ writeFileSync(small, 'tiny')
55
+ const capped = capCmdLogs(root, 1024 * 1024, 1024)
56
+ expect(capped).toEqual([big])
57
+ const after = readFileSync(big)
58
+ expect(after.length).toBe(1024)
59
+ expect(after.toString()).toEndWith('THE-DEATH-TAIL') // the tail is what survives
60
+ expect(readFileSync(small, 'utf8')).toBe('tiny')
61
+ })
62
+
63
+ test('cap on a missing tree is a silent no-op', () => {
64
+ expect(capCmdLogs(join(root, 'absent'))).toEqual([])
65
+ })
66
+
67
+ test('default keep constant stays under the cap', () => {
68
+ expect(CMDLOG_KEEP_BYTES).toBeLessThan(8 * 1024 * 1024)
69
+ })
70
+ })
71
+
72
+ describe.if(tmuxAvailable)('live -v server log (acceptance criterion)', () => {
73
+ test('external client kill-session lands in the server log with client attribution; harden → 0600', () => {
74
+ const dir = prepareCmdLogDir(root, 'claude-live')!
75
+ const sock = join(root, 'live.sock')
76
+ // Start the server FROM the cmdlog dir with -v — exactly the launch wiring.
77
+ const start = spawnSync('tmux', ['-S', sock, '-v', 'new-session', '-d', '-x', '80', '-y', '24', '-s', 'claude-live', 'sleep 60'], {
78
+ cwd: dir,
79
+ encoding: 'utf8',
80
+ })
81
+ expect(start.status).toBe(0)
82
+ hardenCmdLogDir(dir)
83
+ // An EXTERNAL client (separate tmux invocation = separate client pid) kills it.
84
+ const kill = spawnSync('tmux', ['-S', sock, 'kill-session', '-t', 'claude-live'], { encoding: 'utf8' })
85
+ expect(kill.status).toBe(0)
86
+ const serverLog = readdirSync(dir).find(f => f.startsWith('tmux-server-'))
87
+ expect(serverLog).toBeDefined()
88
+ expect(statSync(join(dir, serverLog!)).mode & 0o777).toBe(0o600)
89
+ const text = readFileSync(join(dir, serverLog!), 'utf8')
90
+ // Attribution line: message: client-<pid> command: kill-session -t claude-live
91
+ expect(text).toMatch(/message: client-\d+ command: kill-session -t claude-live/)
92
+ spawnSync('tmux', ['-S', sock, 'kill-server'], { stdio: 'ignore' })
93
+ })
94
+ })
@@ -0,0 +1,130 @@
1
+ // tmux command-log — the SOCKET-COMMAND observability layer of the killer hunt.
2
+ //
3
+ // Why (death class 11.06, boris's session-deaths track): a tmux server killed by a
4
+ // CLIENT COMMAND (`kill-server` / `kill-session` over the unix socket) dies with
5
+ // zero signals — structurally invisible to ALL THREE existing layers: the eslogger
6
+ // trap (no kill(2) anywhere; kernel-originated SIGHUP to the panes is also proven
7
+ // invisible), the pane-died hook (kill-session destroys the pane without retention)
8
+ // and the server-death canary (sees only "server gone"). The tmux server's OWN
9
+ // verbose log is the only witness of that layer: started with `-v`, the server
10
+ // appends `message: client-<pid> command: <verb> <args>` for every client command —
11
+ // the attribution (client pid + exact command + timestamp) that decides the class
12
+ // binarily: "killed by client X" vs "no command ⇒ internal exit".
13
+ //
14
+ // Sensitivity (boris's requirement): the -v log carries FULL send-keys payloads
15
+ // (every delivered envelope, plaintext) — tmux has no commands-only level and the
16
+ // server writes the file itself (name tmux-server-<pid>.log in the STARTING
17
+ // client's cwd — no FIFO/pipe interposition possible). Containment instead:
18
+ // • the per-identity dir is 0700 (others cannot traverse — same protection class
19
+ // as the transcripts that already hold the same texts);
20
+ // • files are chmod'ed 0600 right after the server starts (hardenCmdLogDir);
21
+ // • volume is capped: ~7 KB/s under a 10 fps TUI redraw (measured) — the
22
+ // supervise tick tail-keeps each log over CAP_BYTES down to KEEP_BYTES.
23
+ // tmux writes with O_APPEND (proven live: external truncation is safe, the
24
+ // server keeps appending at the new EOF), and a killer's command is by nature
25
+ // the LAST thing in the log — the death tail always survives capping.
26
+ // • scope: agent-runtime (warm) sessions only — always-on infra sessions relaunch
27
+ // rarely (no rotation point) and are not the dying class.
28
+ // Rotation: prepareCmdLogDir WIPES the identity dir on every launch — one server
29
+ // generation per dir, no unbounded accumulation across relaunches.
30
+ //
31
+ // Best-effort BY CONSTRUCTION: observability must never fail a launch or a
32
+ // supervise tick — every export swallows FS errors.
33
+
34
+ import { chmodSync, mkdirSync, openSync, closeSync, readSync, readdirSync, rmSync, statSync, writeFileSync, fstatSync } from 'fs'
35
+ import { join } from 'path'
36
+
37
+ /** Tail-keep cap: a log over CAP_BYTES is cut down to its last KEEP_BYTES. */
38
+ export const CMDLOG_CAP_BYTES = 8 * 1024 * 1024
39
+ export const CMDLOG_KEEP_BYTES = 1024 * 1024
40
+
41
+ const CMDLOG_SUBDIR = 'tmux-cmdlog'
42
+
43
+ /** The per-identity command-log dir: <eventLogDir>/tmux-cmdlog/<identity>. The
44
+ * session-creating tmux client runs FROM this dir with `-v`, so the server drops
45
+ * tmux-server-<pid>.log (and the client its tmux-client-*.log) here. */
46
+ export function cmdLogDirFor(eventLogDir: string, identity: string): string {
47
+ return join(eventLogDir, CMDLOG_SUBDIR, identity)
48
+ }
49
+
50
+ /** Wipe + recreate the identity's command-log dir (0700) for a fresh server
51
+ * generation. Returns the dir, or undefined when the FS refuses (the caller then
52
+ * launches WITHOUT -v — observability is never load-bearing). */
53
+ export function prepareCmdLogDir(eventLogDir: string, identity: string): string | undefined {
54
+ try {
55
+ const dir = cmdLogDirFor(eventLogDir, identity)
56
+ rmSync(dir, { recursive: true, force: true })
57
+ mkdirSync(dir, { recursive: true, mode: 0o700 })
58
+ return dir
59
+ } catch {
60
+ return undefined
61
+ }
62
+ }
63
+
64
+ /** Clamp every file the freshly-started server/client dropped into the dir to
65
+ * 0600 (the dir's 0700 already blocks traversal; this is the belt to that
66
+ * suspender — boris's acceptance criterion: no file wider than 0600). */
67
+ export function hardenCmdLogDir(dir: string): void {
68
+ try {
69
+ for (const name of readdirSync(dir)) {
70
+ try {
71
+ chmodSync(join(dir, name), 0o600)
72
+ } catch {
73
+ /* per-file best-effort */
74
+ }
75
+ }
76
+ } catch {
77
+ /* dir gone — nothing to harden */
78
+ }
79
+ }
80
+
81
+ /** Tail-keep cap over every command log under <eventLogDir>/tmux-cmdlog/<identity>.
82
+ * A file over capBytes is rewritten as its last keepBytes. Safe against the
83
+ * live writer: tmux logs with O_APPEND, so the server's next write lands at
84
+ * the new EOF (proven by live truncation test). Lines appended between the
85
+ * tail-read and the rewrite are lost — acceptable mid-life noise; the death
86
+ * tail is never here because the peer is alive while being capped. */
87
+ export function capCmdLogs(
88
+ eventLogDir: string,
89
+ capBytes: number = CMDLOG_CAP_BYTES,
90
+ keepBytes: number = CMDLOG_KEEP_BYTES,
91
+ ): string[] {
92
+ const capped: string[] = []
93
+ let identities: string[]
94
+ try {
95
+ identities = readdirSync(join(eventLogDir, CMDLOG_SUBDIR))
96
+ } catch {
97
+ return capped
98
+ }
99
+ for (const identity of identities) {
100
+ const dir = join(eventLogDir, CMDLOG_SUBDIR, identity)
101
+ let files: string[]
102
+ try {
103
+ files = readdirSync(dir)
104
+ } catch {
105
+ continue
106
+ }
107
+ for (const name of files) {
108
+ if (!name.endsWith('.log')) continue
109
+ const path = join(dir, name)
110
+ try {
111
+ if (statSync(path).size <= capBytes) continue
112
+ const fd = openSync(path, 'r')
113
+ let tail: Buffer
114
+ try {
115
+ const size = fstatSync(fd).size
116
+ const start = Math.max(0, size - keepBytes)
117
+ tail = Buffer.alloc(Math.min(keepBytes, size))
118
+ readSync(fd, tail, 0, tail.length, start)
119
+ } finally {
120
+ closeSync(fd)
121
+ }
122
+ writeFileSync(path, tail, { mode: 0o600 })
123
+ capped.push(path)
124
+ } catch {
125
+ /* per-file best-effort */
126
+ }
127
+ }
128
+ }
129
+ return capped
130
+ }
@@ -20,6 +20,7 @@ import { spawnSync } from 'child_process'
20
20
  import { CODEX_BEARER_ENV_VAR, CODEX_DUMMY_BEARER, type Runtime } from '../core/constants.ts'
21
21
  import { readLaunchEnv } from '../storage/index.ts'
22
22
  import { canaryChannel, dismissCanary, ensureServerCanary, exitLogPath, signalCanaryClean } from './canary.ts'
23
+ import { hardenCmdLogDir, prepareCmdLogDir } from './cmdlog.ts'
23
24
  import { appendLifecycleEvent } from '../lifecycle/eventlog.ts'
24
25
  import { claudeAdapter } from './adapters/claude.ts'
25
26
  import { codexAdapter } from './adapters/codex.ts'
@@ -262,14 +263,31 @@ export const launch: LaunchFn = async (
262
263
  PEER_IDENTITY: identity,
263
264
  PATH: env.PATH ?? `${homedir()}/.bun/bin:${homedir()}/.local/bin:/opt/homebrew/bin:/usr/bin:/bin`,
264
265
  }
266
+ // (2.4) tmux command-log (killer-hunt socket-command layer, cmdlog.ts): WARM
267
+ // agent sessions start their server with `-v` so every client command —
268
+ // incl. a killer's `kill-server`/`kill-session`, invisible to signal
269
+ // forensics — lands in tmux-server-<pid>.log with client attribution
270
+ // (`message: client-<pid> command: …`). The starting client runs FROM the
271
+ // per-identity 0700 cmdlog dir (that is where tmux drops the logs; the
272
+ // pane's cwd is still `-c cwd`). Gated like the other observability layers
273
+ // on cfg.exitLogDir; skipped for always-on infra (rare relaunch = no
274
+ // rotation point, and not the dying class). Best-effort: a failed dir
275
+ // prepare just launches without -v.
276
+ const cmdLogDir = !cfg.alwaysOn && cfg.exitLogDir ? prepareCmdLogDir(cfg.exitLogDir, identity) : undefined
265
277
  const start = spawnSync(
266
278
  'tmux',
267
- ['-S', sock, 'new-session', '-d', '-s', identity, '-x', '220', '-y', '50', '-c', cwd, runtimeCmd],
268
- { env: childEnv as Record<string, string>, encoding: 'utf8' },
279
+ [
280
+ '-S', sock,
281
+ ...(cmdLogDir ? ['-v'] : []),
282
+ 'new-session', '-d', '-s', identity, '-x', '220', '-y', '50', '-c', cwd, runtimeCmd,
283
+ ],
284
+ { env: childEnv as Record<string, string>, encoding: 'utf8', ...(cmdLogDir ? { cwd: cmdLogDir } : {}) },
269
285
  )
270
286
  if (start.status !== 0) {
271
287
  return fail(identity, `tmux new-session failed: ${(start.stderr ?? '').trim() || 'exit ' + start.status}`)
272
288
  }
289
+ // The server/client logs exist once new-session returns — clamp them to 0600.
290
+ if (cmdLogDir) hardenCmdLogDir(cmdLogDir)
273
291
 
274
292
  // (2.5) Exit-cause observability: a `pane-died` hook that records WHY this
275
293
  // session's process dies (status/signal) at the moment of death into
@@ -26,6 +26,7 @@ import {
26
26
  } from '../core/constants.ts'
27
27
  import { buildProcessAddress, buildSocketPath, parseSessionName } from '../core/socket.ts'
28
28
  import { err, ok, type Result } from '../core/errors.ts'
29
+ import { capCmdLogs } from '../launch/cmdlog.ts'
29
30
  import { resolveGlobalRoot } from '../storage/index.ts'
30
31
  import { readPeerProfile, resolveIdentity } from '../identity/index.ts'
31
32
  import {
@@ -1071,6 +1072,11 @@ export function superviseTick(cfg: LifecycleConfig, deps: SuperviseDeps = {}): S
1071
1072
  const env = deps.env ?? process.env
1072
1073
  const nowMs = deps.nowMs ?? Date.now()
1073
1074
  const verbose = superviseLogVerbose(env)
1075
+ // tmux command-log volume cap (killer-hunt socket-command layer, launch/cmdlog.ts):
1076
+ // a -v server log under a busy TUI grows ~7 KB/s, so each tick tail-keeps any log
1077
+ // over the cap. Best-effort by construction; the killer's command is the LAST
1078
+ // line of a dying log, so capping a LIVE peer's log never costs the death tail.
1079
+ capCmdLogs(cfg.eventLogDir)
1074
1080
  // Durable decision trace (eventlog.ts): every reap/death/eager-fresh gets a line
1075
1081
  // so a postmortem can answer "when & how did peer X's prior session end" even
1076
1082
  // after the .idle-reaped / .deaths markers are consumed. alive / skipped-launchd