@integrity-labs/agt-cli 0.27.149 → 0.27.150-test.16
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 +4 -4
- package/dist/bin/agt.js.map +1 -1
- package/dist/chunk-354FAVQR.js +173 -0
- package/dist/chunk-354FAVQR.js.map +1 -0
- package/dist/{chunk-JLS7NQFE.js → chunk-7GKJZBTB.js} +52 -209
- package/dist/chunk-7GKJZBTB.js.map +1 -0
- package/dist/{chunk-XWZMKHHG.js → chunk-QHEAAPEG.js} +57 -142
- package/dist/chunk-QHEAAPEG.js.map +1 -0
- package/dist/{chunk-A75AOK6E.js → chunk-WOOYOAPG.js} +1 -1
- package/dist/chunk-WOOYOAPG.js.map +1 -0
- package/dist/{claude-pair-runtime-3ZIOY3Z5.js → claude-pair-runtime-GIUCD7IG.js} +2 -2
- package/dist/daily-session-PNQX5URX.js +27 -0
- package/dist/lib/manager-worker.js +35 -82
- package/dist/lib/manager-worker.js.map +1 -1
- package/dist/{persistent-session-ZLEK4KBF.js → persistent-session-35PWSTLO.js} +4 -3
- package/dist/persistent-session-35PWSTLO.js.map +1 -0
- package/dist/{responsiveness-probe-3EUNCJDU.js → responsiveness-probe-MA4M2QM4.js} +4 -3
- package/dist/{responsiveness-probe-3EUNCJDU.js.map → responsiveness-probe-MA4M2QM4.js.map} +1 -1
- package/package.json +1 -1
- package/dist/chunk-A75AOK6E.js.map +0 -1
- package/dist/chunk-JLS7NQFE.js.map +0 -1
- package/dist/chunk-XWZMKHHG.js.map +0 -1
- /package/dist/{claude-pair-runtime-3ZIOY3Z5.js.map → claude-pair-runtime-GIUCD7IG.js.map} +0 -0
- /package/dist/{persistent-session-ZLEK4KBF.js.map → daily-session-PNQX5URX.js.map} +0 -0
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
// src/lib/daily-session.ts
|
|
2
|
+
import { randomUUID } from "crypto";
|
|
3
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, statSync, writeFileSync } from "fs";
|
|
4
|
+
import { homedir } from "os";
|
|
5
|
+
import { join } from "path";
|
|
6
|
+
var HISTORY_DAYS = 7;
|
|
7
|
+
function profileDir(codeName) {
|
|
8
|
+
return join(homedir(), ".augmented", codeName);
|
|
9
|
+
}
|
|
10
|
+
function dailySessionPath(codeName) {
|
|
11
|
+
return join(profileDir(codeName), "daily-session.json");
|
|
12
|
+
}
|
|
13
|
+
function todayLocalIso(now = /* @__PURE__ */ new Date(), timezone) {
|
|
14
|
+
if (timezone) {
|
|
15
|
+
try {
|
|
16
|
+
const fmt = new Intl.DateTimeFormat("en-CA", {
|
|
17
|
+
timeZone: timezone,
|
|
18
|
+
year: "numeric",
|
|
19
|
+
month: "2-digit",
|
|
20
|
+
day: "2-digit"
|
|
21
|
+
});
|
|
22
|
+
return fmt.format(now);
|
|
23
|
+
} catch {
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
const y = now.getFullYear();
|
|
27
|
+
const m = String(now.getMonth() + 1).padStart(2, "0");
|
|
28
|
+
const d = String(now.getDate()).padStart(2, "0");
|
|
29
|
+
return `${y}-${m}-${d}`;
|
|
30
|
+
}
|
|
31
|
+
function readFile(codeName) {
|
|
32
|
+
const path = dailySessionPath(codeName);
|
|
33
|
+
if (!existsSync(path)) return { current: null, history: [] };
|
|
34
|
+
try {
|
|
35
|
+
const raw = readFileSync(path, "utf-8");
|
|
36
|
+
const parsed = JSON.parse(raw);
|
|
37
|
+
return {
|
|
38
|
+
current: parsed.current ?? null,
|
|
39
|
+
history: Array.isArray(parsed.history) ? parsed.history : []
|
|
40
|
+
};
|
|
41
|
+
} catch {
|
|
42
|
+
return { current: null, history: [] };
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function writeFile(codeName, data) {
|
|
46
|
+
const dir = profileDir(codeName);
|
|
47
|
+
mkdirSync(dir, { recursive: true });
|
|
48
|
+
const finalPath = dailySessionPath(codeName);
|
|
49
|
+
const tmpPath = `${finalPath}.${process.pid}.${randomUUID()}.tmp`;
|
|
50
|
+
writeFileSync(tmpPath, JSON.stringify(data, null, 2), "utf-8");
|
|
51
|
+
renameSync(tmpPath, finalPath);
|
|
52
|
+
}
|
|
53
|
+
function trimHistory(history, now, timezone) {
|
|
54
|
+
const cutoff = new Date(now);
|
|
55
|
+
cutoff.setDate(cutoff.getDate() - HISTORY_DAYS);
|
|
56
|
+
const cutoffIso = todayLocalIso(cutoff, timezone);
|
|
57
|
+
return history.filter((h) => h.date >= cutoffIso).slice(0, HISTORY_DAYS);
|
|
58
|
+
}
|
|
59
|
+
function getOrCreateDailySession(codeName, now = /* @__PURE__ */ new Date(), timezone) {
|
|
60
|
+
const today = todayLocalIso(now, timezone);
|
|
61
|
+
const file = readFile(codeName);
|
|
62
|
+
if (file.current && file.current.date === today) {
|
|
63
|
+
return { sessionId: file.current.sessionId, isNew: false };
|
|
64
|
+
}
|
|
65
|
+
const next = {
|
|
66
|
+
date: today,
|
|
67
|
+
sessionId: randomUUID(),
|
|
68
|
+
startedAt: now.toISOString()
|
|
69
|
+
};
|
|
70
|
+
const history = trimHistory(
|
|
71
|
+
[...file.current ? [file.current] : [], ...file.history],
|
|
72
|
+
now,
|
|
73
|
+
timezone
|
|
74
|
+
);
|
|
75
|
+
writeFile(codeName, { current: next, history });
|
|
76
|
+
return { sessionId: next.sessionId, isNew: true };
|
|
77
|
+
}
|
|
78
|
+
function markDailySessionSpawn(codeName, sessionId, now = /* @__PURE__ */ new Date(), timezone) {
|
|
79
|
+
const today = todayLocalIso(now, timezone);
|
|
80
|
+
const file = readFile(codeName);
|
|
81
|
+
if (file.current && file.current.date === today && file.current.sessionId === sessionId) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
const next = {
|
|
85
|
+
date: today,
|
|
86
|
+
sessionId,
|
|
87
|
+
startedAt: now.toISOString()
|
|
88
|
+
};
|
|
89
|
+
const history = trimHistory(
|
|
90
|
+
[...file.current ? [file.current] : [], ...file.history],
|
|
91
|
+
now,
|
|
92
|
+
timezone
|
|
93
|
+
);
|
|
94
|
+
writeFile(codeName, { current: next, history });
|
|
95
|
+
}
|
|
96
|
+
function rotateDailySession(codeName, now = /* @__PURE__ */ new Date(), timezone) {
|
|
97
|
+
const today = todayLocalIso(now, timezone);
|
|
98
|
+
const file = readFile(codeName);
|
|
99
|
+
const next = {
|
|
100
|
+
date: today,
|
|
101
|
+
sessionId: randomUUID(),
|
|
102
|
+
startedAt: now.toISOString()
|
|
103
|
+
};
|
|
104
|
+
const history = trimHistory(
|
|
105
|
+
[...file.current ? [file.current] : [], ...file.history],
|
|
106
|
+
now,
|
|
107
|
+
timezone
|
|
108
|
+
);
|
|
109
|
+
writeFile(codeName, { current: next, history });
|
|
110
|
+
return next.sessionId;
|
|
111
|
+
}
|
|
112
|
+
function encodeProjectPath(projectDir) {
|
|
113
|
+
return "-" + projectDir.replace(/^\//, "").replace(/[/.]/g, "-");
|
|
114
|
+
}
|
|
115
|
+
function sessionFileExists(projectDir, sessionId) {
|
|
116
|
+
const path = join(
|
|
117
|
+
homedir(),
|
|
118
|
+
".claude",
|
|
119
|
+
"projects",
|
|
120
|
+
encodeProjectPath(projectDir),
|
|
121
|
+
`${sessionId}.jsonl`
|
|
122
|
+
);
|
|
123
|
+
return existsSync(path);
|
|
124
|
+
}
|
|
125
|
+
function sessionTranscriptDir(projectDir) {
|
|
126
|
+
return join(homedir(), ".claude", "projects", encodeProjectPath(projectDir));
|
|
127
|
+
}
|
|
128
|
+
function sessionFilePath(projectDir, sessionId) {
|
|
129
|
+
return join(sessionTranscriptDir(projectDir), `${sessionId}.jsonl`);
|
|
130
|
+
}
|
|
131
|
+
function transcriptActivityAgeSeconds(projectDir, sessionId, now = /* @__PURE__ */ new Date()) {
|
|
132
|
+
if (!sessionId) return null;
|
|
133
|
+
try {
|
|
134
|
+
const mtimeMs = statSync(sessionFilePath(projectDir, sessionId)).mtimeMs;
|
|
135
|
+
return Math.max(0, Math.floor((now.getTime() - mtimeMs) / 1e3));
|
|
136
|
+
} catch {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
function isAgentIdle(projectDir, sessionId, idleSeconds = 60, now = /* @__PURE__ */ new Date()) {
|
|
141
|
+
const path = sessionFilePath(projectDir, sessionId);
|
|
142
|
+
if (!existsSync(path)) return true;
|
|
143
|
+
try {
|
|
144
|
+
const mtimeMs = statSync(path).mtimeMs;
|
|
145
|
+
return now.getTime() - mtimeMs >= idleSeconds * 1e3;
|
|
146
|
+
} catch {
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
function isStaleForToday(codeName, now = /* @__PURE__ */ new Date(), timezone) {
|
|
151
|
+
const file = readFile(codeName);
|
|
152
|
+
if (!file.current) return false;
|
|
153
|
+
return file.current.date !== todayLocalIso(now, timezone);
|
|
154
|
+
}
|
|
155
|
+
function peekCurrentSession(codeName) {
|
|
156
|
+
return readFile(codeName).current;
|
|
157
|
+
}
|
|
158
|
+
var _internals = { todayLocalIso, dailySessionPath, profileDir, encodeProjectPath };
|
|
159
|
+
|
|
160
|
+
export {
|
|
161
|
+
getOrCreateDailySession,
|
|
162
|
+
markDailySessionSpawn,
|
|
163
|
+
rotateDailySession,
|
|
164
|
+
sessionFileExists,
|
|
165
|
+
sessionTranscriptDir,
|
|
166
|
+
sessionFilePath,
|
|
167
|
+
transcriptActivityAgeSeconds,
|
|
168
|
+
isAgentIdle,
|
|
169
|
+
isStaleForToday,
|
|
170
|
+
peekCurrentSession,
|
|
171
|
+
_internals
|
|
172
|
+
};
|
|
173
|
+
//# sourceMappingURL=chunk-354FAVQR.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/daily-session.ts"],"sourcesContent":["/**\n * ENG-4642: per-agent / per-day Claude session pinning.\n *\n * The persistent-session manager kills the tmux session on every spawn\n * (clean slate) and starts a fresh `claude` invocation. Pre-this-module,\n * that meant a new conversation every restart — operators lost context\n * any time the manager respawned.\n *\n * Goal: each calendar day is a fresh conversation, but every spawn\n * inside that day reuses the same conversation. We achieve this by\n * generating a stable UUID up front (Claude CLI accepts\n * `--session-id <uuid>` for the first spawn, `--resume <uuid>` for\n * subsequent ones) and persisting it to a tiny per-agent JSON file.\n *\n * Storage: `~/.augmented/<codeName>/daily-session.json` — same root the\n * persistent-session manager already owns via getProjectDir(). Schema:\n *\n * { \"date\": \"YYYY-MM-DD\", \"sessionId\": \"<uuid>\", \"history\": [...] }\n *\n * `history` keeps the last few days' entries so an operator can debug\n * which session was bound to which day. We trim to 7 days so the file\n * doesn't grow unbounded.\n *\n * Day boundary: defaults to host-local date (server timezone). Callers\n * may pass an IANA timezone (e.g. `Australia/Melbourne`) and the\n * rollover will fire at that zone's midnight instead — see ENG-5371.\n * The manager passes the agent's resolved `agentTimezone` (same source\n * as ENG-5363's channel MCP `TZ` env var: `teamSettings.timezone`,\n * defaulting to UTC) so the daily rollover lines up with what an\n * operator in the agent's timezone calls \"today\".\n *\n * Failure mode: if the on-disk JSONL Claude writes for the resumed\n * session is missing (host moved, profile wiped, claude version\n * incompatibility), `--resume` would fail and the agent would land on\n * the login picker. Callers verify the JSONL exists via\n * `sessionFileExists()` before choosing `--resume`; if it's gone we\n * fall back to `--session-id` (treat the stored UUID as fresh, claude\n * will materialise the JSONL on first turn).\n */\n\nimport { randomUUID } from 'node:crypto';\nimport { existsSync, mkdirSync, readFileSync, renameSync, statSync, writeFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\n\nconst HISTORY_DAYS = 7;\n\ninterface DailySessionEntry {\n date: string; // YYYY-MM-DD\n sessionId: string; // UUID v4\n startedAt: string; // ISO 8601\n}\n\ninterface DailySessionFile {\n current: DailySessionEntry | null;\n history: DailySessionEntry[];\n}\n\nexport interface DailySessionResult {\n sessionId: string;\n /** `true` when this call generated a new UUID (first spawn of a new day or first ever). */\n isNew: boolean;\n}\n\nfunction profileDir(codeName: string): string {\n return join(homedir(), '.augmented', codeName);\n}\n\nfunction dailySessionPath(codeName: string): string {\n return join(profileDir(codeName), 'daily-session.json');\n}\n\nfunction todayLocalIso(now: Date = new Date(), timezone?: string): string {\n // ENG-5371: when an IANA timezone is supplied (e.g. Australia/Melbourne),\n // compute the date in that zone via Intl.DateTimeFormat. Falling back to\n // Date getters preserves the original host-local behaviour for callers\n // (and tests) that don't supply a timezone — important for\n // backward-compatibility with the ENG-4642 contract.\n if (timezone) {\n try {\n const fmt = new Intl.DateTimeFormat('en-CA', {\n timeZone: timezone,\n year: 'numeric',\n month: '2-digit',\n day: '2-digit',\n });\n // en-CA renders as `YYYY-MM-DD` already — no parts assembly needed.\n // Wrapped in try/catch in case the timezone string is invalid, in\n // which case we fall through to host-local rather than throw.\n return fmt.format(now);\n } catch {\n // Invalid IANA zone — fall back to host-local.\n }\n }\n const y = now.getFullYear();\n const m = String(now.getMonth() + 1).padStart(2, '0');\n const d = String(now.getDate()).padStart(2, '0');\n return `${y}-${m}-${d}`;\n}\n\nfunction readFile(codeName: string): DailySessionFile {\n const path = dailySessionPath(codeName);\n if (!existsSync(path)) return { current: null, history: [] };\n try {\n const raw = readFileSync(path, 'utf-8');\n const parsed = JSON.parse(raw) as Partial<DailySessionFile>;\n return {\n current: parsed.current ?? null,\n history: Array.isArray(parsed.history) ? parsed.history : [],\n };\n } catch {\n // Corrupt file — start fresh rather than crashing the manager.\n return { current: null, history: [] };\n }\n}\n\nfunction writeFile(codeName: string, data: DailySessionFile): void {\n const dir = profileDir(codeName);\n mkdirSync(dir, { recursive: true });\n // Atomic write: tmp + rename. A reader catching us mid-writeFileSync\n // would otherwise see truncated JSON and the corrupt-file branch in\n // readFile() would silently treat the agent as fresh state, losing\n // today's UUID and forcing a rollover the operator didn't ask for.\n // PID + randomUUID in the tmp suffix so two managers (or a respawn\n // racing with its predecessor) can't collide on the temp path and\n // have one rename remove the file the other is about to rename.\n // Mirrors the pattern in restart-flags.ts.\n const finalPath = dailySessionPath(codeName);\n const tmpPath = `${finalPath}.${process.pid}.${randomUUID()}.tmp`;\n writeFileSync(tmpPath, JSON.stringify(data, null, 2), 'utf-8');\n renameSync(tmpPath, finalPath);\n}\n\nfunction trimHistory(\n history: DailySessionEntry[],\n now: Date,\n timezone?: string,\n): DailySessionEntry[] {\n // Keep newest first, drop entries older than HISTORY_DAYS by date.\n // Take the injected `now` so callers with a frozen clock (tests\n // walking the day forward) don't get inconsistent cutoffs against\n // `new Date()`. The cutoff is computed in the same timezone as the\n // current entry's date string so equality comparisons hold across DST.\n const cutoff = new Date(now);\n cutoff.setDate(cutoff.getDate() - HISTORY_DAYS);\n const cutoffIso = todayLocalIso(cutoff, timezone);\n return history.filter((h) => h.date >= cutoffIso).slice(0, HISTORY_DAYS);\n}\n\n/**\n * Resolve the session UUID this agent should use right now. Generates\n * (and persists) a new UUID on the first call of a new local day, or\n * when the file is missing/corrupt; otherwise returns the day's\n * existing UUID. Idempotent within the same day.\n *\n * Concurrency: the read-then-write here is not under a file lock.\n * In our deployment the manager runs supervised, one process per\n * host (`agt manager start --supervise` / runSupervisorLoop), so\n * concurrent invocation for the same `codeName` is bounded to the\n * sub-second respawn window when the supervisor restarts the\n * worker. The atomic tmp+rename in writeFile() guarantees we never\n * read torn JSON, so the worst-case under a respawn race is two\n * managers minting different UUIDs and one rename winning — both\n * processes converge on the winner's UUID on the next supervisor\n * tick (which re-reads the file). We've taken that trade-off\n * over a proper inter-process lock because a stale lockfile (from\n * a SIGKILL'd manager) would block all subsequent runs and need\n * its own recovery path; the lossy outcome of a UUID race is one\n * tick of conversation churn, not a permanent block.\n */\nexport function getOrCreateDailySession(\n codeName: string,\n now: Date = new Date(),\n timezone?: string,\n): DailySessionResult {\n const today = todayLocalIso(now, timezone);\n const file = readFile(codeName);\n\n if (file.current && file.current.date === today) {\n return { sessionId: file.current.sessionId, isNew: false };\n }\n\n // Roll over: yesterday's (or older) entry moves to history, new one\n // takes its place.\n const next: DailySessionEntry = {\n date: today,\n sessionId: randomUUID(),\n startedAt: now.toISOString(),\n };\n const history = trimHistory(\n [...(file.current ? [file.current] : []), ...file.history],\n now,\n timezone,\n );\n writeFile(codeName, { current: next, history });\n return { sessionId: next.sessionId, isNew: true };\n}\n\n/**\n * Record the UUID a caller just spawned with so the day-rollover\n * marker (`current.date`) advances to today.\n *\n * ENG-5431: a spawn that bypasses `getOrCreateDailySession` (today the\n * AGT_DISABLE_SESSION_RESUME path, which mints a fresh `randomUUID()`;\n * under ENG-5397 it was every spawn) never writes `daily-session.json`,\n * so `isStaleForToday()` keeps returning true once the previous\n * `current.date` falls behind — re-firing the day-rollover restart on\n * every supervisor tick. Calling this after each spawn keeps the\n * marker in lockstep with the actual running session. On the ENG-6039\n * resume/fresh paths it's an idempotent no-op.\n *\n * Idempotent: re-calling with the same (date, sessionId) is a no-op\n * write of the same content. Different sessionId on the same date\n * just overwrites `current.sessionId` (the old one moves to history).\n */\nexport function markDailySessionSpawn(\n codeName: string,\n sessionId: string,\n now: Date = new Date(),\n timezone?: string,\n): void {\n const today = todayLocalIso(now, timezone);\n const file = readFile(codeName);\n if (file.current && file.current.date === today && file.current.sessionId === sessionId) {\n return;\n }\n const next: DailySessionEntry = {\n date: today,\n sessionId,\n startedAt: now.toISOString(),\n };\n const history = trimHistory(\n [...(file.current ? [file.current] : []), ...file.history],\n now,\n timezone,\n );\n writeFile(codeName, { current: next, history });\n}\n\n/**\n * Reset the day's pin — used as a recovery hatch after `--resume` is\n * rejected by claude (corrupt state, version mismatch). Writes a new\n * UUID for today, demotes the old one to history.\n */\nexport function rotateDailySession(\n codeName: string,\n now: Date = new Date(),\n timezone?: string,\n): string {\n const today = todayLocalIso(now, timezone);\n const file = readFile(codeName);\n const next: DailySessionEntry = {\n date: today,\n sessionId: randomUUID(),\n startedAt: now.toISOString(),\n };\n const history = trimHistory(\n [...(file.current ? [file.current] : []), ...file.history],\n now,\n timezone,\n );\n writeFile(codeName, { current: next, history });\n return next.sessionId;\n}\n\n/**\n * Encode an absolute project dir the way Claude Code stores it under\n * ~/.claude/projects/. Claude collapses runs of `/` and `.` into single\n * `-` separators with a leading `-` (no separator at the start). The\n * earlier \"/ only\" encoder produced a stale path for any project dir\n * containing a `.` (e.g. `/root/.augmented/scout/project`), which made\n * sessionFileExists() return false even when the JSONL was on disk.\n *\n * Diagnosed live on prod scout (ENG-4659): the on-disk dir was\n * /root/.claude/projects/-root--augmented-scout-project/\n * but our encoder produced\n * /root/.claude/projects/-root-.augmented-scout-project/\n * — the dot in `.augmented` wasn't translated. Result: every \"is\n * there JSONL on disk\" check returned false, the manager fell back to\n * `--session-id` reuse, and Claude rejected the same UUID with\n * \"Session ID already in use\" forever.\n *\n * Empirical observations from /root/.claude/projects/ on a live host:\n * /usr/bin -> -usr-bin\n * /root/.augmented/scout/project -> -root--augmented-scout-project\n * Behaviour: `[/.]` -> `-`, with consecutive separators preserved\n * (the `/.` between `root/` and `.augmented` becomes `--`).\n */\nfunction encodeProjectPath(projectDir: string): string {\n return '-' + projectDir.replace(/^\\//, '').replace(/[/.]/g, '-');\n}\n\n/**\n * Check whether claude has actually written a session JSONL for this\n * UUID. If the file is missing the `--resume` would fail and put the\n * agent on the login picker; callers should fall back to `--session-id`\n * instead. See encodeProjectPath() for the encoding rules.\n */\nexport function sessionFileExists(\n projectDir: string,\n sessionId: string,\n): boolean {\n const path = join(\n homedir(),\n '.claude',\n 'projects',\n encodeProjectPath(projectDir),\n `${sessionId}.jsonl`,\n );\n return existsSync(path);\n}\n\n/**\n * Directory under ~/.claude/projects/ where Claude Code stores every session\n * transcript for the given project dir. All of an agent's sessions —\n * persistent respawns (one pinned UUID per agent-tz day, ENG-6039, plus\n * rotations), scheduled tasks, and direct-chat invocations — share this one\n * directory because they all run with the same cwd (getProjectDir). The\n * token-usage monitor enumerates it.\n */\nexport function sessionTranscriptDir(projectDir: string): string {\n return join(homedir(), '.claude', 'projects', encodeProjectPath(projectDir));\n}\n\nexport function sessionFilePath(projectDir: string, sessionId: string): string {\n return join(sessionTranscriptDir(projectDir), `${sessionId}.jsonl`);\n}\n\n/**\n * ENG-6238: age (s) of the current session's transcript JSONL — the wedge\n * detector's \"is the model actually producing tokens right now\" signal. The\n * transcript grows as the model streams turns/tool calls, so a fresh mtime\n * means real work is happening, where pane.log can be kept fresh by a frozen\n * but animated spinner. Returns null when there's no session id or the file\n * can't be stat'd (the detector then degrades to the pane-age fallback).\n */\nexport function transcriptActivityAgeSeconds(\n projectDir: string,\n sessionId: string | null,\n now: Date = new Date(),\n): number | null {\n if (!sessionId) return null;\n try {\n const mtimeMs = statSync(sessionFilePath(projectDir, sessionId)).mtimeMs;\n return Math.max(0, Math.floor((now.getTime() - mtimeMs) / 1000));\n } catch {\n return null;\n }\n}\n\n/**\n * Is the agent's session JSONL idle — i.e. has it not been written for\n * at least `idleSeconds`? Claude appends to the file on every turn\n * (tool calls, assistant messages, user messages) so a stale mtime is\n * a reliable proxy for \"nothing in flight\". Returns true if the file\n * is missing (no in-flight work to interrupt) or if its mtime is\n * older than the threshold.\n *\n * Used by the scheduled-rollover gate so we don't kill a tmux session\n * mid-task at the day boundary — defer the rollover one tick at a\n * time until the agent is between turns.\n */\nexport function isAgentIdle(\n projectDir: string,\n sessionId: string,\n idleSeconds = 60,\n now: Date = new Date(),\n): boolean {\n const path = sessionFilePath(projectDir, sessionId);\n if (!existsSync(path)) return true;\n try {\n const mtimeMs = statSync(path).mtimeMs;\n return now.getTime() - mtimeMs >= idleSeconds * 1000;\n } catch {\n // stat failed (race, permissions). Treat as non-idle to err on the\n // side of NOT interrupting a possibly-running task.\n return false;\n }\n}\n\n/**\n * Cheap \"should we roll over?\" check for the supervisor tick. Reads\n * the persisted current entry and compares its date against today's.\n * Does NOT mint a new UUID — the caller decides what to do with the\n * answer (typically: kill the tmux session iff isAgentIdle is true,\n * letting the next tick respawn fresh via getOrCreateDailySession).\n */\nexport function isStaleForToday(\n codeName: string,\n now: Date = new Date(),\n timezone?: string,\n): boolean {\n const file = readFile(codeName);\n if (!file.current) return false; // never seeded — nothing to roll\n return file.current.date !== todayLocalIso(now, timezone);\n}\n\n/**\n * Read-only accessor for the current entry, returns null when the\n * file doesn't exist or has no current entry. Useful to grab the\n * sessionId for the idle check without triggering a roll-over write.\n */\nexport function peekCurrentSession(codeName: string): {\n date: string;\n sessionId: string;\n startedAt: string;\n} | null {\n return readFile(codeName).current;\n}\n\n// Exported for unit tests — keep the surface small.\nexport const _internals = { todayLocalIso, dailySessionPath, profileDir, encodeProjectPath };\n"],"mappings":"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","names":[]}
|
|
@@ -1,16 +1,22 @@
|
|
|
1
1
|
import {
|
|
2
2
|
claudeModelAlias,
|
|
3
3
|
isClaudeFastMode
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-WOOYOAPG.js";
|
|
5
|
+
import {
|
|
6
|
+
getOrCreateDailySession,
|
|
7
|
+
markDailySessionSpawn,
|
|
8
|
+
rotateDailySession,
|
|
9
|
+
sessionFileExists
|
|
10
|
+
} from "./chunk-354FAVQR.js";
|
|
5
11
|
import {
|
|
6
12
|
reapOrphanChannelMcps
|
|
7
13
|
} from "./chunk-XWVM4KPK.js";
|
|
8
14
|
|
|
9
15
|
// src/lib/persistent-session.ts
|
|
10
16
|
import { spawn, execSync, execFileSync as execFileSync3 } from "child_process";
|
|
11
|
-
import { join
|
|
12
|
-
import { homedir
|
|
13
|
-
import { existsSync as
|
|
17
|
+
import { join, dirname } from "path";
|
|
18
|
+
import { homedir, platform, userInfo } from "os";
|
|
19
|
+
import { existsSync as existsSync2, readFileSync as readFileSync3, readdirSync, writeFileSync as writeFileSync2, appendFileSync, mkdirSync, chmodSync, copyFileSync, rmSync } from "fs";
|
|
14
20
|
import { fileURLToPath } from "url";
|
|
15
21
|
|
|
16
22
|
// src/lib/mcp-sanitize.ts
|
|
@@ -153,165 +159,7 @@ function probeMcpEnvSubstitution(args) {
|
|
|
153
159
|
}
|
|
154
160
|
|
|
155
161
|
// src/lib/persistent-session.ts
|
|
156
|
-
import { randomUUID as randomUUID2 } from "crypto";
|
|
157
|
-
|
|
158
|
-
// src/lib/daily-session.ts
|
|
159
162
|
import { randomUUID } from "crypto";
|
|
160
|
-
import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync3, renameSync, statSync, writeFileSync as writeFileSync2 } from "fs";
|
|
161
|
-
import { homedir } from "os";
|
|
162
|
-
import { join } from "path";
|
|
163
|
-
var HISTORY_DAYS = 7;
|
|
164
|
-
function profileDir(codeName) {
|
|
165
|
-
return join(homedir(), ".augmented", codeName);
|
|
166
|
-
}
|
|
167
|
-
function dailySessionPath(codeName) {
|
|
168
|
-
return join(profileDir(codeName), "daily-session.json");
|
|
169
|
-
}
|
|
170
|
-
function todayLocalIso(now = /* @__PURE__ */ new Date(), timezone) {
|
|
171
|
-
if (timezone) {
|
|
172
|
-
try {
|
|
173
|
-
const fmt = new Intl.DateTimeFormat("en-CA", {
|
|
174
|
-
timeZone: timezone,
|
|
175
|
-
year: "numeric",
|
|
176
|
-
month: "2-digit",
|
|
177
|
-
day: "2-digit"
|
|
178
|
-
});
|
|
179
|
-
return fmt.format(now);
|
|
180
|
-
} catch {
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
const y = now.getFullYear();
|
|
184
|
-
const m = String(now.getMonth() + 1).padStart(2, "0");
|
|
185
|
-
const d = String(now.getDate()).padStart(2, "0");
|
|
186
|
-
return `${y}-${m}-${d}`;
|
|
187
|
-
}
|
|
188
|
-
function readFile(codeName) {
|
|
189
|
-
const path = dailySessionPath(codeName);
|
|
190
|
-
if (!existsSync2(path)) return { current: null, history: [] };
|
|
191
|
-
try {
|
|
192
|
-
const raw = readFileSync3(path, "utf-8");
|
|
193
|
-
const parsed = JSON.parse(raw);
|
|
194
|
-
return {
|
|
195
|
-
current: parsed.current ?? null,
|
|
196
|
-
history: Array.isArray(parsed.history) ? parsed.history : []
|
|
197
|
-
};
|
|
198
|
-
} catch {
|
|
199
|
-
return { current: null, history: [] };
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
function writeFile(codeName, data) {
|
|
203
|
-
const dir = profileDir(codeName);
|
|
204
|
-
mkdirSync(dir, { recursive: true });
|
|
205
|
-
const finalPath = dailySessionPath(codeName);
|
|
206
|
-
const tmpPath = `${finalPath}.${process.pid}.${randomUUID()}.tmp`;
|
|
207
|
-
writeFileSync2(tmpPath, JSON.stringify(data, null, 2), "utf-8");
|
|
208
|
-
renameSync(tmpPath, finalPath);
|
|
209
|
-
}
|
|
210
|
-
function trimHistory(history, now, timezone) {
|
|
211
|
-
const cutoff = new Date(now);
|
|
212
|
-
cutoff.setDate(cutoff.getDate() - HISTORY_DAYS);
|
|
213
|
-
const cutoffIso = todayLocalIso(cutoff, timezone);
|
|
214
|
-
return history.filter((h) => h.date >= cutoffIso).slice(0, HISTORY_DAYS);
|
|
215
|
-
}
|
|
216
|
-
function getOrCreateDailySession(codeName, now = /* @__PURE__ */ new Date(), timezone) {
|
|
217
|
-
const today = todayLocalIso(now, timezone);
|
|
218
|
-
const file = readFile(codeName);
|
|
219
|
-
if (file.current && file.current.date === today) {
|
|
220
|
-
return { sessionId: file.current.sessionId, isNew: false };
|
|
221
|
-
}
|
|
222
|
-
const next = {
|
|
223
|
-
date: today,
|
|
224
|
-
sessionId: randomUUID(),
|
|
225
|
-
startedAt: now.toISOString()
|
|
226
|
-
};
|
|
227
|
-
const history = trimHistory(
|
|
228
|
-
[...file.current ? [file.current] : [], ...file.history],
|
|
229
|
-
now,
|
|
230
|
-
timezone
|
|
231
|
-
);
|
|
232
|
-
writeFile(codeName, { current: next, history });
|
|
233
|
-
return { sessionId: next.sessionId, isNew: true };
|
|
234
|
-
}
|
|
235
|
-
function markDailySessionSpawn(codeName, sessionId, now = /* @__PURE__ */ new Date(), timezone) {
|
|
236
|
-
const today = todayLocalIso(now, timezone);
|
|
237
|
-
const file = readFile(codeName);
|
|
238
|
-
if (file.current && file.current.date === today && file.current.sessionId === sessionId) {
|
|
239
|
-
return;
|
|
240
|
-
}
|
|
241
|
-
const next = {
|
|
242
|
-
date: today,
|
|
243
|
-
sessionId,
|
|
244
|
-
startedAt: now.toISOString()
|
|
245
|
-
};
|
|
246
|
-
const history = trimHistory(
|
|
247
|
-
[...file.current ? [file.current] : [], ...file.history],
|
|
248
|
-
now,
|
|
249
|
-
timezone
|
|
250
|
-
);
|
|
251
|
-
writeFile(codeName, { current: next, history });
|
|
252
|
-
}
|
|
253
|
-
function rotateDailySession(codeName, now = /* @__PURE__ */ new Date(), timezone) {
|
|
254
|
-
const today = todayLocalIso(now, timezone);
|
|
255
|
-
const file = readFile(codeName);
|
|
256
|
-
const next = {
|
|
257
|
-
date: today,
|
|
258
|
-
sessionId: randomUUID(),
|
|
259
|
-
startedAt: now.toISOString()
|
|
260
|
-
};
|
|
261
|
-
const history = trimHistory(
|
|
262
|
-
[...file.current ? [file.current] : [], ...file.history],
|
|
263
|
-
now,
|
|
264
|
-
timezone
|
|
265
|
-
);
|
|
266
|
-
writeFile(codeName, { current: next, history });
|
|
267
|
-
return next.sessionId;
|
|
268
|
-
}
|
|
269
|
-
function encodeProjectPath(projectDir) {
|
|
270
|
-
return "-" + projectDir.replace(/^\//, "").replace(/[/.]/g, "-");
|
|
271
|
-
}
|
|
272
|
-
function sessionFileExists(projectDir, sessionId) {
|
|
273
|
-
const path = join(
|
|
274
|
-
homedir(),
|
|
275
|
-
".claude",
|
|
276
|
-
"projects",
|
|
277
|
-
encodeProjectPath(projectDir),
|
|
278
|
-
`${sessionId}.jsonl`
|
|
279
|
-
);
|
|
280
|
-
return existsSync2(path);
|
|
281
|
-
}
|
|
282
|
-
function sessionTranscriptDir(projectDir) {
|
|
283
|
-
return join(homedir(), ".claude", "projects", encodeProjectPath(projectDir));
|
|
284
|
-
}
|
|
285
|
-
function sessionFilePath(projectDir, sessionId) {
|
|
286
|
-
return join(sessionTranscriptDir(projectDir), `${sessionId}.jsonl`);
|
|
287
|
-
}
|
|
288
|
-
function transcriptActivityAgeSeconds(projectDir, sessionId, now = /* @__PURE__ */ new Date()) {
|
|
289
|
-
if (!sessionId) return null;
|
|
290
|
-
try {
|
|
291
|
-
const mtimeMs = statSync(sessionFilePath(projectDir, sessionId)).mtimeMs;
|
|
292
|
-
return Math.max(0, Math.floor((now.getTime() - mtimeMs) / 1e3));
|
|
293
|
-
} catch {
|
|
294
|
-
return null;
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
function isAgentIdle(projectDir, sessionId, idleSeconds = 60, now = /* @__PURE__ */ new Date()) {
|
|
298
|
-
const path = sessionFilePath(projectDir, sessionId);
|
|
299
|
-
if (!existsSync2(path)) return true;
|
|
300
|
-
try {
|
|
301
|
-
const mtimeMs = statSync(path).mtimeMs;
|
|
302
|
-
return now.getTime() - mtimeMs >= idleSeconds * 1e3;
|
|
303
|
-
} catch {
|
|
304
|
-
return false;
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
function isStaleForToday(codeName, now = /* @__PURE__ */ new Date(), timezone) {
|
|
308
|
-
const file = readFile(codeName);
|
|
309
|
-
if (!file.current) return false;
|
|
310
|
-
return file.current.date !== todayLocalIso(now, timezone);
|
|
311
|
-
}
|
|
312
|
-
function peekCurrentSession(codeName) {
|
|
313
|
-
return readFile(codeName).current;
|
|
314
|
-
}
|
|
315
163
|
|
|
316
164
|
// ../../packages/core/dist/runtime/session-probe.js
|
|
317
165
|
import { execFileSync } from "child_process";
|
|
@@ -593,7 +441,7 @@ function syncClaudeCredsToRoot() {
|
|
|
593
441
|
if (platform() !== "linux") return true;
|
|
594
442
|
if (typeof process.getuid !== "function" || process.getuid() !== 0) return true;
|
|
595
443
|
for (const filename of [".credentials.json", "credentials.json"]) {
|
|
596
|
-
if (
|
|
444
|
+
if (existsSync2(join("/root/.claude", filename))) return true;
|
|
597
445
|
}
|
|
598
446
|
let sourcePath = null;
|
|
599
447
|
try {
|
|
@@ -601,8 +449,8 @@ function syncClaudeCredsToRoot() {
|
|
|
601
449
|
outer: for (const entry of entries) {
|
|
602
450
|
if (!entry.isDirectory()) continue;
|
|
603
451
|
for (const filename of [".credentials.json", "credentials.json"]) {
|
|
604
|
-
const candidate =
|
|
605
|
-
if (
|
|
452
|
+
const candidate = join("/home", entry.name, ".claude", filename);
|
|
453
|
+
if (existsSync2(candidate)) {
|
|
606
454
|
sourcePath = candidate;
|
|
607
455
|
break outer;
|
|
608
456
|
}
|
|
@@ -613,9 +461,9 @@ function syncClaudeCredsToRoot() {
|
|
|
613
461
|
if (!sourcePath) return false;
|
|
614
462
|
const targetDir = "/root/.claude";
|
|
615
463
|
const sourceFilename = sourcePath.endsWith("credentials.json") && !sourcePath.endsWith(".credentials.json") ? "credentials.json" : ".credentials.json";
|
|
616
|
-
const targetPath =
|
|
464
|
+
const targetPath = join(targetDir, sourceFilename);
|
|
617
465
|
try {
|
|
618
|
-
if (!
|
|
466
|
+
if (!existsSync2(targetDir)) mkdirSync(targetDir, { recursive: true, mode: 448 });
|
|
619
467
|
copyFileSync(sourcePath, targetPath);
|
|
620
468
|
chmodSync(targetPath, 384);
|
|
621
469
|
return true;
|
|
@@ -627,13 +475,13 @@ var cachedClaudePath = null;
|
|
|
627
475
|
function resolveClaudeBinary() {
|
|
628
476
|
if (cachedClaudePath) return cachedClaudePath;
|
|
629
477
|
const override = process.env.CLAUDE_PATH;
|
|
630
|
-
if (override &&
|
|
478
|
+
if (override && existsSync2(override)) {
|
|
631
479
|
cachedClaudePath = override;
|
|
632
480
|
return override;
|
|
633
481
|
}
|
|
634
482
|
try {
|
|
635
483
|
const out = execSync("which claude 2>/dev/null", { encoding: "utf-8" }).trim();
|
|
636
|
-
if (out &&
|
|
484
|
+
if (out && existsSync2(out)) {
|
|
637
485
|
cachedClaudePath = out;
|
|
638
486
|
return out;
|
|
639
487
|
}
|
|
@@ -645,7 +493,7 @@ function resolveClaudeBinary() {
|
|
|
645
493
|
"/usr/local/bin/claude"
|
|
646
494
|
];
|
|
647
495
|
for (const p of candidates) {
|
|
648
|
-
if (
|
|
496
|
+
if (existsSync2(p)) {
|
|
649
497
|
cachedClaudePath = p;
|
|
650
498
|
return p;
|
|
651
499
|
}
|
|
@@ -654,8 +502,8 @@ function resolveClaudeBinary() {
|
|
|
654
502
|
}
|
|
655
503
|
function writePersistentClaudeWrapper(args) {
|
|
656
504
|
const { projectDir, claudeBin, initPrompt, claudeArgsJoined } = args;
|
|
657
|
-
const envIntegrationsPath =
|
|
658
|
-
const wrapperPath =
|
|
505
|
+
const envIntegrationsPath = join(projectDir, ".env.integrations");
|
|
506
|
+
const wrapperPath = join(projectDir, ".claude", "persistent-claude.sh");
|
|
659
507
|
const wrapperLines = [
|
|
660
508
|
"#!/usr/bin/env bash",
|
|
661
509
|
"set -e",
|
|
@@ -663,7 +511,7 @@ function writePersistentClaudeWrapper(args) {
|
|
|
663
511
|
// --dangerously-skip-permissions on dedicated EC2 hosts.
|
|
664
512
|
"export IS_SANDBOX=1"
|
|
665
513
|
];
|
|
666
|
-
if (
|
|
514
|
+
if (existsSync2(envIntegrationsPath)) {
|
|
667
515
|
wrapperLines.push(
|
|
668
516
|
"set -a",
|
|
669
517
|
`source ${JSON.stringify(envIntegrationsPath)}`,
|
|
@@ -674,15 +522,15 @@ function writePersistentClaudeWrapper(args) {
|
|
|
674
522
|
wrapperLines.push(
|
|
675
523
|
`exec ${JSON.stringify(claudeBin)} ${initPromptArg}${claudeArgsJoined}`
|
|
676
524
|
);
|
|
677
|
-
|
|
678
|
-
|
|
525
|
+
mkdirSync(join(projectDir, ".claude"), { recursive: true });
|
|
526
|
+
writeFileSync2(wrapperPath, wrapperLines.join("\n") + "\n", { mode: 448 });
|
|
679
527
|
chmodSync(wrapperPath, 448);
|
|
680
528
|
return wrapperPath;
|
|
681
529
|
}
|
|
682
530
|
function collectMcpServerNames(mcpConfigPath) {
|
|
683
|
-
if (!
|
|
531
|
+
if (!existsSync2(mcpConfigPath)) return [];
|
|
684
532
|
try {
|
|
685
|
-
const data = JSON.parse(
|
|
533
|
+
const data = JSON.parse(readFileSync3(mcpConfigPath, "utf-8"));
|
|
686
534
|
const servers = data.mcpServers;
|
|
687
535
|
return servers ? Object.keys(servers) : [];
|
|
688
536
|
} catch {
|
|
@@ -695,8 +543,8 @@ function getAcpxBin() {
|
|
|
695
543
|
const moduleDir = dirname(fileURLToPath(import.meta.url));
|
|
696
544
|
let dir = moduleDir;
|
|
697
545
|
for (let i = 0; i < 6; i++) {
|
|
698
|
-
const candidate =
|
|
699
|
-
if (
|
|
546
|
+
const candidate = join(dir, "node_modules", ".bin", "acpx");
|
|
547
|
+
if (existsSync2(candidate)) {
|
|
700
548
|
_acpxBin = candidate;
|
|
701
549
|
return _acpxBin;
|
|
702
550
|
}
|
|
@@ -744,15 +592,15 @@ function hasAcpxSession(acpxBin, projectDir, codeName) {
|
|
|
744
592
|
return available;
|
|
745
593
|
}
|
|
746
594
|
var sessions = /* @__PURE__ */ new Map();
|
|
747
|
-
var PANE_LOG_DIR =
|
|
595
|
+
var PANE_LOG_DIR = join(homedir(), ".augmented");
|
|
748
596
|
var PANE_TAIL_LINES = 20;
|
|
749
597
|
function paneLogPath(codeName) {
|
|
750
|
-
return
|
|
598
|
+
return join(PANE_LOG_DIR, codeName, "pane.log");
|
|
751
599
|
}
|
|
752
600
|
function setupPaneLog(tmuxSession, codeName, log) {
|
|
753
601
|
const logPath = paneLogPath(codeName);
|
|
754
602
|
try {
|
|
755
|
-
|
|
603
|
+
mkdirSync(dirname(logPath), { recursive: true });
|
|
756
604
|
appendFileSync(
|
|
757
605
|
logPath,
|
|
758
606
|
`
|
|
@@ -770,9 +618,9 @@ function setupPaneLog(tmuxSession, codeName, log) {
|
|
|
770
618
|
}
|
|
771
619
|
function readPaneLogTail(codeName, lines = PANE_TAIL_LINES) {
|
|
772
620
|
const logPath = paneLogPath(codeName);
|
|
773
|
-
if (!
|
|
621
|
+
if (!existsSync2(logPath)) return null;
|
|
774
622
|
try {
|
|
775
|
-
const raw =
|
|
623
|
+
const raw = readFileSync3(logPath, "utf-8");
|
|
776
624
|
if (!raw) return null;
|
|
777
625
|
const stripped = raw.replace(/\x1b\[[0-9;?]*[A-Za-z]/g, "");
|
|
778
626
|
const all = stripped.split("\n").filter((l) => l.length > 0);
|
|
@@ -827,7 +675,7 @@ function resolveSessionSpawnDecision(args) {
|
|
|
827
675
|
const disableFlag = process.env["AGT_DISABLE_SESSION_RESUME"];
|
|
828
676
|
const resumeDisabled = disableFlag === "1" || disableFlag?.toLowerCase() === "true";
|
|
829
677
|
if (resumeDisabled) {
|
|
830
|
-
return { flag: "--session-id", sessionId:
|
|
678
|
+
return { flag: "--session-id", sessionId: randomUUID(), reason: "resume-disabled" };
|
|
831
679
|
}
|
|
832
680
|
const daily = getOrCreateDailySession(codeName, now, agentTimezone);
|
|
833
681
|
if (!daily.isNew && sessionFileExists(projectDir, daily.sessionId)) {
|
|
@@ -887,10 +735,10 @@ function spawnSession(config, session) {
|
|
|
887
735
|
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.`);
|
|
888
736
|
}
|
|
889
737
|
} else {
|
|
890
|
-
const claudeDir =
|
|
738
|
+
const claudeDir = join(homedir(), ".claude");
|
|
891
739
|
for (const filename of [".credentials.json", "credentials.json"]) {
|
|
892
|
-
const p =
|
|
893
|
-
if (
|
|
740
|
+
const p = join(claudeDir, filename);
|
|
741
|
+
if (existsSync2(p)) {
|
|
894
742
|
try {
|
|
895
743
|
rmSync(p, { force: true });
|
|
896
744
|
log(`[persistent-session] Removed ${p} (api_key mode active \u2014 preventing OAuth fallback)`);
|
|
@@ -924,7 +772,7 @@ function spawnSession(config, session) {
|
|
|
924
772
|
if (channels.length > 0) args.push("--channels", ...channels);
|
|
925
773
|
if (devChannels.length > 0) args.push("--dangerously-load-development-channels", ...devChannels);
|
|
926
774
|
args.push("--mcp-config", mcpConfigPath);
|
|
927
|
-
if (
|
|
775
|
+
if (existsSync2(claudeMdPath)) args.push("--system-prompt-file", claudeMdPath);
|
|
928
776
|
const modelAlias = claudeModelAlias(config.primaryModel);
|
|
929
777
|
if (modelAlias) args.push("--model", modelAlias);
|
|
930
778
|
args.push("--allow-dangerously-skip-permissions");
|
|
@@ -952,7 +800,7 @@ function spawnSession(config, session) {
|
|
|
952
800
|
// Treat empty-string as missing too — `HOME=""` makes ~ resolve
|
|
953
801
|
// to cwd, which is the same broken outcome as no HOME, just
|
|
954
802
|
// better hidden.
|
|
955
|
-
HOME: process.env.HOME?.trim() ||
|
|
803
|
+
HOME: process.env.HOME?.trim() || homedir(),
|
|
956
804
|
USER: process.env.USER?.trim() || userInfo().username
|
|
957
805
|
};
|
|
958
806
|
if (config.runId) {
|
|
@@ -960,7 +808,7 @@ function spawnSession(config, session) {
|
|
|
960
808
|
}
|
|
961
809
|
for (const f of probeMcpEnvSubstitution({
|
|
962
810
|
mcpConfigPath,
|
|
963
|
-
envIntegrationsPath:
|
|
811
|
+
envIntegrationsPath: join(projectDir, ".env.integrations"),
|
|
964
812
|
baseEnv: {
|
|
965
813
|
...tmuxEnv,
|
|
966
814
|
...claudeAuthMode === "api_key" && config.anthropicApiKey ? { ANTHROPIC_API_KEY: config.anthropicApiKey } : {}
|
|
@@ -1276,10 +1124,10 @@ async function injectMessageWithStatus(codeName, type, content, meta, log) {
|
|
|
1276
1124
|
const acpx = getAcpxBin();
|
|
1277
1125
|
if (acpx && hasAcpxSession(acpx, projectDir, codeName)) {
|
|
1278
1126
|
try {
|
|
1279
|
-
const tmpDir =
|
|
1280
|
-
|
|
1281
|
-
const tmpFile =
|
|
1282
|
-
|
|
1127
|
+
const tmpDir = join(projectDir, ".claude");
|
|
1128
|
+
mkdirSync(tmpDir, { recursive: true });
|
|
1129
|
+
const tmpFile = join(tmpDir, ".agt-inject-prompt.txt");
|
|
1130
|
+
writeFileSync2(tmpFile, text);
|
|
1283
1131
|
_log(`[inject] acpx exec (fire-and-forget): cwd=${projectDir}, file=${tmpFile}`);
|
|
1284
1132
|
const child = spawn(acpx, ["claude", "exec", "-f", tmpFile], {
|
|
1285
1133
|
cwd: projectDir,
|
|
@@ -1529,7 +1377,7 @@ async function stopAllSessionsAndWait(log, opts) {
|
|
|
1529
1377
|
await new Promise((resolve) => setTimeout(resolve, Math.min(opts.timeoutMs, 2e3)));
|
|
1530
1378
|
}
|
|
1531
1379
|
function getProjectDir(codeName) {
|
|
1532
|
-
return
|
|
1380
|
+
return join(homedir(), ".augmented", codeName, "project");
|
|
1533
1381
|
}
|
|
1534
1382
|
function writeAcpxConfig(config) {
|
|
1535
1383
|
const {
|
|
@@ -1545,7 +1393,7 @@ function writeAcpxConfig(config) {
|
|
|
1545
1393
|
if (channels.length > 0) claudeArgs.push("--channels", ...channels);
|
|
1546
1394
|
if (devChannels.length > 0) claudeArgs.push("--dangerously-load-development-channels", ...devChannels);
|
|
1547
1395
|
claudeArgs.push("--mcp-config", mcpConfigPath);
|
|
1548
|
-
if (
|
|
1396
|
+
if (existsSync2(claudeMdPath)) claudeArgs.push("--system-prompt-file", claudeMdPath);
|
|
1549
1397
|
const acpModelAlias = claudeModelAlias(config.primaryModel);
|
|
1550
1398
|
if (acpModelAlias) claudeArgs.push("--model", acpModelAlias);
|
|
1551
1399
|
claudeArgs.push("--allow-dangerously-skip-permissions");
|
|
@@ -1554,18 +1402,18 @@ function writeAcpxConfig(config) {
|
|
|
1554
1402
|
const mcpServerNames2 = collectMcpServerNames(mcpConfigPath);
|
|
1555
1403
|
claudeArgs.push("--allowedTools", buildAllowedTools(mcpServerNames2));
|
|
1556
1404
|
const acpCmd = `npx -y @agentclientprotocol/claude-agent-acp ${claudeArgs.map((a) => a.includes(" ") || a.includes("*") ? JSON.stringify(a) : a).join(" ")}`;
|
|
1557
|
-
const envIntegrationsPath =
|
|
1558
|
-
const wrapperPath =
|
|
1405
|
+
const envIntegrationsPath = join(projectDir, ".env.integrations");
|
|
1406
|
+
const wrapperPath = join(projectDir, ".claude", "acpx-agent.sh");
|
|
1559
1407
|
const wrapperLines = ["#!/usr/bin/env bash"];
|
|
1560
|
-
if (
|
|
1408
|
+
if (existsSync2(envIntegrationsPath)) {
|
|
1561
1409
|
wrapperLines.push(`set -a`, `source ${JSON.stringify(envIntegrationsPath)}`, `set +a`);
|
|
1562
1410
|
}
|
|
1563
1411
|
if (claudeAuthMode === "api_key" && anthropicApiKey) {
|
|
1564
1412
|
wrapperLines.push(`export ANTHROPIC_API_KEY=${JSON.stringify(anthropicApiKey)}`);
|
|
1565
1413
|
}
|
|
1566
1414
|
wrapperLines.push(`exec ${acpCmd}`);
|
|
1567
|
-
|
|
1568
|
-
|
|
1415
|
+
mkdirSync(join(projectDir, ".claude"), { recursive: true });
|
|
1416
|
+
writeFileSync2(wrapperPath, wrapperLines.join("\n") + "\n", { mode: 493 });
|
|
1569
1417
|
chmodSync(wrapperPath, 493);
|
|
1570
1418
|
const acpxConfig = {
|
|
1571
1419
|
defaultAgent: "claude",
|
|
@@ -1576,7 +1424,7 @@ function writeAcpxConfig(config) {
|
|
|
1576
1424
|
}
|
|
1577
1425
|
}
|
|
1578
1426
|
};
|
|
1579
|
-
|
|
1427
|
+
writeFileSync2(join(projectDir, ".acpxrc.json"), JSON.stringify(acpxConfig, null, 2));
|
|
1580
1428
|
}
|
|
1581
1429
|
|
|
1582
1430
|
export {
|
|
@@ -1586,11 +1434,6 @@ export {
|
|
|
1586
1434
|
probeMcpEnvSubstitution,
|
|
1587
1435
|
sanitizeMcpJson,
|
|
1588
1436
|
buildAllowedTools,
|
|
1589
|
-
sessionTranscriptDir,
|
|
1590
|
-
transcriptActivityAgeSeconds,
|
|
1591
|
-
isAgentIdle,
|
|
1592
|
-
isStaleForToday,
|
|
1593
|
-
peekCurrentSession,
|
|
1594
1437
|
checkChannelInputs,
|
|
1595
1438
|
takeWatchdogGiveUpCount,
|
|
1596
1439
|
creditWatchdogGiveUpCount,
|
|
@@ -1621,4 +1464,4 @@ export {
|
|
|
1621
1464
|
stopAllSessionsAndWait,
|
|
1622
1465
|
getProjectDir
|
|
1623
1466
|
};
|
|
1624
|
-
//# sourceMappingURL=chunk-
|
|
1467
|
+
//# sourceMappingURL=chunk-7GKJZBTB.js.map
|