@integrity-labs/agt-cli 0.27.8 → 0.27.9-test.11

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 (28) hide show
  1. package/dist/assets/impersonate-statusline.sh +7 -2
  2. package/dist/bin/agt.js +946 -129
  3. package/dist/bin/agt.js.map +1 -1
  4. package/dist/{chunk-YSBGIXJG.js → chunk-45I2X4M7.js} +361 -26
  5. package/dist/chunk-45I2X4M7.js.map +1 -0
  6. package/dist/{chunk-IB655E5U.js → chunk-N2EBL4PZ.js} +1086 -285
  7. package/dist/chunk-N2EBL4PZ.js.map +1 -0
  8. package/dist/{chunk-GN4XPQWJ.js → chunk-SJPAXXTW.js} +535 -94
  9. package/dist/chunk-SJPAXXTW.js.map +1 -0
  10. package/dist/{claude-pair-runtime-ZBQKBBMT.js → claude-pair-runtime-OUGV74LY.js} +2 -2
  11. package/dist/lib/manager-worker.js +2071 -517
  12. package/dist/lib/manager-worker.js.map +1 -1
  13. package/dist/mcp/direct-chat-channel.js +49 -455
  14. package/dist/mcp/index.js +232 -137
  15. package/dist/mcp/slack-channel.js +1379 -856
  16. package/dist/mcp/teams-channel.js +16018 -0
  17. package/dist/mcp/telegram-channel.js +1264 -618
  18. package/dist/{persistent-session-ICYFLUAM.js → persistent-session-G3MR7JVI.js} +5 -3
  19. package/dist/responsiveness-probe-UHSBETBR.js +74 -0
  20. package/dist/responsiveness-probe-UHSBETBR.js.map +1 -0
  21. package/package.json +2 -2
  22. package/dist/chunk-GN4XPQWJ.js.map +0 -1
  23. package/dist/chunk-IB655E5U.js.map +0 -1
  24. package/dist/chunk-YSBGIXJG.js.map +0 -1
  25. package/dist/responsiveness-probe-WZNQ2762.js +0 -33
  26. package/dist/responsiveness-probe-WZNQ2762.js.map +0 -1
  27. /package/dist/{claude-pair-runtime-ZBQKBBMT.js.map → claude-pair-runtime-OUGV74LY.js.map} +0 -0
  28. /package/dist/{persistent-session-ICYFLUAM.js.map → persistent-session-G3MR7JVI.js.map} +0 -0
@@ -1,16 +1,16 @@
1
1
  import {
2
2
  claudeModelAlias,
3
3
  isClaudeFastMode
4
- } from "./chunk-YSBGIXJG.js";
4
+ } from "./chunk-45I2X4M7.js";
5
5
  import {
6
6
  reapOrphanChannelMcps
7
7
  } from "./chunk-XWVM4KPK.js";
8
8
 
9
9
  // src/lib/persistent-session.ts
10
- import { spawn, execSync, execFileSync } from "child_process";
10
+ import { spawn, execSync, execFileSync as execFileSync3 } from "child_process";
11
11
  import { join as join2, dirname } from "path";
12
12
  import { homedir as homedir2, platform, userInfo } from "os";
13
- import { existsSync as existsSync2, readFileSync as readFileSync3, readdirSync, writeFileSync as writeFileSync3, appendFileSync, mkdirSync as mkdirSync2, chmodSync, copyFileSync, rmSync } from "fs";
13
+ import { existsSync as existsSync3, readFileSync as readFileSync4, readdirSync, writeFileSync as writeFileSync3, appendFileSync, mkdirSync as mkdirSync2, chmodSync, copyFileSync, rmSync } from "fs";
14
14
  import { fileURLToPath } from "url";
15
15
 
16
16
  // src/lib/mcp-sanitize.ts
@@ -56,18 +56,94 @@ function sanitizeMcpJson(mcpConfigPath, apiHost) {
56
56
  }
57
57
 
58
58
  // src/lib/claude-tools.ts
59
- var BASE_TOOLS = ["Bash", "Read", "Write", "Edit", "Grep", "Glob", "Agent", "Skill"];
59
+ var BASE_TOOLS = ["Bash", "Read", "Write", "Edit", "Grep", "Glob", "Agent", "Skill", "ToolSearch"];
60
60
  function buildAllowedTools(mcpServerNames) {
61
61
  const mcpPatterns = mcpServerNames.map((name) => `mcp__${name.replace(/-/g, "_")}__*`);
62
62
  return [...mcpPatterns, ...BASE_TOOLS].join(",");
63
63
  }
64
64
 
65
+ // src/lib/mcp-env-probe.ts
66
+ import { existsSync, readFileSync as readFileSync2 } from "fs";
67
+ var LATE_BOUND_VARS = /* @__PURE__ */ new Set([
68
+ "AGT_RUN_ID",
69
+ "AGT_TOKEN",
70
+ "ANCHOR_BROWSER_SESSION_ID"
71
+ ]);
72
+ var TEMPLATE_VAR_RE = /\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g;
73
+ function collectVarsFromValue(value, into) {
74
+ if (typeof value === "string") {
75
+ for (const m of value.matchAll(TEMPLATE_VAR_RE)) into.add(m[1]);
76
+ } else if (Array.isArray(value)) {
77
+ for (const v of value) collectVarsFromValue(v, into);
78
+ }
79
+ }
80
+ function findMissingSubstitutionVars(mcpConfig, env) {
81
+ const findings = [];
82
+ if (typeof mcpConfig !== "object" || mcpConfig === null) return findings;
83
+ const servers = mcpConfig.mcpServers;
84
+ if (typeof servers !== "object" || servers === null) return findings;
85
+ for (const [server, raw] of Object.entries(servers)) {
86
+ if (typeof raw !== "object" || raw === null) continue;
87
+ const entry = raw;
88
+ const vars = /* @__PURE__ */ new Set();
89
+ collectVarsFromValue(entry["command"], vars);
90
+ collectVarsFromValue(entry["args"], vars);
91
+ collectVarsFromValue(entry["url"], vars);
92
+ for (const block of [entry["env"], entry["headers"]]) {
93
+ if (typeof block !== "object" || block === null) continue;
94
+ for (const v of Object.values(block)) collectVarsFromValue(v, vars);
95
+ }
96
+ for (const varName of vars) {
97
+ if (LATE_BOUND_VARS.has(varName)) continue;
98
+ const value = env[varName];
99
+ if (value === void 0) {
100
+ findings.push({ varName, server, state: "unset" });
101
+ } else if (value.trim() === "") {
102
+ findings.push({ varName, server, state: "empty" });
103
+ }
104
+ }
105
+ }
106
+ return findings;
107
+ }
108
+ function formatMissingVar(f) {
109
+ return `[mcp-env-substitution] missing var=${f.varName} server=${f.server} state=${f.state}`;
110
+ }
111
+ function parseEnvIntegrations(content) {
112
+ const out = {};
113
+ for (const line of content.split("\n")) {
114
+ if (!line || line.startsWith("#") || !line.includes("=")) continue;
115
+ const eqIdx = line.indexOf("=");
116
+ const key = line.slice(0, eqIdx);
117
+ let value = line.slice(eqIdx + 1);
118
+ if (value.length >= 2 && value.startsWith("'") && value.endsWith("'")) {
119
+ value = value.slice(1, -1).replaceAll("'\\''", "'");
120
+ }
121
+ out[key] = value;
122
+ }
123
+ return out;
124
+ }
125
+ function probeMcpEnvSubstitution(args) {
126
+ try {
127
+ const config = JSON.parse(readFileSync2(args.mcpConfigPath, "utf-8"));
128
+ let env = args.baseEnv;
129
+ if (args.envIntegrationsPath && existsSync(args.envIntegrationsPath)) {
130
+ env = {
131
+ ...args.baseEnv,
132
+ ...parseEnvIntegrations(readFileSync2(args.envIntegrationsPath, "utf-8"))
133
+ };
134
+ }
135
+ return findMissingSubstitutionVars(config, env);
136
+ } catch {
137
+ return [];
138
+ }
139
+ }
140
+
65
141
  // src/lib/persistent-session.ts
