@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.
- package/dist/bin/agt.js +13 -4
- package/dist/bin/agt.js.map +1 -1
- package/dist/{chunk-AFUG4KD3.js → chunk-EG5D3KUV.js} +318 -41
- package/dist/chunk-EG5D3KUV.js.map +1 -0
- package/dist/{chunk-LU6L2J32.js → chunk-MEJGM5RV.js} +207 -15
- package/dist/chunk-MEJGM5RV.js.map +1 -0
- package/dist/{claude-pair-runtime-GS6AOYHS.js → claude-pair-runtime-Q7PNH3ZK.js} +41 -2
- package/dist/claude-pair-runtime-Q7PNH3ZK.js.map +1 -0
- package/dist/lib/manager-worker.js +163 -19
- package/dist/lib/manager-worker.js.map +1 -1
- package/dist/{persistent-session-VRS3MFQ3.js → persistent-session-YEUFJMWF.js} +8 -2
- package/package.json +1 -1
- package/dist/chunk-AFUG4KD3.js.map +0 -1
- package/dist/chunk-LU6L2J32.js.map +0 -1
- package/dist/claude-pair-runtime-GS6AOYHS.js.map +0 -1
- /package/dist/{persistent-session-VRS3MFQ3.js.map → persistent-session-YEUFJMWF.js.map} +0 -0
|
@@ -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
|
|
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 (
|
|
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 =
|
|
63
|
-
if (
|
|
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 =
|
|
194
|
+
const targetPath = join2(targetDir, sourceFilename);
|
|
75
195
|
try {
|
|
76
|
-
if (!
|
|
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 &&
|
|
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 &&
|
|
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 (
|
|
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 (!
|
|
234
|
+
if (!existsSync2(mcpConfigPath)) return [];
|
|
115
235
|
try {
|
|
116
|
-
const data = JSON.parse(
|
|
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 =
|
|
130
|
-
if (
|
|
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 =
|
|
375
|
+
const claudeDir = join2(homedir2(), ".claude");
|
|
188
376
|
for (const filename of [".credentials.json", "credentials.json"]) {
|
|
189
|
-
const p =
|
|
190
|
-
if (
|
|
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 (
|
|
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 =
|
|
215
|
-
if (
|
|
413
|
+
const envIntegrationsPath = join2(projectDir, ".env.integrations");
|
|
414
|
+
if (existsSync2(envIntegrationsPath)) {
|
|
216
415
|
try {
|
|
217
|
-
const envContent =
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
311
|
-
|
|
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 =
|
|
333
|
-
|
|
334
|
-
const tmpFile =
|
|
335
|
-
|
|
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
|
|
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 (
|
|
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 =
|
|
517
|
-
const wrapperPath =
|
|
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 (
|
|
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
|
-
|
|
527
|
-
|
|
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
|
-
|
|
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-
|
|
832
|
+
//# sourceMappingURL=chunk-EG5D3KUV.js.map
|