@cluesmith/codev 3.2.2 → 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 (175) 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/commands/workspace-recover.d.ts +31 -0
  33. package/dist/agent-farm/commands/workspace-recover.d.ts.map +1 -1
  34. package/dist/agent-farm/commands/workspace-recover.js +70 -30
  35. package/dist/agent-farm/commands/workspace-recover.js.map +1 -1
  36. package/dist/agent-farm/db/index.d.ts.map +1 -1
  37. package/dist/agent-farm/db/index.js +8 -2
  38. package/dist/agent-farm/db/index.js.map +1 -1
  39. package/dist/agent-farm/lib/tower-client.d.ts +1 -1
  40. package/dist/agent-farm/lib/tower-client.d.ts.map +1 -1
  41. package/dist/agent-farm/lib/tower-client.js.map +1 -1
  42. package/dist/agent-farm/servers/architect-session-holder.d.ts +133 -0
  43. package/dist/agent-farm/servers/architect-session-holder.d.ts.map +1 -0
  44. package/dist/agent-farm/servers/architect-session-holder.js +268 -0
  45. package/dist/agent-farm/servers/architect-session-holder.js.map +1 -0
  46. package/dist/agent-farm/servers/process-census.d.ts +41 -0
  47. package/dist/agent-farm/servers/process-census.d.ts.map +1 -0
  48. package/dist/agent-farm/servers/process-census.js +62 -0
  49. package/dist/agent-farm/servers/process-census.js.map +1 -0
  50. package/dist/agent-farm/servers/send-buffer.d.ts.map +1 -1
  51. package/dist/agent-farm/servers/send-buffer.js +13 -0
  52. package/dist/agent-farm/servers/send-buffer.js.map +1 -1
  53. package/dist/agent-farm/servers/session-log-sweep.d.ts +93 -0
  54. package/dist/agent-farm/servers/session-log-sweep.d.ts.map +1 -0
  55. package/dist/agent-farm/servers/session-log-sweep.js +165 -0
  56. package/dist/agent-farm/servers/session-log-sweep.js.map +1 -0
  57. package/dist/agent-farm/servers/shellper-husk-sweep.d.ts +87 -0
  58. package/dist/agent-farm/servers/shellper-husk-sweep.d.ts.map +1 -0
  59. package/dist/agent-farm/servers/shellper-husk-sweep.js +129 -0
  60. package/dist/agent-farm/servers/shellper-husk-sweep.js.map +1 -0
  61. package/dist/agent-farm/servers/tower-instances.d.ts.map +1 -1
  62. package/dist/agent-farm/servers/tower-instances.js +203 -36
  63. package/dist/agent-farm/servers/tower-instances.js.map +1 -1
  64. package/dist/agent-farm/servers/tower-routes.d.ts.map +1 -1
  65. package/dist/agent-farm/servers/tower-routes.js +147 -4
  66. package/dist/agent-farm/servers/tower-routes.js.map +1 -1
  67. package/dist/agent-farm/servers/tower-server.js +95 -1
  68. package/dist/agent-farm/servers/tower-server.js.map +1 -1
  69. package/dist/agent-farm/servers/tower-terminals.d.ts.map +1 -1
  70. package/dist/agent-farm/servers/tower-terminals.js +66 -8
  71. package/dist/agent-farm/servers/tower-terminals.js.map +1 -1
  72. package/dist/agent-farm/servers/tower-utils.d.ts +106 -4
  73. package/dist/agent-farm/servers/tower-utils.d.ts.map +1 -1
  74. package/dist/agent-farm/servers/tower-utils.js +155 -19
  75. package/dist/agent-farm/servers/tower-utils.js.map +1 -1
  76. package/dist/agent-farm/state.d.ts +13 -1
  77. package/dist/agent-farm/state.d.ts.map +1 -1
  78. package/dist/agent-farm/state.js +17 -2
  79. package/dist/agent-farm/state.js.map +1 -1
  80. package/dist/agent-farm/utils/claude-session-discovery.d.ts +12 -0
  81. package/dist/agent-farm/utils/claude-session-discovery.d.ts.map +1 -1
  82. package/dist/agent-farm/utils/claude-session-discovery.js +42 -4
  83. package/dist/agent-farm/utils/claude-session-discovery.js.map +1 -1
  84. package/dist/agent-farm/utils/harness.d.ts +15 -3
  85. package/dist/agent-farm/utils/harness.d.ts.map +1 -1
  86. package/dist/agent-farm/utils/harness.js +4 -1
  87. package/dist/agent-farm/utils/harness.js.map +1 -1
  88. package/dist/commands/adopt.d.ts +3 -2
  89. package/dist/commands/adopt.d.ts.map +1 -1
  90. package/dist/commands/adopt.js +7 -6
  91. package/dist/commands/adopt.js.map +1 -1
  92. package/dist/commands/consult/agy-auth-cache.d.ts +97 -0
  93. package/dist/commands/consult/agy-auth-cache.d.ts.map +1 -0
  94. package/dist/commands/consult/agy-auth-cache.js +320 -0
  95. package/dist/commands/consult/agy-auth-cache.js.map +1 -0
  96. package/dist/commands/consult/index.d.ts.map +1 -1
  97. package/dist/commands/consult/index.js +59 -0
  98. package/dist/commands/consult/index.js.map +1 -1
  99. package/dist/commands/doctor.d.ts +28 -0
  100. package/dist/commands/doctor.d.ts.map +1 -1
  101. package/dist/commands/doctor.js +187 -3
  102. package/dist/commands/doctor.js.map +1 -1
  103. package/dist/commands/init.d.ts +3 -2
  104. package/dist/commands/init.d.ts.map +1 -1
  105. package/dist/commands/init.js +7 -6
  106. package/dist/commands/init.js.map +1 -1
  107. package/dist/commands/porch/index.d.ts.map +1 -1
  108. package/dist/commands/porch/index.js +11 -8
  109. package/dist/commands/porch/index.js.map +1 -1
  110. package/dist/commands/update.d.ts +4 -4
  111. package/dist/commands/update.d.ts.map +1 -1
  112. package/dist/commands/update.js +9 -11
  113. package/dist/commands/update.js.map +1 -1
  114. package/dist/lib/config.d.ts +1 -0
  115. package/dist/lib/config.d.ts.map +1 -1
  116. package/dist/lib/config.js.map +1 -1
  117. package/dist/lib/default-branch.d.ts +1 -1
  118. package/dist/lib/default-branch.js +1 -1
  119. package/dist/lib/gitignore.d.ts +25 -2
  120. package/dist/lib/gitignore.d.ts.map +1 -1
  121. package/dist/lib/gitignore.js +70 -0
  122. package/dist/lib/gitignore.js.map +1 -1
  123. package/dist/lib/migration-backup-audit.d.ts +64 -0
  124. package/dist/lib/migration-backup-audit.d.ts.map +1 -0
  125. package/dist/lib/migration-backup-audit.js +145 -0
  126. package/dist/lib/migration-backup-audit.js.map +1 -0
  127. package/dist/lib/protocol-drift-audit.d.ts +118 -0
  128. package/dist/lib/protocol-drift-audit.d.ts.map +1 -0
  129. package/dist/lib/protocol-drift-audit.js +217 -0
  130. package/dist/lib/protocol-drift-audit.js.map +1 -0
  131. package/dist/lib/scaffold.d.ts +10 -3
  132. package/dist/lib/scaffold.d.ts.map +1 -1
  133. package/dist/lib/scaffold.js +29 -26
  134. package/dist/lib/scaffold.js.map +1 -1
  135. package/dist/terminal/pty-manager.d.ts +7 -0
  136. package/dist/terminal/pty-manager.d.ts.map +1 -1
  137. package/dist/terminal/pty-manager.js +16 -0
  138. package/dist/terminal/pty-manager.js.map +1 -1
  139. package/dist/terminal/pty-session.d.ts +25 -4
  140. package/dist/terminal/pty-session.d.ts.map +1 -1
  141. package/dist/terminal/pty-session.js +83 -11
  142. package/dist/terminal/pty-session.js.map +1 -1
  143. package/dist/terminal/session-manager.d.ts +109 -0
  144. package/dist/terminal/session-manager.d.ts.map +1 -1
  145. package/dist/terminal/session-manager.js +314 -45
  146. package/dist/terminal/session-manager.js.map +1 -1
  147. package/dist/terminal/shellper-client.d.ts +19 -4
  148. package/dist/terminal/shellper-client.d.ts.map +1 -1
  149. package/dist/terminal/shellper-client.js +70 -8
  150. package/dist/terminal/shellper-client.js.map +1 -1
  151. package/dist/terminal/shellper-process.d.ts.map +1 -1
  152. package/dist/terminal/shellper-process.js +20 -5
  153. package/dist/terminal/shellper-process.js.map +1 -1
  154. package/dist/terminal/shellper-protocol.d.ts +40 -2
  155. package/dist/terminal/shellper-protocol.d.ts.map +1 -1
  156. package/dist/terminal/shellper-protocol.js +72 -5
  157. package/dist/terminal/shellper-protocol.js.map +1 -1
  158. package/package.json +8 -8
  159. package/skeleton/.claude/skills/afx/SKILL.md +13 -0
  160. package/skeleton/.claude/skills/arch-init/SKILL.md +122 -0
  161. package/skeleton/.claude/skills/codev/SKILL.md +2 -2
  162. package/skeleton/.codex/skills/afx/SKILL.md +157 -0
  163. package/skeleton/.codex/skills/arch-init/SKILL.md +122 -0
  164. package/skeleton/.codex/skills/codev/SKILL.md +59 -0
  165. package/skeleton/.codex/skills/consult/SKILL.md +96 -0
  166. package/skeleton/.codex/skills/generate-image/SKILL.md +45 -0
  167. package/skeleton/.codex/skills/porch/SKILL.md +60 -0
  168. package/skeleton/.codex/skills/update-arch-docs/SKILL.md +132 -0
  169. package/skeleton/protocols/pir/prompts/implement.md +1 -1
  170. package/skeleton/protocols/pir/prompts/review.md +1 -1
  171. package/skeleton/protocols/pir/protocol.md +3 -3
  172. package/skeleton/resources/commands/agent-farm.md +89 -0
  173. package/skeleton/resources/commands/codev.md +5 -0
  174. package/skeleton/resources/commands/consult.md +26 -0
  175. package/skeleton/roles/architect.md +11 -0
