@agfpd/iapeer 0.4.83 → 0.4.84
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/docs/14-configuration-and-logs.md +4 -0
- package/docs/ru/14-/320/272/320/276/320/275/321/204/320/270/320/263/321/203/321/200/320/260/321/206/320/270/321/217-/320/270-/320/266/321/203/321/200/320/275/320/260/320/273/321/213.md +4 -0
- package/package.json +1 -1
- package/src/launch/cmdlog.ts +108 -40
- package/src/lifecycle/index.ts +10 -8
|
@@ -61,6 +61,8 @@ The daemon's supervisory tick is fixed — once a minute, not configurable.
|
|
|
61
61
|
| `IAPEER_LIFECYCLE_LOG_KEEP` | `5` | How many `lifecycle.log` rotations to keep. |
|
|
62
62
|
| `IAPEER_DELIVERY_LOG_MAX_BYTES` | `5 MiB` | The `delivery.log` size for rotation. |
|
|
63
63
|
| `IAPEER_DELIVERY_LOG_KEEP` | `5` | How many `delivery.log` rotations to keep. |
|
|
64
|
+
| `IAPEER_PANELOG_MAX_BYTES` | `16 MiB` | The per-peer pane-log (`logs/lifecycle/<runtime>-<peer>.log`, the raw terminal byte stream) size at which rotation happens. |
|
|
65
|
+
| `IAPEER_PANELOG_KEEP` | `2` | How many rotated pane-log copies to keep (`<log>.1` newest … `.N`). |
|
|
64
66
|
| `IAPEER_SUPERVISE_LOG_VERBOSE` | off | A verbose log of supervisory ticks (by default only decisions are written, not routine "doing nothing"). |
|
|
65
67
|
| `IAPEER_DAEMON_LOG` | off | An extended daemon log. |
|
|
66
68
|
|
|
@@ -80,6 +82,8 @@ The daemon keeps three durable logs in `~/.iapeer/logs/iapeer/`:
|
|
|
80
82
|
- **`delivery.log`** — the outcome of each delivery: who, to whom, into which runtime, the result, sizes, topic. Metadata only — **the message body is not written**.
|
|
81
83
|
- **`exits.log`** — session deaths with postmortem data: the supervisor records the cause (exit code / signal) at the moment a session dies.
|
|
82
84
|
|
|
85
|
+
Per-peer **pane-logs** live separately under `~/.iapeer/logs/lifecycle/<runtime>-<peer>.log` — the raw terminal (pty) byte stream the supervisor appends for a session's whole life (occupancy/typing detection, the boot ready-gate, and `tail`-based monitors read them). They are rotated on the supervise tick by **copytruncate** (`IAPEER_PANELOG_MAX_BYTES` × `IAPEER_PANELOG_KEEP`, default 16 MiB × 2): the base file keeps its path and inode (its last 8 MiB tail is retained in place, so the live writer and every reader are undisturbed) while the preceding window is promoted to `<log>.1` … `.N`. Total per peer is bounded by roughly `MAX_BYTES × (KEEP + 1)`.
|
|
86
|
+
|
|
83
87
|
### Postmortem diagnostics
|
|
84
88
|
|
|
85
89
|
The cause of a session's death (exit code / signal) the supervisor records at the moment of death to `exits.log` — what an ordinary "is the process alive" check misses. It's the material for answering "why did the agent die".
|
|
@@ -61,6 +61,8 @@
|
|
|
61
61
|
| `IAPEER_LIFECYCLE_LOG_KEEP` | `5` | Сколько ротаций `lifecycle.log` хранить. |
|
|
62
62
|
| `IAPEER_DELIVERY_LOG_MAX_BYTES` | `5 МиБ` | Размер `delivery.log` для ротации. |
|
|
63
63
|
| `IAPEER_DELIVERY_LOG_KEEP` | `5` | Сколько ротаций `delivery.log` хранить. |
|
|
64
|
+
| `IAPEER_PANELOG_MAX_BYTES` | `16 МиБ` | Размер пер-пирового pane-лога (`logs/lifecycle/<runtime>-<peer>.log`, сырой байтовый поток терминала), при котором происходит ротация. |
|
|
65
|
+
| `IAPEER_PANELOG_KEEP` | `2` | Сколько ротированных копий pane-лога хранить (`<log>.1` — свежайшая … `.N`). |
|
|
64
66
|
| `IAPEER_SUPERVISE_LOG_VERBOSE` | выкл. | Подробный лог надзорных тиков (по умолчанию пишутся только решения, не рутинные «ничего не делаю»). |
|
|
65
67
|
| `IAPEER_DAEMON_LOG` | выкл. | Расширенный лог демона. |
|
|
66
68
|
|
|
@@ -80,6 +82,8 @@
|
|
|
80
82
|
- **`delivery.log`** — исход каждой доставки: кто, кому, в какой рантайм, результат, размеры, тема. Только метаданные — **тело сообщения не пишется**.
|
|
81
83
|
- **`exits.log`** — смерти сессий с посмертными данными: супервизор фиксирует причину (код выхода / сигнал) в момент смерти сессии.
|
|
82
84
|
|
|
85
|
+
Пер-пировые **pane-логи** лежат отдельно в `~/.iapeer/logs/lifecycle/<runtime>-<peer>.log` — сырой байтовый поток терминала (pty), который супервизор дописывает всю жизнь сессии (его читают детект занятости/typing, boot ready-gate и `tail`-мониторы). Они ротируются на надзорном тике методом **copytruncate** (`IAPEER_PANELOG_MAX_BYTES` × `IAPEER_PANELOG_KEEP`, по умолчанию 16 МиБ × 2): базовый файл сохраняет путь и inode (его последние 8 МиБ остаются на месте, поэтому живой писатель и все читатели не задеты), а предшествующее окно повышается в `<log>.1` … `.N`. Суммарный объём на пира ограничен примерно `MAX_BYTES × (KEEP + 1)`.
|
|
86
|
+
|
|
83
87
|
### Посмертная диагностика
|
|
84
88
|
|
|
85
89
|
Причину смерти сессии (код выхода / сигнал) супервизор фиксирует в момент смерти в `exits.log` — это то, что обычная проверка «жив ли процесс» не ловит. Это материал для ответа на вопрос «почему агент умер».
|
package/package.json
CHANGED
package/src/launch/cmdlog.ts
CHANGED
|
@@ -1,69 +1,137 @@
|
|
|
1
|
-
// Pane-log volume
|
|
2
|
-
// pty byte stream the supervisor appends for a session's whole life. Unlike
|
|
3
|
-
// (their own size
|
|
4
|
-
//
|
|
5
|
-
//
|
|
1
|
+
// Pane-log volume ROTATION — bound the per-identity pane-logs (<logDir>/<identity>.log),
|
|
2
|
+
// the RAW child pty byte stream the supervisor appends for a session's whole life. Unlike
|
|
3
|
+
// the daemon lifecycle/delivery/approvals logs (their own size+copies rotation under
|
|
4
|
+
// logs/iapeer via storage/rotatelog.ts), the pane-log had NO bound and grew to tens of MB
|
|
5
|
+
// per warm peer AND per always-on router on this host; the supervise tick rotates it here.
|
|
6
|
+
//
|
|
7
|
+
// MECHANISM — COPYTRUNCATE, not rename. The pane-log is written by a LONG-LIVED supervisor
|
|
8
|
+
// holding a persistent O_APPEND fd (a different process from this daemon-side janitor), and
|
|
9
|
+
// it is READ live by load-bearing consumers: telegram-runtime's occupancy signal keys on
|
|
10
|
+
// the file's MTIME at its fixed path (canon: the pane-log occupancy contract), the boot
|
|
11
|
+
// ready-gate seeds a headless xterm from the tail, doc's monitor tail -c's it. A rename
|
|
12
|
+
// would move the writer's fd onto the rotated inode and briefly vanish the base path —
|
|
13
|
+
// breaking mtime-occupancy. So rotation keeps the base INODE and PATH: it copies the base's
|
|
14
|
+
// last maxBytes out to <path>.1 (shifting older copies up), then TRUNCATES the base in place
|
|
15
|
+
// to its last tailBytes. Same inode ⇒ the supervisor's O_APPEND fd resumes at the new EOF
|
|
16
|
+
// (proven by the live truncation test), mtime stays fresh, and the В19 heal never sees an
|
|
17
|
+
// inode change (no spurious reopen). Readers keep reading the same path; the rotated copies
|
|
18
|
+
// (<path>.1..N) are history nobody reads live.
|
|
19
|
+
//
|
|
20
|
+
// Best-effort BY CONSTRUCTION: rotation must never fail a supervise tick — every FS op
|
|
21
|
+
// swallows errors. The dropped head of an already-oversized legacy file is redraw noise; the
|
|
22
|
+
// last maxBytes is snapshotted, older bytes are discarded (never a giant copy of the bloat).
|
|
6
23
|
|
|
7
|
-
import { openSync, closeSync, readSync, readdirSync, statSync, writeFileSync, fstatSync } from 'fs'
|
|
24
|
+
import { openSync, closeSync, readSync, readdirSync, statSync, unlinkSync, writeFileSync, fstatSync, renameSync } from 'fs'
|
|
8
25
|
import { join } from 'path'
|
|
9
26
|
|
|
10
|
-
/**
|
|
11
|
-
*
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
27
|
+
/** Rotate threshold (env IAPEER_PANELOG_MAX_BYTES) — a base pane-log over this is rotated.
|
|
28
|
+
* Default 16 MiB (owner direction 12.07: order 10–20 MiB × 2–3 copies). */
|
|
29
|
+
export const PANELOG_MAX_BYTES = 16 * 1024 * 1024
|
|
30
|
+
/** Number of rotated copies kept (env IAPEER_PANELOG_KEEP) — <path>.1 (newest) … .N. Default 2. */
|
|
31
|
+
export const PANELOG_COPIES = 2
|
|
32
|
+
/** Base TAIL kept in place after a rotation. INVARIANT: ≥ the pane-log reader's seed window
|
|
33
|
+
* (readyGateModel SEED_BYTES = 4 MiB, the tail fed into the headless xterm for
|
|
34
|
+
* composer-occupancy / viewport). 8 MiB = 2× the seed. This is the reader-floor the base is
|
|
35
|
+
* truncated to — NOT the copy count (that is IAPEER_PANELOG_KEEP). Internal invariant, not
|
|
36
|
+
* env-tunable: capping the base below the seed would starve occupancy/ready-gate detection. */
|
|
37
|
+
export const PANELOG_TAIL_BYTES = 8 * 1024 * 1024
|
|
17
38
|
|
|
18
|
-
/**
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
39
|
+
/** Parse a positive integer env value, else the default (mirror of the daemon-log config helpers). */
|
|
40
|
+
function envPosInt(raw: string | undefined, dflt: number): number {
|
|
41
|
+
const n = raw !== undefined ? Number(raw) : NaN
|
|
42
|
+
return Number.isInteger(n) && n > 0 ? n : dflt
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Resolve the pane-log rotation knobs from env (defaults above). maxBytes is floored just
|
|
46
|
+
* above the reader-tail so the base can always drop below the threshold after a rotation
|
|
47
|
+
* (else rotation would fire every tick). */
|
|
48
|
+
export function paneLogRotateConfig(env: NodeJS.ProcessEnv = process.env): { maxBytes: number; copies: number } {
|
|
49
|
+
const maxBytes = Math.max(envPosInt(env.IAPEER_PANELOG_MAX_BYTES, PANELOG_MAX_BYTES), PANELOG_TAIL_BYTES + 1024 * 1024)
|
|
50
|
+
return { maxBytes, copies: envPosInt(env.IAPEER_PANELOG_KEEP, PANELOG_COPIES) }
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Read the last wantBytes of a file into a Buffer (or null on error / absent). */
|
|
54
|
+
function readTail(path: string, wantBytes: number): Buffer | null {
|
|
24
55
|
try {
|
|
25
|
-
if (statSync(path).size <= capBytes) return false
|
|
26
56
|
const fd = openSync(path, 'r')
|
|
27
|
-
let tail: Buffer
|
|
28
57
|
try {
|
|
29
58
|
const size = fstatSync(fd).size
|
|
30
|
-
const start = Math.max(0, size -
|
|
31
|
-
|
|
32
|
-
readSync(fd,
|
|
59
|
+
const start = Math.max(0, size - wantBytes)
|
|
60
|
+
const buf = Buffer.alloc(Math.min(wantBytes, size))
|
|
61
|
+
if (buf.length > 0) readSync(fd, buf, 0, buf.length, start)
|
|
62
|
+
return buf
|
|
33
63
|
} finally {
|
|
34
64
|
closeSync(fd)
|
|
35
65
|
}
|
|
66
|
+
} catch {
|
|
67
|
+
return null
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Rotate ONE pane-log file if it exceeds maxBytes (copytruncate — see file header):
|
|
72
|
+
* 1. snapshot the base's last maxBytes (bounded — never a giant copy of legacy bloat)
|
|
73
|
+
* 2. shift .(k)→.(k+1), dropping the oldest beyond `copies`; write the snapshot to .1
|
|
74
|
+
* 3. truncate the base IN PLACE to its last tailBytes (same inode: writer fd + mtime +
|
|
75
|
+
* reader seed all survive; no В19 reopen)
|
|
76
|
+
* Returns true iff rotated. Best-effort: swallows FS errors. */
|
|
77
|
+
function rotateFile(path: string, maxBytes: number, copies: number, tailBytes: number): boolean {
|
|
78
|
+
try {
|
|
79
|
+
if (statSync(path).size <= maxBytes) return false
|
|
80
|
+
// 1 — snapshot the tail we promote to .1, taken BEFORE any truncate (copytruncate race:
|
|
81
|
+
// bytes appended between here and the truncate are lost — accepted mid-life noise,
|
|
82
|
+
// same as the pre-rotation tail-keep).
|
|
83
|
+
const rotBuf = readTail(path, maxBytes)
|
|
84
|
+
if (rotBuf === null || rotBuf.length === 0) return false
|
|
85
|
+
// 2 — shift existing copies up, dropping the oldest; also sweep one orphan beyond the
|
|
86
|
+
// current copy count (handles a just-lowered IAPEER_PANELOG_KEEP).
|
|
87
|
+
try {
|
|
88
|
+
unlinkSync(`${path}.${copies + 1}`)
|
|
89
|
+
} catch {
|
|
90
|
+
/* absent → fine */
|
|
91
|
+
}
|
|
92
|
+
for (let k = copies - 1; k >= 1; k--) {
|
|
93
|
+
try {
|
|
94
|
+
renameSync(`${path}.${k}`, `${path}.${k + 1}`)
|
|
95
|
+
} catch {
|
|
96
|
+
/* absent → skip */
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (copies >= 1) writeFileSync(`${path}.1`, rotBuf, { mode: 0o600 })
|
|
100
|
+
// 3 — tail-keep the base in place (last tailBytes of the snapshot; same inode).
|
|
101
|
+
const tail = tailBytes >= rotBuf.length ? rotBuf : rotBuf.subarray(rotBuf.length - tailBytes)
|
|
36
102
|
writeFileSync(path, tail, { mode: 0o600 })
|
|
37
103
|
return true
|
|
38
104
|
} catch {
|
|
39
|
-
return false // per-file best-effort
|
|
105
|
+
return false // per-file best-effort — a rotation never fails a supervise tick
|
|
40
106
|
}
|
|
41
107
|
}
|
|
42
108
|
|
|
43
|
-
/**
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
|
|
109
|
+
/**
|
|
110
|
+
* Rotate the per-identity PANE-LOGS in logDir (logs/lifecycle). Scans top-level `*.log`
|
|
111
|
+
* only — the rotated copies are `<identity>.log.1..N` (do NOT end in `.log`), so they are
|
|
112
|
+
* never re-processed, and no other log lives here. Called each supervise tick (NOT H4-gated:
|
|
113
|
+
* log janitoring is orthogonal to lifecycle ownership, so launchd-managed routers' pane-logs
|
|
114
|
+
* are rotated too). maxBytes/copies come from paneLogRotateConfig(env). Best-effort by
|
|
115
|
+
* construction. Returns the rotated paths.
|
|
116
|
+
*/
|
|
51
117
|
export function capPaneLogs(
|
|
52
118
|
logDir: string,
|
|
53
|
-
|
|
54
|
-
|
|
119
|
+
maxBytes: number = PANELOG_MAX_BYTES,
|
|
120
|
+
copies: number = PANELOG_COPIES,
|
|
121
|
+
tailBytes: number = PANELOG_TAIL_BYTES,
|
|
55
122
|
): string[] {
|
|
56
|
-
const
|
|
123
|
+
const cap = Math.max(maxBytes, tailBytes + 1024 * 1024) // base(tail) must be able to drop below cap
|
|
124
|
+
const rotated: string[] = []
|
|
57
125
|
let names: string[]
|
|
58
126
|
try {
|
|
59
127
|
names = readdirSync(logDir)
|
|
60
128
|
} catch {
|
|
61
|
-
return
|
|
129
|
+
return rotated
|
|
62
130
|
}
|
|
63
131
|
for (const name of names) {
|
|
64
|
-
if (!name.endsWith('.log')) continue
|
|
132
|
+
if (!name.endsWith('.log')) continue // skips the rotated <identity>.log.1/.2
|
|
65
133
|
const path = join(logDir, name)
|
|
66
|
-
if (
|
|
134
|
+
if (rotateFile(path, cap, copies, tailBytes)) rotated.push(path)
|
|
67
135
|
}
|
|
68
|
-
return
|
|
136
|
+
return rotated
|
|
69
137
|
}
|
package/src/lifecycle/index.ts
CHANGED
|
@@ -26,7 +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 { capPaneLogs } from '../launch/cmdlog.ts'
|
|
29
|
+
import { capPaneLogs, paneLogRotateConfig } from '../launch/cmdlog.ts'
|
|
30
30
|
import { resolveGlobalRoot } from '../storage/index.ts'
|
|
31
31
|
import { resolveMemoryWritableRoots } from '../status/index.ts'
|
|
32
32
|
import { approvalModeOf, readPeerProfile, resolveIdentity, type ApprovalMode } from '../identity/index.ts'
|
|
@@ -1491,14 +1491,16 @@ export function superviseTick(cfg: LifecycleConfig, deps: SuperviseDeps = {}): S
|
|
|
1491
1491
|
const isAlive = deps.sessionAlive ?? ((sock: string, identity: string) => sessionAlive(sock, identity, env))
|
|
1492
1492
|
const kill = deps.killSession ?? ((sock: string, identity: string) => killSession(sock, identity, env))
|
|
1493
1493
|
const verbose = superviseLogVerbose(env)
|
|
1494
|
-
// Pane-log volume
|
|
1494
|
+
// Pane-log volume ROTATION (launch/cmdlog.ts capPaneLogs): the per-identity pane-log
|
|
1495
1495
|
// (<logDir>/<identity>.log) is the RAW TUI byte stream pipe-pane/the supervisor
|
|
1496
|
-
// append for a session's whole life — it had NO
|
|
1497
|
-
// per warm peer
|
|
1498
|
-
//
|
|
1499
|
-
//
|
|
1500
|
-
//
|
|
1501
|
-
|
|
1496
|
+
// append for a session's whole life — it had NO copies-rotation and grew to tens of MB
|
|
1497
|
+
// per warm peer AND per always-on router on this host. Copytruncate-rotate it each tick
|
|
1498
|
+
// (base kept in place — reader seed + telegram mtime-occupancy intact; last maxBytes
|
|
1499
|
+
// snapshotted to <path>.1..N). Threshold/copies from IAPEER_PANELOG_MAX_BYTES /
|
|
1500
|
+
// IAPEER_PANELOG_KEEP (defaults 16 MiB × 2). Not H4-gated: log janitoring is orthogonal
|
|
1501
|
+
// to lifecycle ownership, so launchd-managed routers' pane-logs are rotated too.
|
|
1502
|
+
const paneLogRc = paneLogRotateConfig(env)
|
|
1503
|
+
capPaneLogs(cfg.logDir, paneLogRc.maxBytes, paneLogRc.copies)
|
|
1502
1504
|
// Durable decision trace (eventlog.ts): every reap/death/eager-fresh gets a line
|
|
1503
1505
|
// so a postmortem can answer "when & how did peer X's prior session end" even
|
|
1504
1506
|
// after the .idle-reaped / .deaths markers are consumed. alive / skipped-launchd
|