66
142
  import { randomUUID as randomUUID2 } from "crypto";
67
143
 
68
144
  // src/lib/daily-session.ts
69
145
  import { randomUUID } from "crypto";
70
- import { existsSync, mkdirSync, readFileSync as readFileSync2, renameSync, statSync, writeFileSync as writeFileSync2 } from "fs";
146
+ import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync3, renameSync, statSync, writeFileSync as writeFileSync2 } from "fs";
71
147
  import { homedir } from "os";
72
148
  import { join } from "path";
73
149
  var HISTORY_DAYS = 7;
@@ -97,9 +173,9 @@ function todayLocalIso(now = /* @__PURE__ */ new Date(), timezone) {
97
173
  }
98
174
  function readFile(codeName) {
99
175
  const path = dailySessionPath(codeName);
100
- if (!existsSync(path)) return { current: null, history: [] };
176
+ if (!existsSync2(path)) return { current: null, history: [] };
101
177
  try {
102
- const raw = readFileSync2(path, "utf-8");
178
+ const raw = readFileSync3(path, "utf-8");
103
179
  const parsed = JSON.parse(raw);
104
180
  return {
105
181
  current: parsed.current ?? null,
@@ -123,6 +199,25 @@ function trimHistory(history, now, timezone) {
123
199
  const cutoffIso = todayLocalIso(cutoff, timezone);
124
200
  return history.filter((h) => h.date >= cutoffIso).slice(0, HISTORY_DAYS);
125
201
  }
202
+ function getOrCreateDailySession(codeName, now = /* @__PURE__ */ new Date(), timezone) {
203
+ const today = todayLocalIso(now, timezone);
204
+ const file = readFile(codeName);
205
+ if (file.current && file.current.date === today) {
206
+ return { sessionId: file.current.sessionId, isNew: false };
207
+ }
208
+ const next = {
209
+ date: today,
210
+ sessionId: randomUUID(),
211
+ startedAt: now.toISOString()
212
+ };
213
+ const history = trimHistory(
214
+ [...file.current ? [file.current] : [], ...file.history],
215
+ now,
216
+ timezone
217
+ );
218
+ writeFile(codeName, { current: next, history });
219
+ return { sessionId: next.sessionId, isNew: true };
220
+ }
126
221
  function markDailySessionSpawn(codeName, sessionId, now = /* @__PURE__ */ new Date(), timezone) {
127
222
  const today = todayLocalIso(now, timezone);
128
223
  const file = readFile(codeName);
@@ -160,6 +255,16 @@ function rotateDailySession(codeName, now = /* @__PURE__ */ new Date(), timezone
160
255
  function encodeProjectPath(projectDir) {
161
256
  return "-" + projectDir.replace(/^\//, "").replace(/[/.]/g, "-");
162
257
  }
258
+ function sessionFileExists(projectDir, sessionId) {
259
+ const path = join(
260
+ homedir(),
261
+ ".claude",
262
+ "projects",
263
+ encodeProjectPath(projectDir),
264
+ `${sessionId}.jsonl`
265
+ );
266
+ return existsSync2(path);
267
+ }
163
268
  function sessionTranscriptDir(projectDir) {
164
269
  return join(homedir(), ".claude", "projects", encodeProjectPath(projectDir));
165
270
  }
@@ -168,7 +273,7 @@ function sessionFilePath(projectDir, sessionId) {
168
273
  }
169
274
  function isAgentIdle(projectDir, sessionId, idleSeconds = 60, now = /* @__PURE__ */ new Date()) {
170
275
  const path = sessionFilePath(projectDir, sessionId);
171
- if (!existsSync(path)) return true;
276
+ if (!existsSync2(path)) return true;
172
277
  try {
173
278
  const mtimeMs = statSync(path).mtimeMs;
174
279
  return now.getTime() - mtimeMs >= idleSeconds * 1e3;
@@ -185,12 +290,287 @@ function peekCurrentSession(codeName) {
185
290
  return readFile(codeName).current;
186
291
  }
187
292
 
293
+ // ../../packages/core/dist/runtime/session-probe.js
294
+ import { execFileSync } from "child_process";
295
+ function escapePgrepRegex(value) {
296
+ return value.replace(/[.[\]{}()*+?^$|\\]/g, "\\$&");
297
+ }
298
+ function probeClaudeProcessInTmux(tmuxSession) {
299
+ const escapedSession = escapePgrepRegex(tmuxSession);
300
+ const pattern = `(^|[[:space:]])--name ${escapedSession}([[:space:]]|$)`;
301
+ try {
302
+ const out = execFileSync("pgrep", ["-f", "--", pattern], {
303
+ encoding: "utf-8",
304
+ timeout: 3e3
305
+ }).trim();
306
+ return out.length > 0 ? "alive" : "dead";
307
+ } catch (err) {
308
+ const e = err;
309
+ if (e?.code === "ENOENT")
310
+ return "unknown";
311
+ return e?.status === 1 ? "dead" : "unknown";
312
+ }
313
+ }
314
+
315
+ // src/lib/claude-dialogs.ts
316
+ import { execFileSync as execFileSync2 } from "child_process";
317
+ function isLoginPickerVisible(screen) {
318
+ return screen.includes("Select login method") || screen.includes("Claude account with subscription") && screen.includes("Anthropic Console account");
319
+ }
320
+ function isResumeModeDialogVisible(screen) {
321
+ return screen.includes("Resume from summary") && screen.includes("Don't ask me again");
322
+ }
323
+ function isSessionFeedbackDialogVisible(screen) {
324
+ return screen.includes("How is Claude doing this session") && screen.includes("0: Dismiss");
325
+ }
326
+ function sweepDialogs(screen) {
327
+ if (screen.includes("Choose the text style") || screen.includes("Dark mode") && screen.includes("Light mode")) {
328
+ return {
329
+ kind: "theme-picker",
330
+ keys: ["Enter"],
331
+ interKeyDelayMs: 0,
332
+ logMessage: "Auto-accepted theme picker"
333
+ };
334
+ }
335
+ if (screen.includes("Yes, I trust this folder")) {
336
+ return {
337
+ kind: "folder-trust",
338
+ keys: ["Enter"],
339
+ interKeyDelayMs: 0,
340
+ logMessage: "Auto-accepted folder trust"
341
+ };
342
+ }
343
+ if (isResumeModeDialogVisible(screen)) {
344
+ return {
345
+ kind: "resume-mode",
346
+ keys: ["3", "Enter"],
347
+ interKeyDelayMs: 300,
348
+ logMessage: "Auto-dismissed resume-mode dialog (picked 'Don't ask me again')"
349
+ };
350
+ }
351
+ if (screen.includes("I am using this for local development")) {
352
+ return {
353
+ kind: "dev-channels",
354
+ keys: ["Enter"],
355
+ interKeyDelayMs: 0,
356
+ logMessage: "Auto-accepted dev channels"
357
+ };
358
+ }
359
+ if (screen.includes("Enter to confirm") && screen.includes("MCP")) {
360
+ return {
361
+ kind: "mcp-servers",
362
+ keys: ["Enter"],
363
+ interKeyDelayMs: 0,
364
+ logMessage: "Auto-accepted MCP servers"
365
+ };
366
+ }
367
+ if (screen.includes("Yes, I accept") && screen.includes("Bypass Permissions")) {
368
+ return {
369
+ kind: "bypass-permissions",
370
+ keys: ["2", "Enter"],
371
+ interKeyDelayMs: 300,
372
+ logMessage: "Auto-accepted bypass permissions"
373
+ };
374
+ }
375
+ if (isSessionFeedbackDialogVisible(screen)) {
376
+ return {
377
+ kind: "session-feedback",
378
+ keys: ["0"],
379
+ interKeyDelayMs: 0,
380
+ logMessage: "Auto-dismissed session-feedback dialog"
381
+ };
382
+ }
383
+ return null;
384
+ }
385
+ async function sendDialogKeys(tmuxSession, action) {
386
+ for (let i = 0; i < action.keys.length; i++) {
387
+ if (i > 0 && action.interKeyDelayMs > 0) {
388
+ await new Promise((r) => setTimeout(r, action.interKeyDelayMs));
389
+ }
390
+ execFileSync2("tmux", ["send-keys", "-t", tmuxSession, action.keys[i]], {
391
+ stdio: "ignore"
392
+ });
393
+ }
394
+ }
395
+ function simpleTextHash(s) {
396
+ let h = 0;
397
+ for (let i = 0; i < s.length; i++) {
398
+ h = (h << 5) - h + s.charCodeAt(i) | 0;
399
+ }
400
+ return h.toString(16);
401
+ }
402
+
403
+ // src/lib/channel-input-watchdog.ts
404
+ var STUCK_THRESHOLD_MS = 5e3;
405
+ var MAX_ENTER_FIRES = 3;
406
+ var DISTURB_HEAL_KEYS = ["x", "BSpace", "Enter"];
407
+ var DISTURB_INTER_KEY_DELAY_MS = 200;
408
+ var ATTACHED_STUCK_THRESHOLD_MS = 15e3;
409
+ var INPUT_BOX_DIVIDER = /^[─━]{10,}/;
410
+ var PROMPT_PREFIX = "\u276F ";
411
+ function selectFireKeys(attempt, healMode = "disturb") {
412
+ if (healMode === "disturb" && attempt >= 2) return DISTURB_HEAL_KEYS;
413
+ return ["Enter"];
414
+ }
415
+ function decide(pane, prev, now, config = {}) {
416
+ const threshold = config.stuckThresholdMs ?? STUCK_THRESHOLD_MS;
417
+ const maxFires = config.maxEnterFires ?? MAX_ENTER_FIRES;
418
+ const dialogAction = sweepDialogs(pane);
419
+ if (dialogAction) {
420
+ return { fire: false, dialog: dialogAction, next: prev };
421
+ }
422
+ const inputText = extractInputBoxText(pane);
423
+ if (!inputText) {
424
+ return { fire: false, next: void 0 };
425
+ }
426
+ if (inputText === "\u2026") {
427
+ return { fire: false, next: void 0 };
428
+ }
429
+ const hash = simpleTextHash(inputText);
430
+ if (!prev || prev.lastInputHash !== hash) {
431
+ return {
432
+ fire: false,
433
+ next: { lastInputHash: hash, firstSeenAt: now, fires: 0, lastFireAt: 0, gaveUpLogged: false }
434
+ };
435
+ }
436
+ if (prev.fires >= maxFires) {
437
+ if (!prev.gaveUpLogged) {
438
+ return { fire: false, gaveUp: true, next: { ...prev, gaveUpLogged: true } };
439
+ }
440
+ return { fire: false, next: prev };
441
+ }
442
+ const sinceLastAttempt = prev.fires === 0 ? now - prev.firstSeenAt : now - prev.lastFireAt;
443
+ if (sinceLastAttempt < threshold) return { fire: false, next: prev };
444
+ return {
445
+ fire: true,
446
+ next: { ...prev, fires: prev.fires + 1, lastFireAt: now }
447
+ };
448
+ }
449
+ function extractInputBoxText(pane) {
450
+ const lines = pane.split("\n");
451
+ for (let i = 1; i < lines.length; i++) {
452
+ const line = lines[i] ?? "";
453
+ if (!line.startsWith(PROMPT_PREFIX)) continue;
454
+ let j = i - 1;
455
+ while (j >= 0 && (lines[j] ?? "").trim() === "") j--;
456
+ if (j < 0) continue;
457
+ if (!INPUT_BOX_DIVIDER.test((lines[j] ?? "").trim())) continue;
458
+ const text = line.slice(PROMPT_PREFIX.length).trim();
459
+ return text.length > 0 ? text : null;
460
+ }
461
+ return null;
462
+ }
463
+ var SPINNER_GLYPHS = ["\u273B", "\u273D", "\u2736", "\u2733", "\u2722"];
464
+ function isActivelyProcessing(pane) {
465
+ const lines = pane.split("\n");
466
+ for (let i = lines.length - 1; i >= 0; i--) {
467
+ const line = (lines[i] ?? "").trim();
468
+ if (!SPINNER_GLYPHS.some((g) => line.startsWith(g))) continue;
469
+ if (/\bfor\s+\d+s\s*$/.test(line)) return false;
470
+ if (/\b\w+ing[…\.]{0,3}(\s*\([^)]*\))?\s*$/i.test(line)) return true;
471
+ return false;
472
+ }
473
+ return false;
474
+ }
475
+ function checkChannelInputs(codeNames, io, config = {}, states = sharedStates) {
476
+ const live = new Set(codeNames);
477
+ for (const codeName of codeNames) {
478
+ try {
479
+ checkOne(codeName, io, config, states);
480
+ } catch (err) {
481
+ io.log(`[channel-input-watchdog] '${codeName}': ${err.message}`);
482
+ }
483
+ }
484
+ for (const key of [...states.keys()]) {
485
+ if (!live.has(key)) states.delete(key);
486
+ }
487
+ for (const key of [...giveUpCounts.keys()]) {
488
+ if (!live.has(key)) giveUpCounts.delete(key);
489
+ }
490
+ }
491
+ function checkOne(codeName, io, config, states) {
492
+ const pane = io.capturePane(codeName);
493
+ if (!pane) {
494
+ states.delete(codeName);
495
+ return;
496
+ }
497
+ const attached = io.isClientAttached(codeName);
498
+ const effectiveConfig = attached ? {
499
+ ...config,
500
+ stuckThresholdMs: config.attachedStuckThresholdMs ?? ATTACHED_STUCK_THRESHOLD_MS
501
+ } : config;
502
+ const prev = states.get(codeName);
503
+ const { fire, dialog, gaveUp, next } = decide(pane, prev, io.now(), effectiveConfig);
504
+ if (next === void 0) {
505
+ states.delete(codeName);
506
+ if (prev && prev.fires > 0) {
507
+ io.log(
508
+ `[channel-input-watchdog] '${codeName}': recovered after ${prev.fires} fire(s) \u2014 input submitted (input_hash=${prev.lastInputHash})`
509
+ );
510
+ }
511
+ } else {
512
+ states.set(codeName, next);
513
+ }
514
+ if (dialog) {
515
+ io.log(
516
+ `[channel-input-watchdog] '${codeName}': ${dialog.logMessage} (dialog was blocking the input box)`
517
+ );
518
+ io.sendKeys(codeName, dialog.keys, dialog.interKeyDelayMs);
519
+ return;
520
+ }
521
+ const text = extractInputBoxText(pane) ?? "";
522
+ const hash = next?.lastInputHash ?? simpleTextHash(text);
523
+ if (gaveUp) {
524
+ const maxFires = effectiveConfig.maxEnterFires ?? MAX_ENTER_FIRES;
525
+ io.log(
526
+ `[channel-input-watchdog] '${codeName}': GIVING UP after ${maxFires} Enter attempts \u2014 input remains unsubmitted (input_hash=${hash}, len=${text.length})`
527
+ );
528
+ giveUpCounts.set(codeName, (giveUpCounts.get(codeName) ?? 0) + 1);
529
+ try {
530
+ io.signalGiveUp?.(codeName);
531
+ } catch (err) {
532
+ io.log(
533
+ `[channel-input-watchdog] '${codeName}': give-up signal write failed: ${err.message}`
534
+ );
535
+ }
536
+ return;
537
+ }
538
+ if (fire) {
539
+ const maxFires = effectiveConfig.maxEnterFires ?? MAX_ENTER_FIRES;
540
+ const attempt = next?.fires ?? 1;
541
+ const busy = isActivelyProcessing(pane);
542
+ const keys = selectFireKeys(attempt, effectiveConfig.healMode);
543
+ if (keys.length === 1 && keys[0] === "Enter") {
544
+ io.log(
545
+ `[channel-input-watchdog] '${codeName}': stuck channel input \u2014 firing Enter (attempt ${attempt}/${maxFires}, busy=${busy}, input_hash=${hash}, len=${text.length})`
546
+ );
547
+ io.sendEnter(codeName);
548
+ } else {
549
+ io.log(
550
+ `[channel-input-watchdog] '${codeName}': stuck channel input \u2014 escalating to disturb sequence ${keys.join("\u2192")} (attempt ${attempt}/${maxFires}, busy=${busy}, input_hash=${hash}, len=${text.length})`
551
+ );
552
+ io.sendKeys(codeName, keys, DISTURB_INTER_KEY_DELAY_MS);
553
+ }
554
+ }
555
+ }
556
+ var sharedStates = /* @__PURE__ */ new Map();
557
+ var giveUpCounts = /* @__PURE__ */ new Map();
558
+ function takeWatchdogGiveUpCount(codeName) {
559
+ const count = giveUpCounts.get(codeName) ?? 0;
560
+ giveUpCounts.delete(codeName);
561
+ return count;
562
+ }
563
+ function creditWatchdogGiveUpCount(codeName, count) {
564
+ if (count <= 0) return;
565
+ giveUpCounts.set(codeName, (giveUpCounts.get(codeName) ?? 0) + count);
566
+ }
567
+
188
568
  // src/lib/persistent-session.ts
189
569
  function syncClaudeCredsToRoot() {
190
570
  if (platform() !== "linux") return true;
191
571
  if (typeof process.getuid !== "function" || process.getuid() !== 0) return true;
192
572
  for (const filename of [".credentials.json", "credentials.json"]) {
193
- if (existsSync2(join2("/root/.claude", filename))) return true;
573
+ if (existsSync3(join2("/root/.claude", filename))) return true;
194
574
  }
195
575
  let sourcePath = null;
196
576
  try {
@@ -199,7 +579,7 @@ function syncClaudeCredsToRoot() {
199
579
  if (!entry.isDirectory()) continue;
200
580
  for (const filename of [".credentials.json", "credentials.json"]) {
201
581
  const candidate = join2("/home", entry.name, ".claude", filename);
202
- if (existsSync2(candidate)) {
582
+ if (existsSync3(candidate)) {
203
583
  sourcePath = candidate;
204
584
  break outer;
205
585
  }
@@ -212,7 +592,7 @@ function syncClaudeCredsToRoot() {
212
592
  const sourceFilename = sourcePath.endsWith("credentials.json") && !sourcePath.endsWith(".credentials.json") ? "credentials.json" : ".credentials.json";
213
593
  const targetPath = join2(targetDir, sourceFilename);
214
594
  try {
215
- if (!existsSync2(targetDir)) mkdirSync2(targetDir, { recursive: true, mode: 448 });
595
+ if (!existsSync3(targetDir)) mkdirSync2(targetDir, { recursive: true, mode: 448 });
216
596
  copyFileSync(sourcePath, targetPath);
217
597
  chmodSync(targetPath, 384);
218
598
  return true;
@@ -224,13 +604,13 @@ var cachedClaudePath = null;
224
604
  function resolveClaudeBinary() {
225
605
  if (cachedClaudePath) return cachedClaudePath;
226
606
  const override = process.env.CLAUDE_PATH;
227
- if (override && existsSync2(override)) {
607
+ if (override && existsSync3(override)) {
228
608
  cachedClaudePath = override;
229
609
  return override;
230
610
  }
231
611
  try {
232
612
  const out = execSync("which claude 2>/dev/null", { encoding: "utf-8" }).trim();
233
- if (out && existsSync2(out)) {
613
+ if (out && existsSync3(out)) {
234
614
  cachedClaudePath = out;
235
615
  return out;
236
616
  }
@@ -242,7 +622,7 @@ function resolveClaudeBinary() {
242
622
  "/usr/local/bin/claude"
243
623
  ];
244
624
  for (const p of candidates) {
245
- if (existsSync2(p)) {
625
+ if (existsSync3(p)) {
246
626
  cachedClaudePath = p;
247
627
  return p;
248
628
  }
@@ -260,7 +640,7 @@ function writePersistentClaudeWrapper(args) {
260
640
  // --dangerously-skip-permissions on dedicated EC2 hosts.
261
641
  "export IS_SANDBOX=1"
262
642
  ];
263
- if (existsSync2(envIntegrationsPath)) {
643
+ if (existsSync3(envIntegrationsPath)) {
264
644
  wrapperLines.push(
265
645
  "set -a",
266
646
  `source ${JSON.stringify(envIntegrationsPath)}`,
@@ -277,9 +657,9 @@ function writePersistentClaudeWrapper(args) {
277
657
  return wrapperPath;
278
658
  }
279
659
  function collectMcpServerNames(mcpConfigPath) {
280
- if (!existsSync2(mcpConfigPath)) return [];
660
+ if (!existsSync3(mcpConfigPath)) return [];
281
661
  try {
282
- const data = JSON.parse(readFileSync3(mcpConfigPath, "utf-8"));
662
+ const data = JSON.parse(readFileSync4(mcpConfigPath, "utf-8"));
283
663
  const servers = data.mcpServers;
284
664
  return servers ? Object.keys(servers) : [];
285
665
  } catch {
@@ -293,7 +673,7 @@ function getAcpxBin() {
293
673
  let dir = moduleDir;
294
674
  for (let i = 0; i < 6; i++) {
295
675
  const candidate = join2(dir, "node_modules", ".bin", "acpx");
296
- if (existsSync2(candidate)) {
676
+ if (existsSync3(candidate)) {
297
677
  _acpxBin = candidate;
298
678
  return _acpxBin;
299
679
  }
@@ -311,7 +691,7 @@ function getAcpxBin() {
311
691
  }
312
692
  function defaultAcpxSessionProbe(acpxBin, projectDir) {
313
693
  try {
314
- execFileSync(acpxBin, ["claude", "list-sessions"], {
694
+ execFileSync3(acpxBin, ["claude", "list-sessions"], {
315
695
  cwd: projectDir,
316
696
  timeout: 5e3,
317
697
  stdio: "ignore"
@@ -357,9 +737,9 @@ function setupPaneLog(tmuxSession, codeName, log) {
357
737
  }
358
738
  function readPaneLogTail(codeName, lines = PANE_TAIL_LINES) {
359
739
  const logPath = paneLogPath(codeName);
360
- if (!existsSync2(logPath)) return null;
740
+ if (!existsSync3(logPath)) return null;
361
741
  try {
362
- const raw = readFileSync3(logPath, "utf-8");
742
+ const raw = readFileSync4(logPath, "utf-8");
363
743
  if (!raw) return null;
364
744
  const stripped = raw.replace(/\x1b\[[0-9;?]*[A-Za-z]/g, "");
365
745
  const all = stripped.split("\n").filter((l) => l.length > 0);
@@ -377,7 +757,7 @@ function prepareForRespawn(codeName) {
377
757
  const session = sessions.get(codeName);
378
758
  if (!session) return null;
379
759
  const signature = detectFailureSignature(session.lastFailureTail);
380
- if (signature === "session_id_in_use" && session.consecutiveSameUuidFailures >= 2) {
760
+ if (session.consecutiveSameUuidFailures >= 2) {
381
761
  const failureCount = session.consecutiveSameUuidFailures;
382
762
  const newId = rotateDailySession(
383
763
  codeName,
@@ -386,7 +766,7 @@ function prepareForRespawn(codeName) {
386
766
  );
387
767
  session.consecutiveSameUuidFailures = 0;
388
768
  session.lastFailureSessionId = null;
389
- return `rotated daily-session UUID to ${newId} after ${failureCount}+ "Session ID already in use" failures`;
769
+ return `rotated daily-session UUID to ${newId} after ${failureCount} consecutive failures on the same UUID (signature=${signature})`;
390
770
  }
391
771
  return null;
392
772
  }
@@ -399,6 +779,27 @@ function getLastFailureContext(codeName) {
399
779
  restartCount: session?.restartCount ?? 0
400
780
  };
401
781
  }
782
+ function resolveSessionSpawnDecision(args) {
783
+ const { codeName, projectDir, agentTimezone } = args;
784
+ const now = args.now ?? /* @__PURE__ */ new Date();
785
+ const disableFlag = process.env["AGT_DISABLE_SESSION_RESUME"];
786
+ const resumeDisabled = disableFlag === "1" || disableFlag?.toLowerCase() === "true";
787
+ if (resumeDisabled) {
788
+ return { flag: "--session-id", sessionId: randomUUID2(), reason: "resume-disabled" };
789
+ }
790
+ const daily = getOrCreateDailySession(codeName, now, agentTimezone);
791
+ if (!daily.isNew && sessionFileExists(projectDir, daily.sessionId)) {
792
+ return { flag: "--resume", sessionId: daily.sessionId, reason: "resume-today" };
793
+ }
794
+ if (daily.isNew) {
795
+ return { flag: "--session-id", sessionId: daily.sessionId, reason: "fresh-new-day" };
796
+ }
797
+ return {
798
+ flag: "--session-id",
799
+ sessionId: rotateDailySession(codeName, now, agentTimezone),
800
+ reason: "rotated-missing-transcript"
801
+ };
802
+ }
402
803
  function startPersistentSession(config) {
403
804
  const existing = sessions.get(config.codeName);
404
805
  if (existing && existing.status === "running") {
@@ -447,7 +848,7 @@ function spawnSession(config, session) {
447
848
  const claudeDir = join2(homedir2(), ".claude");
448
849
  for (const filename of [".credentials.json", "credentials.json"]) {
449
850
  const p = join2(claudeDir, filename);
450
- if (existsSync2(p)) {
851
+ if (existsSync3(p)) {
451
852
  try {
452
853
  rmSync(p, { force: true });
453
854
  log(`[persistent-session] Removed ${p} (api_key mode active \u2014 preventing OAuth fallback)`);
@@ -460,9 +861,17 @@ function spawnSession(config, session) {
460
861
  }
461
862
  }
462
863
  const args = [];
463
- const sessionId = randomUUID2();
464
- args.push("--session-id", sessionId);
465
- log(`[persistent-session] Starting fresh session ${sessionId} for '${codeName}'`);
864
+ const decision = resolveSessionSpawnDecision({
865
+ codeName,
866
+ projectDir,
867
+ agentTimezone: config.agentTimezone ?? void 0
868
+ });
869
+ const sessionId = decision.sessionId;
870
+ const resuming = decision.flag === "--resume";
871
+ args.push(decision.flag, sessionId);
872
+ log(
873
+ `[persistent-session] ${resuming ? "Resuming" : "Starting"} session ${sessionId} for '${codeName}' (${decision.reason})`
874
+ );
466
875
  try {
467
876
  markDailySessionSpawn(codeName, sessionId, /* @__PURE__ */ new Date(), config.agentTimezone ?? void 0);
468
877
  } catch (err) {
@@ -473,7 +882,7 @@ function spawnSession(config, session) {
473
882
  if (channels.length > 0) args.push("--channels", ...channels);
474
883
  if (devChannels.length > 0) args.push("--dangerously-load-development-channels", ...devChannels);
475
884
  args.push("--mcp-config", mcpConfigPath);
476
- if (existsSync2(claudeMdPath)) args.push("--system-prompt-file", claudeMdPath);
885
+ if (existsSync3(claudeMdPath)) args.push("--system-prompt-file", claudeMdPath);
477
886
  const modelAlias = claudeModelAlias(config.primaryModel);
478
887
  if (modelAlias) args.push("--model", modelAlias);
479
888
  args.push("--allow-dangerously-skip-permissions");
@@ -482,7 +891,7 @@ function spawnSession(config, session) {
482
891
  args.push("--name", tmuxSession);
483
892
  const mcpServerNames = collectMcpServerNames(mcpConfigPath);
484
893
  args.push("--allowedTools", buildAllowedTools(mcpServerNames));
485
- 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.';
894
+ const initPrompt = resuming ? "" : 'You are now online. Say "Ready." and wait for incoming messages. Do not run any tools or load any data until a message arrives.';
486
895
  const claudeBin = resolveClaudeBinary();
487
896
  const claudeArgsJoined = args.map((a) => a.includes(" ") || a.includes("*") ? JSON.stringify(a) : a).join(" ");
488
897
  const wrapperPath = writePersistentClaudeWrapper({
@@ -507,6 +916,16 @@ function spawnSession(config, session) {
507
916
  if (config.runId) {
508
917
  tmuxEnv["AGT_RUN_ID"] = config.runId;
509
918
  }
919
+ for (const f of probeMcpEnvSubstitution({
920
+ mcpConfigPath,
921
+ envIntegrationsPath: join2(projectDir, ".env.integrations"),
922
+ baseEnv: {
923
+ ...tmuxEnv,
924
+ ...claudeAuthMode === "api_key" && config.anthropicApiKey ? { ANTHROPIC_API_KEY: config.anthropicApiKey } : {}
925
+ }
926
+ })) {
927
+ log(`[persistent-session] ${formatMissingVar(f)} agent=${codeName}`);
928
+ }
510
929
  const child = spawn("tmux", [
511
930
  "new-session",
512
931
  "-d",
@@ -551,12 +970,6 @@ function spawnSession(config, session) {
551
970
  session.restartCount++;
552
971
  }
553
972
  }
554
- function isLoginPickerVisible(screen) {
555
- return screen.includes("Select login method") || screen.includes("Claude account with subscription") && screen.includes("Anthropic Console account");
556
- }
557
- function isResumeModeDialogVisible(screen) {
558
- return screen.includes("Resume from summary") && screen.includes("Don't ask me again");
559
- }
560
973
  function hasMcpChildren(tmuxSession) {
561
974
  try {
562
975
  const claudePidOut = execSync(
@@ -606,40 +1019,10 @@ async function acceptDialogs(tmuxSession, codeName, log, primaryModel = null, se
606
1019
  continue;
607
1020
  }
608
1021
  dialogIterations++;
609
- if (screen.includes("Choose the text style") || screen.includes("Dark mode") && screen.includes("Light mode")) {
610
- execSync(`tmux send-keys -t ${tmuxSession} Enter`, { stdio: "ignore" });
611
- log(`[persistent-session] Auto-accepted theme picker for '${codeName}'`);
612
- continue;
613
- }
614
- if (screen.includes("Yes, I trust this folder")) {
615
- execSync(`tmux send-keys -t ${tmuxSession} Enter`, { stdio: "ignore" });
616
- log(`[persistent-session] Auto-accepted workspace trust for '${codeName}'`);
617
- continue;
618
- }
619
- if (isResumeModeDialogVisible(screen)) {
620
- execFileSync("tmux", ["send-keys", "-t", tmuxSession, "3"], { stdio: "ignore" });
621
- await new Promise((r) => setTimeout(r, 300));
622
- execFileSync("tmux", ["send-keys", "-t", tmuxSession, "Enter"], { stdio: "ignore" });
623
- log(
624
- `[persistent-session] Auto-dismissed resume-mode dialog for '${codeName}' (picked 'Don't ask me again')`
625
- );
626
- continue;
627
- }
628
- if (screen.includes("I am using this for local development")) {
629
- execSync(`tmux send-keys -t ${tmuxSession} Enter`, { stdio: "ignore" });
630
- log(`[persistent-session] Auto-accepted dev channels for '${codeName}'`);
631
- continue;
632
- }
633
- if (screen.includes("Enter to confirm") && screen.includes("MCP")) {
634
- execSync(`tmux send-keys -t ${tmuxSession} Enter`, { stdio: "ignore" });
635
- log(`[persistent-session] Auto-accepted MCP servers for '${codeName}'`);
636
- continue;
637
- }
638
- if (screen.includes("Yes, I accept") && screen.includes("Bypass Permissions")) {
639
- execSync(`tmux send-keys -t ${tmuxSession} 2`, { stdio: "ignore" });
640
- await new Promise((r) => setTimeout(r, 300));
641
- execSync(`tmux send-keys -t ${tmuxSession} Enter`, { stdio: "ignore" });
642
- log(`[persistent-session] Auto-accepted bypass permissions for '${codeName}'`);
1022
+ const dialogAction = sweepDialogs(screen);
1023
+ if (dialogAction) {
1024
+ await sendDialogKeys(tmuxSession, dialogAction);
1025
+ log(`[persistent-session] ${dialogAction.logMessage} for '${codeName}'`);
643
1026
  continue;
644
1027
  }
645
1028
  if (screen.includes("\u276F") && !screen.includes("Enter to confirm")) {
@@ -690,7 +1073,7 @@ async function waitForPromptReady(tmuxSession) {
690
1073
  const deadline = Date.now() + 1e4;
691
1074
  while (Date.now() < deadline) {
692
1075
  try {
693
- const screen = execFileSync(
1076
+ const screen = execFileSync3(
694
1077
  "tmux",
695
1078
  ["capture-pane", "-t", tmuxSession, "-p"],
696
1079
  { encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"] }
@@ -709,11 +1092,11 @@ function sleepBlockingMs(ms) {
709
1092
  }
710
1093
  function defaultArmSender(tmuxSession, command) {
711
1094
  try {
712
- execFileSync("tmux", ["send-keys", "-t", tmuxSession, "-l", command], {
1095
+ execFileSync3("tmux", ["send-keys", "-t", tmuxSession, "-l", command], {
713
1096
  stdio: ["ignore", "ignore", "pipe"]
714
1097
  });
715
1098
  sleepBlockingMs(SEND_KEYS_ENTER_DELAY_MS);
716
- execFileSync("tmux", ["send-keys", "-t", tmuxSession, "Enter"], {
1099
+ execFileSync3("tmux", ["send-keys", "-t", tmuxSession, "Enter"], {
717
1100
  stdio: ["ignore", "ignore", "pipe"]
718
1101
  });
719
1102
  return true;
@@ -722,6 +1105,50 @@ function defaultArmSender(tmuxSession, command) {
722
1105
  }
723
1106
  }
724
1107
  var armSender = defaultArmSender;
1108
+ function defaultPaneCapture(tmuxSession) {
1109
+ try {
1110
+ return execFileSync3("tmux", ["capture-pane", "-t", tmuxSession, "-p"], {
1111
+ encoding: "utf-8",
1112
+ stdio: ["ignore", "pipe", "ignore"],
1113
+ timeout: 2e3
1114
+ });
1115
+ } catch {
1116
+ return null;
1117
+ }
1118
+ }
1119
+ var paneCapture = defaultPaneCapture;
1120
+ var defaultHygieneKeySender = async (tmuxSession, keys, interKeyDelayMs) => {
1121
+ for (let i = 0; i < keys.length; i++) {
1122
+ if (i > 0 && interKeyDelayMs > 0) {
1123
+ await new Promise((r) => setTimeout(r, interKeyDelayMs));
1124
+ }
1125
+ execFileSync3("tmux", ["send-keys", "-t", tmuxSession, keys[i]], {
1126
+ stdio: "ignore"
1127
+ });
1128
+ }
1129
+ };
1130
+ var hygieneKeySender = defaultHygieneKeySender;
1131
+ async function preSendPaneHygiene(tmuxSession, codeName, log) {
1132
+ try {
1133
+ let screen = paneCapture(tmuxSession);
1134
+ if (screen === null) return;
1135
+ const action = sweepDialogs(screen);
1136
+ if (action) {
1137
+ await hygieneKeySender(tmuxSession, action.keys, action.interKeyDelayMs);
1138
+ log(`[inject] ${action.logMessage} for '${codeName}' before injection`);
1139
+ await new Promise((r) => setTimeout(r, 300));
1140
+ screen = paneCapture(tmuxSession) ?? "";
1141
+ }
1142
+ const orphan = extractInputBoxText(screen);
1143
+ if (orphan) {
1144
+ log(
1145
+ `[inject] clearing orphaned input for '${codeName}' before injection (input_hash=${simpleTextHash(orphan)}, len=${orphan.length})`
1146
+ );
1147
+ await hygieneKeySender(tmuxSession, ["C-u"], 0);
1148
+ }
1149
+ } catch {
1150
+ }
1151
+ }
725
1152
  function sendToAgent(tmuxSession, command) {
726
1153
  return armSender(tmuxSession, command);
727
1154
  }
@@ -732,6 +1159,14 @@ var _internals = {
732
1159
  isLoginPickerVisible,
733
1160
  isResumeModeDialogVisible,
734
1161
  detectFailureSignature,
1162
+ // ENG-6039 test seams: seed/clear the module-private session map so
1163
+ // prepareForRespawn's rotation gate can be exercised without tmux.
1164
+ __seedSession(session) {
1165
+ sessions.set(session.codeName, session);
1166
+ },
1167
+ __clearSessions() {
1168
+ sessions.clear();
1169
+ },
735
1170
  isClaudeProcessAliveInTmux,
736
1171
  waitForPromptReady,
737
1172
  // ENG-5770: exported so the unit test in claude-model-alias.test.ts can
@@ -744,6 +1179,17 @@ var _internals = {
744
1179
  __setArmSender(fn) {
745
1180
  armSender = fn ?? defaultArmSender;
746
1181
  },
1182
+ // ENG-6017 test seam: swap the pane capture used by the inject-time
1183
+ // hygiene so unit tests can simulate dialog overlays / orphaned input
1184
+ // without a tmux server. null restores the real capture.
1185
+ __setPaneCapture(fn) {
1186
+ paneCapture = fn ?? defaultPaneCapture;
1187
+ },
1188
+ // ENG-6017 test seam: swap the hygiene key sender (dialog dismissal +
1189
+ // C-u clear) so unit tests can assert keystrokes without tmux.
1190
+ __setHygieneKeySender(fn) {
1191
+ hygieneKeySender = fn ?? defaultHygieneKeySender;
1192
+ },
747
1193
  // ENG-5758 test seam: swap the acpx session probe so unit tests can
748
1194
  // simulate "session present" / "no session" without spawning acpx.
749
1195
  __setAcpxSessionProbe(fn) {
@@ -818,6 +1264,7 @@ async function injectMessageWithStatus(codeName, type, content, meta, log) {
818
1264
  _log(`[inject] acpx binary not found \u2014 falling back to tmux send-keys`);
819
1265
  }
820
1266
  const singleLineText = text.replace(/\s*\n+\s*/g, " ").trim();
1267
+ await preSendPaneHygiene(`agt-${codeName}`, codeName, _log);
821
1268
  const sent = sendToAgent(`agt-${codeName}`, singleLineText);
822
1269
  if (sent) {
823
1270
  _log(`[inject] tmux send-keys sent for '${codeName}' \u2014 unverified (delivered=false, fallbackUsed=true)`);
@@ -838,7 +1285,7 @@ function stopPersistentSession(codeName, log) {
838
1285
  try {
839
1286
  const acpx = getAcpxBin();
840
1287
  if (acpx) {
841
- execFileSync(acpx, ["claude", "sessions", "close", `agt-${codeName}`], {
1288
+ execFileSync3(acpx, ["claude", "sessions", "close", `agt-${codeName}`], {
842
1289
  cwd: getProjectDir(codeName),
843
1290
  timeout: 5e3,
844
1291
  stdio: "ignore"
@@ -859,21 +1306,8 @@ var ZOMBIE_PROBE_TTL_MS = 3e4;
859
1306
  var ZOMBIE_STARTUP_GRACE_MS = 6e4;
860
1307
  var zombieProbeCache = /* @__PURE__ */ new Map();
861
1308
  var pendingZombieDetections = /* @__PURE__ */ new Map();
862
- function escapePgrepRegex(value) {
863
- return value.replace(/[.[\]{}()*+?^$|\\]/g, "\\$&");
864
- }
865
1309
  function isClaudeProcessAliveInTmux(tmuxSession) {
866
- try {
867
- const escapedSession = escapePgrepRegex(tmuxSession);
868
- const pattern = `(^|[[:space:]])--name ${escapedSession}([[:space:]]|$)`;
869
- const out = execFileSync("pgrep", ["-f", "--", pattern], {
870
- encoding: "utf-8",
871
- timeout: 3e3
872
- }).trim();
873
- return out.length > 0;
874
- } catch {
875
- return false;
876
- }
1310
+ return probeClaudeProcessInTmux(tmuxSession) === "alive";
877
1311
  }
878
1312
  function takeZombieDetection(codeName) {
879
1313
  const record = pendingZombieDetections.get(codeName);
@@ -928,7 +1362,7 @@ function isSessionHealthy(codeName) {
928
1362
  if (!claudeAlive) {
929
1363
  const paneTail = readPaneLogTail(codeName);
930
1364
  try {
931
- execFileSync("tmux", ["kill-session", "-t", tmuxSession], { stdio: "ignore" });
1365
+ execFileSync3("tmux", ["kill-session", "-t", tmuxSession], { stdio: "ignore" });
932
1366
  } catch {
933
1367
  }
934
1368
  session.status = "crashed";
@@ -968,13 +1402,13 @@ function collectDiagnostics(codeNames) {
968
1402
  let launchArgs = null;
969
1403
  let channelStatus = null;
970
1404
  try {
971
- execFileSync("tmux", ["has-session", "-t", tmuxSession], { stdio: "ignore" });
1405
+ execFileSync3("tmux", ["has-session", "-t", tmuxSession], { stdio: "ignore" });
972
1406
  tmuxAlive = true;
973
1407
  } catch {
974
1408
  }
975
1409
  if (tmuxAlive) {
976
1410
  try {
977
- screenCapture = execFileSync("tmux", ["capture-pane", "-t", tmuxSession, "-p", "-S", "-30"], {
1411
+ screenCapture = execFileSync3("tmux", ["capture-pane", "-t", tmuxSession, "-p", "-S", "-30"], {
978
1412
  encoding: "utf-8",
979
1413
  timeout: 3e3
980
1414
  }).trim();
@@ -982,7 +1416,7 @@ function collectDiagnostics(codeNames) {
982
1416
  }
983
1417
  }
984
1418
  try {
985
- const psOutput = execFileSync("ps", ["aux"], { encoding: "utf-8", timeout: 3e3 });
1419
+ const psOutput = execFileSync3("ps", ["aux"], { encoding: "utf-8", timeout: 3e3 });
986
1420
  const line = psOutput.split("\n").find((l) => l.includes(`agt-${codeName}`) && !l.includes("grep"));
987
1421
  if (line) {
988
1422
  const match = line.match(/claude\s+.*/);
@@ -1052,7 +1486,7 @@ function writeAcpxConfig(config) {
1052
1486
  if (channels.length > 0) claudeArgs.push("--channels", ...channels);
1053
1487
  if (devChannels.length > 0) claudeArgs.push("--dangerously-load-development-channels", ...devChannels);
1054
1488
  claudeArgs.push("--mcp-config", mcpConfigPath);
1055
- if (existsSync2(claudeMdPath)) claudeArgs.push("--system-prompt-file", claudeMdPath);
1489
+ if (existsSync3(claudeMdPath)) claudeArgs.push("--system-prompt-file", claudeMdPath);
1056
1490
  const acpModelAlias = claudeModelAlias(config.primaryModel);
1057
1491
  if (acpModelAlias) claudeArgs.push("--model", acpModelAlias);
1058
1492
  claudeArgs.push("--allow-dangerously-skip-permissions");
@@ -1064,7 +1498,7 @@ function writeAcpxConfig(config) {
1064
1498
  const envIntegrationsPath = join2(projectDir, ".env.integrations");
1065
1499
  const wrapperPath = join2(projectDir, ".claude", "acpx-agent.sh");
1066
1500
  const wrapperLines = ["#!/usr/bin/env bash"];
1067
- if (existsSync2(envIntegrationsPath)) {
1501
+ if (existsSync3(envIntegrationsPath)) {
1068
1502
  wrapperLines.push(`set -a`, `source ${JSON.stringify(envIntegrationsPath)}`, `set +a`);
1069
1503
  }
1070
1504
  if (claudeAuthMode === "api_key" && anthropicApiKey) {
@@ -1087,18 +1521,25 @@ function writeAcpxConfig(config) {
1087
1521
  }
1088
1522
 
1089
1523
  export {
1524
+ formatMissingVar,
1525
+ parseEnvIntegrations,
1526
+ probeMcpEnvSubstitution,
1090
1527
  sanitizeMcpJson,
1091
1528
  buildAllowedTools,
1092
1529
  sessionTranscriptDir,
1093
1530
  isAgentIdle,
1094
1531
  isStaleForToday,
1095
1532
  peekCurrentSession,
1533
+ checkChannelInputs,
1534
+ takeWatchdogGiveUpCount,
1535
+ creditWatchdogGiveUpCount,
1096
1536
  resolveClaudeBinary,
1097
1537
  writePersistentClaudeWrapper,
1098
1538
  paneLogPath,
1099
1539
  readPaneLogTail,
1100
1540
  prepareForRespawn,
1101
1541
  getLastFailureContext,
1542
+ resolveSessionSpawnDecision,
1102
1543
  startPersistentSession,
1103
1544
  SEND_KEYS_ENTER_DELAY_MS,
1104
1545
  sendToAgent,
@@ -1116,4 +1557,4 @@ export {
1116
1557
  stopAllSessionsAndWait,
1117
1558
  getProjectDir
1118
1559
  };
1119
- //# sourceMappingURL=chunk-GN4XPQWJ.js.map
1560
+ //# sourceMappingURL=chunk-SJPAXXTW.js.map