@@ -42,6 +42,9 @@ import { fetchIssue, searchIssues, fetchPRList, fetchCurrentUser, parseLinkedIss
42
42
  import { computeAnalytics } from './analytics.js';
43
43
  import { getAllTasks, executeTask, getTaskId } from './tower-cron.js';
44
44
  import { getGlobalDb } from '../db/index.js';
45
+ import { listProcessCensus } from './process-census.js';
46
+ import { SHELLPER_MARKER, computeRegisteredShellperPids, findHuskShellpers, sweepShellperHusks, resolveHuskGraceMs, } from './shellper-husk-sweep.js';
47
+ import { getProcessStartTime } from '../../terminal/session-manager.js';
45
48
  import { getWorkspaceTerminals, getTerminalManager, getWorkspaceTerminalsEntry, getNextShellId, saveTerminalSession, isSessionPersistent, deleteTerminalSession, removeTerminalFromRegistry, deleteWorkspaceTerminalSessions, deleteFileTabsForWorkspace, saveFileTab, deleteFileTab, getRehydratedTerminalsEntry, isStartupReconcileSettled, getTerminalSessionById, getActiveShellLabels, updateTerminalLabel, } from './tower-terminals.js';
46
49
  const __filename = fileURLToPath(import.meta.url);
47
50
  const __dirname = path.dirname(__filename);
@@ -65,7 +68,9 @@ export function stopSendBuffer() {
65
68
  sendBuffer.stop();
66
69
  }
67
70
  const ROUTES = {
68
- 'GET /health': (_req, res) => handleHealthCheck(res),
71
+ 'GET /health': (_req, res, _url, ctx) => handleHealthCheck(res, ctx),
72
+ 'GET /api/shellpers/husks': (_req, res, _url, ctx) => handleHuskPreview(res, ctx),
73
+ 'POST /api/shellpers/husks/sweep': (_req, res, _url, ctx) => handleHuskSweep(res, ctx),
69
74
  'GET /api/workspaces': (_req, res) => handleListWorkspaces(res),
70
75
  'POST /api/terminals': (req, res, _url, ctx) => handleTerminalCreate(req, res, ctx),
71
76
  'GET /api/terminals': (_req, res) => handleTerminalList(res),
@@ -177,9 +182,56 @@ export async function handleRequest(req, res, ctx) {
177
182
  // ============================================================================
178
183
  // Global route handlers
179
184
  // ============================================================================
180
- async function handleHealthCheck(res) {
185
+ /**
186
+ * Issue #1227: fleet RSS + unregistered-shellper count, scoped to this Tower
187
+ * instance's socketDir. `fleetRssKb` sums every in-scope shellper AND its
188
+ * direct children (the full process-group tree Tower manages) regardless of
189
+ * DB-registration state, so a husk not yet swept still counts toward the
190
+ * visible cost. `unregisteredShellperCount` is the lighter, ungated signal
191
+ * ("N shellpers Tower doesn't currently track") — distinct from the stricter
192
+ * childless+aged reap predicate in shellper-husk-sweep.ts.
193
+ *
194
+ * Best-effort: returns undefined fields (never throws) if `ps` or the DB read
195
+ * fails, so a fleet-accounting hiccup never takes down /health itself.
196
+ */
197
+ async function computeFleetHealthFields(ctx) {
198
+ const shellperManager = ctx.getShellperManager();
199
+ if (!shellperManager)
200
+ return {};
201
+ try {
202
+ const census = await listProcessCensus();
203
+ const scopeMarker = shellperManager.socketDir.endsWith('/')
204
+ ? shellperManager.socketDir
205
+ : `${shellperManager.socketDir}/`;
206
+ const inScopePids = new Set();
207
+ let fleetRssKb = 0;
208
+ for (const entry of census) {
209
+ if (entry.cmdline.includes(SHELLPER_MARKER) && entry.cmdline.includes(scopeMarker)) {
210
+ inScopePids.add(entry.pid);
211
+ fleetRssKb += entry.rssKb;
212
+ }
213
+ }
214
+ for (const entry of census) {
215
+ if (inScopePids.has(entry.ppid)) {
216
+ fleetRssKb += entry.rssKb;
217
+ }
218
+ }
219
+ const registered = await computeRegisteredShellperPids(getGlobalDb());
220
+ let unregisteredShellperCount = 0;
221
+ for (const pid of inScopePids) {
222
+ if (!registered.has(pid))
223
+ unregisteredShellperCount++;
224
+ }
225
+ return { fleetRssKb, unregisteredShellperCount };
226
+ }
227
+ catch {
228
+ return {};
229
+ }
230
+ }
231
+ async function handleHealthCheck(res, ctx) {
181
232
  const instances = await getInstances();
182
233
  const activeCount = instances.filter((i) => i.running).length;
234
+ const fleetFields = await computeFleetHealthFields(ctx);
183
235
  res.writeHead(200, { 'Content-Type': 'application/json' });
184
236
  res.end(JSON.stringify({
185
237
  status: 'healthy',
@@ -191,9 +243,82 @@ async function handleHealthCheck(res) {
191
243
  activeWorkspaces: activeCount,
192
244
  totalWorkspaces: instances.length,
193
245
  memoryUsage: process.memoryUsage().heapUsed,
246
+ ...fleetFields,
194
247
  timestamp: new Date().toISOString(),
195
248
  }));
196
249
  }
250
+ /**
251
+ * Issue #1227: GET /api/shellpers/husks — preview, read-only. Returns the
252
+ * husk candidates the next sweep (periodic or `--apply`) would reap, without
253
+ * touching anything.
254
+ */
255
+ async function handleHuskPreview(res, ctx) {
256
+ const shellperManager = ctx.getShellperManager();
257
+ if (!shellperManager) {
258
+ res.writeHead(503, { 'Content-Type': 'application/json' });
259
+ res.end(JSON.stringify({ error: 'Shellper manager not initialized' }));
260
+ return;
261
+ }
262
+ try {
263
+ const graceMs = resolveHuskGraceMs();
264
+ // One census snapshot, shared between the candidate decision and the
265
+ // displayed RSS — passing it via the `census` seam avoids a second `ps`
266
+ // scan and guarantees the RSS shown is for the exact snapshot that decided
267
+ // candidacy, not a later, possibly-different one (codex #1227 PR review).
268
+ const census = await listProcessCensus();
269
+ const rssByPid = new Map(census.map((entry) => [entry.pid, entry.rssKb]));
270
+ const registered = await computeRegisteredShellperPids(getGlobalDb());
271
+ const pids = await findHuskShellpers({
272
+ socketDir: shellperManager.socketDir,
273
+ registeredShellperPids: registered,
274
+ graceMs,
275
+ census: () => census,
276
+ });
277
+ const now = Date.now();
278
+ const candidates = await Promise.all(pids.map(async (pid) => {
279
+ const startTime = await getProcessStartTime(pid);
280
+ return {
281
+ pid,
282
+ rssKb: rssByPid.get(pid) ?? 0,
283
+ ageMs: startTime !== null ? now - startTime : null,
284
+ };
285
+ }));
286
+ res.writeHead(200, { 'Content-Type': 'application/json' });
287
+ res.end(JSON.stringify({ candidates, graceMs }));
288
+ }
289
+ catch (err) {
290
+ res.writeHead(500, { 'Content-Type': 'application/json' });
291
+ res.end(JSON.stringify({ error: err.message }));
292
+ }
293
+ }
294
+ /**
295
+ * Issue #1227: POST /api/shellpers/husks/sweep — apply. Actually reaps the
296
+ * current husk candidates. Destructive; the `afx tower sweep-husks` CLI is
297
+ * the only caller expected in practice, and it gates this behind `--apply`
298
+ * plus a confirmation prompt (unless `--yes`).
299
+ */
300
+ async function handleHuskSweep(res, ctx) {
301
+ const shellperManager = ctx.getShellperManager();
302
+ if (!shellperManager) {
303
+ res.writeHead(503, { 'Content-Type': 'application/json' });
304
+ res.end(JSON.stringify({ error: 'Shellper manager not initialized' }));
305
+ return;
306
+ }
307
+ try {
308
+ const result = await sweepShellperHusks({
309
+ socketDir: shellperManager.socketDir,
310
+ db: getGlobalDb(),
311
+ graceMs: resolveHuskGraceMs(),
312
+ log: (msg) => ctx.log('INFO', msg),
313
+ });
314
+ res.writeHead(200, { 'Content-Type': 'application/json' });
315
+ res.end(JSON.stringify(result));
316
+ }
317
+ catch (err) {
318
+ res.writeHead(500, { 'Content-Type': 'application/json' });
319
+ res.end(JSON.stringify({ error: err.message }));
320
+ }
321
+ }
197
322
  /**
198
323
  * GET /api/version (#983) — report the version of the *running* Tower process.
199
324
  *
@@ -448,8 +573,11 @@ async function handleTerminalCreate(req, res, ctx) {
448
573
  ...defaultSessionOptions(),
449
574
  cols: cols || DEFAULT_COLS,
450
575
  });
451
- const replayData = client.getReplayData() ?? Buffer.alloc(0);
576
+ // Read session info BEFORE awaiting replay: an instantly-exiting
577
+ // child's EXIT frame can remove the session from the manager during
578
+ // the await, and this lookup must not miss (#1198).
452
579
  const shellperInfo = shellperManager.getSessionInfo(sessionId);
580
+ const replayData = await client.waitForReplay(); // #1198: fresh shellpers always send REPLAY (possibly empty); awaiting avoids racing early child output
453
581
  const session = manager.createSessionRaw({
454
582
  label: label || `terminal-${sessionId.slice(0, 8)}`,
455
583
  cwd,
@@ -1083,6 +1211,18 @@ async function handleSend(req, res, ctx) {
1083
1211
  }));
1084
1212
  return;
1085
1213
  }
1214
+ // #1198: a session whose shellper connection died still reports status
1215
+ // 'running', but every write to it is dropped. Fail the send loudly
1216
+ // instead of logging "Message sent" for a message that went nowhere.
1217
+ if (!session.writable) {
1218
+ ctx.log('ERROR', `Message DROPPED: ${from ?? 'unknown'} → ${result.agent} (terminal ${result.terminalId.slice(0, 8)}...): terminal not writable (shellper connection down)`);
1219
+ res.writeHead(503, { 'Content-Type': 'application/json' });
1220
+ res.end(JSON.stringify({
1221
+ error: 'TERMINAL_NOT_WRITABLE',
1222
+ message: `Terminal for '${result.agent}' is not accepting input (its process connection is down). Retry shortly; if this persists, check Tower logs.`,
1223
+ }));
1224
+ return;
1225
+ }
1086
1226
  // Format the message based on sender/target
1087
1227
  const isArchitectTarget = result.agent === 'architect';
1088
1228
  let formattedMessage;
@@ -1706,8 +1846,11 @@ async function handleWorkspaceShellCreate(res, ctx, workspacePath) {
1706
1846
  env: shellEnv,
1707
1847
  ...defaultSessionOptions(),
1708
1848
  });
1709
- const replayData = client.getReplayData() ?? Buffer.alloc(0);
1849
+ // Read session info BEFORE awaiting replay: an instantly-exiting
1850
+ // child's EXIT frame can remove the session from the manager during
1851
+ // the await, and this lookup must not miss (#1198).
1710
1852
  const shellperInfo = shellperManager.getSessionInfo(sessionId);
1853
+ const replayData = await client.waitForReplay(); // #1198: fresh shellpers always send REPLAY (possibly empty); awaiting avoids racing early child output
1711
1854
  const session = manager.createSessionRaw({
1712
1855
  label: `Shell ${shellId.replace('shell-', '')}`,
1713
1856
  cwd: workspacePath,