@integrity-labs/agt-cli 0.27.8-test.9 → 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.
- package/dist/assets/impersonate-statusline.sh +7 -2
- package/dist/bin/agt.js +798 -98
- package/dist/bin/agt.js.map +1 -1
- package/dist/{chunk-HT6EETEL.js → chunk-45I2X4M7.js} +361 -26
- package/dist/chunk-45I2X4M7.js.map +1 -0
- package/dist/{chunk-NFBRJGNU.js → chunk-N2EBL4PZ.js} +915 -239
- package/dist/chunk-N2EBL4PZ.js.map +1 -0
- package/dist/{chunk-F4NG4EXD.js → chunk-SJPAXXTW.js} +512 -80
- package/dist/chunk-SJPAXXTW.js.map +1 -0
- package/dist/{claude-pair-runtime-OBAJZDXK.js → claude-pair-runtime-OUGV74LY.js} +2 -2
- package/dist/lib/manager-worker.js +2058 -508
- package/dist/lib/manager-worker.js.map +1 -1
- package/dist/mcp/index.js +143 -67
- package/dist/mcp/slack-channel.js +1059 -297
- package/dist/mcp/teams-channel.js +16018 -0
- package/dist/mcp/telegram-channel.js +999 -140
- package/dist/{persistent-session-SBSOZG74.js → persistent-session-G3MR7JVI.js} +5 -3
- package/dist/responsiveness-probe-UHSBETBR.js +74 -0
- package/dist/responsiveness-probe-UHSBETBR.js.map +1 -0
- package/package.json +2 -2
- package/dist/chunk-F4NG4EXD.js.map +0 -1
- package/dist/chunk-HT6EETEL.js.map +0 -1
- package/dist/chunk-NFBRJGNU.js.map +0 -1
- package/dist/responsiveness-probe-DU4IJ2RZ.js +0 -33
- package/dist/responsiveness-probe-DU4IJ2RZ.js.map +0 -1
- /package/dist/{claude-pair-runtime-OBAJZDXK.js.map → claude-pair-runtime-OUGV74LY.js.map} +0 -0
- /package/dist/{persistent-session-SBSOZG74.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-
|
|
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 as
|
|
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
|
|
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
|
|
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 (!
|
|
176
|
+
if (!existsSync2(path)) return { current: null, history: [] };
|
|
101
177
|
try {
|
|
102
|
-
const raw =
|
|
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 (!
|
|
276
|
+
if (!existsSync2(path)) return true;
|
|
172
277
|
try {
|
|
173
278
|
const mtimeMs = statSync(path).mtimeMs;
|
|
174
279
|
return now.getTime() - mtimeMs >= idleSeconds * 1e3;
|
|
@@ -207,12 +312,265 @@ function probeClaudeProcessInTmux(tmuxSession) {
|
|
|
207
312
|
}
|
|
208
313
|
}
|
|
209
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
|
+
|
|
210
568
|
// src/lib/persistent-session.ts
|
|
211
569
|
function syncClaudeCredsToRoot() {
|
|
212
570
|
if (platform() !== "linux") return true;
|
|
213
571
|
if (typeof process.getuid !== "function" || process.getuid() !== 0) return true;
|
|
214
572
|
for (const filename of [".credentials.json", "credentials.json"]) {
|
|
215
|
-
if (
|
|
573
|
+
if (existsSync3(join2("/root/.claude", filename))) return true;
|
|
216
574
|
}
|
|
217
575
|
let sourcePath = null;
|
|
218
576
|
try {
|
|
@@ -221,7 +579,7 @@ function syncClaudeCredsToRoot() {
|
|
|
221
579
|
if (!entry.isDirectory()) continue;
|
|
222
580
|
for (const filename of [".credentials.json", "credentials.json"]) {
|
|
223
581
|
const candidate = join2("/home", entry.name, ".claude", filename);
|
|
224
|
-
if (
|
|
582
|
+
if (existsSync3(candidate)) {
|
|
225
583
|
sourcePath = candidate;
|
|
226
584
|
break outer;
|
|
227
585
|
}
|
|
@@ -234,7 +592,7 @@ function syncClaudeCredsToRoot() {
|
|
|
234
592
|
const sourceFilename = sourcePath.endsWith("credentials.json") && !sourcePath.endsWith(".credentials.json") ? "credentials.json" : ".credentials.json";
|
|
235
593
|
const targetPath = join2(targetDir, sourceFilename);
|
|
236
594
|
try {
|
|
237
|
-
if (!
|
|
595
|
+
if (!existsSync3(targetDir)) mkdirSync2(targetDir, { recursive: true, mode: 448 });
|
|
238
596
|
copyFileSync(sourcePath, targetPath);
|
|
239
597
|
chmodSync(targetPath, 384);
|
|
240
598
|
return true;
|
|
@@ -246,13 +604,13 @@ var cachedClaudePath = null;
|
|
|
246
604
|
function resolveClaudeBinary() {
|
|
247
605
|
if (cachedClaudePath) return cachedClaudePath;
|
|
248
606
|
const override = process.env.CLAUDE_PATH;
|
|
249
|
-
if (override &&
|
|
607
|
+
if (override && existsSync3(override)) {
|
|
250
608
|
cachedClaudePath = override;
|
|
251
609
|
return override;
|
|
252
610
|
}
|
|
253
611
|
try {
|
|
254
612
|
const out = execSync("which claude 2>/dev/null", { encoding: "utf-8" }).trim();
|
|
255
|
-
if (out &&
|
|
613
|
+
if (out && existsSync3(out)) {
|
|
256
614
|
cachedClaudePath = out;
|
|
257
615
|
return out;
|
|
258
616
|
}
|
|
@@ -264,7 +622,7 @@ function resolveClaudeBinary() {
|
|
|
264
622
|
"/usr/local/bin/claude"
|
|
265
623
|
];
|
|
266
624
|
for (const p of candidates) {
|
|
267
|
-
if (
|
|
625
|
+
if (existsSync3(p)) {
|
|
268
626
|
cachedClaudePath = p;
|
|
269
627
|
return p;
|
|
270
628
|
}
|
|
@@ -282,7 +640,7 @@ function writePersistentClaudeWrapper(args) {
|
|
|
282
640
|
// --dangerously-skip-permissions on dedicated EC2 hosts.
|
|
283
641
|
"export IS_SANDBOX=1"
|
|
284
642
|
];
|
|
285
|
-
if (
|
|
643
|
+
if (existsSync3(envIntegrationsPath)) {
|
|
286
644
|
wrapperLines.push(
|
|
287
645
|
"set -a",
|
|
288
646
|
`source ${JSON.stringify(envIntegrationsPath)}`,
|
|
@@ -299,9 +657,9 @@ function writePersistentClaudeWrapper(args) {
|
|
|
299
657
|
return wrapperPath;
|
|
300
658
|
}
|
|
301
659
|
function collectMcpServerNames(mcpConfigPath) {
|
|
302
|
-
if (!
|
|
660
|
+
if (!existsSync3(mcpConfigPath)) return [];
|
|
303
661
|
try {
|
|
304
|
-
const data = JSON.parse(
|
|
662
|
+
const data = JSON.parse(readFileSync4(mcpConfigPath, "utf-8"));
|
|
305
663
|
const servers = data.mcpServers;
|
|
306
664
|
return servers ? Object.keys(servers) : [];
|
|
307
665
|
} catch {
|
|
@@ -315,7 +673,7 @@ function getAcpxBin() {
|
|
|
315
673
|
let dir = moduleDir;
|
|
316
674
|
for (let i = 0; i < 6; i++) {
|
|
317
675
|
const candidate = join2(dir, "node_modules", ".bin", "acpx");
|
|
318
|
-
if (
|
|
676
|
+
if (existsSync3(candidate)) {
|
|
319
677
|
_acpxBin = candidate;
|
|
320
678
|
return _acpxBin;
|
|
321
679
|
}
|
|
@@ -333,7 +691,7 @@ function getAcpxBin() {
|
|
|
333
691
|
}
|
|
334
692
|
function defaultAcpxSessionProbe(acpxBin, projectDir) {
|
|
335
693
|
try {
|
|
336
|
-
|
|
694
|
+
execFileSync3(acpxBin, ["claude", "list-sessions"], {
|
|
337
695
|
cwd: projectDir,
|
|
338
696
|
timeout: 5e3,
|
|
339
697
|
stdio: "ignore"
|
|
@@ -379,9 +737,9 @@ function setupPaneLog(tmuxSession, codeName, log) {
|
|
|
379
737
|
}
|
|
380
738
|
function readPaneLogTail(codeName, lines = PANE_TAIL_LINES) {
|
|
381
739
|
const logPath = paneLogPath(codeName);
|
|
382
|
-
if (!
|
|
740
|
+
if (!existsSync3(logPath)) return null;
|
|
383
741
|
try {
|
|
384
|
-
const raw =
|
|
742
|
+
const raw = readFileSync4(logPath, "utf-8");
|
|
385
743
|
if (!raw) return null;
|
|
386
744
|
const stripped = raw.replace(/\x1b\[[0-9;?]*[A-Za-z]/g, "");
|
|
387
745
|
const all = stripped.split("\n").filter((l) => l.length > 0);
|
|
@@ -399,7 +757,7 @@ function prepareForRespawn(codeName) {
|
|
|
399
757
|
const session = sessions.get(codeName);
|
|
400
758
|
if (!session) return null;
|
|
401
759
|
const signature = detectFailureSignature(session.lastFailureTail);
|
|
402
|
-
if (
|
|
760
|
+
if (session.consecutiveSameUuidFailures >= 2) {
|
|
403
761
|
const failureCount = session.consecutiveSameUuidFailures;
|
|
404
762
|
const newId = rotateDailySession(
|
|
405
763
|
codeName,
|
|
@@ -408,7 +766,7 @@ function prepareForRespawn(codeName) {
|
|
|
408
766
|
);
|
|
409
767
|
session.consecutiveSameUuidFailures = 0;
|
|
410
768
|
session.lastFailureSessionId = null;
|
|
411
|
-
return `rotated daily-session UUID to ${newId} after ${failureCount}
|
|
769
|
+
return `rotated daily-session UUID to ${newId} after ${failureCount} consecutive failures on the same UUID (signature=${signature})`;
|
|
412
770
|
}
|
|
413
771
|
return null;
|
|
414
772
|
}
|
|
@@ -421,6 +779,27 @@ function getLastFailureContext(codeName) {
|
|
|
421
779
|
restartCount: session?.restartCount ?? 0
|
|
422
780
|
};
|
|
423
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
|
+
}
|
|
424
803
|
function startPersistentSession(config) {
|
|
425
804
|
const existing = sessions.get(config.codeName);
|
|
426
805
|
if (existing && existing.status === "running") {
|
|
@@ -469,7 +848,7 @@ function spawnSession(config, session) {
|
|
|
469
848
|
const claudeDir = join2(homedir2(), ".claude");
|
|
470
849
|
for (const filename of [".credentials.json", "credentials.json"]) {
|
|
471
850
|
const p = join2(claudeDir, filename);
|
|
472
|
-
if (
|
|
851
|
+
if (existsSync3(p)) {
|
|
473
852
|
try {
|
|
474
853
|
rmSync(p, { force: true });
|
|
475
854
|
log(`[persistent-session] Removed ${p} (api_key mode active \u2014 preventing OAuth fallback)`);
|
|
@@ -482,9 +861,17 @@ function spawnSession(config, session) {
|
|
|
482
861
|
}
|
|
483
862
|
}
|
|
484
863
|
const args = [];
|
|
485
|
-
const
|
|
486
|
-
|
|
487
|
-
|
|
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
|
+
);
|
|
488
875
|
try {
|
|
489
876
|
markDailySessionSpawn(codeName, sessionId, /* @__PURE__ */ new Date(), config.agentTimezone ?? void 0);
|
|
490
877
|
} catch (err) {
|
|
@@ -495,7 +882,7 @@ function spawnSession(config, session) {
|
|
|
495
882
|
if (channels.length > 0) args.push("--channels", ...channels);
|
|
496
883
|
if (devChannels.length > 0) args.push("--dangerously-load-development-channels", ...devChannels);
|
|
497
884
|
args.push("--mcp-config", mcpConfigPath);
|
|
498
|
-
if (
|
|
885
|
+
if (existsSync3(claudeMdPath)) args.push("--system-prompt-file", claudeMdPath);
|
|
499
886
|
const modelAlias = claudeModelAlias(config.primaryModel);
|
|
500
887
|
if (modelAlias) args.push("--model", modelAlias);
|
|
501
888
|
args.push("--allow-dangerously-skip-permissions");
|
|
@@ -504,7 +891,7 @@ function spawnSession(config, session) {
|
|
|
504
891
|
args.push("--name", tmuxSession);
|
|
505
892
|
const mcpServerNames = collectMcpServerNames(mcpConfigPath);
|
|
506
893
|
args.push("--allowedTools", buildAllowedTools(mcpServerNames));
|
|
507
|
-
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.';
|
|
508
895
|
const claudeBin = resolveClaudeBinary();
|
|
509
896
|
const claudeArgsJoined = args.map((a) => a.includes(" ") || a.includes("*") ? JSON.stringify(a) : a).join(" ");
|
|
510
897
|
const wrapperPath = writePersistentClaudeWrapper({
|
|
@@ -529,6 +916,16 @@ function spawnSession(config, session) {
|
|
|
529
916
|
if (config.runId) {
|
|
530
917
|
tmuxEnv["AGT_RUN_ID"] = config.runId;
|
|
531
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
|
+
}
|
|
532
929
|
const child = spawn("tmux", [
|
|
533
930
|
"new-session",
|
|
534
931
|
"-d",
|
|
@@ -573,12 +970,6 @@ function spawnSession(config, session) {
|
|
|
573
970
|
session.restartCount++;
|
|
574
971
|
}
|
|
575
972
|
}
|
|
576
|
-
function isLoginPickerVisible(screen) {
|
|
577
|
-
return screen.includes("Select login method") || screen.includes("Claude account with subscription") && screen.includes("Anthropic Console account");
|
|
578
|
-
}
|
|
579
|
-
function isResumeModeDialogVisible(screen) {
|
|
580
|
-
return screen.includes("Resume from summary") && screen.includes("Don't ask me again");
|
|
581
|
-
}
|
|
582
973
|
function hasMcpChildren(tmuxSession) {
|
|
583
974
|
try {
|
|
584
975
|
const claudePidOut = execSync(
|
|
@@ -628,40 +1019,10 @@ async function acceptDialogs(tmuxSession, codeName, log, primaryModel = null, se
|
|
|
628
1019
|
continue;
|
|
629
1020
|
}
|
|
630
1021
|
dialogIterations++;
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
}
|
|
636
|
-
if (screen.includes("Yes, I trust this folder")) {
|
|
637
|
-
execSync(`tmux send-keys -t ${tmuxSession} Enter`, { stdio: "ignore" });
|
|
638
|
-
log(`[persistent-session] Auto-accepted workspace trust for '${codeName}'`);
|
|
639
|
-
continue;
|
|
640
|
-
}
|
|
641
|
-
if (isResumeModeDialogVisible(screen)) {
|
|
642
|
-
execFileSync2("tmux", ["send-keys", "-t", tmuxSession, "3"], { stdio: "ignore" });
|
|
643
|
-
await new Promise((r) => setTimeout(r, 300));
|
|
644
|
-
execFileSync2("tmux", ["send-keys", "-t", tmuxSession, "Enter"], { stdio: "ignore" });
|
|
645
|
-
log(
|
|
646
|
-
`[persistent-session] Auto-dismissed resume-mode dialog for '${codeName}' (picked 'Don't ask me again')`
|
|
647
|
-
);
|
|
648
|
-
continue;
|
|
649
|
-
}
|
|
650
|
-
if (screen.includes("I am using this for local development")) {
|
|
651
|
-
execSync(`tmux send-keys -t ${tmuxSession} Enter`, { stdio: "ignore" });
|
|
652
|
-
log(`[persistent-session] Auto-accepted dev channels for '${codeName}'`);
|
|
653
|
-
continue;
|
|
654
|
-
}
|
|
655
|
-
if (screen.includes("Enter to confirm") && screen.includes("MCP")) {
|
|
656
|
-
execSync(`tmux send-keys -t ${tmuxSession} Enter`, { stdio: "ignore" });
|
|
657
|
-
log(`[persistent-session] Auto-accepted MCP servers for '${codeName}'`);
|
|
658
|
-
continue;
|
|
659
|
-
}
|
|
660
|
-
if (screen.includes("Yes, I accept") && screen.includes("Bypass Permissions")) {
|
|
661
|
-
execSync(`tmux send-keys -t ${tmuxSession} 2`, { stdio: "ignore" });
|
|
662
|
-
await new Promise((r) => setTimeout(r, 300));
|
|
663
|
-
execSync(`tmux send-keys -t ${tmuxSession} Enter`, { stdio: "ignore" });
|
|
664
|
-
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}'`);
|
|
665
1026
|
continue;
|
|
666
1027
|
}
|
|
667
1028
|
if (screen.includes("\u276F") && !screen.includes("Enter to confirm")) {
|
|
@@ -712,7 +1073,7 @@ async function waitForPromptReady(tmuxSession) {
|
|
|
712
1073
|
const deadline = Date.now() + 1e4;
|
|
713
1074
|
while (Date.now() < deadline) {
|
|
714
1075
|
try {
|
|
715
|
-
const screen =
|
|
1076
|
+
const screen = execFileSync3(
|
|
716
1077
|
"tmux",
|
|
717
1078
|
["capture-pane", "-t", tmuxSession, "-p"],
|
|
718
1079
|
{ encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"] }
|
|
@@ -731,11 +1092,11 @@ function sleepBlockingMs(ms) {
|
|
|
731
1092
|
}
|
|
732
1093
|
function defaultArmSender(tmuxSession, command) {
|
|
733
1094
|
try {
|
|
734
|
-
|
|
1095
|
+
execFileSync3("tmux", ["send-keys", "-t", tmuxSession, "-l", command], {
|
|
735
1096
|
stdio: ["ignore", "ignore", "pipe"]
|
|
736
1097
|
});
|
|
737
1098
|
sleepBlockingMs(SEND_KEYS_ENTER_DELAY_MS);
|
|
738
|
-
|
|
1099
|
+
execFileSync3("tmux", ["send-keys", "-t", tmuxSession, "Enter"], {
|
|
739
1100
|
stdio: ["ignore", "ignore", "pipe"]
|
|
740
1101
|
});
|
|
741
1102
|
return true;
|
|
@@ -744,6 +1105,50 @@ function defaultArmSender(tmuxSession, command) {
|
|
|
744
1105
|
}
|
|
745
1106
|
}
|
|
746
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
|
+
}
|
|
747
1152
|
function sendToAgent(tmuxSession, command) {
|
|
748
1153
|
return armSender(tmuxSession, command);
|
|
749
1154
|
}
|
|
@@ -754,6 +1159,14 @@ var _internals = {
|
|
|
754
1159
|
isLoginPickerVisible,
|
|
755
1160
|
isResumeModeDialogVisible,
|
|
756
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
|
+
},
|
|
757
1170
|
isClaudeProcessAliveInTmux,
|
|
758
1171
|
waitForPromptReady,
|
|
759
1172
|
// ENG-5770: exported so the unit test in claude-model-alias.test.ts can
|
|
@@ -766,6 +1179,17 @@ var _internals = {
|
|
|
766
1179
|
__setArmSender(fn) {
|
|
767
1180
|
armSender = fn ?? defaultArmSender;
|
|
768
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
|
+
},
|
|
769
1193
|
// ENG-5758 test seam: swap the acpx session probe so unit tests can
|
|
770
1194
|
// simulate "session present" / "no session" without spawning acpx.
|
|
771
1195
|
__setAcpxSessionProbe(fn) {
|
|
@@ -840,6 +1264,7 @@ async function injectMessageWithStatus(codeName, type, content, meta, log) {
|
|
|
840
1264
|
_log(`[inject] acpx binary not found \u2014 falling back to tmux send-keys`);
|
|
841
1265
|
}
|
|
842
1266
|
const singleLineText = text.replace(/\s*\n+\s*/g, " ").trim();
|
|
1267
|
+
await preSendPaneHygiene(`agt-${codeName}`, codeName, _log);
|
|
843
1268
|
const sent = sendToAgent(`agt-${codeName}`, singleLineText);
|
|
844
1269
|
if (sent) {
|
|
845
1270
|
_log(`[inject] tmux send-keys sent for '${codeName}' \u2014 unverified (delivered=false, fallbackUsed=true)`);
|
|
@@ -860,7 +1285,7 @@ function stopPersistentSession(codeName, log) {
|
|
|
860
1285
|
try {
|
|
861
1286
|
const acpx = getAcpxBin();
|
|
862
1287
|
if (acpx) {
|
|
863
|
-
|
|
1288
|
+
execFileSync3(acpx, ["claude", "sessions", "close", `agt-${codeName}`], {
|
|
864
1289
|
cwd: getProjectDir(codeName),
|
|
865
1290
|
timeout: 5e3,
|
|
866
1291
|
stdio: "ignore"
|
|
@@ -937,7 +1362,7 @@ function isSessionHealthy(codeName) {
|
|
|
937
1362
|
if (!claudeAlive) {
|
|
938
1363
|
const paneTail = readPaneLogTail(codeName);
|
|
939
1364
|
try {
|
|
940
|
-
|
|
1365
|
+
execFileSync3("tmux", ["kill-session", "-t", tmuxSession], { stdio: "ignore" });
|
|
941
1366
|
} catch {
|
|
942
1367
|
}
|
|
943
1368
|
session.status = "crashed";
|
|
@@ -977,13 +1402,13 @@ function collectDiagnostics(codeNames) {
|
|
|
977
1402
|
let launchArgs = null;
|
|
978
1403
|
let channelStatus = null;
|
|
979
1404
|
try {
|
|
980
|
-
|
|
1405
|
+
execFileSync3("tmux", ["has-session", "-t", tmuxSession], { stdio: "ignore" });
|
|
981
1406
|
tmuxAlive = true;
|
|
982
1407
|
} catch {
|
|
983
1408
|
}
|
|
984
1409
|
if (tmuxAlive) {
|
|
985
1410
|
try {
|
|
986
|
-
screenCapture =
|
|
1411
|
+
screenCapture = execFileSync3("tmux", ["capture-pane", "-t", tmuxSession, "-p", "-S", "-30"], {
|
|
987
1412
|
encoding: "utf-8",
|
|
988
1413
|
timeout: 3e3
|
|
989
1414
|
}).trim();
|
|
@@ -991,7 +1416,7 @@ function collectDiagnostics(codeNames) {
|
|
|
991
1416
|
}
|
|
992
1417
|
}
|
|
993
1418
|
try {
|
|
994
|
-
const psOutput =
|
|
1419
|
+
const psOutput = execFileSync3("ps", ["aux"], { encoding: "utf-8", timeout: 3e3 });
|
|
995
1420
|
const line = psOutput.split("\n").find((l) => l.includes(`agt-${codeName}`) && !l.includes("grep"));
|
|
996
1421
|
if (line) {
|
|
997
1422
|
const match = line.match(/claude\s+.*/);
|
|
@@ -1061,7 +1486,7 @@ function writeAcpxConfig(config) {
|
|
|
1061
1486
|
if (channels.length > 0) claudeArgs.push("--channels", ...channels);
|
|
1062
1487
|
if (devChannels.length > 0) claudeArgs.push("--dangerously-load-development-channels", ...devChannels);
|
|
1063
1488
|
claudeArgs.push("--mcp-config", mcpConfigPath);
|
|
1064
|
-
if (
|
|
1489
|
+
if (existsSync3(claudeMdPath)) claudeArgs.push("--system-prompt-file", claudeMdPath);
|
|
1065
1490
|
const acpModelAlias = claudeModelAlias(config.primaryModel);
|
|
1066
1491
|
if (acpModelAlias) claudeArgs.push("--model", acpModelAlias);
|
|
1067
1492
|
claudeArgs.push("--allow-dangerously-skip-permissions");
|
|
@@ -1073,7 +1498,7 @@ function writeAcpxConfig(config) {
|
|
|
1073
1498
|
const envIntegrationsPath = join2(projectDir, ".env.integrations");
|
|
1074
1499
|
const wrapperPath = join2(projectDir, ".claude", "acpx-agent.sh");
|
|
1075
1500
|
const wrapperLines = ["#!/usr/bin/env bash"];
|
|
1076
|
-
if (
|
|
1501
|
+
if (existsSync3(envIntegrationsPath)) {
|
|
1077
1502
|
wrapperLines.push(`set -a`, `source ${JSON.stringify(envIntegrationsPath)}`, `set +a`);
|
|
1078
1503
|
}
|
|
1079
1504
|
if (claudeAuthMode === "api_key" && anthropicApiKey) {
|
|
@@ -1096,18 +1521,25 @@ function writeAcpxConfig(config) {
|
|
|
1096
1521
|
}
|
|
1097
1522
|
|
|
1098
1523
|
export {
|
|
1524
|
+
formatMissingVar,
|
|
1525
|
+
parseEnvIntegrations,
|
|
1526
|
+
probeMcpEnvSubstitution,
|
|
1099
1527
|
sanitizeMcpJson,
|
|
1100
1528
|
buildAllowedTools,
|
|
1101
1529
|
sessionTranscriptDir,
|
|
1102
1530
|
isAgentIdle,
|
|
1103
1531
|
isStaleForToday,
|
|
1104
1532
|
peekCurrentSession,
|
|
1533
|
+
checkChannelInputs,
|
|
1534
|
+
takeWatchdogGiveUpCount,
|
|
1535
|
+
creditWatchdogGiveUpCount,
|
|
1105
1536
|
resolveClaudeBinary,
|
|
1106
1537
|
writePersistentClaudeWrapper,
|
|
1107
1538
|
paneLogPath,
|
|
1108
1539
|
readPaneLogTail,
|
|
1109
1540
|
prepareForRespawn,
|
|
1110
1541
|
getLastFailureContext,
|
|
1542
|
+
resolveSessionSpawnDecision,
|
|
1111
1543
|
startPersistentSession,
|
|
1112
1544
|
SEND_KEYS_ENTER_DELAY_MS,
|
|
1113
1545
|
sendToAgent,
|
|
@@ -1125,4 +1557,4 @@ export {
|
|
|
1125
1557
|
stopAllSessionsAndWait,
|
|
1126
1558
|
getProjectDir
|
|
1127
1559
|
};
|
|
1128
|
-
//# sourceMappingURL=chunk-
|
|
1560
|
+
//# sourceMappingURL=chunk-SJPAXXTW.js.map
|