@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.
Files changed (157) hide show
  1. package/dashboard-dist/assets/index-BZ1x7x1X.js.map +1 -1
  2. package/dist/agent-farm/cli.d.ts.map +1 -1
  3. package/dist/agent-farm/cli.js +31 -0
  4. package/dist/agent-farm/cli.js.map +1 -1
  5. package/dist/agent-farm/commands/attach.js +2 -2
  6. package/dist/agent-farm/commands/attach.js.map +1 -1
  7. package/dist/agent-farm/commands/send.d.ts +2 -2
  8. package/dist/agent-farm/commands/send.js +3 -3
  9. package/dist/agent-farm/commands/send.js.map +1 -1
  10. package/dist/agent-farm/commands/setup.d.ts +4 -1
  11. package/dist/agent-farm/commands/setup.d.ts.map +1 -1
  12. package/dist/agent-farm/commands/setup.js +8 -4
  13. package/dist/agent-farm/commands/setup.js.map +1 -1
  14. package/dist/agent-farm/commands/spawn-worktree.d.ts +11 -0
  15. package/dist/agent-farm/commands/spawn-worktree.d.ts.map +1 -1
  16. package/dist/agent-farm/commands/spawn-worktree.js +64 -16
  17. package/dist/agent-farm/commands/spawn-worktree.js.map +1 -1
  18. package/dist/agent-farm/commands/status.d.ts.map +1 -1
  19. package/dist/agent-farm/commands/status.js +26 -2
  20. package/dist/agent-farm/commands/status.js.map +1 -1
  21. package/dist/agent-farm/commands/tower-sweep-husks.d.ts +6 -0
  22. package/dist/agent-farm/commands/tower-sweep-husks.d.ts.map +1 -0
  23. package/dist/agent-farm/commands/tower-sweep-husks.js +77 -0
  24. package/dist/agent-farm/commands/tower-sweep-husks.js.map +1 -0
  25. package/dist/agent-farm/commands/tower.d.ts.map +1 -1
  26. package/dist/agent-farm/commands/tower.js +34 -0
  27. package/dist/agent-farm/commands/tower.js.map +1 -1
  28. package/dist/agent-farm/commands/whoami.d.ts +65 -0
  29. package/dist/agent-farm/commands/whoami.d.ts.map +1 -0
  30. package/dist/agent-farm/commands/whoami.js +196 -0
  31. package/dist/agent-farm/commands/whoami.js.map +1 -0
  32. package/dist/agent-farm/lib/tower-client.d.ts +1 -1
  33. package/dist/agent-farm/lib/tower-client.d.ts.map +1 -1
  34. package/dist/agent-farm/lib/tower-client.js.map +1 -1
  35. package/dist/agent-farm/servers/architect-session-holder.d.ts +133 -0
  36. package/dist/agent-farm/servers/architect-session-holder.d.ts.map +1 -0
  37. package/dist/agent-farm/servers/architect-session-holder.js +268 -0
  38. package/dist/agent-farm/servers/architect-session-holder.js.map +1 -0
  39. package/dist/agent-farm/servers/process-census.d.ts +41 -0
  40. package/dist/agent-farm/servers/process-census.d.ts.map +1 -0
  41. package/dist/agent-farm/servers/process-census.js +62 -0
  42. package/dist/agent-farm/servers/process-census.js.map +1 -0
  43. package/dist/agent-farm/servers/send-buffer.d.ts.map +1 -1
  44. package/dist/agent-farm/servers/send-buffer.js +13 -0
  45. package/dist/agent-farm/servers/send-buffer.js.map +1 -1
  46. package/dist/agent-farm/servers/session-log-sweep.d.ts +93 -0
  47. package/dist/agent-farm/servers/session-log-sweep.d.ts.map +1 -0
  48. package/dist/agent-farm/servers/session-log-sweep.js +165 -0
  49. package/dist/agent-farm/servers/session-log-sweep.js.map +1 -0
  50. package/dist/agent-farm/servers/shellper-husk-sweep.d.ts +87 -0
  51. package/dist/agent-farm/servers/shellper-husk-sweep.d.ts.map +1 -0
  52. package/dist/agent-farm/servers/shellper-husk-sweep.js +129 -0
  53. package/dist/agent-farm/servers/shellper-husk-sweep.js.map +1 -0
  54. package/dist/agent-farm/servers/tower-instances.d.ts.map +1 -1
  55. package/dist/agent-farm/servers/tower-instances.js +52 -5
  56. package/dist/agent-farm/servers/tower-instances.js.map +1 -1
  57. package/dist/agent-farm/servers/tower-routes.d.ts.map +1 -1
  58. package/dist/agent-farm/servers/tower-routes.js +147 -4
  59. package/dist/agent-farm/servers/tower-routes.js.map +1 -1
  60. package/dist/agent-farm/servers/tower-server.js +95 -1
  61. package/dist/agent-farm/servers/tower-server.js.map +1 -1
  62. package/dist/agent-farm/servers/tower-terminals.d.ts.map +1 -1
  63. package/dist/agent-farm/servers/tower-terminals.js +38 -5
  64. package/dist/agent-farm/servers/tower-terminals.js.map +1 -1
  65. package/dist/agent-farm/servers/tower-utils.d.ts +35 -0
  66. package/dist/agent-farm/servers/tower-utils.d.ts.map +1 -1
  67. package/dist/agent-farm/servers/tower-utils.js +74 -5
  68. package/dist/agent-farm/servers/tower-utils.js.map +1 -1
  69. package/dist/agent-farm/state.d.ts +6 -1
  70. package/dist/agent-farm/state.d.ts.map +1 -1
  71. package/dist/agent-farm/state.js +5 -2
  72. package/dist/agent-farm/state.js.map +1 -1
  73. package/dist/commands/adopt.d.ts +3 -2
  74. package/dist/commands/adopt.d.ts.map +1 -1
  75. package/dist/commands/adopt.js +7 -6
  76. package/dist/commands/adopt.js.map +1 -1
  77. package/dist/commands/consult/agy-auth-cache.d.ts +97 -0
  78. package/dist/commands/consult/agy-auth-cache.d.ts.map +1 -0
  79. package/dist/commands/consult/agy-auth-cache.js +320 -0
  80. package/dist/commands/consult/agy-auth-cache.js.map +1 -0
  81. package/dist/commands/consult/index.d.ts.map +1 -1
  82. package/dist/commands/consult/index.js +59 -0
  83. package/dist/commands/consult/index.js.map +1 -1
  84. package/dist/commands/doctor.d.ts +28 -0
  85. package/dist/commands/doctor.d.ts.map +1 -1
  86. package/dist/commands/doctor.js +187 -3
  87. package/dist/commands/doctor.js.map +1 -1
  88. package/dist/commands/init.d.ts +3 -2
  89. package/dist/commands/init.d.ts.map +1 -1
  90. package/dist/commands/init.js +7 -6
  91. package/dist/commands/init.js.map +1 -1
  92. package/dist/commands/porch/index.d.ts.map +1 -1
  93. package/dist/commands/porch/index.js +11 -8
  94. package/dist/commands/porch/index.js.map +1 -1
  95. package/dist/commands/update.d.ts +4 -4
  96. package/dist/commands/update.d.ts.map +1 -1
  97. package/dist/commands/update.js +9 -11
  98. package/dist/commands/update.js.map +1 -1
  99. package/dist/lib/config.d.ts +1 -0
  100. package/dist/lib/config.d.ts.map +1 -1
  101. package/dist/lib/config.js.map +1 -1
  102. package/dist/lib/default-branch.d.ts +1 -1
  103. package/dist/lib/default-branch.js +1 -1
  104. package/dist/lib/gitignore.d.ts +25 -2
  105. package/dist/lib/gitignore.d.ts.map +1 -1
  106. package/dist/lib/gitignore.js +70 -0
  107. package/dist/lib/gitignore.js.map +1 -1
  108. package/dist/lib/migration-backup-audit.d.ts +64 -0
  109. package/dist/lib/migration-backup-audit.d.ts.map +1 -0
  110. package/dist/lib/migration-backup-audit.js +145 -0
  111. package/dist/lib/migration-backup-audit.js.map +1 -0
  112. package/dist/lib/protocol-drift-audit.d.ts +118 -0
  113. package/dist/lib/protocol-drift-audit.d.ts.map +1 -0
  114. package/dist/lib/protocol-drift-audit.js +217 -0
  115. package/dist/lib/protocol-drift-audit.js.map +1 -0
  116. package/dist/lib/scaffold.d.ts +10 -3
  117. package/dist/lib/scaffold.d.ts.map +1 -1
  118. package/dist/lib/scaffold.js +29 -26
  119. package/dist/lib/scaffold.js.map +1 -1
  120. package/dist/terminal/pty-manager.d.ts +7 -0
  121. package/dist/terminal/pty-manager.d.ts.map +1 -1
  122. package/dist/terminal/pty-manager.js +16 -0
  123. package/dist/terminal/pty-manager.js.map +1 -1
  124. package/dist/terminal/pty-session.d.ts +25 -4
  125. package/dist/terminal/pty-session.d.ts.map +1 -1
  126. package/dist/terminal/pty-session.js +83 -11
  127. package/dist/terminal/pty-session.js.map +1 -1
  128. package/dist/terminal/session-manager.d.ts +76 -0
  129. package/dist/terminal/session-manager.d.ts.map +1 -1
  130. package/dist/terminal/session-manager.js +252 -44
  131. package/dist/terminal/session-manager.js.map +1 -1
  132. package/dist/terminal/shellper-client.d.ts +19 -4
  133. package/dist/terminal/shellper-client.d.ts.map +1 -1
  134. package/dist/terminal/shellper-client.js +70 -8
  135. package/dist/terminal/shellper-client.js.map +1 -1
  136. package/dist/terminal/shellper-process.d.ts.map +1 -1
  137. package/dist/terminal/shellper-process.js +20 -5
  138. package/dist/terminal/shellper-process.js.map +1 -1
  139. package/dist/terminal/shellper-protocol.d.ts +40 -2
  140. package/dist/terminal/shellper-protocol.d.ts.map +1 -1
  141. package/dist/terminal/shellper-protocol.js +72 -5
  142. package/dist/terminal/shellper-protocol.js.map +1 -1
  143. package/package.json +7 -7
  144. package/skeleton/.claude/skills/afx/SKILL.md +13 -0
  145. package/skeleton/.claude/skills/arch-init/SKILL.md +122 -0
  146. package/skeleton/.claude/skills/codev/SKILL.md +2 -2
  147. package/skeleton/.codex/skills/afx/SKILL.md +157 -0
  148. package/skeleton/.codex/skills/arch-init/SKILL.md +122 -0
  149. package/skeleton/.codex/skills/codev/SKILL.md +59 -0
  150. package/skeleton/.codex/skills/consult/SKILL.md +96 -0
  151. package/skeleton/.codex/skills/generate-image/SKILL.md +45 -0
  152. package/skeleton/.codex/skills/porch/SKILL.md +60 -0
  153. package/skeleton/.codex/skills/update-arch-docs/SKILL.md +132 -0
  154. package/skeleton/resources/commands/agent-farm.md +89 -0
  155. package/skeleton/resources/commands/codev.md +5 -0
  156. package/skeleton/resources/commands/consult.md +26 -0
  157. 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;IA0C7B,qEAAqE;IACrE,IAAI,YAAY,IAAI,MAAM,CAMzB;IAED,+DAA+D;IAC/D,IAAI,YAAY,IAAI,MAAM,CAEzB;CACF"}
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":"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"}
@@ -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"}