@cluesmith/codev 2.0.0-rc.73 → 2.0.0-rc.74

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 (62) hide show
  1. package/dashboard/dist/assets/{index-CH_utkcW.js → index-b38SaXk5.js} +31 -31
  2. package/dashboard/dist/assets/{index-CH_utkcW.js.map → index-b38SaXk5.js.map} +1 -1
  3. package/dashboard/dist/index.html +1 -1
  4. package/dist/agent-farm/commands/spawn-worktree.js +1 -1
  5. package/dist/agent-farm/db/index.d.ts.map +1 -1
  6. package/dist/agent-farm/db/index.js +56 -2
  7. package/dist/agent-farm/db/index.js.map +1 -1
  8. package/dist/agent-farm/db/schema.d.ts +1 -1
  9. package/dist/agent-farm/db/schema.js +3 -3
  10. package/dist/agent-farm/servers/tower-instances.d.ts +6 -6
  11. package/dist/agent-farm/servers/tower-instances.d.ts.map +1 -1
  12. package/dist/agent-farm/servers/tower-instances.js +47 -34
  13. package/dist/agent-farm/servers/tower-instances.js.map +1 -1
  14. package/dist/agent-farm/servers/tower-routes.d.ts +1 -1
  15. package/dist/agent-farm/servers/tower-routes.js +34 -34
  16. package/dist/agent-farm/servers/tower-server.js +17 -17
  17. package/dist/agent-farm/servers/tower-terminals.d.ts +8 -8
  18. package/dist/agent-farm/servers/tower-terminals.js +46 -46
  19. package/dist/agent-farm/servers/tower-terminals.js.map +1 -1
  20. package/dist/agent-farm/servers/tower-types.d.ts +5 -4
  21. package/dist/agent-farm/servers/tower-types.d.ts.map +1 -1
  22. package/dist/agent-farm/servers/tower-utils.d.ts +7 -0
  23. package/dist/agent-farm/servers/tower-utils.d.ts.map +1 -1
  24. package/dist/agent-farm/servers/tower-utils.js +21 -0
  25. package/dist/agent-farm/servers/tower-utils.js.map +1 -1
  26. package/dist/agent-farm/utils/shell.d.ts +1 -1
  27. package/dist/agent-farm/utils/shell.js +1 -1
  28. package/dist/commands/porch/next.js +4 -4
  29. package/dist/commands/porch/next.js.map +1 -1
  30. package/dist/terminal/pty-manager.d.ts +1 -1
  31. package/dist/terminal/pty-manager.js +5 -5
  32. package/dist/terminal/pty-session.d.ts +20 -20
  33. package/dist/terminal/pty-session.js +55 -55
  34. package/dist/terminal/session-manager.d.ts +15 -15
  35. package/dist/terminal/session-manager.js +34 -34
  36. package/dist/terminal/{shepherd-client.d.ts → shellper-client.d.ts} +10 -10
  37. package/dist/terminal/{shepherd-client.d.ts.map → shellper-client.d.ts.map} +1 -1
  38. package/dist/terminal/{shepherd-client.js → shellper-client.js} +20 -20
  39. package/dist/terminal/{shepherd-client.js.map → shellper-client.js.map} +1 -1
  40. package/dist/terminal/{shepherd-main.d.ts → shellper-main.d.ts} +3 -3
  41. package/dist/terminal/shellper-main.d.ts.map +1 -0
  42. package/dist/terminal/{shepherd-main.js → shellper-main.js} +17 -17
  43. package/dist/terminal/{shepherd-main.js.map → shellper-main.js.map} +1 -1
  44. package/dist/terminal/{shepherd-process.d.ts → shellper-process.d.ts} +8 -8
  45. package/dist/terminal/{shepherd-process.d.ts.map → shellper-process.d.ts.map} +1 -1
  46. package/dist/terminal/{shepherd-process.js → shellper-process.js} +11 -11
  47. package/dist/terminal/{shepherd-process.js.map → shellper-process.js.map} +1 -1
  48. package/dist/terminal/{shepherd-protocol.d.ts → shellper-protocol.d.ts} +5 -5
  49. package/dist/terminal/{shepherd-protocol.d.ts.map → shellper-protocol.d.ts.map} +1 -1
  50. package/dist/terminal/{shepherd-protocol.js → shellper-protocol.js} +5 -5
  51. package/dist/terminal/{shepherd-protocol.js.map → shellper-protocol.js.map} +1 -1
  52. package/dist/terminal/{shepherd-replay-buffer.d.ts → shellper-replay-buffer.d.ts} +4 -4
  53. package/dist/terminal/{shepherd-replay-buffer.d.ts.map → shellper-replay-buffer.d.ts.map} +1 -1
  54. package/dist/terminal/{shepherd-replay-buffer.js → shellper-replay-buffer.js} +4 -4
  55. package/dist/terminal/{shepherd-replay-buffer.js.map → shellper-replay-buffer.js.map} +1 -1
  56. package/package.json +1 -1
  57. package/skeleton/protocols/bugfix/builder-prompt.md +7 -1
  58. package/skeleton/protocols/maintain/protocol.md +3 -3
  59. package/skeleton/protocols/spir/builder-prompt.md +7 -0
  60. package/skeleton/resources/commands/agent-farm.md +2 -2
  61. package/skeleton/roles/builder.md +15 -1
  62. package/dist/terminal/shepherd-main.d.ts.map +0 -1
