@integrity-labs/agt-cli 0.16.0 → 0.16.2

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.
@@ -1,8 +1,8 @@
1
1
  // src/lib/persistent-session.ts
2
2
  import { spawn, execSync, execFileSync } from "child_process";
3
- import { join, dirname } from "path";
4
- import { homedir, platform } from "os";
5
- import { existsSync, readFileSync as readFileSync2, readdirSync, writeFileSync as writeFileSync2, mkdirSync, chmodSync, copyFileSync, rmSync } from "fs";
3
+ import { join as join2, dirname } from "path";
4
+ import { homedir as homedir2, platform, userInfo } from "os";
5
+ import { existsSync as existsSync2, readFileSync as readFileSync3, readdirSync, writeFileSync as writeFileSync3, appendFileSync, mkdirSync as mkdirSync2, chmodSync, copyFileSync, rmSync } from "fs";
6
6
  import { fileURLToPath } from "url";
7
7
 
8
8
  // src/lib/mcp-sanitize.ts
@@ -46,12 +46,132 @@ function buildAllowedTools(mcpServerNames) {
46
46
  return [...mcpPatterns, ...BASE_TOOLS].join(",");
47
47
  }
48
48
 
49
+ // src/lib/daily-session.ts
50
+ import { randomUUID } from "crypto";
51
+ import { existsSync, mkdirSync, readFileSync as readFileSync2, renameSync, statSync, writeFileSync as writeFileSync2 } from "fs";
52
+ import { homedir } from "os";
53
+ import { join } from "path";
54
+ var HISTORY_DAYS = 7;
55
+ function profileDir(codeName) {
56
+ return join(homedir(), ".augmented", codeName);
57
+ }
58
+ function dailySessionPath(codeName) {
59
+ return join(profileDir(codeName), "daily-session.json");
60
+ }
61
+ function todayLocalIso(now = /* @__PURE__ */ new Date()) {
62
+ const y = now.getFullYear();
63
+ const m = String(now.getMonth() + 1).padStart(2, "0");
64
+ const d = String(now.getDate()).padStart(2, "0");
65
+ return `${y}-${m}-${d}`;
66
+ }
67
+ function readFile(codeName) {
68
+ const path = dailySessionPath(codeName);
69
+ if (!existsSync(path)) return { current: null, history: [] };
70
+ try {
71
+ const raw = readFileSync2(path, "utf-8");
72
+ const parsed = JSON.parse(raw);
73
+ return {
74
+ current: parsed.current ?? null,
75
+ history: Array.isArray(parsed.history) ? parsed.history : []
76
+ };
77
+ } catch {
78
+ return { current: null, history: [] };
79
+ }
80
+ }
81
+ function writeFile(codeName, data) {
82
+ const dir = profileDir(codeName);
83
+ mkdirSync(dir, { recursive: true });
84
+ const finalPath = dailySessionPath(codeName);
85
+ const tmpPath = `${finalPath}.${process.pid}.${randomUUID()}.tmp`;
86
+ writeFileSync2(tmpPath, JSON.stringify(data, null, 2), "utf-8");
87
+ renameSync(tmpPath, finalPath);
88
+ }
89
+ function trimHistory(history, now) {
90
+ const cutoff = new Date(now);
91
+ cutoff.setDate(cutoff.getDate() - HISTORY_DAYS);
92
+ const cutoffIso = todayLocalIso(cutoff);
93
+ return history.filter((h) => h.date >= cutoffIso).slice(0, HISTORY_DAYS);
94
+ }
95
+ function getOrCreateDailySession(codeName, now = /* @__PURE__ */ new Date()) {
96
+ const today = todayLocalIso(now);
97
+ const file = readFile(codeName);
98
+ if (file.current && file.current.date === today) {
99
+ return { sessionId: file.current.sessionId, isNew: false };
100
+ }
101
+ const next = {
102
+ date: today,
103
+ sessionId: randomUUID(),
104
+ startedAt: now.toISOString()
105
+ };
106
+ const history = trimHistory(
107
+ [...file.current ? [file.current] : [], ...file.history],
108
+ now
109
+ );
110
+ writeFile(codeName, { current: next, history });
111
+ return { sessionId: next.sessionId, isNew: true };
112
+ }
113
+ function rotateDailySession(codeName, now = /* @__PURE__ */ new Date()) {
114
+ const today = todayLocalIso(now);
115
+ const file = readFile(codeName);
116
+ const next = {
117
+ date: today,
118
+ sessionId: randomUUID(),
119
+ startedAt: now.toISOString()
120
+ };
121
+ const history = trimHistory(
122
+ [...file.current ? [file.current] : [], ...file.history],
123
+ now
124
+ );
125
+ writeFile(codeName, { current: next, history });
126
+ return next.sessionId;
127
+ }
128
+ function encodeProjectPath(projectDir) {
129
+ return "-" + projectDir.replace(/^\//, "").replace(/[/.]/g, "-");
130
+ }
131
+ function sessionFileExists(projectDir, sessionId) {
132
+ const path = join(
133
+ homedir(),
134
+ ".claude",
135
+ "projects",
136
+ encodeProjectPath(projectDir),
137
+ `${sessionId}.jsonl`
138
+ );
139
+ return existsSync(path);
140
+ }
141
+ function sessionFilePath(projectDir, sessionId) {
142
+ return join(
143
+ homedir(),
144
+ ".claude",
145
+ "projects",
146
+ encodeProjectPath(projectDir),
147
+ `${sessionId}.jsonl`
148
+ );
149
+ }
150
+ function isAgentIdle(projectDir, sessionId, idleSeconds = 60, now = /* @__PURE__ */ new Date()) {
151
+ const path = sessionFilePath(projectDir, sessionId);
152
+ if (!existsSync(path)) return true;
153
+ try {
154
+ const mtimeMs = statSync(path).mtimeMs;
155
+ return now.getTime() - mtimeMs >= idleSeconds * 1e3;
156
+ } catch {
157
+ return false;
158
+ }
159
+ }
160
+ function isStaleForToday(codeName, now = /* @__PURE__ */ new Date()) {
161
+ const file = readFile(codeName);
162
+ if (!file.current) return false;
163
+ return file.current.date !== todayLocalIso(now);
164
+ }
165
+ function peekCurrentSession(codeName) {
166
+ return readFile(codeName).current;
167
+ }
168
+
49
169
  // src/lib/persistent-session.ts
50
170
  function syncClaudeCredsToRoot() {
51
171
  if (platform() !== "linux") return true;
52
172
  if (typeof process.getuid !== "function" || process.getuid() !== 0) return true;
53
173
  for (const filename of [".credentials.json", "credentials.json"]) {
54
- if (existsSync(join("/root/.claude", filename))) return true;
174
+ if (existsSync2(join2("/root/.claude", filename))) return true;
55
175
  }
56
176
  let sourcePath = null;
57
177
  try {
@@ -59,8 +179,8 @@ function syncClaudeCredsToRoot() {
59
179
  outer: for (const entry of entries) {
60
180
  if (!entry.isDirectory()) continue;
61
181
  for (const filename of [".credentials.json", "credentials.json"]) {
62
- const candidate = join("/home", entry.name, ".claude", filename);
63
- if (existsSync(candidate)) {
182
+ const candidate = join2("/home", entry.name, ".claude", filename);
183
+ if (existsSync2(candidate)) {
64
184
  sourcePath = candidate;
65
185
  break outer;
66
186
  }
@@ -71,9 +191,9 @@ function syncClaudeCredsToRoot() {
71
191
  if (!sourcePath) return false;
72
192
  const targetDir = "/root/.claude";
73
193
  const sourceFilename = sourcePath.endsWith("credentials.json") && !sourcePath.endsWith(".credentials.json") ? "credentials.json" : ".credentials.json";
74
- const targetPath = join(targetDir, sourceFilename);
194
+ const targetPath = join2(targetDir, sourceFilename);
75
195
  try {
76
- if (!existsSync(targetDir)) mkdirSync(targetDir, { recursive: true, mode: 448 });
196
+ if (!existsSync2(targetDir)) mkdirSync2(targetDir, { recursive: true, mode: 448 });
77
197
  copyFileSync(sourcePath, targetPath);
78
198
  chmodSync(targetPath, 384);
79
199
  return true;
@@ -85,13 +205,13 @@ var cachedClaudePath = null;
85
205
  function resolveClaudeBinary() {
86
206
  if (cachedClaudePath) return cachedClaudePath;
87
207
  const override = process.env.CLAUDE_PATH;
88
- if (override && existsSync(override)) {
208
+ if (override && existsSync2(override)) {
89
209
  cachedClaudePath = override;
90
210
  return override;
91
211
  }
92
212
  try {
93
213
  const out = execSync("which claude 2>/dev/null", { encoding: "utf-8" }).trim();
94
- if (out && existsSync(out)) {
214
+ if (out && existsSync2(out)) {
95
215
  cachedClaudePath = out;
96
216
  return out;
97
217
  }
@@ -103,7 +223,7 @@ function resolveClaudeBinary() {
103
223
  "/usr/local/bin/claude"
104
224
  ];
105
225
  for (const p of candidates) {
106
- if (existsSync(p)) {
226
+ if (existsSync2(p)) {
107
227
  cachedClaudePath = p;
108
228
  return p;
109
229
  }
@@ -111,9 +231,9 @@ function resolveClaudeBinary() {
111
231
  return "claude";
112
232
  }
113
233
  function collectMcpServerNames(mcpConfigPath) {
114
- if (!existsSync(mcpConfigPath)) return [];
234
+ if (!existsSync2(mcpConfigPath)) return [];
115
235
  try {
116
- const data = JSON.parse(readFileSync2(mcpConfigPath, "utf-8"));
236
+ const data = JSON.parse(readFileSync3(mcpConfigPath, "utf-8"));
117
237
  const servers = data.mcpServers;
118
238
  return servers ? Object.keys(servers) : [];
119
239
  } catch {
@@ -126,8 +246,8 @@ function getAcpxBin() {
126
246
  const moduleDir = dirname(fileURLToPath(import.meta.url));
127
247
  let dir = moduleDir;
128
248
  for (let i = 0; i < 6; i++) {
129
- const candidate = join(dir, "node_modules", ".bin", "acpx");
130
- if (existsSync(candidate)) {
249
+ const candidate = join2(dir, "node_modules", ".bin", "acpx");
250
+ if (existsSync2(candidate)) {
131
251
  _acpxBin = candidate;
132
252
  return _acpxBin;
133
253
  }
@@ -144,6 +264,70 @@ function getAcpxBin() {
144
264
  }
145
265
  }
146
266
  var sessions = /* @__PURE__ */ new Map();
267
+ var PANE_LOG_DIR = join2(homedir2(), ".augmented");
268
+ var PANE_TAIL_LINES = 20;
269
+ function paneLogPath(codeName) {
270
+ return join2(PANE_LOG_DIR, codeName, "pane.log");
271
+ }
272
+ function setupPaneLog(tmuxSession, codeName, log) {
273
+ const logPath = paneLogPath(codeName);
274
+ try {
275
+ mkdirSync2(dirname(logPath), { recursive: true });
276
+ appendFileSync(
277
+ logPath,
278
+ `
279
+ --- spawn ${(/* @__PURE__ */ new Date()).toISOString()} (session ${tmuxSession}) ---
280
+ `,
281
+ "utf-8"
282
+ );
283
+ execSync(
284
+ `tmux pipe-pane -o -t ${tmuxSession} 'cat >> ${logPath.replace(/'/g, `'\\''`)}'`,
285
+ { stdio: "ignore" }
286
+ );
287
+ } catch (err) {
288
+ log(`[persistent-session] pipe-pane setup failed for '${codeName}': ${err.message}`);
289
+ }
290
+ }
291
+ function readPaneLogTail(codeName, lines = PANE_TAIL_LINES) {
292
+ const logPath = paneLogPath(codeName);
293
+ if (!existsSync2(logPath)) return null;
294
+ try {
295
+ const raw = readFileSync3(logPath, "utf-8");
296
+ if (!raw) return null;
297
+ const stripped = raw.replace(/\x1b\[[0-9;?]*[A-Za-z]/g, "");
298
+ const all = stripped.split("\n").filter((l) => l.length > 0);
299
+ return all.slice(-lines).join("\n");
300
+ } catch {
301
+ return null;
302
+ }
303
+ }
304
+ function detectFailureSignature(tail) {
305
+ if (!tail) return "unknown";
306
+ if (/Session ID .* is already in use/i.test(tail)) return "session_id_in_use";
307
+ return "unknown";
308
+ }
309
+ function prepareForRespawn(codeName) {
310
+ const session = sessions.get(codeName);
311
+ if (!session) return null;
312
+ const signature = detectFailureSignature(session.lastFailureTail);
313
+ if (signature === "session_id_in_use" && session.consecutiveSameUuidFailures >= 2) {
314
+ const failureCount = session.consecutiveSameUuidFailures;
315
+ const newId = rotateDailySession(codeName);
316
+ session.consecutiveSameUuidFailures = 0;
317
+ session.lastFailureSessionId = null;
318
+ return `rotated daily-session UUID to ${newId} after ${failureCount}+ "Session ID already in use" failures`;
319
+ }
320
+ return null;
321
+ }
322
+ function getLastFailureContext(codeName) {
323
+ const session = sessions.get(codeName);
324
+ return {
325
+ tail: session?.lastFailureTail ?? null,
326
+ signature: detectFailureSignature(session?.lastFailureTail ?? null),
327
+ consecutiveSameUuid: session?.consecutiveSameUuidFailures ?? 0,
328
+ restartCount: session?.restartCount ?? 0
329
+ };
330
+ }
147
331
  function startPersistentSession(config) {
148
332
  const existing = sessions.get(config.codeName);
149
333
  if (existing && existing.status === "running") {
@@ -160,7 +344,11 @@ function startPersistentSession(config) {
160
344
  codeName: config.codeName,
161
345
  startedAt: null,
162
346
  restartCount,
163
- status: "starting"
347
+ status: "starting",
348
+ currentSessionId: existing?.currentSessionId ?? null,
349
+ lastFailureTail: existing?.lastFailureTail ?? null,
350
+ lastFailureSessionId: existing?.lastFailureSessionId ?? null,
351
+ consecutiveSameUuidFailures: existing?.consecutiveSameUuidFailures ?? 0
164
352
  };
165
353
  sessions.set(config.codeName, session);
166
354
  spawnSession(config, session);
@@ -184,10 +372,10 @@ function spawnSession(config, session) {
184
372
  log(`[persistent-session] No Claude Code credentials found under /root/.claude or /home/*. Pair via browser from the host page, or run 'claude /login' on the host.`);
185
373
  }
186
374
  } else {
187
- const claudeDir = join(homedir(), ".claude");
375
+ const claudeDir = join2(homedir2(), ".claude");
188
376
  for (const filename of [".credentials.json", "credentials.json"]) {
189
- const p = join(claudeDir, filename);
190
- if (existsSync(p)) {
377
+ const p = join2(claudeDir, filename);
378
+ if (existsSync2(p)) {
191
379
  try {
192
380
  rmSync(p, { force: true });
193
381
  log(`[persistent-session] Removed ${p} (api_key mode active \u2014 preventing OAuth fallback)`);
@@ -200,10 +388,21 @@ function spawnSession(config, session) {
200
388
  }
201
389
  }
202
390
  const args = [];
391
+ const dailySession = getOrCreateDailySession(codeName);
392
+ const claudeWillResume = !dailySession.isNew && sessionFileExists(projectDir, dailySession.sessionId);
393
+ if (claudeWillResume) {
394
+ args.push("--resume", dailySession.sessionId);
395
+ log(`[persistent-session] Resuming today's session ${dailySession.sessionId} for '${codeName}'`);
396
+ } else {
397
+ args.push("--session-id", dailySession.sessionId);
398
+ log(
399
+ `[persistent-session] Starting fresh session ${dailySession.sessionId} for '${codeName}' (${dailySession.isNew ? "new day" : "no JSONL on disk yet"})`
400
+ );
401
+ }
203
402
  if (channels.length > 0) args.push("--channels", ...channels);
204
403
  if (devChannels.length > 0) args.push("--dangerously-load-development-channels", ...devChannels);
205
404
  args.push("--mcp-config", mcpConfigPath);
206
- if (existsSync(claudeMdPath)) args.push("--system-prompt-file", claudeMdPath);
405
+ if (existsSync2(claudeMdPath)) args.push("--system-prompt-file", claudeMdPath);
207
406
  args.push("--allow-dangerously-skip-permissions");
208
407
  args.push("--dangerously-skip-permissions");
209
408
  args.push("--strict-mcp-config");
@@ -211,10 +410,10 @@ function spawnSession(config, session) {
211
410
  const mcpServerNames = collectMcpServerNames(mcpConfigPath);
212
411
  args.push("--allowedTools", buildAllowedTools(mcpServerNames));
213
412
  let envPrefix = "IS_SANDBOX=1 ";
214
- const envIntegrationsPath = join(projectDir, ".env.integrations");
215
- if (existsSync(envIntegrationsPath)) {
413
+ const envIntegrationsPath = join2(projectDir, ".env.integrations");
414
+ if (existsSync2(envIntegrationsPath)) {
216
415
  try {
217
- const envContent = readFileSync2(envIntegrationsPath, "utf-8");
416
+ const envContent = readFileSync3(envIntegrationsPath, "utf-8");
218
417
  const envVars = envContent.split("\n").filter((line) => line && !line.startsWith("#") && line.includes("=")).map((line) => {
219
418
  const eqIdx = line.indexOf("=");
220
419
  const key = line.slice(0, eqIdx);
@@ -232,6 +431,14 @@ function spawnSession(config, session) {
232
431
  const initPrompt = 'You are now online. Say "Ready." and wait for incoming messages. Do not run any tools or load any data until a message arrives.';
233
432
  const claudeBin = resolveClaudeBinary();
234
433
  const claudeCmd = `${envPrefix}${JSON.stringify(claudeBin)} ${JSON.stringify(initPrompt)} ${args.map((a) => a.includes(" ") || a.includes("*") ? JSON.stringify(a) : a).join(" ")}`;
434
+ const tmuxEnv = {
435
+ ...process.env,
436
+ // Treat empty-string as missing too — `HOME=""` makes ~ resolve
437
+ // to cwd, which is the same broken outcome as no HOME, just
438
+ // better hidden.
439
+ HOME: process.env.HOME?.trim() || homedir2(),
440
+ USER: process.env.USER?.trim() || userInfo().username
441
+ };
235
442
  const child = spawn("tmux", [
236
443
  "new-session",
237
444
  "-d",
@@ -244,7 +451,7 @@ function spawnSession(config, session) {
244
451
  ], {
245
452
  cwd: projectDir,
246
453
  stdio: ["ignore", "pipe", "pipe"],
247
- env: process.env
454
+ env: tmuxEnv
248
455
  });
249
456
  child.on("close", (code) => {
250
457
  if (code !== 0) {
@@ -255,6 +462,8 @@ function spawnSession(config, session) {
255
462
  return;
256
463
  }
257
464
  log(`[persistent-session] tmux session '${tmuxSession}' created for '${codeName}'`);
465
+ setupPaneLog(tmuxSession, codeName, log);
466
+ session.currentSessionId = dailySession.sessionId;
258
467
  acceptDialogs(tmuxSession, codeName, log).catch(() => {
259
468
  });
260
469
  });
@@ -274,11 +483,58 @@ function spawnSession(config, session) {
274
483
  session.restartCount++;
275
484
  }
276
485
  }
486
+ function isLoginPickerVisible(screen) {
487
+ return screen.includes("Select login method") || screen.includes("Claude account with subscription") && screen.includes("Anthropic Console account");
488
+ }
489
+ function hasMcpChildren(tmuxSession) {
490
+ try {
491
+ const claudePidOut = execSync(
492
+ `pgrep -f -- "--name ${tmuxSession}" 2>/dev/null || true`,
493
+ { encoding: "utf-8" }
494
+ ).trim();
495
+ if (!claudePidOut) return false;
496
+ const pids = claudePidOut.split("\n").map((p) => Number(p)).filter((p) => p > 0);
497
+ if (pids.length === 0) return false;
498
+ const claudePid = Math.max(...pids);
499
+ const childrenOut = execSync(
500
+ `pgrep -P ${claudePid} 2>/dev/null || true`,
501
+ { encoding: "utf-8" }
502
+ ).trim();
503
+ if (!childrenOut) return false;
504
+ const childPids = childrenOut.split("\n").map((p) => p.trim()).filter(Boolean);
505
+ for (const cp of childPids) {
506
+ const cmdline = execSync(
507
+ `cat /proc/${cp}/cmdline 2>/dev/null | tr '\\0' ' ' || ps -p ${cp} -o args= 2>/dev/null || true`,
508
+ { encoding: "utf-8" }
509
+ );
510
+ if (/slack-channel\.js|telegram-channel\.js|direct-chat-channel\.js|composio_/i.test(cmdline)) {
511
+ return true;
512
+ }
513
+ }
514
+ return false;
515
+ } catch {
516
+ return false;
517
+ }
518
+ }
277
519
  async function acceptDialogs(tmuxSession, codeName, log) {
278
- for (let i = 0; i < 15; i++) {
520
+ let loginPickerReported = false;
521
+ let dialogIterations = 0;
522
+ const MAX_DIALOG_ITERATIONS = 15;
523
+ let loginPickerIterations = 0;
524
+ const MAX_LOGIN_PICKER_ITERATIONS = 450;
525
+ while (dialogIterations < MAX_DIALOG_ITERATIONS && loginPickerIterations < MAX_LOGIN_PICKER_ITERATIONS) {
279
526
  await new Promise((r) => setTimeout(r, 2e3));
280
527
  try {
281
528
  const screen = execSync(`tmux capture-pane -t ${tmuxSession} -p 2>/dev/null`, { encoding: "utf-8" });
529
+ if (isLoginPickerVisible(screen)) {
530
+ if (!loginPickerReported) {
531
+ log(`[persistent-session] CLAUDE LOGIN REQUIRED for '${codeName}' \u2014 agent cannot start until ~/.claude.json is provisioned. Pair via the Hosts page or run 'claude /login' on the host.`);
532
+ loginPickerReported = true;
533
+ }
534
+ loginPickerIterations++;
535
+ continue;
536
+ }
537
+ dialogIterations++;
282
538
  if (screen.includes("Choose the text style") || screen.includes("Dark mode") && screen.includes("Light mode")) {
283
539
  execSync(`tmux send-keys -t ${tmuxSession} Enter`, { stdio: "ignore" });
284
540
  log(`[persistent-session] Auto-accepted theme picker for '${codeName}'`);
@@ -307,14 +563,17 @@ async function acceptDialogs(tmuxSession, codeName, log) {
307
563
  continue;
308
564
  }
309
565
  if (screen.includes("\u276F") && !screen.includes("Enter to confirm")) {
310
- log(`[persistent-session] Session ready for '${codeName}' \u2014 no more dialogs`);
311
- break;
566
+ if (hasMcpChildren(tmuxSession)) {
567
+ log(`[persistent-session] Session ready for '${codeName}' \u2014 MCP servers spawned`);
568
+ break;
569
+ }
312
570
  }
313
571
  } catch {
314
572
  break;
315
573
  }
316
574
  }
317
575
  }
576
+ var _internals = { isLoginPickerVisible, detectFailureSignature };
318
577
  async function injectMessage(codeName, type, content, meta, log) {
319
578
  const _log = log ?? ((_) => {
320
579
  });
@@ -329,10 +588,10 @@ async function injectMessage(codeName, type, content, meta, log) {
329
588
  const acpx = getAcpxBin();
330
589
  if (acpx) {
331
590
  try {
332
- const tmpDir = join(projectDir, ".claude");
333
- mkdirSync(tmpDir, { recursive: true });
334
- const tmpFile = join(tmpDir, ".agt-inject-prompt.txt");
335
- writeFileSync2(tmpFile, text);
591
+ const tmpDir = join2(projectDir, ".claude");
592
+ mkdirSync2(tmpDir, { recursive: true });
593
+ const tmpFile = join2(tmpDir, ".agt-inject-prompt.txt");
594
+ writeFileSync3(tmpFile, text);
336
595
  _log(`[inject] acpx exec (fire-and-forget): cwd=${projectDir}, file=${tmpFile}`);
337
596
  const child = spawn(acpx, ["claude", "exec", "-f", tmpFile], {
338
597
  cwd: projectDir,
@@ -392,6 +651,14 @@ function isSessionHealthy(codeName) {
392
651
  const session2 = sessions.get(codeName);
393
652
  if (session2 && session2.status === "running") {
394
653
  session2.status = "crashed";
654
+ session2.lastFailureTail = readPaneLogTail(codeName);
655
+ const failedUuid = session2.currentSessionId;
656
+ if (failedUuid && failedUuid === session2.lastFailureSessionId) {
657
+ session2.consecutiveSameUuidFailures += 1;
658
+ } else {
659
+ session2.consecutiveSameUuidFailures = 1;
660
+ }
661
+ session2.lastFailureSessionId = failedUuid;
395
662
  }
396
663
  return false;
397
664
  }
@@ -400,7 +667,11 @@ function isSessionHealthy(codeName) {
400
667
  codeName,
401
668
  startedAt: Date.now(),
402
669
  restartCount: 0,
403
- status: "running"
670
+ status: "running",
671
+ currentSessionId: null,
672
+ lastFailureTail: null,
673
+ lastFailureSessionId: null,
674
+ consecutiveSameUuidFailures: 0
404
675
  });
405
676
  }
406
677
  const session = sessions.get(codeName);
@@ -490,7 +761,7 @@ async function stopAllSessionsAndWait(log, opts) {
490
761
  await new Promise((resolve) => setTimeout(resolve, Math.min(opts.timeoutMs, 2e3)));
491
762
  }
492
763
  function getProjectDir(codeName) {
493
- return join(homedir(), ".augmented", codeName, "project");
764
+ return join2(homedir2(), ".augmented", codeName, "project");
494
765
  }
495
766
  function writeAcpxConfig(config) {
496
767
  const {
@@ -506,25 +777,25 @@ function writeAcpxConfig(config) {
506
777
  if (channels.length > 0) claudeArgs.push("--channels", ...channels);
507
778
  if (devChannels.length > 0) claudeArgs.push("--dangerously-load-development-channels", ...devChannels);
508
779
  claudeArgs.push("--mcp-config", mcpConfigPath);
509
- if (existsSync(claudeMdPath)) claudeArgs.push("--system-prompt-file", claudeMdPath);
780
+ if (existsSync2(claudeMdPath)) claudeArgs.push("--system-prompt-file", claudeMdPath);
510
781
  claudeArgs.push("--allow-dangerously-skip-permissions");
511
782
  claudeArgs.push("--dangerously-skip-permissions");
512
783
  claudeArgs.push("--strict-mcp-config");
513
784
  const mcpServerNames2 = collectMcpServerNames(mcpConfigPath);
514
785
  claudeArgs.push("--allowedTools", buildAllowedTools(mcpServerNames2));
515
786
  const acpCmd = `npx -y @agentclientprotocol/claude-agent-acp ${claudeArgs.map((a) => a.includes(" ") || a.includes("*") ? JSON.stringify(a) : a).join(" ")}`;
516
- const envIntegrationsPath = join(projectDir, ".env.integrations");
517
- const wrapperPath = join(projectDir, ".claude", "acpx-agent.sh");
787
+ const envIntegrationsPath = join2(projectDir, ".env.integrations");
788
+ const wrapperPath = join2(projectDir, ".claude", "acpx-agent.sh");
518
789
  const wrapperLines = ["#!/usr/bin/env bash"];
519
- if (existsSync(envIntegrationsPath)) {
790
+ if (existsSync2(envIntegrationsPath)) {
520
791
  wrapperLines.push(`set -a`, `source ${JSON.stringify(envIntegrationsPath)}`, `set +a`);
521
792
  }
522
793
  if (claudeAuthMode === "api_key" && anthropicApiKey) {
523
794
  wrapperLines.push(`export ANTHROPIC_API_KEY=${JSON.stringify(anthropicApiKey)}`);
524
795
  }
525
796
  wrapperLines.push(`exec ${acpCmd}`);
526
- mkdirSync(join(projectDir, ".claude"), { recursive: true });
527
- writeFileSync2(wrapperPath, wrapperLines.join("\n") + "\n", { mode: 493 });
797
+ mkdirSync2(join2(projectDir, ".claude"), { recursive: true });
798
+ writeFileSync3(wrapperPath, wrapperLines.join("\n") + "\n", { mode: 493 });
528
799
  const acpxConfig = {
529
800
  defaultAgent: "claude",
530
801
  defaultPermissions: "approve-all",
@@ -534,14 +805,20 @@ function writeAcpxConfig(config) {
534
805
  }
535
806
  }
536
807
  };
537
- writeFileSync2(join(projectDir, ".acpxrc.json"), JSON.stringify(acpxConfig, null, 2));
808
+ writeFileSync3(join2(projectDir, ".acpxrc.json"), JSON.stringify(acpxConfig, null, 2));
538
809
  }
539
810
 
540
811
  export {
541
812
  sanitizeMcpJson,
542
813
  buildAllowedTools,
814
+ isAgentIdle,
815
+ isStaleForToday,
816
+ peekCurrentSession,
543
817
  resolveClaudeBinary,
818
+ prepareForRespawn,
819
+ getLastFailureContext,
544
820
  startPersistentSession,
821
+ _internals,
545
822
  injectMessage,
546
823
  stopPersistentSession,
547
824
  getSessionState,
@@ -552,4 +829,4 @@ export {
552
829
  stopAllSessionsAndWait,
553
830
  getProjectDir
554
831
  };
555
- //# sourceMappingURL=chunk-AFUG4KD3.js.map
832
+ //# sourceMappingURL=chunk-EG5D3KUV.js.map