@cluesmith/codev 3.2.3 → 3.2.4
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/dashboard-dist/assets/index-BZ1x7x1X.js.map +1 -1
- package/dist/agent-farm/cli.d.ts.map +1 -1
- package/dist/agent-farm/cli.js +31 -0
- package/dist/agent-farm/cli.js.map +1 -1
- package/dist/agent-farm/commands/attach.js +2 -2
- package/dist/agent-farm/commands/attach.js.map +1 -1
- package/dist/agent-farm/commands/send.d.ts +2 -2
- package/dist/agent-farm/commands/send.js +3 -3
- package/dist/agent-farm/commands/send.js.map +1 -1
- package/dist/agent-farm/commands/setup.d.ts +4 -1
- package/dist/agent-farm/commands/setup.d.ts.map +1 -1
- package/dist/agent-farm/commands/setup.js +8 -4
- package/dist/agent-farm/commands/setup.js.map +1 -1
- package/dist/agent-farm/commands/spawn-worktree.d.ts +11 -0
- package/dist/agent-farm/commands/spawn-worktree.d.ts.map +1 -1
- package/dist/agent-farm/commands/spawn-worktree.js +64 -16
- package/dist/agent-farm/commands/spawn-worktree.js.map +1 -1
- package/dist/agent-farm/commands/status.d.ts.map +1 -1
- package/dist/agent-farm/commands/status.js +26 -2
- package/dist/agent-farm/commands/status.js.map +1 -1
- package/dist/agent-farm/commands/tower-sweep-husks.d.ts +6 -0
- package/dist/agent-farm/commands/tower-sweep-husks.d.ts.map +1 -0
- package/dist/agent-farm/commands/tower-sweep-husks.js +77 -0
- package/dist/agent-farm/commands/tower-sweep-husks.js.map +1 -0
- package/dist/agent-farm/commands/tower.d.ts.map +1 -1
- package/dist/agent-farm/commands/tower.js +34 -0
- package/dist/agent-farm/commands/tower.js.map +1 -1
- package/dist/agent-farm/commands/whoami.d.ts +65 -0
- package/dist/agent-farm/commands/whoami.d.ts.map +1 -0
- package/dist/agent-farm/commands/whoami.js +196 -0
- package/dist/agent-farm/commands/whoami.js.map +1 -0
- package/dist/agent-farm/lib/tower-client.d.ts +1 -1
- package/dist/agent-farm/lib/tower-client.d.ts.map +1 -1
- package/dist/agent-farm/lib/tower-client.js.map +1 -1
- package/dist/agent-farm/servers/architect-session-holder.d.ts +133 -0
- package/dist/agent-farm/servers/architect-session-holder.d.ts.map +1 -0
- package/dist/agent-farm/servers/architect-session-holder.js +268 -0
- package/dist/agent-farm/servers/architect-session-holder.js.map +1 -0
- package/dist/agent-farm/servers/process-census.d.ts +41 -0
- package/dist/agent-farm/servers/process-census.d.ts.map +1 -0
- package/dist/agent-farm/servers/process-census.js +62 -0
- package/dist/agent-farm/servers/process-census.js.map +1 -0
- package/dist/agent-farm/servers/send-buffer.d.ts.map +1 -1
- package/dist/agent-farm/servers/send-buffer.js +13 -0
- package/dist/agent-farm/servers/send-buffer.js.map +1 -1
- package/dist/agent-farm/servers/session-log-sweep.d.ts +93 -0
- package/dist/agent-farm/servers/session-log-sweep.d.ts.map +1 -0
- package/dist/agent-farm/servers/session-log-sweep.js +165 -0
- package/dist/agent-farm/servers/session-log-sweep.js.map +1 -0
- package/dist/agent-farm/servers/shellper-husk-sweep.d.ts +87 -0
- package/dist/agent-farm/servers/shellper-husk-sweep.d.ts.map +1 -0
- package/dist/agent-farm/servers/shellper-husk-sweep.js +129 -0
- package/dist/agent-farm/servers/shellper-husk-sweep.js.map +1 -0
- package/dist/agent-farm/servers/tower-instances.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-instances.js +52 -5
- package/dist/agent-farm/servers/tower-instances.js.map +1 -1
- package/dist/agent-farm/servers/tower-routes.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-routes.js +147 -4
- package/dist/agent-farm/servers/tower-routes.js.map +1 -1
- package/dist/agent-farm/servers/tower-server.js +95 -1
- package/dist/agent-farm/servers/tower-server.js.map +1 -1
- package/dist/agent-farm/servers/tower-terminals.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-terminals.js +38 -5
- package/dist/agent-farm/servers/tower-terminals.js.map +1 -1
- package/dist/agent-farm/servers/tower-utils.d.ts +35 -0
- package/dist/agent-farm/servers/tower-utils.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-utils.js +74 -5
- package/dist/agent-farm/servers/tower-utils.js.map +1 -1
- package/dist/agent-farm/state.d.ts +6 -1
- package/dist/agent-farm/state.d.ts.map +1 -1
- package/dist/agent-farm/state.js +5 -2
- package/dist/agent-farm/state.js.map +1 -1
- package/dist/commands/adopt.d.ts +3 -2
- package/dist/commands/adopt.d.ts.map +1 -1
- package/dist/commands/adopt.js +7 -6
- package/dist/commands/adopt.js.map +1 -1
- package/dist/commands/consult/agy-auth-cache.d.ts +97 -0
- package/dist/commands/consult/agy-auth-cache.d.ts.map +1 -0
- package/dist/commands/consult/agy-auth-cache.js +320 -0
- package/dist/commands/consult/agy-auth-cache.js.map +1 -0
- package/dist/commands/consult/index.d.ts.map +1 -1
- package/dist/commands/consult/index.js +59 -0
- package/dist/commands/consult/index.js.map +1 -1
- package/dist/commands/doctor.d.ts +28 -0
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +187 -3
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/init.d.ts +3 -2
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +7 -6
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/porch/index.d.ts.map +1 -1
- package/dist/commands/porch/index.js +11 -8
- package/dist/commands/porch/index.js.map +1 -1
- package/dist/commands/update.d.ts +4 -4
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js +9 -11
- package/dist/commands/update.js.map +1 -1
- package/dist/lib/config.d.ts +1 -0
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/default-branch.d.ts +1 -1
- package/dist/lib/default-branch.js +1 -1
- package/dist/lib/gitignore.d.ts +25 -2
- package/dist/lib/gitignore.d.ts.map +1 -1
- package/dist/lib/gitignore.js +70 -0
- package/dist/lib/gitignore.js.map +1 -1
- package/dist/lib/migration-backup-audit.d.ts +64 -0
- package/dist/lib/migration-backup-audit.d.ts.map +1 -0
- package/dist/lib/migration-backup-audit.js +145 -0
- package/dist/lib/migration-backup-audit.js.map +1 -0
- package/dist/lib/protocol-drift-audit.d.ts +118 -0
- package/dist/lib/protocol-drift-audit.d.ts.map +1 -0
- package/dist/lib/protocol-drift-audit.js +217 -0
- package/dist/lib/protocol-drift-audit.js.map +1 -0
- package/dist/lib/scaffold.d.ts +10 -3
- package/dist/lib/scaffold.d.ts.map +1 -1
- package/dist/lib/scaffold.js +29 -26
- package/dist/lib/scaffold.js.map +1 -1
- package/dist/terminal/pty-manager.d.ts +7 -0
- package/dist/terminal/pty-manager.d.ts.map +1 -1
- package/dist/terminal/pty-manager.js +16 -0
- package/dist/terminal/pty-manager.js.map +1 -1
- package/dist/terminal/pty-session.d.ts +25 -4
- package/dist/terminal/pty-session.d.ts.map +1 -1
- package/dist/terminal/pty-session.js +83 -11
- package/dist/terminal/pty-session.js.map +1 -1
- package/dist/terminal/session-manager.d.ts +76 -0
- package/dist/terminal/session-manager.d.ts.map +1 -1
- package/dist/terminal/session-manager.js +252 -44
- package/dist/terminal/session-manager.js.map +1 -1
- package/dist/terminal/shellper-client.d.ts +19 -4
- package/dist/terminal/shellper-client.d.ts.map +1 -1
- package/dist/terminal/shellper-client.js +70 -8
- package/dist/terminal/shellper-client.js.map +1 -1
- package/dist/terminal/shellper-process.d.ts.map +1 -1
- package/dist/terminal/shellper-process.js +20 -5
- package/dist/terminal/shellper-process.js.map +1 -1
- package/dist/terminal/shellper-protocol.d.ts +40 -2
- package/dist/terminal/shellper-protocol.d.ts.map +1 -1
- package/dist/terminal/shellper-protocol.js +72 -5
- package/dist/terminal/shellper-protocol.js.map +1 -1
- package/package.json +7 -7
- package/skeleton/.claude/skills/afx/SKILL.md +13 -0
- package/skeleton/.claude/skills/arch-init/SKILL.md +122 -0
- package/skeleton/.claude/skills/codev/SKILL.md +2 -2
- package/skeleton/.codex/skills/afx/SKILL.md +157 -0
- package/skeleton/.codex/skills/arch-init/SKILL.md +122 -0
- package/skeleton/.codex/skills/codev/SKILL.md +59 -0
- package/skeleton/.codex/skills/consult/SKILL.md +96 -0
- package/skeleton/.codex/skills/generate-image/SKILL.md +45 -0
- package/skeleton/.codex/skills/porch/SKILL.md +60 -0
- package/skeleton/.codex/skills/update-arch-docs/SKILL.md +132 -0
- package/skeleton/resources/commands/agent-farm.md +89 -0
- package/skeleton/resources/commands/codev.md +5 -0
- package/skeleton/resources/commands/consult.md +26 -0
- package/skeleton/roles/architect.md +11 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Issue #1227: a shared single-`ps`-call process snapshot, reused by both the
|
|
3
|
+
* shellper husk sweep (predicate needs pid/ppid to detect "childless") and the
|
|
4
|
+
* fleet-RSS observability feature (needs rss). Centralizing the scan avoids
|
|
5
|
+
* growing a fourth bespoke `ps` caller alongside the three that already exist
|
|
6
|
+
* (session-manager.ts, architect-session-holder.ts, commands/cleanup.ts).
|
|
7
|
+
*
|
|
8
|
+
* Async (`execFile`, not `execFileSync`) is load-bearing, not a style choice:
|
|
9
|
+
* this is called from `/health` (tower-routes.ts), a hot Tower HTTP path.
|
|
10
|
+
* `execFileSync` blocks the entire Node.js event loop for the duration of the
|
|
11
|
+
* `ps` call, freezing every open terminal's WebSocket traffic — the exact
|
|
12
|
+
* previously-fixed anti-pattern documented at lessons-learned.md:160
|
|
13
|
+
* ("execSync in HTTP request handlers blocks the entire Node.js event loop").
|
|
14
|
+
*/
|
|
15
|
+
export interface ProcessCensusEntry {
|
|
16
|
+
pid: number;
|
|
17
|
+
ppid: number;
|
|
18
|
+
/** Resident set size in kilobytes, as `ps -o rss=` reports it. */
|
|
19
|
+
rssKb: number;
|
|
20
|
+
/** The full joined argv (as `ps ... -o args=` reports it). */
|
|
21
|
+
cmdline: string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Snapshot every running process as {pid, ppid, rssKb, cmdline}. `ps -ww`
|
|
25
|
+
* prevents argv truncation (shellper config blobs are large) on both BSD and
|
|
26
|
+
* coreutils `ps`. Resolves to `[]` on `ps` failure (missing binary, timeout,
|
|
27
|
+
* non-zero exit) — callers decide how to degrade, mirroring the async
|
|
28
|
+
* `findShellperProcesses` convention in `../../terminal/session-manager.ts`.
|
|
29
|
+
*
|
|
30
|
+
* Deliberately raw `execFile` + a hand-rolled Promise, not `execFileSync` and
|
|
31
|
+
* not `util.promisify(execFile)`: this is called from `/health`
|
|
32
|
+
* (tower-routes.ts), a hot Tower HTTP path, so it must never block the event
|
|
33
|
+
* loop — `execFileSync` would freeze every open terminal's WebSocket traffic
|
|
34
|
+
* for the duration of the `ps` call, the exact previously-fixed anti-pattern
|
|
35
|
+
* documented at lessons-learned.md:160. `util.promisify` is avoided because
|
|
36
|
+
* `child_process.execFile` carries a built-in custom promisify resolving
|
|
37
|
+
* `{stdout, stderr}` via a non-standard symbol that a test mock would need to
|
|
38
|
+
* reproduce exactly to avoid silently promisifying to the wrong shape.
|
|
39
|
+
*/
|
|
40
|
+
export declare function listProcessCensus(): Promise<ProcessCensusEntry[]>;
|
|
41
|
+
//# sourceMappingURL=process-census.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"process-census.d.ts","sourceRoot":"","sources":["../../../src/agent-farm/servers/process-census.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAIH,MAAM,WAAW,kBAAkB;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,kEAAkE;IAClE,KAAK,EAAE,MAAM,CAAC;IACd,8DAA8D;IAC9D,OAAO,EAAE,MAAM,CAAC;CACjB;AAkBD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,iBAAiB,IAAI,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAUjE"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Issue #1227: a shared single-`ps`-call process snapshot, reused by both the
|
|
3
|
+
* shellper husk sweep (predicate needs pid/ppid to detect "childless") and the
|
|
4
|
+
* fleet-RSS observability feature (needs rss). Centralizing the scan avoids
|
|
5
|
+
* growing a fourth bespoke `ps` caller alongside the three that already exist
|
|
6
|
+
* (session-manager.ts, architect-session-holder.ts, commands/cleanup.ts).
|
|
7
|
+
*
|
|
8
|
+
* Async (`execFile`, not `execFileSync`) is load-bearing, not a style choice:
|
|
9
|
+
* this is called from `/health` (tower-routes.ts), a hot Tower HTTP path.
|
|
10
|
+
* `execFileSync` blocks the entire Node.js event loop for the duration of the
|
|
11
|
+
* `ps` call, freezing every open terminal's WebSocket traffic — the exact
|
|
12
|
+
* previously-fixed anti-pattern documented at lessons-learned.md:160
|
|
13
|
+
* ("execSync in HTTP request handlers blocks the entire Node.js event loop").
|
|
14
|
+
*/
|
|
15
|
+
import { execFile } from 'node:child_process';
|
|
16
|
+
function parseCensus(out) {
|
|
17
|
+
const entries = [];
|
|
18
|
+
for (const line of out.split('\n')) {
|
|
19
|
+
const trimmed = line.trimStart();
|
|
20
|
+
if (!trimmed)
|
|
21
|
+
continue;
|
|
22
|
+
const fields = trimmed.match(/^(\d+)\s+(\d+)\s+(\d+)\s+(.*)$/s);
|
|
23
|
+
if (!fields)
|
|
24
|
+
continue;
|
|
25
|
+
const pid = parseInt(fields[1], 10);
|
|
26
|
+
const ppid = parseInt(fields[2], 10);
|
|
27
|
+
const rssKb = parseInt(fields[3], 10);
|
|
28
|
+
if (Number.isNaN(pid) || pid <= 0 || Number.isNaN(ppid) || Number.isNaN(rssKb))
|
|
29
|
+
continue;
|
|
30
|
+
entries.push({ pid, ppid, rssKb, cmdline: fields[4] });
|
|
31
|
+
}
|
|
32
|
+
return entries;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Snapshot every running process as {pid, ppid, rssKb, cmdline}. `ps -ww`
|
|
36
|
+
* prevents argv truncation (shellper config blobs are large) on both BSD and
|
|
37
|
+
* coreutils `ps`. Resolves to `[]` on `ps` failure (missing binary, timeout,
|
|
38
|
+
* non-zero exit) — callers decide how to degrade, mirroring the async
|
|
39
|
+
* `findShellperProcesses` convention in `../../terminal/session-manager.ts`.
|
|
40
|
+
*
|
|
41
|
+
* Deliberately raw `execFile` + a hand-rolled Promise, not `execFileSync` and
|
|
42
|
+
* not `util.promisify(execFile)`: this is called from `/health`
|
|
43
|
+
* (tower-routes.ts), a hot Tower HTTP path, so it must never block the event
|
|
44
|
+
* loop — `execFileSync` would freeze every open terminal's WebSocket traffic
|
|
45
|
+
* for the duration of the `ps` call, the exact previously-fixed anti-pattern
|
|
46
|
+
* documented at lessons-learned.md:160. `util.promisify` is avoided because
|
|
47
|
+
* `child_process.execFile` carries a built-in custom promisify resolving
|
|
48
|
+
* `{stdout, stderr}` via a non-standard symbol that a test mock would need to
|
|
49
|
+
* reproduce exactly to avoid silently promisifying to the wrong shape.
|
|
50
|
+
*/
|
|
51
|
+
export function listProcessCensus() {
|
|
52
|
+
return new Promise((resolve) => {
|
|
53
|
+
execFile('ps', ['-A', '-ww', '-eo', 'pid=,ppid=,rss=,args='], { timeout: 5000, maxBuffer: 16 * 1024 * 1024 }, (err, stdout) => {
|
|
54
|
+
if (err || !stdout) {
|
|
55
|
+
resolve([]);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
resolve(parseCensus(stdout));
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=process-census.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"process-census.js","sourceRoot":"","sources":["../../../src/agent-farm/servers/process-census.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAW9C,SAAS,WAAW,CAAC,GAAW;IAC9B,MAAM,OAAO,GAAyB,EAAE,CAAC;IACzC,KAAK,MAAM,IAAI,IAAI,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACnC,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC;QACjC,IAAI,CAAC,OAAO;YAAE,SAAS;QACvB,MAAM,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,iCAAiC,CAAC,CAAC;QAChE,IAAI,CAAC,MAAM;YAAE,SAAS;QACtB,MAAM,GAAG,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACpC,MAAM,IAAI,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACrC,MAAM,KAAK,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACtC,IAAI,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC;YAAE,SAAS;QACzF,OAAO,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACzD,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,iBAAiB;IAC/B,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,QAAQ,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,uBAAuB,CAAC,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,GAAG,IAAI,GAAG,IAAI,EAAE,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE;YAC5H,IAAI,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;gBACnB,OAAO,CAAC,EAAE,CAAC,CAAC;gBACZ,OAAO;YACT,CAAC;YACD,OAAO,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC;QAC/B,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"send-buffer.d.ts","sourceRoot":"","sources":["../../../src/agent-farm/servers/send-buffer.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAEhE,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,gBAAgB,EAAE,MAAM,CAAC;IACzB,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,gBAAgB,EAAE;QAChB,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE;YAAE,OAAO,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,MAAM,CAAA;SAAE,CAAC;QACzC,EAAE,EAAE;YAAE,OAAO,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,MAAM,CAAA;SAAE,CAAC;QACvC,OAAO,EAAE,MAAM,CAAC;QAChB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAClC,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;IACF,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,MAAM,YAAY,GAAG,CAAC,EAAE,EAAE,MAAM,KAAK,UAAU,GAAG,SAAS,CAAC;AAClE,0FAA0F;AAC1F,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,EAAE,UAAU,EAAE,GAAG,EAAE,eAAe,EAAE,WAAW,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;AACpG,MAAM,MAAM,KAAK,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;AAMhF,qBAAa,UAAU;IACrB,OAAO,CAAC,OAAO,CAAwC;IACvD,OAAO,CAAC,UAAU,CAA+C;IACjE,OAAO,CAAC,UAAU,CAA6B;IAC/C,OAAO,CAAC,OAAO,CAA0B;IACzC,OAAO,CAAC,GAAG,CAAsB;IACjC,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;gBAEpB,IAAI,CAAC,EAAE;QAAE,eAAe,CAAC,EAAE,MAAM,CAAC;QAAC,cAAc,CAAC,EAAE,MAAM,CAAA;KAAE;IAKxE,8CAA8C;IAC9C,OAAO,CAAC,GAAG,EAAE,eAAe,GAAG,IAAI;IASnC,uEAAuE;IACvE,KAAK,CAAC,UAAU,EAAE,YAAY,EAAE,OAAO,EAAE,SAAS,EAAE,GAAG,EAAE,KAAK,GAAG,IAAI;IAQrE,+DAA+D;IAC/D,IAAI,IAAI,IAAI;IASZ,yEAAyE;IACzE,KAAK,CAAC,QAAQ,UAAQ,GAAG,IAAI;
|
|
1
|
+
{"version":3,"file":"send-buffer.d.ts","sourceRoot":"","sources":["../../../src/agent-farm/servers/send-buffer.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAEhE,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,gBAAgB,EAAE,MAAM,CAAC;IACzB,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,gBAAgB,EAAE;QAChB,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE;YAAE,OAAO,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,MAAM,CAAA;SAAE,CAAC;QACzC,EAAE,EAAE;YAAE,OAAO,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,MAAM,CAAA;SAAE,CAAC;QACvC,OAAO,EAAE,MAAM,CAAC;QAChB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAClC,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;IACF,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,MAAM,YAAY,GAAG,CAAC,EAAE,EAAE,MAAM,KAAK,UAAU,GAAG,SAAS,CAAC;AAClE,0FAA0F;AAC1F,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,EAAE,UAAU,EAAE,GAAG,EAAE,eAAe,EAAE,WAAW,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;AACpG,MAAM,MAAM,KAAK,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;AAMhF,qBAAa,UAAU;IACrB,OAAO,CAAC,OAAO,CAAwC;IACvD,OAAO,CAAC,UAAU,CAA+C;IACjE,OAAO,CAAC,UAAU,CAA6B;IAC/C,OAAO,CAAC,OAAO,CAA0B;IACzC,OAAO,CAAC,GAAG,CAAsB;IACjC,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;gBAEpB,IAAI,CAAC,EAAE;QAAE,eAAe,CAAC,EAAE,MAAM,CAAC;QAAC,cAAc,CAAC,EAAE,MAAM,CAAA;KAAE;IAKxE,8CAA8C;IAC9C,OAAO,CAAC,GAAG,EAAE,eAAe,GAAG,IAAI;IASnC,uEAAuE;IACvE,KAAK,CAAC,UAAU,EAAE,YAAY,EAAE,OAAO,EAAE,SAAS,EAAE,GAAG,EAAE,KAAK,GAAG,IAAI;IAQrE,+DAA+D;IAC/D,IAAI,IAAI,IAAI;IASZ,yEAAyE;IACzE,KAAK,CAAC,QAAQ,UAAQ,GAAG,IAAI;IAwD7B,qEAAqE;IACrE,IAAI,YAAY,IAAI,MAAM,CAMzB;IAED,+DAA+D;IAC/D,IAAI,YAAY,IAAI,MAAM,CAEzB;CACF"}
|
|
@@ -65,6 +65,19 @@ export class SendBuffer {
|
|
|
65
65
|
const now = Date.now();
|
|
66
66
|
const maxAgeExceeded = messages.some(m => now - m.timestamp >= this.maxBufferAgeMs);
|
|
67
67
|
const isIdle = session.isUserIdle(this.idleThresholdMs);
|
|
68
|
+
// #1198: writes to a session whose shellper connection is down are
|
|
69
|
+
// dropped silently. Hold the messages while the connection recovers
|
|
70
|
+
// (in-place reconnect takes a few seconds); if it never does, drop
|
|
71
|
+
// loudly instead of logging a successful delivery.
|
|
72
|
+
if (!session.writable) {
|
|
73
|
+
if (forceAll || maxAgeExceeded) {
|
|
74
|
+
if (this.log) {
|
|
75
|
+
this.log('ERROR', `Dropping ${messages.length} buffered message(s) for unwritable session ${sessionId.slice(0, 8)}... (shellper connection down)`);
|
|
76
|
+
}
|
|
77
|
+
this.buffers.delete(sessionId);
|
|
78
|
+
}
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
68
81
|
// Deliver when: forced, user idle, or max age exceeded.
|
|
69
82
|
// Bugfix #492: removed composing check — it gets stuck true after non-Enter
|
|
70
83
|
// keystrokes (Ctrl+C, arrows, Tab), causing messages to wait 60s max age.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"send-buffer.js","sourceRoot":"","sources":["../../../src/agent-farm/servers/send-buffer.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"send-buffer.js","sourceRoot":"","sources":["../../../src/agent-farm/servers/send-buffer.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAyBH,MAAM,yBAAyB,GAAG,IAAI,CAAC;AACvC,MAAM,yBAAyB,GAAG,MAAM,CAAC;AACzC,MAAM,iBAAiB,GAAG,GAAG,CAAC;AAE9B,MAAM,OAAO,UAAU;IACb,OAAO,GAAG,IAAI,GAAG,EAA6B,CAAC;IAC/C,UAAU,GAA0C,IAAI,CAAC;IACzD,UAAU,GAAwB,IAAI,CAAC;IACvC,OAAO,GAAqB,IAAI,CAAC;IACjC,GAAG,GAAiB,IAAI,CAAC;IACxB,eAAe,CAAS;IACxB,cAAc,CAAS;IAEhC,YAAY,IAA4D;QACtE,IAAI,CAAC,eAAe,GAAG,IAAI,EAAE,eAAe,IAAI,yBAAyB,CAAC;QAC1E,IAAI,CAAC,cAAc,GAAG,IAAI,EAAE,cAAc,IAAI,yBAAyB,CAAC;IAC1E,CAAC;IAED,8CAA8C;IAC9C,OAAO,CAAC,GAAoB;QAC1B,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QAC9C,IAAI,KAAK,EAAE,CAAC;YACV,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAClB,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,SAAS,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;QACzC,CAAC;IACH,CAAC;IAED,uEAAuE;IACvE,KAAK,CAAC,UAAwB,EAAE,OAAkB,EAAE,GAAU;QAC5D,IAAI,IAAI,CAAC,UAAU;YAAE,aAAa,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QACpD,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;QAC7B,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;QACf,IAAI,CAAC,UAAU,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,EAAE,EAAE,iBAAiB,CAAC,CAAC;IACvE,CAAC;IAED,+DAA+D;IAC/D,IAAI;QACF,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YACpB,aAAa,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;YAC/B,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC;QACzB,CAAC;QACD,6CAA6C;QAC7C,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACnB,CAAC;IAED,yEAAyE;IACzE,KAAK,CAAC,QAAQ,GAAG,KAAK;QACpB,IAAI,CAAC,IAAI,CAAC,UAAU,IAAI,CAAC,IAAI,CAAC,OAAO;YAAE,OAAO;QAE9C,KAAK,MAAM,CAAC,SAAS,EAAE,QAAQ,CAAC,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACjD,MAAM,OAAO,GAAG,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;YAE3C,IAAI,CAAC,OAAO,EAAE,CAAC;gBACb,yCAAyC;gBACzC,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC;oBACb,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,cAAc,QAAQ,CAAC,MAAM,yCAAyC,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC;gBACrH,CAAC;gBACD,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;gBAC/B,SAAS;YACX,CAAC;YAED,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YACvB,MAAM,cAAc,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,SAAS,IAAI,IAAI,CAAC,cAAc,CAAC,CAAC;YACpF,MAAM,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;YAExD,mEAAmE;YACnE,oEAAoE;YACpE,mEAAmE;YACnE,mDAAmD;YACnD,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC;gBACtB,IAAI,QAAQ,IAAI,cAAc,EAAE,CAAC;oBAC/B,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC;wBACb,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,YAAY,QAAQ,CAAC,MAAM,+CAA+C,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,gCAAgC,CAAC,CAAC;oBACrJ,CAAC;oBACD,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;gBACjC,CAAC;gBACD,SAAS;YACX,CAAC;YAED,wDAAwD;YACxD,4EAA4E;YAC5E,0EAA0E;YAC1E,IAAI,QAAQ,IAAI,MAAM,IAAI,cAAc,EAAE,CAAC;gBACzC,yEAAyE;gBACzE,0EAA0E;gBAC1E,kDAAkD;gBAClD,IAAI,MAAM,GAAG,CAAC,CAAC;gBACf,KAAK,MAAM,GAAG,IAAI,QAAQ,EAAE,CAAC;oBAC3B,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,EAAE,MAAM,CAAC,CAAC;oBAC5C,IAAI,IAAI,CAAC,GAAG,IAAI,GAAG,CAAC,UAAU,EAAE,CAAC;wBAC/B,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,UAAU,CAAC,CAAC;oBACnC,CAAC;gBACH,CAAC;gBACD,IAAI,IAAI,CAAC,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC;oBAC1B,MAAM,MAAM,GAAG,cAAc,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,WAAW,CAAC;oBACjE,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,aAAa,QAAQ,CAAC,MAAM,mCAAmC,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,QAAQ,MAAM,GAAG,CAAC,CAAC;gBAC1H,CAAC;gBACD,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;YACjC,CAAC;QACH,CAAC;IACH,CAAC;IAED,qEAAqE;IACrE,IAAI,YAAY;QACd,IAAI,KAAK,GAAG,CAAC,CAAC;QACd,KAAK,MAAM,QAAQ,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;YAC7C,KAAK,IAAI,QAAQ,CAAC,MAAM,CAAC;QAC3B,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAED,+DAA+D;IAC/D,IAAI,YAAY;QACd,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC;IAC3B,CAAC;CACF"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Issue #1238: retention for PTY session logs.
|
|
3
|
+
*
|
|
4
|
+
* `PtySession` already bounds a *single* session's log (50 MB, one `.1`
|
|
5
|
+
* rotation — see `../../terminal/pty-session.ts`), but nothing has ever
|
|
6
|
+
* deleted the log of a session that ended. Every terminal Tower has ever
|
|
7
|
+
* opened leaves a file behind forever: an audited machine reached 29,728 files
|
|
8
|
+
* / 19 GB in `~/.agent-farm/logs`, 97% of it untouched for over a month.
|
|
9
|
+
*
|
|
10
|
+
* This module adds the missing cross-file policy: a log is deleted once its
|
|
11
|
+
* last write is older than the retention window. mtime is the retention key
|
|
12
|
+
* because the last byte written to a session log is, in effect, the moment
|
|
13
|
+
* that session stopped producing output — the "session ended" signal, without
|
|
14
|
+
* needing to join against DB session state (which is itself pruned, so dead
|
|
15
|
+
* sessions leave no row to join against).
|
|
16
|
+
*
|
|
17
|
+
* Live sessions are excluded by id regardless of mtime. A session log's fd is
|
|
18
|
+
* held open for the session's whole life, so unlinking it under a live writer
|
|
19
|
+
* would silently redirect every subsequent write into an unlinked inode —
|
|
20
|
+
* losing the log of the one session someone might actually want to read. A
|
|
21
|
+
* 30-day-idle live session is vanishingly unlikely, but the guard is cheap.
|
|
22
|
+
*
|
|
23
|
+
* Wired in `./tower-server.ts` on two triggers (Tower startup, and a daily
|
|
24
|
+
* in-process timer), mirroring the husk sweep in `./shellper-husk-sweep.ts`.
|
|
25
|
+
*/
|
|
26
|
+
export declare const DEFAULT_LOG_RETENTION_DAYS = 30;
|
|
27
|
+
export declare const DEFAULT_LOG_SWEEP_INTERVAL_MS: number;
|
|
28
|
+
export declare const MS_PER_DAY: number;
|
|
29
|
+
/**
|
|
30
|
+
* Retention window in days (env `AGENT_FARM_LOG_RETENTION_DAYS`, default 30).
|
|
31
|
+
* `0` disables the sweep entirely — an explicit "keep everything" opt-out for
|
|
32
|
+
* anyone who treats these logs as an audit trail.
|
|
33
|
+
*
|
|
34
|
+
* NaN-checked rather than `parsed || default` because `0` is a meaningful
|
|
35
|
+
* override and is falsy in JS (same reasoning as `resolveHuskGraceMs`).
|
|
36
|
+
*/
|
|
37
|
+
export declare function resolveLogRetentionDays(env?: NodeJS.ProcessEnv): number;
|
|
38
|
+
/**
|
|
39
|
+
* Sweep cadence (env `AGENT_FARM_LOG_SWEEP_INTERVAL_MS`, default 24h). Floored
|
|
40
|
+
* at 1s so a bad value can't spin the event loop; tests shorten it deliberately.
|
|
41
|
+
*/
|
|
42
|
+
export declare function resolveLogSweepIntervalMs(env?: NodeJS.ProcessEnv): number;
|
|
43
|
+
export interface SessionLogFootprint {
|
|
44
|
+
/** Number of session log files (including `.log.1` rotations). */
|
|
45
|
+
files: number;
|
|
46
|
+
/** Total bytes on disk. */
|
|
47
|
+
bytes: number;
|
|
48
|
+
/** mtime of the oldest log, or null when there are none. */
|
|
49
|
+
oldestMtimeMs: number | null;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Total footprint of the session log directory. Read-only — used by
|
|
53
|
+
* `codev doctor` to surface the number the issue was filed about, and by the
|
|
54
|
+
* sweep's own logging. A missing directory is a healthy zero, not an error.
|
|
55
|
+
*/
|
|
56
|
+
export declare function measureSessionLogs(logDir: string): SessionLogFootprint;
|
|
57
|
+
export interface SweepSessionLogsOptions {
|
|
58
|
+
/** The session log directory, i.e. `~/.agent-farm/logs`. */
|
|
59
|
+
logDir: string;
|
|
60
|
+
/** Age at which a log becomes eligible for deletion. `0` disables the sweep. */
|
|
61
|
+
retentionMs: number;
|
|
62
|
+
/**
|
|
63
|
+
* Ids of sessions that are alive right now. Their logs are never deleted,
|
|
64
|
+
* however old the mtime looks, because their fd is still open.
|
|
65
|
+
*/
|
|
66
|
+
activeSessionIds?: Iterable<string>;
|
|
67
|
+
/** Seam, defaults to `Date.now()`. */
|
|
68
|
+
now?: number;
|
|
69
|
+
log?: (msg: string) => void;
|
|
70
|
+
}
|
|
71
|
+
export interface SweepSessionLogsResult {
|
|
72
|
+
/** Session log files examined. */
|
|
73
|
+
scanned: number;
|
|
74
|
+
/** Files unlinked. */
|
|
75
|
+
deleted: number;
|
|
76
|
+
/** Bytes reclaimed (sum of the deleted files' sizes). */
|
|
77
|
+
bytesFreed: number;
|
|
78
|
+
/** Aged-out files kept because their session is still live. */
|
|
79
|
+
skippedActive: number;
|
|
80
|
+
/** Files that were eligible but could not be unlinked. */
|
|
81
|
+
failed: number;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Delete session logs whose last write is older than `retentionMs`.
|
|
85
|
+
*
|
|
86
|
+
* Never throws: a log directory that can't be read, or an individual file that
|
|
87
|
+
* can't be stat'd or unlinked, is counted and skipped. Log retention is
|
|
88
|
+
* janitorial — it must not be able to take Tower's startup path down with it.
|
|
89
|
+
*/
|
|
90
|
+
export declare function sweepSessionLogs(opts: SweepSessionLogsOptions): SweepSessionLogsResult;
|
|
91
|
+
/** Human-readable byte count for log lines and `codev doctor` output. */
|
|
92
|
+
export declare function formatBytes(bytes: number): string;
|
|
93
|
+
//# sourceMappingURL=session-log-sweep.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-log-sweep.d.ts","sourceRoot":"","sources":["../../../src/agent-farm/servers/session-log-sweep.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAQH,eAAO,MAAM,0BAA0B,KAAK,CAAC;AAC7C,eAAO,MAAM,6BAA6B,QAAsB,CAAC;AAEjE,eAAO,MAAM,UAAU,QAAsB,CAAC;AAE9C;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAGpF;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAGtF;AAED,MAAM,WAAW,mBAAmB;IAClC,kEAAkE;IAClE,KAAK,EAAE,MAAM,CAAC;IACd,2BAA2B;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,4DAA4D;IAC5D,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,mBAAmB,CAyBtE;AAED,MAAM,WAAW,uBAAuB;IACtC,4DAA4D;IAC5D,MAAM,EAAE,MAAM,CAAC;IACf,gFAAgF;IAChF,WAAW,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,gBAAgB,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IACpC,sCAAsC;IACtC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CAC7B;AAED,MAAM,WAAW,sBAAsB;IACrC,kCAAkC;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,sBAAsB;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,yDAAyD;IACzD,UAAU,EAAE,MAAM,CAAC;IACnB,+DAA+D;IAC/D,aAAa,EAAE,MAAM,CAAC;IACtB,0DAA0D;IAC1D,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,uBAAuB,GAAG,sBAAsB,CA2DtF;AAED,yEAAyE;AACzE,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAUjD"}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Issue #1238: retention for PTY session logs.
|
|
3
|
+
*
|
|
4
|
+
* `PtySession` already bounds a *single* session's log (50 MB, one `.1`
|
|
5
|
+
* rotation — see `../../terminal/pty-session.ts`), but nothing has ever
|
|
6
|
+
* deleted the log of a session that ended. Every terminal Tower has ever
|
|
7
|
+
* opened leaves a file behind forever: an audited machine reached 29,728 files
|
|
8
|
+
* / 19 GB in `~/.agent-farm/logs`, 97% of it untouched for over a month.
|
|
9
|
+
*
|
|
10
|
+
* This module adds the missing cross-file policy: a log is deleted once its
|
|
11
|
+
* last write is older than the retention window. mtime is the retention key
|
|
12
|
+
* because the last byte written to a session log is, in effect, the moment
|
|
13
|
+
* that session stopped producing output — the "session ended" signal, without
|
|
14
|
+
* needing to join against DB session state (which is itself pruned, so dead
|
|
15
|
+
* sessions leave no row to join against).
|
|
16
|
+
*
|
|
17
|
+
* Live sessions are excluded by id regardless of mtime. A session log's fd is
|
|
18
|
+
* held open for the session's whole life, so unlinking it under a live writer
|
|
19
|
+
* would silently redirect every subsequent write into an unlinked inode —
|
|
20
|
+
* losing the log of the one session someone might actually want to read. A
|
|
21
|
+
* 30-day-idle live session is vanishingly unlikely, but the guard is cheap.
|
|
22
|
+
*
|
|
23
|
+
* Wired in `./tower-server.ts` on two triggers (Tower startup, and a daily
|
|
24
|
+
* in-process timer), mirroring the husk sweep in `./shellper-husk-sweep.ts`.
|
|
25
|
+
*/
|
|
26
|
+
import fs from 'node:fs';
|
|
27
|
+
import path from 'node:path';
|
|
28
|
+
/** `<session-uuid>.log` and its single rotation `<session-uuid>.log.1`. */
|
|
29
|
+
const SESSION_LOG_RE = /^(.+)\.log(\.1)?$/;
|
|
30
|
+
export const DEFAULT_LOG_RETENTION_DAYS = 30;
|
|
31
|
+
export const DEFAULT_LOG_SWEEP_INTERVAL_MS = 24 * 60 * 60 * 1000;
|
|
32
|
+
export const MS_PER_DAY = 24 * 60 * 60 * 1000;
|
|
33
|
+
/**
|
|
34
|
+
* Retention window in days (env `AGENT_FARM_LOG_RETENTION_DAYS`, default 30).
|
|
35
|
+
* `0` disables the sweep entirely — an explicit "keep everything" opt-out for
|
|
36
|
+
* anyone who treats these logs as an audit trail.
|
|
37
|
+
*
|
|
38
|
+
* NaN-checked rather than `parsed || default` because `0` is a meaningful
|
|
39
|
+
* override and is falsy in JS (same reasoning as `resolveHuskGraceMs`).
|
|
40
|
+
*/
|
|
41
|
+
export function resolveLogRetentionDays(env = process.env) {
|
|
42
|
+
const parsed = parseInt(env.AGENT_FARM_LOG_RETENTION_DAYS ?? '', 10);
|
|
43
|
+
return Math.max(Number.isNaN(parsed) ? DEFAULT_LOG_RETENTION_DAYS : parsed, 0);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Sweep cadence (env `AGENT_FARM_LOG_SWEEP_INTERVAL_MS`, default 24h). Floored
|
|
47
|
+
* at 1s so a bad value can't spin the event loop; tests shorten it deliberately.
|
|
48
|
+
*/
|
|
49
|
+
export function resolveLogSweepIntervalMs(env = process.env) {
|
|
50
|
+
const parsed = parseInt(env.AGENT_FARM_LOG_SWEEP_INTERVAL_MS ?? '', 10);
|
|
51
|
+
return Math.max(Number.isNaN(parsed) ? DEFAULT_LOG_SWEEP_INTERVAL_MS : parsed, 1000);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Total footprint of the session log directory. Read-only — used by
|
|
55
|
+
* `codev doctor` to surface the number the issue was filed about, and by the
|
|
56
|
+
* sweep's own logging. A missing directory is a healthy zero, not an error.
|
|
57
|
+
*/
|
|
58
|
+
export function measureSessionLogs(logDir) {
|
|
59
|
+
const footprint = { files: 0, bytes: 0, oldestMtimeMs: null };
|
|
60
|
+
let entries;
|
|
61
|
+
try {
|
|
62
|
+
entries = fs.readdirSync(logDir);
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return footprint;
|
|
66
|
+
}
|
|
67
|
+
for (const name of entries) {
|
|
68
|
+
if (!SESSION_LOG_RE.test(name))
|
|
69
|
+
continue;
|
|
70
|
+
let stat;
|
|
71
|
+
try {
|
|
72
|
+
stat = fs.statSync(path.join(logDir, name));
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
continue; // vanished mid-scan — nothing to account for
|
|
76
|
+
}
|
|
77
|
+
if (!stat.isFile())
|
|
78
|
+
continue;
|
|
79
|
+
footprint.files += 1;
|
|
80
|
+
footprint.bytes += stat.size;
|
|
81
|
+
if (footprint.oldestMtimeMs === null || stat.mtimeMs < footprint.oldestMtimeMs) {
|
|
82
|
+
footprint.oldestMtimeMs = stat.mtimeMs;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return footprint;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Delete session logs whose last write is older than `retentionMs`.
|
|
89
|
+
*
|
|
90
|
+
* Never throws: a log directory that can't be read, or an individual file that
|
|
91
|
+
* can't be stat'd or unlinked, is counted and skipped. Log retention is
|
|
92
|
+
* janitorial — it must not be able to take Tower's startup path down with it.
|
|
93
|
+
*/
|
|
94
|
+
export function sweepSessionLogs(opts) {
|
|
95
|
+
const result = {
|
|
96
|
+
scanned: 0,
|
|
97
|
+
deleted: 0,
|
|
98
|
+
bytesFreed: 0,
|
|
99
|
+
skippedActive: 0,
|
|
100
|
+
failed: 0,
|
|
101
|
+
};
|
|
102
|
+
if (opts.retentionMs <= 0)
|
|
103
|
+
return result;
|
|
104
|
+
const now = opts.now ?? Date.now();
|
|
105
|
+
const active = new Set(opts.activeSessionIds ?? []);
|
|
106
|
+
let entries;
|
|
107
|
+
try {
|
|
108
|
+
entries = fs.readdirSync(opts.logDir);
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
return result; // no log dir yet (fresh install) — nothing to sweep
|
|
112
|
+
}
|
|
113
|
+
for (const name of entries) {
|
|
114
|
+
const match = SESSION_LOG_RE.exec(name);
|
|
115
|
+
if (!match)
|
|
116
|
+
continue;
|
|
117
|
+
const fullPath = path.join(opts.logDir, name);
|
|
118
|
+
let stat;
|
|
119
|
+
try {
|
|
120
|
+
stat = fs.statSync(fullPath);
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
if (!stat.isFile())
|
|
126
|
+
continue;
|
|
127
|
+
result.scanned += 1;
|
|
128
|
+
if (now - stat.mtimeMs < opts.retentionMs)
|
|
129
|
+
continue;
|
|
130
|
+
if (active.has(match[1])) {
|
|
131
|
+
result.skippedActive += 1;
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
try {
|
|
135
|
+
fs.unlinkSync(fullPath);
|
|
136
|
+
result.deleted += 1;
|
|
137
|
+
result.bytesFreed += stat.size;
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
result.failed += 1;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
if (result.deleted > 0 || result.failed > 0) {
|
|
144
|
+
opts.log?.(`Session log sweep: deleted ${result.deleted} log(s), freed ${formatBytes(result.bytesFreed)}` +
|
|
145
|
+
` (${result.scanned} scanned` +
|
|
146
|
+
(result.skippedActive > 0 ? `, ${result.skippedActive} kept as live` : '') +
|
|
147
|
+
(result.failed > 0 ? `, ${result.failed} failed` : '') +
|
|
148
|
+
')');
|
|
149
|
+
}
|
|
150
|
+
return result;
|
|
151
|
+
}
|
|
152
|
+
/** Human-readable byte count for log lines and `codev doctor` output. */
|
|
153
|
+
export function formatBytes(bytes) {
|
|
154
|
+
if (bytes < 1024)
|
|
155
|
+
return `${bytes} B`;
|
|
156
|
+
const units = ['KB', 'MB', 'GB', 'TB'];
|
|
157
|
+
let value = bytes / 1024;
|
|
158
|
+
let unit = 0;
|
|
159
|
+
while (value >= 1024 && unit < units.length - 1) {
|
|
160
|
+
value /= 1024;
|
|
161
|
+
unit += 1;
|
|
162
|
+
}
|
|
163
|
+
return `${value < 10 ? value.toFixed(1) : Math.round(value)} ${units[unit]}`;
|
|
164
|
+
}
|
|
165
|
+
//# sourceMappingURL=session-log-sweep.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-log-sweep.js","sourceRoot":"","sources":["../../../src/agent-farm/servers/session-log-sweep.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Issue #1227: a stricter, second-pass reap for shellper "husks" — a shellper
|
|
3
|
+
* whose PTY child has exited (deliberate lingering behavior from #905/#1198 so
|
|
4
|
+
* a reconnect can replay history) but which `killOrphanedShellpers`
|
|
5
|
+
* (`../../terminal/session-manager.ts`) can never reap, because that function
|
|
6
|
+
* unconditionally protects any shellper whose Unix socket still responds.
|
|
7
|
+
*
|
|
8
|
+
* A husk still answers its socket, so it survives `killOrphanedShellpers`
|
|
9
|
+
* forever. The predicate here is stricter and orthogonal: a shellper is a husk
|
|
10
|
+
* only when it is simultaneously
|
|
11
|
+
* - unregistered — not a live PID in `terminal_sessions.shellper_pid`
|
|
12
|
+
* - childless — no process in the table has it as a parent (OS-level
|
|
13
|
+
* signal; there is no wire-protocol "has-child" frame)
|
|
14
|
+
* - aged — older than `graceMs`, which absorbs any legitimate
|
|
15
|
+
* mid-respawn / mid-adoption transient (seconds), so the
|
|
16
|
+
* three conditions together can only ever be true for a
|
|
17
|
+
* genuine husk (per the issue: "unregistered and
|
|
18
|
+
* childless" is a terminal state — a shellper that lost
|
|
19
|
+
* its registration can never receive another SPAWN).
|
|
20
|
+
*
|
|
21
|
+
* The actual kill is delegated to `reapShellpers` (`./architect-session-holder.js`,
|
|
22
|
+
* SIGTERM → poll → SIGKILL → confirm-death) rather than reimplemented here.
|
|
23
|
+
*/
|
|
24
|
+
import type Database from 'better-sqlite3';
|
|
25
|
+
import { type ProcessCensusEntry } from './process-census.js';
|
|
26
|
+
export declare const SHELLPER_MARKER = "shellper-main.js";
|
|
27
|
+
/**
|
|
28
|
+
* The set of shellper PIDs Tower currently believes are registered, per
|
|
29
|
+
* `terminal_sessions` — validated against each PID's actual process start
|
|
30
|
+
* time to guard against PID reuse (a stale row whose PID was reused by an
|
|
31
|
+
* unrelated process does not count as registered).
|
|
32
|
+
*/
|
|
33
|
+
export declare function computeRegisteredShellperPids(db: Database.Database, getStartTime?: (pid: number) => Promise<number | null>): Promise<Set<number>>;
|
|
34
|
+
export interface FindHuskShellpersOptions {
|
|
35
|
+
/** This Tower instance's socket directory — scopes the sweep so another
|
|
36
|
+
* instance's (or another user's) legitimate shellpers are never touched. */
|
|
37
|
+
socketDir: string;
|
|
38
|
+
registeredShellperPids: Set<number>;
|
|
39
|
+
graceMs: number;
|
|
40
|
+
/**
|
|
41
|
+
* Seam, defaults to `listProcessCensus`. Accepts a plain array too (not just
|
|
42
|
+
* a function returning one) so a caller that already took a snapshot for
|
|
43
|
+
* another purpose (e.g. RSS display) can pass it through and avoid a second
|
|
44
|
+
* `ps` scan — see `handleHuskPreview` in tower-routes.ts.
|
|
45
|
+
*/
|
|
46
|
+
census?: () => ProcessCensusEntry[] | Promise<ProcessCensusEntry[]>;
|
|
47
|
+
/** Seam, defaults to `Date.now()`. */
|
|
48
|
+
now?: number;
|
|
49
|
+
/** Seam, defaults to `getProcessStartTime`. */
|
|
50
|
+
getStartTime?: (pid: number) => Promise<number | null>;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Returns the PIDs of shellper-main.js processes, scoped to `socketDir`, that
|
|
54
|
+
* are unregistered AND childless AND older than `graceMs`.
|
|
55
|
+
*/
|
|
56
|
+
export declare function findHuskShellpers(opts: FindHuskShellpersOptions): Promise<number[]>;
|
|
57
|
+
/**
|
|
58
|
+
* The husk grace period (env `SHELLPER_HUSK_GRACE_MS`, default 1 hour), shared
|
|
59
|
+
* by every trigger (startup, periodic, on-demand preview/apply) so a preview
|
|
60
|
+
* always predicts what the periodic sweep would actually do.
|
|
61
|
+
*
|
|
62
|
+
* NaN-checked rather than `parsed || default`: `0` is a legitimate override
|
|
63
|
+
* (tests want an immediate-eligibility grace period) and `0` is falsy in JS,
|
|
64
|
+
* so `parsed || default` would silently discard a deliberate zero.
|
|
65
|
+
*/
|
|
66
|
+
export declare function resolveHuskGraceMs(env?: NodeJS.ProcessEnv): number;
|
|
67
|
+
export interface SweepShellperHusksOptions {
|
|
68
|
+
socketDir: string;
|
|
69
|
+
db: Database.Database;
|
|
70
|
+
graceMs: number;
|
|
71
|
+
log?: (msg: string) => void;
|
|
72
|
+
census?: () => ProcessCensusEntry[] | Promise<ProcessCensusEntry[]>;
|
|
73
|
+
now?: number;
|
|
74
|
+
getStartTime?: (pid: number) => Promise<number | null>;
|
|
75
|
+
/** Seam, defaults to `reapShellpers`. */
|
|
76
|
+
reap?: (pids: number[]) => Promise<void>;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Compute the registered-shellper set from the DB, find husk candidates, and
|
|
80
|
+
* reap them. Returns the swept PIDs so callers (startup log, periodic-timer
|
|
81
|
+
* log, on-demand CLI) can report what happened.
|
|
82
|
+
*/
|
|
83
|
+
export declare function sweepShellperHusks(opts: SweepShellperHusksOptions): Promise<{
|
|
84
|
+
swept: number;
|
|
85
|
+
pids: number[];
|
|
86
|
+
}>;
|
|
87
|
+
//# sourceMappingURL=shellper-husk-sweep.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shellper-husk-sweep.d.ts","sourceRoot":"","sources":["../../../src/agent-farm/servers/shellper-husk-sweep.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAqB,KAAK,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAIjF,eAAO,MAAM,eAAe,qBAAqB,CAAC;AAgBlD;;;;;GAKG;AACH,wBAAsB,6BAA6B,CACjD,EAAE,EAAE,QAAQ,CAAC,QAAQ,EACrB,YAAY,GAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAuB,GAC1E,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAmBtB;AAED,MAAM,WAAW,wBAAwB;IACvC;gFAC4E;IAC5E,SAAS,EAAE,MAAM,CAAC;IAClB,sBAAsB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACpC,OAAO,EAAE,MAAM,CAAC;IAChB;;;;;OAKG;IACH,MAAM,CAAC,EAAE,MAAM,kBAAkB,EAAE,GAAG,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAAC;IACpE,sCAAsC;IACtC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,+CAA+C;IAC/C,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;CACxD;AAED;;;GAGG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,wBAAwB,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAyBzF;AAED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAG/E;AAED,MAAM,WAAW,yBAAyB;IACxC,SAAS,EAAE,MAAM,CAAC;IAClB,EAAE,EAAE,QAAQ,CAAC,QAAQ,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5B,MAAM,CAAC,EAAE,MAAM,kBAAkB,EAAE,GAAG,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAAC;IACpE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACvD,yCAAyC;IACzC,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1C;AAED;;;;GAIG;AACH,wBAAsB,kBAAkB,CACtC,IAAI,EAAE,yBAAyB,GAC9B,OAAO,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,CAmB5C"}
|