@@ -20,7 +20,7 @@ import { fileURLToPath } from 'node:url';
20
20
  import { parseJsonBody, isRequestAllowed } from '../utils/server-utils.js';
21
21
  import { isRateLimited, normalizeProjectPath, getLanguageForExt, getMimeTypeForFile, serveStaticFile, } from './tower-utils.js';
22
22
  import { handleTunnelEndpoint } from './tower-tunnel.js';
23
- import { getKnownProjectPaths, getInstances, getDirectorySuggestions, launchInstance, killTerminalWithShepherd, stopInstance, } from './tower-instances.js';
23
+ import { getKnownProjectPaths, getInstances, getDirectorySuggestions, launchInstance, killTerminalWithShellper, stopInstance, } from './tower-instances.js';
24
24
  import { getProjectTerminals, getTerminalManager, getProjectTerminalsEntry, getNextShellId, saveTerminalSession, isSessionPersistent, deleteTerminalSession, deleteProjectTerminalSessions, saveFileTab, deleteFileTab, getTerminalsForProject, } from './tower-terminals.js';
25
25
  const __filename = fileURLToPath(import.meta.url);
26
26
  const __dirname = path.dirname(__filename);
@@ -223,22 +223,22 @@ async function handleTerminalCreate(req, res, ctx) {
223
223
  const cwd = typeof body.cwd === 'string' ? body.cwd : undefined;
224
224
  const env = typeof body.env === 'object' && body.env !== null ? body.env : undefined;
225
225
  const label = typeof body.label === 'string' ? body.label : undefined;
226
- // Optional session persistence via shepherd
226
+ // Optional session persistence via shellper
227
227
  const projectPath = typeof body.projectPath === 'string' ? body.projectPath : null;
228
228
  const termType = typeof body.type === 'string' && ['builder', 'shell'].includes(body.type) ? body.type : null;
229
229
  const roleId = typeof body.roleId === 'string' ? body.roleId : null;
230
230
  const requestPersistence = body.persistent === true;
231
231
  let info;
232
232
  let persistent = false;
233
- // Try shepherd if persistence was requested
234
- const shepherdManager = ctx.getShepherdManager();
235
- if (requestPersistence && shepherdManager && command && cwd) {
233
+ // Try shellper if persistence was requested
234
+ const shellperManager = ctx.getShellperManager();
235
+ if (requestPersistence && shellperManager && command && cwd) {
236
236
  try {
237
237
  const sessionId = crypto.randomUUID();
238
238
  // Strip CLAUDECODE so spawned Claude processes don't detect nesting
239
239
  const sessionEnv = { ...(env || process.env) };
240
240
  delete sessionEnv['CLAUDECODE'];
241
- const client = await shepherdManager.createSession({
241
+ const client = await shellperManager.createSession({
242
242
  sessionId,
243
243
  command,
244
244
  args: args || [],
@@ -249,14 +249,14 @@ async function handleTerminalCreate(req, res, ctx) {
249
249
  restartOnExit: false,
250
250
  });
251
251
  const replayData = client.getReplayData() ?? Buffer.alloc(0);
252
- const shepherdInfo = shepherdManager.getSessionInfo(sessionId);
252
+ const shellperInfo = shellperManager.getSessionInfo(sessionId);
253
253
  const session = manager.createSessionRaw({
254
254
  label: label || `terminal-${sessionId.slice(0, 8)}`,
255
255
  cwd,
256
256
  });
257
257
  const ptySession = manager.getSession(session.id);
258
258
  if (ptySession) {
259
- ptySession.attachShepherd(client, replayData, shepherdInfo.pid, sessionId);
259
+ ptySession.attachShellper(client, replayData, shellperInfo.pid, sessionId);
260
260
  }
261
261
  info = session;
262
262
  persistent = true;
@@ -268,16 +268,16 @@ async function handleTerminalCreate(req, res, ctx) {
268
268
  else {
269
269
  entry.shells.set(roleId, session.id);
270
270
  }
271
- saveTerminalSession(session.id, projectPath, termType, roleId, shepherdInfo.pid, shepherdInfo.socketPath, shepherdInfo.pid, shepherdInfo.startTime);
272
- ctx.log('INFO', `Registered shepherd terminal ${session.id} as ${termType} "${roleId}" for project ${projectPath}`);
271
+ saveTerminalSession(session.id, projectPath, termType, roleId, shellperInfo.pid, shellperInfo.socketPath, shellperInfo.pid, shellperInfo.startTime);
272
+ ctx.log('INFO', `Registered shellper terminal ${session.id} as ${termType} "${roleId}" for project ${projectPath}`);
273
273
  }
274
274
  }
275
- catch (shepherdErr) {
276
- ctx.log('WARN', `Shepherd creation failed for terminal, falling back: ${shepherdErr.message}`);
275
+ catch (shellperErr) {
276
+ ctx.log('WARN', `Shellper creation failed for terminal, falling back: ${shellperErr.message}`);
277
277
  }
278
278
  }
279
279
  // Fallback: non-persistent session (graceful degradation per plan)
280
- // Shepherd is the only persistence backend for new sessions.
280
+ // Shellper is the only persistence backend for new sessions.
281
281
  if (!info) {
282
282
  info = await manager.createSession({ command, args, cols, rows, cwd, env, label });
283
283
  persistent = false;
@@ -290,7 +290,7 @@ async function handleTerminalCreate(req, res, ctx) {
290
290
  entry.shells.set(roleId, info.id);
291
291
  }
292
292
  saveTerminalSession(info.id, projectPath, termType, roleId, info.pid);
293
- ctx.log('WARN', `Terminal ${info.id} for ${projectPath} is non-persistent (shepherd unavailable)`);
293
+ ctx.log('WARN', `Terminal ${info.id} for ${projectPath} is non-persistent (shellper unavailable)`);
294
294
  }
295
295
  }
296
296
  res.writeHead(201, { 'Content-Type': 'application/json' });
@@ -324,9 +324,9 @@ async function handleTerminalRoutes(req, res, url, match) {
324
324
  res.end(JSON.stringify(session.info));
325
325
  return;
326
326
  }
327
- // DELETE /api/terminals/:id - Kill terminal (disable shepherd auto-restart if applicable)
327
+ // DELETE /api/terminals/:id - Kill terminal (disable shellper auto-restart if applicable)
328
328
  if (req.method === 'DELETE' && (!subpath || subpath === '')) {
329
- if (!(await killTerminalWithShepherd(manager, terminalId))) {
329
+ if (!(await killTerminalWithShellper(manager, terminalId))) {
330
330
  res.writeHead(404, { 'Content-Type': 'application/json' });
331
331
  res.end(JSON.stringify({ error: 'NOT_FOUND', message: `Session ${terminalId} not found` }));
332
332
  return;
@@ -798,7 +798,7 @@ async function handleProjectRoutes(req, res, ctx, url) {
798
798
  // ============================================================================
799
799
  async function handleProjectState(res, projectPath) {
800
800
  // Refresh cache via getTerminalsForProject (handles SQLite sync
801
- // and shepherd reconnection in one place)
801
+ // and shellper reconnection in one place)
802
802
  const encodedPath = Buffer.from(projectPath).toString('base64url');
803
803
  const proxyUrl = `/project/${encodedPath}/`;
804
804
  const { gateStatus } = await getTerminalsForProject(projectPath, proxyUrl);
@@ -877,15 +877,15 @@ async function handleProjectShellCreate(res, ctx, projectPath) {
877
877
  const shellCmd = process.env.SHELL || '/bin/bash';
878
878
  const shellArgs = [];
879
879
  let shellCreated = false;
880
- // Try shepherd first for persistent shell session
881
- const shepherdManager = ctx.getShepherdManager();
882
- if (shepherdManager) {
880
+ // Try shellper first for persistent shell session
881
+ const shellperManager = ctx.getShellperManager();
882
+ if (shellperManager) {
883
883
  try {
884
884
  const sessionId = crypto.randomUUID();
885
885
  // Strip CLAUDECODE so spawned Claude processes don't detect nesting
886
886
  const shellEnv = { ...process.env };
887
887
  delete shellEnv['CLAUDECODE'];
888
- const client = await shepherdManager.createSession({
888
+ const client = await shellperManager.createSession({
889
889
  sessionId,
890
890
  command: shellCmd,
891
891
  args: shellArgs,
@@ -896,18 +896,18 @@ async function handleProjectShellCreate(res, ctx, projectPath) {
896
896
  restartOnExit: false,
897
897
  });
898
898
  const replayData = client.getReplayData() ?? Buffer.alloc(0);
899
- const shepherdInfo = shepherdManager.getSessionInfo(sessionId);
899
+ const shellperInfo = shellperManager.getSessionInfo(sessionId);
900
900
  const session = manager.createSessionRaw({
901
901
  label: `Shell ${shellId.replace('shell-', '')}`,
902
902
  cwd: projectPath,
903
903
  });
904
904
  const ptySession = manager.getSession(session.id);
905
905
  if (ptySession) {
906
- ptySession.attachShepherd(client, replayData, shepherdInfo.pid, sessionId);
906
+ ptySession.attachShellper(client, replayData, shellperInfo.pid, sessionId);
907
907
  }
908
908
  const entry = getProjectTerminalsEntry(projectPath);
909
909
  entry.shells.set(shellId, session.id);
910
- saveTerminalSession(session.id, projectPath, 'shell', shellId, shepherdInfo.pid, shepherdInfo.socketPath, shepherdInfo.pid, shepherdInfo.startTime);
910
+ saveTerminalSession(session.id, projectPath, 'shell', shellId, shellperInfo.pid, shellperInfo.socketPath, shellperInfo.pid, shellperInfo.startTime);
911
911
  shellCreated = true;
912
912
  res.writeHead(200, { 'Content-Type': 'application/json' });
913
913
  res.end(JSON.stringify({
@@ -918,12 +918,12 @@ async function handleProjectShellCreate(res, ctx, projectPath) {
918
918
  persistent: true,
919
919
  }));
920
920
  }
921
- catch (shepherdErr) {
922
- ctx.log('WARN', `Shepherd creation failed for shell, falling back: ${shepherdErr.message}`);
921
+ catch (shellperErr) {
922
+ ctx.log('WARN', `Shellper creation failed for shell, falling back: ${shellperErr.message}`);
923
923
  }
924
924
  }
925
925
  // Fallback: non-persistent session (graceful degradation per plan)
926
- // Shepherd is the only persistence backend for new sessions.
926
+ // Shellper is the only persistence backend for new sessions.
927
927
  if (!shellCreated) {
928
928
  const session = await manager.createSession({
929
929
  command: shellCmd,
@@ -935,7 +935,7 @@ async function handleProjectShellCreate(res, ctx, projectPath) {
935
935
  const entry = getProjectTerminalsEntry(projectPath);
936
936
  entry.shells.set(shellId, session.id);
937
937
  saveTerminalSession(session.id, projectPath, 'shell', shellId, session.pid);
938
- ctx.log('WARN', `Shell ${shellId} for ${projectPath} is non-persistent (shepherd unavailable)`);
938
+ ctx.log('WARN', `Shell ${shellId} for ${projectPath} is non-persistent (shellper unavailable)`);
939
939
  res.writeHead(200, { 'Content-Type': 'application/json' });
940
940
  res.end(JSON.stringify({
941
941
  id: shellId,
@@ -1173,8 +1173,8 @@ async function handleProjectTabDelete(res, ctx, projectPath, tabId) {
1173
1173
  }
1174
1174
  }
1175
1175
  if (terminalId) {
1176
- // Disable shepherd auto-restart if applicable, then kill the PtySession
1177
- await killTerminalWithShepherd(manager, terminalId);
1176
+ // Disable shellper auto-restart if applicable, then kill the PtySession
1177
+ await killTerminalWithShellper(manager, terminalId);
1178
1178
  // TICK-001: Delete from SQLite
1179
1179
  deleteTerminalSession(terminalId);
1180
1180
  res.writeHead(204);
@@ -1188,15 +1188,15 @@ async function handleProjectTabDelete(res, ctx, projectPath, tabId) {
1188
1188
  async function handleProjectStopAll(res, projectPath) {
1189
1189
  const entry = getProjectTerminalsEntry(projectPath);
1190
1190
  const manager = getTerminalManager();
1191
- // Kill all terminals (disable shepherd auto-restart if applicable)
1191
+ // Kill all terminals (disable shellper auto-restart if applicable)
1192
1192
  if (entry.architect) {
1193
- await killTerminalWithShepherd(manager, entry.architect);
1193
+ await killTerminalWithShellper(manager, entry.architect);
1194
1194
  }
1195
1195
  for (const terminalId of entry.shells.values()) {
1196
- await killTerminalWithShepherd(manager, terminalId);
1196
+ await killTerminalWithShellper(manager, terminalId);
1197
1197
  }
1198
1198
  for (const terminalId of entry.builders.values()) {
1199
- await killTerminalWithShepherd(manager, terminalId);
1199
+ await killTerminalWithShellper(manager, terminalId);
1200
1200
  }
1201
1201
  // Clear registry
1202
1202
  getProjectTerminals().delete(projectPath);
@@ -26,8 +26,8 @@ const __dirname = path.dirname(__filename);
26
26
  const DEFAULT_PORT = 4100;
27
27
  // Rate limiting: cleanup interval for token bucket
28
28
  const rateLimitCleanupInterval = startRateLimitCleanup();
29
- // Shepherd session manager (initialized at startup)
30
- let shepherdManager = null;
29
+ // Shellper session manager (initialized at startup)
30
+ let shellperManager = null;
31
31
  // Parse arguments with Commander
32
32
  const program = new Command()
33
33
  .name('tower-server')
@@ -84,14 +84,14 @@ async function gracefulShutdown(signal) {
84
84
  }
85
85
  terminalWss.close();
86
86
  }
87
- // 3. Shepherd clients: do NOT call shepherdManager.shutdown() here.
88
- // SessionManager.shutdown() disconnects sockets, which triggers ShepherdClient
87
+ // 3. Shellper clients: do NOT call shellperManager.shutdown() here.
88
+ // SessionManager.shutdown() disconnects sockets, which triggers ShellperClient
89
89
  // 'close' events → PtySession exit(-1) → SQLite row deletion. This would erase
90
90
  // the rows that reconcileTerminalSessions() needs on restart.
91
- // Instead, let the process exit naturally — OS closes all sockets, and shepherds
91
+ // Instead, let the process exit naturally — OS closes all sockets, and shellpers
92
92
  // detect the disconnection and keep running. SQLite rows are preserved.
93
- if (shepherdManager) {
94
- log('INFO', 'Shepherd sessions will continue running (sockets close on process exit)');
93
+ if (shellperManager) {
94
+ log('INFO', 'Shellper sessions will continue running (sockets close on process exit)');
95
95
  }
96
96
  // 4. Stop rate limit cleanup
97
97
  clearInterval(rateLimitCleanupInterval);
@@ -168,7 +168,7 @@ const routeCtx = {
168
168
  templatePath,
169
169
  reactDashboardPath,
170
170
  hasReactDashboard,
171
- getShepherdManager: () => shepherdManager,
171
+ getShellperManager: () => shellperManager,
172
172
  broadcastNotification,
173
173
  addSseClient: (client) => {
174
174
  sseClients.push(client);
@@ -189,23 +189,23 @@ const server = http.createServer(async (req, res) => {
189
189
  // SECURITY: Bind to localhost only to prevent network exposure
190
190
  server.listen(port, '127.0.0.1', async () => {
191
191
  log('INFO', `Tower server listening at http://localhost:${port}`);
192
- // Initialize shepherd session manager for persistent terminals
192
+ // Initialize shellper session manager for persistent terminals
193
193
  const socketDir = path.join(homedir(), '.codev', 'run');
194
- const shepherdScript = path.join(__dirname, '..', '..', 'terminal', 'shepherd-main.js');
195
- shepherdManager = new SessionManager({
194
+ const shellperScript = path.join(__dirname, '..', '..', 'terminal', 'shellper-main.js');
195
+ shellperManager = new SessionManager({
196
196
  socketDir,
197
- shepherdScript,
197
+ shellperScript,
198
198
  nodeExecutable: process.execPath,
199
199
  });
200
- const staleCleaned = await shepherdManager.cleanupStaleSockets();
200
+ const staleCleaned = await shellperManager.cleanupStaleSockets();
201
201
  if (staleCleaned > 0) {
202
- log('INFO', `Cleaned up ${staleCleaned} stale shepherd socket(s)`);
202
+ log('INFO', `Cleaned up ${staleCleaned} stale shellper socket(s)`);
203
203
  }
204
- log('INFO', 'Shepherd session manager initialized');
204
+ log('INFO', 'Shellper session manager initialized');
205
205
  // Spec 0105 Phase 4: Initialize terminal management module
206
206
  initTerminals({
207
207
  log,
208
- shepherdManager,
208
+ shellperManager,
209
209
  registerKnownProject,
210
210
  getKnownProjectPaths,
211
211
  });
@@ -216,7 +216,7 @@ server.listen(port, '127.0.0.1', async () => {
216
216
  log,
217
217
  projectTerminals: getProjectTerminals(),
218
218
  getTerminalManager,
219
- shepherdManager,
219
+ shellperManager,
220
220
  getProjectTerminalsEntry,
221
221
  saveTerminalSession,
222
222
  deleteTerminalSession,
@@ -15,8 +15,8 @@ import type { ProjectTerminals, TerminalEntry, DbTerminalSession } from './tower
15
15
  export interface TerminalDeps {
16
16
  /** Logging function */
17
17
  log: (level: 'INFO' | 'ERROR' | 'WARN', msg: string) => void;
18
- /** Shepherd session manager for persistent terminals */
19
- shepherdManager: SessionManager | null;
18
+ /** Shellper session manager for persistent terminals */
19
+ shellperManager: SessionManager | null;
20
20
  /** Register a known project path (from tower-instances) */
21
21
  registerKnownProject: (projectPath: string) => void;
22
22
  /** Get all known project paths (from tower-instances) */
@@ -47,9 +47,9 @@ export declare function getNextShellId(projectPath: string): string;
47
47
  * Save a terminal session to SQLite.
48
48
  * Guards against race conditions by checking if project is still active.
49
49
  */
50
- export declare function saveTerminalSession(terminalId: string, projectPath: string, type: 'architect' | 'builder' | 'shell', roleId: string | null, pid: number | null, shepherdSocket?: string | null, shepherdPid?: number | null, shepherdStartTime?: number | null): void;
50
+ export declare function saveTerminalSession(terminalId: string, projectPath: string, type: 'architect' | 'builder' | 'shell', roleId: string | null, pid: number | null, shellperSocket?: string | null, shellperPid?: number | null, shellperStartTime?: number | null): void;
51
51
  /**
52
- * Check if a terminal session is persistent (shepherd-backed).
52
+ * Check if a terminal session is persistent (shellper-backed).
53
53
  * A session is persistent if it can survive a Tower restart.
54
54
  */
55
55
  export declare function isSessionPersistent(_terminalId: string, session: PtySession): boolean;
@@ -89,11 +89,11 @@ export declare function processExists(pid: number): boolean;
89
89
  /**
90
90
  * Reconcile terminal sessions on startup.
91
91
  *
92
- * DUAL-SOURCE STRATEGY (shepherd + SQLite):
92
+ * DUAL-SOURCE STRATEGY (shellper + SQLite):
93
93
  *
94
- * Phase 1 — Shepherd reconnection:
95
- * For SQLite rows with shepherd_socket IS NOT NULL, attempt to reconnect
96
- * via SessionManager.reconnectSession(). Shepherd processes survive Tower
94
+ * Phase 1 — Shellper reconnection:
95
+ * For SQLite rows with shellper_socket IS NOT NULL, attempt to reconnect
96
+ * via SessionManager.reconnectSession(). Shellper processes survive Tower
97
97
  * restarts as detached OS processes.
98
98
  *
99
99
  * Phase 2 — SQLite sweep:
@@ -13,7 +13,7 @@ import { getGateStatusForProject } from '../utils/gate-status.js';
13
13
  import { GateWatcher } from '../utils/gate-watcher.js';
14
14
  import { saveFileTab as saveFileTabToDb, deleteFileTab as deleteFileTabFromDb, loadFileTabsForProject as loadFileTabsFromDb, } from '../utils/file-tabs.js';
15
15
  import { TerminalManager } from '../../terminal/pty-manager.js';
16
- import { normalizeProjectPath } from './tower-utils.js';
16
+ import { normalizeProjectPath, buildArchitectArgs } from './tower-utils.js';
17
17
  // ============================================================================
18
18
  // Module-private state (lifecycle driven by orchestrator)
19
19
  // ============================================================================
@@ -110,7 +110,7 @@ export function getNextShellId(projectPath) {
110
110
  * Save a terminal session to SQLite.
111
111
  * Guards against race conditions by checking if project is still active.
112
112
  */
113
- export function saveTerminalSession(terminalId, projectPath, type, roleId, pid, shepherdSocket = null, shepherdPid = null, shepherdStartTime = null) {
113
+ export function saveTerminalSession(terminalId, projectPath, type, roleId, pid, shellperSocket = null, shellperPid = null, shellperStartTime = null) {
114
114
  try {
115
115
  const normalizedPath = normalizeProjectPath(projectPath);
116
116
  // Race condition guard: only save if project is still in the active registry
@@ -121,9 +121,9 @@ export function saveTerminalSession(terminalId, projectPath, type, roleId, pid,
121
121
  }
122
122
  const db = getGlobalDb();
123
123
  db.prepare(`
124
- INSERT OR REPLACE INTO terminal_sessions (id, project_path, type, role_id, pid, shepherd_socket, shepherd_pid, shepherd_start_time)
124
+ INSERT OR REPLACE INTO terminal_sessions (id, project_path, type, role_id, pid, shellper_socket, shellper_pid, shellper_start_time)
125
125
  VALUES (?, ?, ?, ?, ?, ?, ?, ?)
126
- `).run(terminalId, normalizedPath, type, roleId, pid, shepherdSocket, shepherdPid, shepherdStartTime);
126
+ `).run(terminalId, normalizedPath, type, roleId, pid, shellperSocket, shellperPid, shellperStartTime);
127
127
  _deps?.log('INFO', `Saved terminal session to SQLite: ${terminalId} (${type}) for ${path.basename(normalizedPath)}`);
128
128
  }
129
129
  catch (err) {
@@ -131,11 +131,11 @@ export function saveTerminalSession(terminalId, projectPath, type, roleId, pid,
131
131
  }
132
132
  }
133
133
  /**
134
- * Check if a terminal session is persistent (shepherd-backed).
134
+ * Check if a terminal session is persistent (shellper-backed).
135
135
  * A session is persistent if it can survive a Tower restart.
136
136
  */
137
137
  export function isSessionPersistent(_terminalId, session) {
138
- return session.shepherdBacked;
138
+ return session.shellperBacked;
139
139
  }
140
140
  /**
141
141
  * Delete a terminal session from SQLite
@@ -244,11 +244,11 @@ export function processExists(pid) {
244
244
  /**
245
245
  * Reconcile terminal sessions on startup.
246
246
  *
247
- * DUAL-SOURCE STRATEGY (shepherd + SQLite):
247
+ * DUAL-SOURCE STRATEGY (shellper + SQLite):
248
248
  *
249
- * Phase 1 — Shepherd reconnection:
250
- * For SQLite rows with shepherd_socket IS NOT NULL, attempt to reconnect
251
- * via SessionManager.reconnectSession(). Shepherd processes survive Tower
249
+ * Phase 1 — Shellper reconnection:
250
+ * For SQLite rows with shellper_socket IS NOT NULL, attempt to reconnect
251
+ * via SessionManager.reconnectSession(). Shellper processes survive Tower
252
252
  * restarts as detached OS processes.
253
253
  *
254
254
  * Phase 2 — SQLite sweep:
@@ -262,13 +262,13 @@ export async function reconcileTerminalSessions() {
262
262
  return;
263
263
  const manager = getTerminalManager();
264
264
  const db = getGlobalDb();
265
- let shepherdReconnected = 0;
265
+ let shellperReconnected = 0;
266
266
  let orphanReconnected = 0;
267
267
  let killed = 0;
268
268
  let cleaned = 0;
269
269
  // Track matched session IDs across all phases
270
270
  const matchedSessionIds = new Set();
271
- // ---- Phase 1: Shepherd reconnection ----
271
+ // ---- Phase 1: Shellper reconnection ----
272
272
  let allDbSessions;
273
273
  try {
274
274
  allDbSessions = db.prepare('SELECT * FROM terminal_sessions').all();
@@ -277,19 +277,19 @@ export async function reconcileTerminalSessions() {
277
277
  _deps.log('WARN', `Failed to read terminal sessions: ${err.message}`);
278
278
  allDbSessions = [];
279
279
  }
280
- const shepherdSessions = allDbSessions.filter(s => s.shepherd_socket !== null);
281
- if (shepherdSessions.length > 0) {
282
- _deps.log('INFO', `Found ${shepherdSessions.length} shepherd session(s) in SQLite — reconnecting...`);
280
+ const shellperSessions = allDbSessions.filter(s => s.shellper_socket !== null);
281
+ if (shellperSessions.length > 0) {
282
+ _deps.log('INFO', `Found ${shellperSessions.length} shellper session(s) in SQLite — reconnecting...`);
283
283
  }
284
- for (const dbSession of shepherdSessions) {
284
+ for (const dbSession of shellperSessions) {
285
285
  const projectPath = dbSession.project_path;
286
286
  // Skip sessions whose project path doesn't exist or is in temp directory
287
287
  if (!fs.existsSync(projectPath)) {
288
- _deps.log('INFO', `Skipping shepherd session ${dbSession.id} — project path no longer exists: ${projectPath}`);
289
- // Kill orphaned shepherd process before removing row
290
- if (dbSession.shepherd_pid && processExists(dbSession.shepherd_pid)) {
288
+ _deps.log('INFO', `Skipping shellper session ${dbSession.id} — project path no longer exists: ${projectPath}`);
289
+ // Kill orphaned shellper process before removing row
290
+ if (dbSession.shellper_pid && processExists(dbSession.shellper_pid)) {
291
291
  try {
292
- process.kill(dbSession.shepherd_pid, 'SIGTERM');
292
+ process.kill(dbSession.shellper_pid, 'SIGTERM');
293
293
  killed++;
294
294
  }
295
295
  catch { /* not killable */ }
@@ -300,11 +300,11 @@ export async function reconcileTerminalSessions() {
300
300
  }
301
301
  const tmpDirs = ['/tmp', '/private/tmp', '/var/folders', '/private/var/folders'];
302
302
  if (tmpDirs.some(d => projectPath === d || projectPath.startsWith(d + '/'))) {
303
- _deps.log('INFO', `Skipping shepherd session ${dbSession.id} — project is in temp directory: ${projectPath}`);
304
- // Kill orphaned shepherd process before removing row
305
- if (dbSession.shepherd_pid && processExists(dbSession.shepherd_pid)) {
303
+ _deps.log('INFO', `Skipping shellper session ${dbSession.id} — project is in temp directory: ${projectPath}`);
304
+ // Kill orphaned shellper process before removing row
305
+ if (dbSession.shellper_pid && processExists(dbSession.shellper_pid)) {
306
306
  try {
307
- process.kill(dbSession.shepherd_pid, 'SIGTERM');
307
+ process.kill(dbSession.shellper_pid, 'SIGTERM');
308
308
  killed++;
309
309
  }
310
310
  catch { /* not killable */ }
@@ -313,8 +313,8 @@ export async function reconcileTerminalSessions() {
313
313
  cleaned++;
314
314
  continue;
315
315
  }
316
- if (!_deps.shepherdManager) {
317
- _deps.log('WARN', `Shepherd manager not initialized — cannot reconnect ${dbSession.id}`);
316
+ if (!_deps.shellperManager) {
317
+ _deps.log('WARN', `Shellper manager not initialized — cannot reconnect ${dbSession.id}`);
318
318
  continue;
319
319
  }
320
320
  try {
@@ -337,25 +337,25 @@ export async function reconcileTerminalSessions() {
337
337
  delete cleanEnv['CLAUDECODE'];
338
338
  restartOptions = {
339
339
  command: cmdParts[0],
340
- args: cmdParts.slice(1),
340
+ args: buildArchitectArgs(cmdParts.slice(1), projectPath),
341
341
  cwd: projectPath,
342
342
  env: cleanEnv,
343
343
  restartDelay: 2000,
344
344
  maxRestarts: 50,
345
345
  };
346
346
  }
347
- const client = await _deps.shepherdManager.reconnectSession(dbSession.id, dbSession.shepherd_socket, dbSession.shepherd_pid, dbSession.shepherd_start_time, restartOptions);
347
+ const client = await _deps.shellperManager.reconnectSession(dbSession.id, dbSession.shellper_socket, dbSession.shellper_pid, dbSession.shellper_start_time, restartOptions);
348
348
  if (!client) {
349
- _deps.log('INFO', `Shepherd session ${dbSession.id} is stale (PID/socket dead) — will clean up`);
349
+ _deps.log('INFO', `Shellper session ${dbSession.id} is stale (PID/socket dead) — will clean up`);
350
350
  continue; // Will be cleaned up in Phase 2
351
351
  }
352
352
  const replayData = client.getReplayData() ?? Buffer.alloc(0);
353
353
  const label = dbSession.type === 'architect' ? 'Architect' : `${dbSession.type} ${dbSession.role_id || 'unknown'}`;
354
- // Create a PtySession backed by the reconnected shepherd client
354
+ // Create a PtySession backed by the reconnected shellper client
355
355
  const session = manager.createSessionRaw({ label, cwd: projectPath });
356
356
  const ptySession = manager.getSession(session.id);
357
357
  if (ptySession) {
358
- ptySession.attachShepherd(client, replayData, dbSession.shepherd_pid, dbSession.id);
358
+ ptySession.attachShellper(client, replayData, dbSession.shellper_pid, dbSession.id);
359
359
  }
360
360
  // Register in projectTerminals Map
361
361
  const entry = getProjectTerminalsEntry(projectPath);
@@ -370,7 +370,7 @@ export async function reconcileTerminalSessions() {
370
370
  }
371
371
  // Update SQLite with new terminal ID
372
372
  db.prepare('DELETE FROM terminal_sessions WHERE id = ?').run(dbSession.id);
373
- saveTerminalSession(session.id, projectPath, dbSession.type, dbSession.role_id, dbSession.shepherd_pid, dbSession.shepherd_socket, dbSession.shepherd_pid, dbSession.shepherd_start_time);
373
+ saveTerminalSession(session.id, projectPath, dbSession.type, dbSession.role_id, dbSession.shellper_pid, dbSession.shellper_socket, dbSession.shellper_pid, dbSession.shellper_start_time);
374
374
  _deps.registerKnownProject(projectPath);
375
375
  // Clean up on exit
376
376
  if (ptySession) {
@@ -383,11 +383,11 @@ export async function reconcileTerminalSessions() {
383
383
  });
384
384
  }
385
385
  matchedSessionIds.add(dbSession.id);
386
- shepherdReconnected++;
387
- _deps.log('INFO', `Reconnected shepherd session → ${session.id} (${dbSession.type} for ${path.basename(projectPath)})`);
386
+ shellperReconnected++;
387
+ _deps.log('INFO', `Reconnected shellper session → ${session.id} (${dbSession.type} for ${path.basename(projectPath)})`);
388
388
  }
389
389
  catch (err) {
390
- _deps.log('WARN', `Failed to reconnect shepherd session ${dbSession.id}: ${err.message}`);
390
+ _deps.log('WARN', `Failed to reconnect shellper session ${dbSession.id}: ${err.message}`);
391
391
  }
392
392
  }
393
393
  // ---- Phase 2: Sweep stale SQLite rows ----
@@ -409,9 +409,9 @@ export async function reconcileTerminalSessions() {
409
409
  db.prepare('DELETE FROM terminal_sessions WHERE id = ?').run(session.id);
410
410
  cleaned++;
411
411
  }
412
- const total = shepherdReconnected + orphanReconnected;
412
+ const total = shellperReconnected + orphanReconnected;
413
413
  if (total > 0 || killed > 0 || cleaned > 0) {
414
- _deps.log('INFO', `Reconciliation complete: ${shepherdReconnected} shepherd, ${orphanReconnected} orphan, ${killed} killed, ${cleaned} stale rows cleaned`);
414
+ _deps.log('INFO', `Reconciliation complete: ${shellperReconnected} shellper, ${orphanReconnected} orphan, ${killed} killed, ${cleaned} stale rows cleaned`);
415
415
  }
416
416
  else {
417
417
  _deps.log('INFO', 'No terminal sessions to reconcile');
@@ -457,7 +457,7 @@ export function stopGateWatcher() {
457
457
  export async function getTerminalsForProject(projectPath, proxyUrl) {
458
458
  const manager = getTerminalManager();
459
459
  const terminals = [];
460
- // Query SQLite first, then augment with shepherd reconnection
460
+ // Query SQLite first, then augment with shellper reconnection
461
461
  const dbSessions = getTerminalSessionsForProject(projectPath);
462
462
  // Use normalized path for cache consistency
463
463
  const normalizedPath = normalizeProjectPath(projectPath);
@@ -478,8 +478,8 @@ export async function getTerminalsForProject(projectPath, proxyUrl) {
478
478
  for (const dbSession of dbSessions) {
479
479
  // Verify session still exists in TerminalManager (runtime state)
480
480
  let session = manager.getSession(dbSession.id);
481
- if (!session && dbSession.shepherd_socket && _deps?.shepherdManager) {
482
- // PTY session gone but shepherd may still be alive — reconnect on-the-fly
481
+ if (!session && dbSession.shellper_socket && _deps?.shellperManager) {
482
+ // PTY session gone but shellper may still be alive — reconnect on-the-fly
483
483
  try {
484
484
  // Restore auto-restart for architect sessions (same as startup reconciliation)
485
485
  let restartOptions;
@@ -500,21 +500,21 @@ export async function getTerminalsForProject(projectPath, proxyUrl) {
500
500
  delete cleanEnv['CLAUDECODE'];
501
501
  restartOptions = {
502
502
  command: cmdParts[0],
503
- args: cmdParts.slice(1),
503
+ args: buildArchitectArgs(cmdParts.slice(1), dbSession.project_path),
504
504
  cwd: dbSession.project_path,
505
505
  env: cleanEnv,
506
506
  restartDelay: 2000,
507
507
  maxRestarts: 50,
508
508
  };
509
509
  }
510
- const client = await _deps.shepherdManager.reconnectSession(dbSession.id, dbSession.shepherd_socket, dbSession.shepherd_pid, dbSession.shepherd_start_time, restartOptions);
510
+ const client = await _deps.shellperManager.reconnectSession(dbSession.id, dbSession.shellper_socket, dbSession.shellper_pid, dbSession.shellper_start_time, restartOptions);
511
511
  if (client) {
512
512
  const replayData = client.getReplayData() ?? Buffer.alloc(0);
513
513
  const label = dbSession.type === 'architect' ? 'Architect' : `${dbSession.type} ${dbSession.role_id || dbSession.id}`;
514
514
  const newSession = manager.createSessionRaw({ label, cwd: dbSession.project_path });
515
515
  const ptySession = manager.getSession(newSession.id);
516
516
  if (ptySession) {
517
- ptySession.attachShepherd(client, replayData, dbSession.shepherd_pid, dbSession.id);
517
+ ptySession.attachShellper(client, replayData, dbSession.shellper_pid, dbSession.id);
518
518
  // Clean up on exit (same as startup reconciliation path)
519
519
  ptySession.on('exit', () => {
520
520
  const currentEntry = getProjectTerminalsEntry(dbSession.project_path);
@@ -525,14 +525,14 @@ export async function getTerminalsForProject(projectPath, proxyUrl) {
525
525
  });
526
526
  }
527
527
  deleteTerminalSession(dbSession.id);
528
- saveTerminalSession(newSession.id, dbSession.project_path, dbSession.type, dbSession.role_id, dbSession.shepherd_pid, dbSession.shepherd_socket, dbSession.shepherd_pid, dbSession.shepherd_start_time);
528
+ saveTerminalSession(newSession.id, dbSession.project_path, dbSession.type, dbSession.role_id, dbSession.shellper_pid, dbSession.shellper_socket, dbSession.shellper_pid, dbSession.shellper_start_time);
529
529
  dbSession.id = newSession.id;
530
530
  session = manager.getSession(newSession.id);
531
- _deps.log('INFO', `Reconnected to shepherd on-the-fly → ${newSession.id}`);
531
+ _deps.log('INFO', `Reconnected to shellper on-the-fly → ${newSession.id}`);
532
532
  }
533
533
  }
534
534
  catch (err) {
535
- _deps.log('WARN', `Failed shepherd on-the-fly reconnect for ${dbSession.id}: ${err.message}`);
535
+ _deps.log('WARN', `Failed shellper on-the-fly reconnect for ${dbSession.id}: ${err.message}`);
536
536
  }
537
537
  }
538
538
  if (!session) {