agent-lookout 0.2.8 → 0.2.9
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/CHANGELOG.md +18 -0
- package/PRIVACY.md +24 -10
- package/SECURITY.md +7 -3
- package/dist/assets/{HistoryPanel-CQ7zovsM.js → HistoryPanel-Cr6VQlz-.js} +1 -1
- package/dist/assets/SessionPanel-YVGY4vbo.js +1 -0
- package/dist/assets/index-BRNSMV9A.css +1 -0
- package/dist/assets/index-DlDz48ci.js +9 -0
- package/dist/cli/agent-lookout.js +4 -4
- package/dist/cli/{chunk-LZ62RW3N.js → chunk-AHCLJEYU.js} +1 -1
- package/dist/cli/{chunk-JSBQMY3Q.js → chunk-CMDNRNYP.js} +1 -1
- package/dist/cli/{chunk-I6Q35DHC.js → chunk-XV7BYWVY.js} +17 -0
- package/dist/cli/{mcpServer-BMSF2TXC.js → mcpServer-33QB6COH.js} +3 -3
- package/dist/cli/{startCommand-ANC7NVAO.js → startCommand-XUZHHWZG.js} +538 -241
- package/dist/index.html +2 -2
- package/docs/API.md +56 -29
- package/docs/GUIDE.md +54 -50
- package/package.json +1 -1
- package/dist/assets/SessionPanel-DS29Z04P.js +0 -1
- package/dist/assets/index-BWFkRW4g.js +0 -3
- package/dist/assets/index-B_1Its0R.css +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
sortSessions
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-CMDNRNYP.js";
|
|
4
4
|
import {
|
|
5
5
|
ANSWER_DECISIONS,
|
|
6
6
|
CAPABILITIES,
|
|
@@ -34,6 +34,7 @@ import {
|
|
|
34
34
|
isRemoteSource,
|
|
35
35
|
isStatusEvent,
|
|
36
36
|
loopbackAddress,
|
|
37
|
+
messageText,
|
|
37
38
|
needsYou,
|
|
38
39
|
noticeTitle,
|
|
39
40
|
oneLine,
|
|
@@ -48,7 +49,7 @@ import {
|
|
|
48
49
|
waitingPhrase,
|
|
49
50
|
waitingText,
|
|
50
51
|
withoutWaitingText
|
|
51
|
-
} from "./chunk-
|
|
52
|
+
} from "./chunk-XV7BYWVY.js";
|
|
52
53
|
|
|
53
54
|
// src/collector/hosts/server.ts
|
|
54
55
|
import { createServer } from "node:http";
|
|
@@ -132,14 +133,14 @@ function sessionChanges(memory, snapshot) {
|
|
|
132
133
|
}
|
|
133
134
|
const found = [];
|
|
134
135
|
for (const session of snapshot.sessions) {
|
|
135
|
-
const
|
|
136
|
-
if (!
|
|
137
|
-
|
|
136
|
+
const answer2 = answers2.get(session.source);
|
|
137
|
+
if (!answer2 || answer2.sessions.has(session.id)) continue;
|
|
138
|
+
answer2.sessions.set(session.id, withoutWaitingText(session));
|
|
138
139
|
if (startedWaiting.has(session)) {
|
|
139
140
|
found.push({ event: "needs-you", session });
|
|
140
141
|
continue;
|
|
141
142
|
}
|
|
142
|
-
const before = memory.seen.get(session.source)?.get(
|
|
143
|
+
const before = memory.seen.get(session.source)?.get(answer2.basis)?.get(session.id);
|
|
143
144
|
if (before && before.status !== session.status && isOverStatus(session.status)) {
|
|
144
145
|
found.push({ event: session.status, session });
|
|
145
146
|
}
|
|
@@ -186,6 +187,7 @@ function sessionChanges(memory, snapshot) {
|
|
|
186
187
|
|
|
187
188
|
// src/core/api.ts
|
|
188
189
|
var CLEAR_HISTORY_ACTION = "clear-history";
|
|
190
|
+
var LAST_MESSAGE_PATH = "/api/sessions/last-message";
|
|
189
191
|
var TERMINAL_JUMP_TIMEOUT_MS = 6e4;
|
|
190
192
|
var JUMP_INTERVAL_MS = 1e3;
|
|
191
193
|
var STOP_ACTION = "stop";
|
|
@@ -2067,6 +2069,13 @@ function send(res, status, body, headers = {}) {
|
|
|
2067
2069
|
function fail(res, status, error, headers = {}) {
|
|
2068
2070
|
send(res, status, { error }, headers);
|
|
2069
2071
|
}
|
|
2072
|
+
function sendWhenReady(res, answer2) {
|
|
2073
|
+
answer2.then((ready) => send(res, ready.status, ready.body, ready.headers)).catch(() => {
|
|
2074
|
+
if (res.headersSent) res.end();
|
|
2075
|
+
else fail(res, 500, "The collector ran into an unexpected problem.");
|
|
2076
|
+
}).catch(() => {
|
|
2077
|
+
});
|
|
2078
|
+
}
|
|
2070
2079
|
function notificationsSaid(req) {
|
|
2071
2080
|
const value = req.headers[NOTIFICATIONS_HEADER.toLowerCase()];
|
|
2072
2081
|
if (typeof value !== "string") return null;
|
|
@@ -2105,11 +2114,7 @@ function createApiHandler(options) {
|
|
|
2105
2114
|
}
|
|
2106
2115
|
const action = url === null ? void 0 : actions.get(url.pathname);
|
|
2107
2116
|
if (action) {
|
|
2108
|
-
|
|
2109
|
-
if (res.headersSent) res.end();
|
|
2110
|
-
else fail(res, 500, "The collector ran into an unexpected problem.");
|
|
2111
|
-
}).catch(() => {
|
|
2112
|
-
});
|
|
2117
|
+
sendWhenReady(res, action(req));
|
|
2113
2118
|
return;
|
|
2114
2119
|
}
|
|
2115
2120
|
if (req.method !== "GET") {
|
|
@@ -2131,6 +2136,14 @@ function createApiHandler(options) {
|
|
|
2131
2136
|
send(res, 200, serveSnapshot(poller.getSnapshot()));
|
|
2132
2137
|
return;
|
|
2133
2138
|
}
|
|
2139
|
+
case LAST_MESSAGE_PATH: {
|
|
2140
|
+
if (options.lastMessage) {
|
|
2141
|
+
sendWhenReady(res, options.lastMessage(req, url));
|
|
2142
|
+
return;
|
|
2143
|
+
}
|
|
2144
|
+
fail(res, 404, "There is nothing at that address.");
|
|
2145
|
+
return;
|
|
2146
|
+
}
|
|
2134
2147
|
case "/api/events": {
|
|
2135
2148
|
const since = numberParam(url.searchParams.get("since"));
|
|
2136
2149
|
if (since === "invalid") {
|
|
@@ -2216,7 +2229,7 @@ function createApiHandler(options) {
|
|
|
2216
2229
|
}
|
|
2217
2230
|
}
|
|
2218
2231
|
}
|
|
2219
|
-
function
|
|
2232
|
+
function answer2(req, res) {
|
|
2220
2233
|
try {
|
|
2221
2234
|
route(req, res);
|
|
2222
2235
|
} catch {
|
|
@@ -2227,12 +2240,12 @@ function createApiHandler(options) {
|
|
|
2227
2240
|
return (req, res) => {
|
|
2228
2241
|
const reading = options.ready?.() ?? null;
|
|
2229
2242
|
if (reading === null) {
|
|
2230
|
-
|
|
2243
|
+
answer2(req, res);
|
|
2231
2244
|
return;
|
|
2232
2245
|
}
|
|
2233
2246
|
void reading.then(
|
|
2234
|
-
() =>
|
|
2235
|
-
() =>
|
|
2247
|
+
() => answer2(req, res),
|
|
2248
|
+
() => answer2(req, res)
|
|
2236
2249
|
);
|
|
2237
2250
|
};
|
|
2238
2251
|
}
|
|
@@ -2451,7 +2464,7 @@ function urlFor(address) {
|
|
|
2451
2464
|
|
|
2452
2465
|
// src/collector/hosts/standalone.ts
|
|
2453
2466
|
import { existsSync } from "node:fs";
|
|
2454
|
-
import
|
|
2467
|
+
import path26 from "node:path";
|
|
2455
2468
|
|
|
2456
2469
|
// src/core/mapping/claudeCodeMapping.ts
|
|
2457
2470
|
var LIVE_STATUS = /* @__PURE__ */ new Map([
|
|
@@ -2838,6 +2851,16 @@ import os2 from "node:os";
|
|
|
2838
2851
|
// src/collector/adapters/claude-code/feed.ts
|
|
2839
2852
|
import { spawn } from "node:child_process";
|
|
2840
2853
|
|
|
2854
|
+
// src/collector/processes/childEnvironment.ts
|
|
2855
|
+
var OWN_SETTING = /^AGENT_LOOKOUT_/i;
|
|
2856
|
+
function childEnvironment(env) {
|
|
2857
|
+
const given = {};
|
|
2858
|
+
for (const [name, value] of Object.entries(env)) {
|
|
2859
|
+
if (!OWN_SETTING.test(name)) given[name] = value;
|
|
2860
|
+
}
|
|
2861
|
+
return given;
|
|
2862
|
+
}
|
|
2863
|
+
|
|
2841
2864
|
// src/collector/processes/pids.ts
|
|
2842
2865
|
function validPid(value) {
|
|
2843
2866
|
return typeof value === "number" && Number.isSafeInteger(value) && value > 0 ? value : void 0;
|
|
@@ -2954,7 +2977,7 @@ var runProgram = (file, args, options) => new Promise((resolve) => {
|
|
|
2954
2977
|
}, options.timeoutMs);
|
|
2955
2978
|
try {
|
|
2956
2979
|
child = spawn(file, [...args], {
|
|
2957
|
-
env: options.env,
|
|
2980
|
+
env: childEnvironment(options.env),
|
|
2958
2981
|
shell: false,
|
|
2959
2982
|
// stdin is the null device, so a program that reads it gets end-of-file.
|
|
2960
2983
|
// stderr is not wanted and goes the same way.
|
|
@@ -3261,6 +3284,12 @@ function createStopTargets() {
|
|
|
3261
3284
|
};
|
|
3262
3285
|
}
|
|
3263
3286
|
|
|
3287
|
+
// src/collector/messages/lastMessageSettings.ts
|
|
3288
|
+
var LAST_MESSAGE_ENV = "AGENT_LOOKOUT_LAST_MESSAGE";
|
|
3289
|
+
function lastMessageOff(env) {
|
|
3290
|
+
return env[LAST_MESSAGE_ENV]?.trim().toLowerCase() === "off";
|
|
3291
|
+
}
|
|
3292
|
+
|
|
3264
3293
|
// src/collector/processes/ps.ts
|
|
3265
3294
|
import { execFile } from "node:child_process";
|
|
3266
3295
|
var PS_TIMEOUT_MS = 2e3;
|
|
@@ -3334,8 +3363,8 @@ function parseProcessStarts(stdout) {
|
|
|
3334
3363
|
}
|
|
3335
3364
|
var readProcessStartsWithPs = async (pids) => {
|
|
3336
3365
|
if (pids.length === 0) return /* @__PURE__ */ new Map();
|
|
3337
|
-
const
|
|
3338
|
-
return parseProcessStarts(
|
|
3366
|
+
const answer2 = await runPs(psStartArgs(pids), { utc: true });
|
|
3367
|
+
return parseProcessStarts(answer2.stdout);
|
|
3339
3368
|
};
|
|
3340
3369
|
function createProcessStartCheck(read = readProcessStartsWithPs, now = Date.now, ttlMs = START_CHECK_TTL_MS) {
|
|
3341
3370
|
const remembered = /* @__PURE__ */ new Map();
|
|
@@ -3676,13 +3705,13 @@ function uniqueById(sessions) {
|
|
|
3676
3705
|
}
|
|
3677
3706
|
|
|
3678
3707
|
// src/collector/adapters/claude-code/stopOffers.ts
|
|
3679
|
-
function jobIdOf(entry,
|
|
3680
|
-
const listed =
|
|
3708
|
+
function jobIdOf(entry, answer2) {
|
|
3709
|
+
const listed = answer2.find(
|
|
3681
3710
|
(candidate) => candidate.id !== void 0 && (candidate.sessionId === entry.sessionId || candidate.sessionId === void 0 && candidate.pid === entry.pid)
|
|
3682
3711
|
);
|
|
3683
3712
|
return listed?.id !== void 0 && JOB_ID.test(listed.id) ? listed.id : void 0;
|
|
3684
3713
|
}
|
|
3685
|
-
function targetFor(entry,
|
|
3714
|
+
function targetFor(entry, answer2, options) {
|
|
3686
3715
|
if (entry.sessionId === void 0 || entry.procStart === void 0) return null;
|
|
3687
3716
|
const surface = mapClaudeCodeSurface(entry.entrypoint);
|
|
3688
3717
|
const found = {
|
|
@@ -3695,12 +3724,12 @@ function targetFor(entry, answer, options) {
|
|
|
3695
3724
|
return surface === "terminal" || surface === "vscode" ? { ...found, how: "signal" } : null;
|
|
3696
3725
|
}
|
|
3697
3726
|
if (entry.kind === "bg" && options.background && surface !== "desktop") {
|
|
3698
|
-
const jobId =
|
|
3727
|
+
const jobId = answer2 === null ? void 0 : jobIdOf(entry, answer2);
|
|
3699
3728
|
return jobId === void 0 ? null : { ...found, how: "background", jobId };
|
|
3700
3729
|
}
|
|
3701
3730
|
return null;
|
|
3702
3731
|
}
|
|
3703
|
-
function withStopOffers(sessions, live2,
|
|
3732
|
+
function withStopOffers(sessions, live2, answer2, options) {
|
|
3704
3733
|
const byId = /* @__PURE__ */ new Map();
|
|
3705
3734
|
for (const entry of live2) {
|
|
3706
3735
|
if (entry.sessionId !== void 0) byId.set(`${SOURCE_ID}:${entry.sessionId}`, entry);
|
|
@@ -3711,7 +3740,7 @@ function withStopOffers(sessions, live2, answer, options) {
|
|
|
3711
3740
|
if (session.status === "finished" || session.status === "failed") return session;
|
|
3712
3741
|
const entry = byId.get(session.id);
|
|
3713
3742
|
if (entry === void 0 || entry.pid !== session.pid) return session;
|
|
3714
|
-
const target = targetFor(entry,
|
|
3743
|
+
const target = targetFor(entry, answer2, options);
|
|
3715
3744
|
if (target === null) return session;
|
|
3716
3745
|
targets.set(session.id, target);
|
|
3717
3746
|
return { ...session, stop: { how: target.how } };
|
|
@@ -3719,8 +3748,8 @@ function withStopOffers(sessions, live2, answer, options) {
|
|
|
3719
3748
|
return { sessions: offered, targets };
|
|
3720
3749
|
}
|
|
3721
3750
|
|
|
3722
|
-
// src/collector/adapters/claude-code/transcript/
|
|
3723
|
-
import
|
|
3751
|
+
// src/collector/adapters/claude-code/transcript/lastMessages.ts
|
|
3752
|
+
import path9 from "node:path";
|
|
3724
3753
|
|
|
3725
3754
|
// src/collector/files/readOnlyIo.ts
|
|
3726
3755
|
import { constants as constants4 } from "node:fs";
|
|
@@ -3774,7 +3803,7 @@ function isMissing2(error) {
|
|
|
3774
3803
|
return code === "ENOENT" || code === "ENOTDIR";
|
|
3775
3804
|
}
|
|
3776
3805
|
|
|
3777
|
-
// src/collector/adapters/claude-code/transcript/
|
|
3806
|
+
// src/collector/adapters/claude-code/transcript/transcriptLines.ts
|
|
3778
3807
|
function isRecord3(value) {
|
|
3779
3808
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3780
3809
|
}
|
|
@@ -3800,6 +3829,112 @@ function linesOf(tail, fromStart) {
|
|
|
3800
3829
|
}
|
|
3801
3830
|
return lines;
|
|
3802
3831
|
}
|
|
3832
|
+
|
|
3833
|
+
// src/collector/adapters/claude-code/transcript/lastSaid.ts
|
|
3834
|
+
var PART_BREAK = "\n\n";
|
|
3835
|
+
function textsOf(line) {
|
|
3836
|
+
if (line.type !== "assistant" || line.isSidechain === true || line.isMeta === true) return [];
|
|
3837
|
+
const texts = [];
|
|
3838
|
+
for (const block of blocksOf(line)) {
|
|
3839
|
+
const text4 = block.type === "text" ? nonEmpty(block.text) : null;
|
|
3840
|
+
if (text4 !== null) texts.push(text4);
|
|
3841
|
+
}
|
|
3842
|
+
return texts;
|
|
3843
|
+
}
|
|
3844
|
+
function messageIdOf(line) {
|
|
3845
|
+
return isRecord3(line.message) ? nonEmpty(line.message.id) : null;
|
|
3846
|
+
}
|
|
3847
|
+
function lastSaidInTail(tail, fromStart) {
|
|
3848
|
+
const lines = linesOf(tail, fromStart);
|
|
3849
|
+
const newest2 = lines.findLastIndex((line) => textsOf(line).length > 0);
|
|
3850
|
+
if (newest2 === -1) return { text: null, reason: fromStart ? "nothing-yet" : "too-far-back" };
|
|
3851
|
+
const parts = [textsOf(lines[newest2])];
|
|
3852
|
+
const messageId = messageIdOf(lines[newest2]);
|
|
3853
|
+
if (messageId === null) return { text: parts[0].join(PART_BREAK) };
|
|
3854
|
+
let index = newest2 - 1;
|
|
3855
|
+
for (; index >= 0; index -= 1) {
|
|
3856
|
+
const line = lines[index];
|
|
3857
|
+
if (line.isSidechain === true) continue;
|
|
3858
|
+
if (line.type === "user") break;
|
|
3859
|
+
if (line.type !== "assistant") continue;
|
|
3860
|
+
if (messageIdOf(line) !== messageId) break;
|
|
3861
|
+
parts.unshift(textsOf(line));
|
|
3862
|
+
}
|
|
3863
|
+
const text4 = parts.flat().join(PART_BREAK);
|
|
3864
|
+
return index < 0 && !fromStart ? { text: text4, startCut: true } : { text: text4 };
|
|
3865
|
+
}
|
|
3866
|
+
|
|
3867
|
+
// src/collector/adapters/claude-code/transcript/transcriptFile.ts
|
|
3868
|
+
import path7 from "node:path";
|
|
3869
|
+
var TRANSCRIPT_TAIL_BYTES = 256 * 1024;
|
|
3870
|
+
var SESSION_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
3871
|
+
function isSessionId(value) {
|
|
3872
|
+
return typeof value === "string" && SESSION_ID.test(value);
|
|
3873
|
+
}
|
|
3874
|
+
var ID_PREFIX = "claude-code:";
|
|
3875
|
+
function sessionIdOf(session) {
|
|
3876
|
+
if (session.source !== "claude-code" || !session.id.startsWith(ID_PREFIX)) return null;
|
|
3877
|
+
const id = session.id.slice(ID_PREFIX.length);
|
|
3878
|
+
return isSessionId(id) ? id : null;
|
|
3879
|
+
}
|
|
3880
|
+
function projectFolderName(cwd) {
|
|
3881
|
+
return cwd.replace(/[^A-Za-z0-9]/g, "-");
|
|
3882
|
+
}
|
|
3883
|
+
function fileName(sessionId) {
|
|
3884
|
+
return `${sessionId}.jsonl`;
|
|
3885
|
+
}
|
|
3886
|
+
async function findTranscript(projectsDir, sessionId, cwd, io) {
|
|
3887
|
+
if (!isSessionId(sessionId)) return null;
|
|
3888
|
+
const name = fileName(sessionId);
|
|
3889
|
+
const isFile2 = async (file) => {
|
|
3890
|
+
try {
|
|
3891
|
+
return (await io.lstat(file)).kind === "file";
|
|
3892
|
+
} catch {
|
|
3893
|
+
return false;
|
|
3894
|
+
}
|
|
3895
|
+
};
|
|
3896
|
+
if (cwd !== null && cwd !== "") {
|
|
3897
|
+
const likely = path7.join(projectsDir, projectFolderName(cwd), name);
|
|
3898
|
+
if (await isFile2(likely)) return likely;
|
|
3899
|
+
}
|
|
3900
|
+
let folders;
|
|
3901
|
+
try {
|
|
3902
|
+
folders = await io.readdir(projectsDir);
|
|
3903
|
+
} catch {
|
|
3904
|
+
return null;
|
|
3905
|
+
}
|
|
3906
|
+
const candidates = folders.filter((folder) => folder !== "" && !folder.startsWith(".")).sort().map((folder) => path7.join(projectsDir, folder, name));
|
|
3907
|
+
const found = await Promise.all(candidates.map(isFile2));
|
|
3908
|
+
return candidates.find((_, index) => found[index]) ?? null;
|
|
3909
|
+
}
|
|
3910
|
+
function stampOf(info) {
|
|
3911
|
+
return { size: info.size, mtimeMs: info.mtimeMs, ino: info.ino };
|
|
3912
|
+
}
|
|
3913
|
+
function sameStamp(a, b) {
|
|
3914
|
+
return a.size === b.size && a.mtimeMs === b.mtimeMs && a.ino === b.ino;
|
|
3915
|
+
}
|
|
3916
|
+
async function readTranscriptTail(file, io, known, maxBytes = TRANSCRIPT_TAIL_BYTES) {
|
|
3917
|
+
const open2 = await io.openRegular(file);
|
|
3918
|
+
try {
|
|
3919
|
+
const stamp = stampOf(open2.info);
|
|
3920
|
+
if (known !== null && sameStamp(known, stamp)) return { unchanged: true, stamp };
|
|
3921
|
+
const start = Math.max(0, stamp.size - maxBytes);
|
|
3922
|
+
const bytes3 = await open2.read(start, stamp.size - start);
|
|
3923
|
+
return {
|
|
3924
|
+
unchanged: false,
|
|
3925
|
+
stamp,
|
|
3926
|
+
tail: new TextDecoder("utf-8").decode(bytes3),
|
|
3927
|
+
fromStart: start === 0
|
|
3928
|
+
};
|
|
3929
|
+
} finally {
|
|
3930
|
+
await open2.close();
|
|
3931
|
+
}
|
|
3932
|
+
}
|
|
3933
|
+
|
|
3934
|
+
// src/collector/adapters/claude-code/transcript/waitingTexts.ts
|
|
3935
|
+
import path8 from "node:path";
|
|
3936
|
+
|
|
3937
|
+
// src/collector/adapters/claude-code/transcript/lastAsk.ts
|
|
3803
3938
|
function isPrompt(line) {
|
|
3804
3939
|
if (line.isMeta === true) return false;
|
|
3805
3940
|
const message = isRecord3(line.message) ? line.message : null;
|
|
@@ -3898,79 +4033,12 @@ function askedInTail(tail, fromStart, cwd) {
|
|
|
3898
4033
|
return waitingText(describeToolUse(pending.name, pending.input, cwd));
|
|
3899
4034
|
}
|
|
3900
4035
|
|
|
3901
|
-
// src/collector/adapters/claude-code/transcript/transcriptFile.ts
|
|
3902
|
-
import path7 from "node:path";
|
|
3903
|
-
var TRANSCRIPT_TAIL_BYTES = 256 * 1024;
|
|
3904
|
-
var SESSION_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
3905
|
-
function isSessionId(value) {
|
|
3906
|
-
return typeof value === "string" && SESSION_ID.test(value);
|
|
3907
|
-
}
|
|
3908
|
-
function projectFolderName(cwd) {
|
|
3909
|
-
return cwd.replace(/[^A-Za-z0-9]/g, "-");
|
|
3910
|
-
}
|
|
3911
|
-
function fileName(sessionId) {
|
|
3912
|
-
return `${sessionId}.jsonl`;
|
|
3913
|
-
}
|
|
3914
|
-
async function findTranscript(projectsDir, sessionId, cwd, io) {
|
|
3915
|
-
if (!isSessionId(sessionId)) return null;
|
|
3916
|
-
const name = fileName(sessionId);
|
|
3917
|
-
const isFile2 = async (file) => {
|
|
3918
|
-
try {
|
|
3919
|
-
return (await io.lstat(file)).kind === "file";
|
|
3920
|
-
} catch {
|
|
3921
|
-
return false;
|
|
3922
|
-
}
|
|
3923
|
-
};
|
|
3924
|
-
if (cwd !== null && cwd !== "") {
|
|
3925
|
-
const likely = path7.join(projectsDir, projectFolderName(cwd), name);
|
|
3926
|
-
if (await isFile2(likely)) return likely;
|
|
3927
|
-
}
|
|
3928
|
-
let folders;
|
|
3929
|
-
try {
|
|
3930
|
-
folders = await io.readdir(projectsDir);
|
|
3931
|
-
} catch {
|
|
3932
|
-
return null;
|
|
3933
|
-
}
|
|
3934
|
-
const candidates = folders.filter((folder) => folder !== "" && !folder.startsWith(".")).sort().map((folder) => path7.join(projectsDir, folder, name));
|
|
3935
|
-
const found = await Promise.all(candidates.map(isFile2));
|
|
3936
|
-
return candidates.find((_, index) => found[index]) ?? null;
|
|
3937
|
-
}
|
|
3938
|
-
function stampOf(info) {
|
|
3939
|
-
return { size: info.size, mtimeMs: info.mtimeMs, ino: info.ino };
|
|
3940
|
-
}
|
|
3941
|
-
function sameStamp(a, b) {
|
|
3942
|
-
return a.size === b.size && a.mtimeMs === b.mtimeMs && a.ino === b.ino;
|
|
3943
|
-
}
|
|
3944
|
-
async function readTranscriptTail(file, io, known, maxBytes = TRANSCRIPT_TAIL_BYTES) {
|
|
3945
|
-
const open2 = await io.openRegular(file);
|
|
3946
|
-
try {
|
|
3947
|
-
const stamp = stampOf(open2.info);
|
|
3948
|
-
if (known !== null && sameStamp(known, stamp)) return { unchanged: true, stamp };
|
|
3949
|
-
const start = Math.max(0, stamp.size - maxBytes);
|
|
3950
|
-
const bytes3 = await open2.read(start, stamp.size - start);
|
|
3951
|
-
return {
|
|
3952
|
-
unchanged: false,
|
|
3953
|
-
stamp,
|
|
3954
|
-
tail: new TextDecoder("utf-8").decode(bytes3),
|
|
3955
|
-
fromStart: start === 0
|
|
3956
|
-
};
|
|
3957
|
-
} finally {
|
|
3958
|
-
await open2.close();
|
|
3959
|
-
}
|
|
3960
|
-
}
|
|
3961
|
-
|
|
3962
4036
|
// src/collector/adapters/claude-code/transcript/waitingTexts.ts
|
|
3963
4037
|
var WAITING_TEXT_ENV = "AGENT_LOOKOUT_WAITING_TEXT";
|
|
3964
4038
|
function waitingTextOff(env) {
|
|
3965
4039
|
return env[WAITING_TEXT_ENV]?.trim().toLowerCase() === "off";
|
|
3966
4040
|
}
|
|
3967
4041
|
var LOOK_AGAIN_MS = 1e4;
|
|
3968
|
-
var ID_PREFIX = "claude-code:";
|
|
3969
|
-
function sessionIdOf(session) {
|
|
3970
|
-
if (session.source !== "claude-code" || !session.id.startsWith(ID_PREFIX)) return null;
|
|
3971
|
-
const id = session.id.slice(ID_PREFIX.length);
|
|
3972
|
-
return isSessionId(id) ? id : null;
|
|
3973
|
-
}
|
|
3974
4042
|
function createWaitingTextReader(options) {
|
|
3975
4043
|
const io = options.io ?? nodeIo2;
|
|
3976
4044
|
const now = options.now ?? Date.now;
|
|
@@ -4028,6 +4096,160 @@ function createWaitingTextReader(options) {
|
|
|
4028
4096
|
};
|
|
4029
4097
|
}
|
|
4030
4098
|
|
|
4099
|
+
// src/collector/adapters/claude-code/transcript/lastMessages.ts
|
|
4100
|
+
var ANSWER_AGAIN_MS = 1e3;
|
|
4101
|
+
var READS_PER_SECOND = 4;
|
|
4102
|
+
var MAX_KEPT = 8;
|
|
4103
|
+
var KEEP_MS = 15e3;
|
|
4104
|
+
var NOT_FOUND = { message: null, reason: "not-found" };
|
|
4105
|
+
var UNREADABLE = { message: null, reason: "unreadable" };
|
|
4106
|
+
function runLater(run, ms) {
|
|
4107
|
+
setTimeout(run, ms).unref();
|
|
4108
|
+
}
|
|
4109
|
+
function answerFrom(tail, fromStart) {
|
|
4110
|
+
const said = lastSaidInTail(tail, fromStart);
|
|
4111
|
+
if (said.text === null) return { message: null, reason: said.reason };
|
|
4112
|
+
const shown = messageText(said.text);
|
|
4113
|
+
if (shown === void 0) {
|
|
4114
|
+
return { message: null, reason: fromStart ? "nothing-yet" : "too-far-back" };
|
|
4115
|
+
}
|
|
4116
|
+
return { message: { text: shown.text, cut: shown.cut || said.startCut === true } };
|
|
4117
|
+
}
|
|
4118
|
+
function createLastMessageReader(options) {
|
|
4119
|
+
const io = options.io ?? nodeIo2;
|
|
4120
|
+
const now = options.now ?? Date.now;
|
|
4121
|
+
const later = options.later ?? runLater;
|
|
4122
|
+
const projectsDir = path9.join(options.claudeHome, "projects");
|
|
4123
|
+
const listed = /* @__PURE__ */ new Map();
|
|
4124
|
+
const kept = /* @__PURE__ */ new Map();
|
|
4125
|
+
let reads = [];
|
|
4126
|
+
let dropSet = false;
|
|
4127
|
+
function dropOld(at) {
|
|
4128
|
+
for (const [id, entry] of kept) {
|
|
4129
|
+
if (at - entry.askedAt >= KEEP_MS) kept.delete(id);
|
|
4130
|
+
}
|
|
4131
|
+
}
|
|
4132
|
+
function dropOnTime(at) {
|
|
4133
|
+
if (dropSet || kept.size === 0) return;
|
|
4134
|
+
const oldest = Math.min(...[...kept.values()].map((entry) => entry.askedAt));
|
|
4135
|
+
dropSet = true;
|
|
4136
|
+
later(
|
|
4137
|
+
() => {
|
|
4138
|
+
dropSet = false;
|
|
4139
|
+
const then = now();
|
|
4140
|
+
dropOld(then);
|
|
4141
|
+
dropOnTime(then);
|
|
4142
|
+
},
|
|
4143
|
+
Math.max(0, oldest + KEEP_MS - at)
|
|
4144
|
+
);
|
|
4145
|
+
}
|
|
4146
|
+
function mayRead(at) {
|
|
4147
|
+
reads = reads.filter((started) => at - started < 1e3 && started <= at);
|
|
4148
|
+
if (reads.length >= READS_PER_SECOND) return false;
|
|
4149
|
+
reads.push(at);
|
|
4150
|
+
return true;
|
|
4151
|
+
}
|
|
4152
|
+
function makeRoom() {
|
|
4153
|
+
while (kept.size >= MAX_KEPT) {
|
|
4154
|
+
let leastRecent;
|
|
4155
|
+
for (const pair of kept) {
|
|
4156
|
+
if (leastRecent === void 0 || pair[1].askedAt < leastRecent[1].askedAt) {
|
|
4157
|
+
leastRecent = pair;
|
|
4158
|
+
}
|
|
4159
|
+
}
|
|
4160
|
+
if (leastRecent === void 0) return;
|
|
4161
|
+
kept.delete(leastRecent[0]);
|
|
4162
|
+
}
|
|
4163
|
+
}
|
|
4164
|
+
async function check(entry, target, at) {
|
|
4165
|
+
try {
|
|
4166
|
+
if (entry.file === null) {
|
|
4167
|
+
entry.lookedAt = at;
|
|
4168
|
+
entry.file = await findTranscript(projectsDir, target.sessionId, target.cwd, io);
|
|
4169
|
+
if (entry.file === null) return NOT_FOUND;
|
|
4170
|
+
}
|
|
4171
|
+
const read = await readTranscriptTail(entry.file, io, entry.last?.stamp ?? null);
|
|
4172
|
+
if (read.unchanged) return entry.last?.answer ?? UNREADABLE;
|
|
4173
|
+
const answer2 = answerFrom(read.tail, read.fromStart);
|
|
4174
|
+
entry.last = { stamp: read.stamp, answer: answer2 };
|
|
4175
|
+
return answer2;
|
|
4176
|
+
} catch (error) {
|
|
4177
|
+
entry.last = null;
|
|
4178
|
+
if (isMissing2(error)) {
|
|
4179
|
+
entry.file = null;
|
|
4180
|
+
entry.lookedAt = at;
|
|
4181
|
+
return NOT_FOUND;
|
|
4182
|
+
}
|
|
4183
|
+
return UNREADABLE;
|
|
4184
|
+
}
|
|
4185
|
+
}
|
|
4186
|
+
return {
|
|
4187
|
+
read(id) {
|
|
4188
|
+
const target = listed.get(id);
|
|
4189
|
+
if (target === void 0) return Promise.resolve(null);
|
|
4190
|
+
if (target === null) return Promise.resolve(NOT_FOUND);
|
|
4191
|
+
const at = now();
|
|
4192
|
+
dropOld(at);
|
|
4193
|
+
const entry = kept.get(id);
|
|
4194
|
+
if (entry !== void 0) {
|
|
4195
|
+
entry.askedAt = at;
|
|
4196
|
+
dropOnTime(at);
|
|
4197
|
+
if (entry.checking !== null) return entry.checking;
|
|
4198
|
+
if (entry.answer !== null && at - entry.checkedAt < ANSWER_AGAIN_MS) {
|
|
4199
|
+
return Promise.resolve(entry.answer);
|
|
4200
|
+
}
|
|
4201
|
+
if (entry.answer !== null && entry.file === null && entry.lookedAt !== null && at - entry.lookedAt < LOOK_AGAIN_MS) {
|
|
4202
|
+
return Promise.resolve(entry.answer);
|
|
4203
|
+
}
|
|
4204
|
+
}
|
|
4205
|
+
if (!mayRead(at)) return Promise.resolve("busy");
|
|
4206
|
+
let fresh = entry;
|
|
4207
|
+
if (fresh === void 0) {
|
|
4208
|
+
makeRoom();
|
|
4209
|
+
fresh = {
|
|
4210
|
+
file: null,
|
|
4211
|
+
lookedAt: null,
|
|
4212
|
+
last: null,
|
|
4213
|
+
answer: null,
|
|
4214
|
+
checkedAt: at,
|
|
4215
|
+
askedAt: at,
|
|
4216
|
+
checking: null
|
|
4217
|
+
};
|
|
4218
|
+
kept.set(id, fresh);
|
|
4219
|
+
dropOnTime(at);
|
|
4220
|
+
}
|
|
4221
|
+
const reading = fresh;
|
|
4222
|
+
const checking = check(reading, target, at).then((answer2) => {
|
|
4223
|
+
reading.answer = answer2;
|
|
4224
|
+
reading.checkedAt = at;
|
|
4225
|
+
reading.checking = null;
|
|
4226
|
+
return answer2;
|
|
4227
|
+
});
|
|
4228
|
+
reading.checking = checking;
|
|
4229
|
+
return checking;
|
|
4230
|
+
},
|
|
4231
|
+
keep(sessions) {
|
|
4232
|
+
listed.clear();
|
|
4233
|
+
for (const session of sessions) {
|
|
4234
|
+
const sessionId = sessionIdOf(session);
|
|
4235
|
+
listed.set(session.id, sessionId === null ? null : { sessionId, cwd: session.cwd });
|
|
4236
|
+
}
|
|
4237
|
+
for (const id of kept.keys()) {
|
|
4238
|
+
if (!listed.has(id)) kept.delete(id);
|
|
4239
|
+
}
|
|
4240
|
+
dropOld(now());
|
|
4241
|
+
},
|
|
4242
|
+
forget() {
|
|
4243
|
+
listed.clear();
|
|
4244
|
+
kept.clear();
|
|
4245
|
+
reads = [];
|
|
4246
|
+
},
|
|
4247
|
+
get size() {
|
|
4248
|
+
return kept.size;
|
|
4249
|
+
}
|
|
4250
|
+
};
|
|
4251
|
+
}
|
|
4252
|
+
|
|
4031
4253
|
// src/collector/adapters/claude-code/index.ts
|
|
4032
4254
|
var CLAUDE_HOME_ENV = "AGENT_LOOKOUT_CLAUDE_HOME";
|
|
4033
4255
|
var CLAUDE_FEED_ENV = "AGENT_LOOKOUT_CLAUDE_FEED";
|
|
@@ -4128,6 +4350,8 @@ function createClaudeCodeAdapter(options = {}) {
|
|
|
4128
4350
|
const homeName = tildify(claudeHome, homeDir, platform);
|
|
4129
4351
|
const registryName = tildify(sessionsDir, homeDir, platform);
|
|
4130
4352
|
const waitingTexts = transcriptsOff ? null : createWaitingTextReader({ claudeHome, io: options.transcriptIo, now });
|
|
4353
|
+
const lastMessagesOff = lastMessageOff(env);
|
|
4354
|
+
const lastMessages = transcriptsOff || lastMessagesOff ? null : createLastMessageReader({ claudeHome, io: options.transcriptIo, now });
|
|
4131
4355
|
let lastDue = null;
|
|
4132
4356
|
let lastRead = null;
|
|
4133
4357
|
let feedSoon = false;
|
|
@@ -4149,7 +4373,7 @@ function createClaudeCodeAdapter(options = {}) {
|
|
|
4149
4373
|
{ label: "Command run", value: commandRun },
|
|
4150
4374
|
{
|
|
4151
4375
|
label: "Transcript read",
|
|
4152
|
-
value: transcriptsOff ? "Off" : "last message of a waiting session"
|
|
4376
|
+
value: transcriptsOff ? "Off" : lastMessagesOff ? "last message of a waiting session" : "last message of a waiting session, and of one whose details are open"
|
|
4153
4377
|
}
|
|
4154
4378
|
];
|
|
4155
4379
|
}
|
|
@@ -4245,12 +4469,12 @@ function createClaudeCodeAdapter(options = {}) {
|
|
|
4245
4469
|
}
|
|
4246
4470
|
reading ??= await readRegistryNow(checkedAt);
|
|
4247
4471
|
const read = lastRead;
|
|
4248
|
-
const
|
|
4472
|
+
const answer2 = read?.feed.ok ? read.feed.entries : null;
|
|
4249
4473
|
const problem = problemWith(reading, read);
|
|
4250
4474
|
const { registry, live: live2 } = reading;
|
|
4251
4475
|
const offered = (result) => {
|
|
4252
4476
|
if (!stops) return result;
|
|
4253
|
-
const found = withStopOffers(result.sessions, live2,
|
|
4477
|
+
const found = withStopOffers(result.sessions, live2, answer2, {
|
|
4254
4478
|
sessionsDir,
|
|
4255
4479
|
background: !neverRun
|
|
4256
4480
|
});
|
|
@@ -4259,7 +4483,7 @@ function createClaudeCodeAdapter(options = {}) {
|
|
|
4259
4483
|
};
|
|
4260
4484
|
if (options.panes || options.terminals) {
|
|
4261
4485
|
const pids = new Set(live2.map((entry) => entry.pid));
|
|
4262
|
-
for (const entry of
|
|
4486
|
+
for (const entry of answer2 ?? []) {
|
|
4263
4487
|
if (entry.pid !== void 0 && isAlive(entry.pid)) pids.add(entry.pid);
|
|
4264
4488
|
}
|
|
4265
4489
|
await Promise.all([options.panes?.look([...pids]), options.terminals?.look([...pids])]);
|
|
@@ -4279,16 +4503,16 @@ function createClaudeCodeAdapter(options = {}) {
|
|
|
4279
4503
|
...advice2 !== void 0 && { advice: advice2 },
|
|
4280
4504
|
checkedAt
|
|
4281
4505
|
});
|
|
4282
|
-
if (
|
|
4283
|
-
const listedPids = new Set(
|
|
4284
|
-
const listedSessionIds = new Set(
|
|
4506
|
+
if (answer2 !== null && read !== null && problem === null) {
|
|
4507
|
+
const listedPids = new Set(answer2.map((entry) => entry.pid));
|
|
4508
|
+
const listedSessionIds = new Set(answer2.map((entry) => entry.sessionId));
|
|
4285
4509
|
const tooNewSince = read.askedAt - NEW_SESSION_GRACE_MS;
|
|
4286
4510
|
const fromRegistry = live2.filter(
|
|
4287
4511
|
(entry) => listedPids.has(entry.pid) || listedSessionIds.has(entry.sessionId) || startedAfter(entry, tooNewSince, checkedAt)
|
|
4288
4512
|
);
|
|
4289
4513
|
const livePids = new Set(live2.map((entry) => entry.pid));
|
|
4290
4514
|
const liveSessionIds = new Set(live2.map((entry) => entry.sessionId));
|
|
4291
|
-
const jobs =
|
|
4515
|
+
const jobs = answer2.filter(
|
|
4292
4516
|
(entry) => (entry.id !== void 0 || entry.state !== void 0) && !(entry.pid !== void 0 && livePids.has(entry.pid)) && !(entry.sessionId !== void 0 && liveSessionIds.has(entry.sessionId))
|
|
4293
4517
|
).map((entry) => {
|
|
4294
4518
|
const gone = entry.pid !== void 0 && !isAlive(entry.pid);
|
|
@@ -4309,9 +4533,9 @@ function createClaudeCodeAdapter(options = {}) {
|
|
|
4309
4533
|
basis: "registry+feed"
|
|
4310
4534
|
});
|
|
4311
4535
|
}
|
|
4312
|
-
if (
|
|
4536
|
+
if (answer2 !== null && problem !== null) {
|
|
4313
4537
|
const entries = registry.readable ? registry.entries : void 0;
|
|
4314
|
-
const sessions =
|
|
4538
|
+
const sessions = answer2.map(
|
|
4315
4539
|
(entry) => sessionFromFeed(
|
|
4316
4540
|
entry,
|
|
4317
4541
|
entry.pid !== void 0 ? entries?.get(entry.pid) : void 0,
|
|
@@ -4365,9 +4589,12 @@ function createClaudeCodeAdapter(options = {}) {
|
|
|
4365
4589
|
lookingIn: neverRun ? `Looking for sessions in ${registryName}.` : `Looking for sessions in ${registryName} and with claude ${FEED_ARGS.join(" ")}.`,
|
|
4366
4590
|
async poll() {
|
|
4367
4591
|
try {
|
|
4368
|
-
|
|
4592
|
+
const result = await withWaitingText(await poll());
|
|
4593
|
+
lastMessages?.keep(result.sessions);
|
|
4594
|
+
return result;
|
|
4369
4595
|
} catch {
|
|
4370
4596
|
await waitingTexts?.annotate([]);
|
|
4597
|
+
lastMessages?.forget();
|
|
4371
4598
|
stops?.set(/* @__PURE__ */ new Map());
|
|
4372
4599
|
return {
|
|
4373
4600
|
health: {
|
|
@@ -4385,6 +4612,19 @@ function createClaudeCodeAdapter(options = {}) {
|
|
|
4385
4612
|
sessions: []
|
|
4386
4613
|
};
|
|
4387
4614
|
}
|
|
4615
|
+
},
|
|
4616
|
+
async lastMessage(sessionId) {
|
|
4617
|
+
if (lastMessages === null) {
|
|
4618
|
+
return {
|
|
4619
|
+
message: null,
|
|
4620
|
+
reason: "off",
|
|
4621
|
+
setting: transcriptsOff ? WAITING_TEXT_ENV : LAST_MESSAGE_ENV
|
|
4622
|
+
};
|
|
4623
|
+
}
|
|
4624
|
+
return lastMessages.read(sessionId);
|
|
4625
|
+
},
|
|
4626
|
+
get lastMessagesKept() {
|
|
4627
|
+
return lastMessages?.size ?? 0;
|
|
4388
4628
|
}
|
|
4389
4629
|
};
|
|
4390
4630
|
}
|
|
@@ -4400,8 +4640,8 @@ function parseProcessParents(stdout) {
|
|
|
4400
4640
|
return parents;
|
|
4401
4641
|
}
|
|
4402
4642
|
var readProcessParentsWithPs = async () => {
|
|
4403
|
-
const
|
|
4404
|
-
return
|
|
4643
|
+
const answer2 = await runPs(PS_PARENTS_ARGS);
|
|
4644
|
+
return answer2.ok ? parseProcessParents(answer2.stdout) : /* @__PURE__ */ new Map();
|
|
4405
4645
|
};
|
|
4406
4646
|
|
|
4407
4647
|
// src/collector/actions/stopSession.ts
|
|
@@ -4869,7 +5109,7 @@ function createAnswerer(options) {
|
|
|
4869
5109
|
};
|
|
4870
5110
|
}
|
|
4871
5111
|
function createAnswerRoute(options) {
|
|
4872
|
-
const
|
|
5112
|
+
const answer2 = createAnswerer(options);
|
|
4873
5113
|
return async function answerPermission(req) {
|
|
4874
5114
|
const refused = answerRefusalFor(req);
|
|
4875
5115
|
if (refused) {
|
|
@@ -4888,7 +5128,7 @@ function createAnswerRoute(options) {
|
|
|
4888
5128
|
'The body must be {"sessionId": "...", "requestId": "...", "decision": "allow" or "deny"} and nothing else.'
|
|
4889
5129
|
);
|
|
4890
5130
|
}
|
|
4891
|
-
const outcome = await
|
|
5131
|
+
const outcome = await answer2(asked.sessionId, asked.requestId, asked.decision);
|
|
4892
5132
|
if (outcome !== "answered") return answerFailed(outcome);
|
|
4893
5133
|
return {
|
|
4894
5134
|
status: 200,
|
|
@@ -5187,7 +5427,7 @@ function createHeldAsks(options) {
|
|
|
5187
5427
|
if (inFlight === run) inFlight = null;
|
|
5188
5428
|
}
|
|
5189
5429
|
}
|
|
5190
|
-
async function
|
|
5430
|
+
async function answer2(sessionId, requestId, decision) {
|
|
5191
5431
|
const entry = held.get(sessionId);
|
|
5192
5432
|
if (!entry || entry.requestId !== requestId || !entry.confirmed) return "no-ask";
|
|
5193
5433
|
if (decision === "allow" && !entry.shown.allow) return "not-allowable";
|
|
@@ -5219,7 +5459,7 @@ function createHeldAsks(options) {
|
|
|
5219
5459
|
const verdict = verdictOf(entry);
|
|
5220
5460
|
if (verdict === null) return;
|
|
5221
5461
|
entry.byRule = true;
|
|
5222
|
-
void
|
|
5462
|
+
void answer2(entry.sessionId, entry.requestId, verdict.decision).then((outcome) => {
|
|
5223
5463
|
if (outcome === "answered") rules.answered(request2, verdict);
|
|
5224
5464
|
});
|
|
5225
5465
|
}
|
|
@@ -5276,7 +5516,7 @@ function createHeldAsks(options) {
|
|
|
5276
5516
|
void check();
|
|
5277
5517
|
},
|
|
5278
5518
|
check,
|
|
5279
|
-
answer,
|
|
5519
|
+
answer: answer2,
|
|
5280
5520
|
withAsks(snapshot) {
|
|
5281
5521
|
const answering = status();
|
|
5282
5522
|
if (held.size === 0) return { ...snapshot, answering };
|
|
@@ -5339,7 +5579,7 @@ function createHeldAsks(options) {
|
|
|
5339
5579
|
import { chmod, lstat as lstat3, mkdir, stat as stat4, unlink } from "node:fs/promises";
|
|
5340
5580
|
import http from "node:http";
|
|
5341
5581
|
import net from "node:net";
|
|
5342
|
-
import
|
|
5582
|
+
import path10 from "node:path";
|
|
5343
5583
|
var HOOK_PATH = "/hooks/permission-request";
|
|
5344
5584
|
var HOOK_HEADER = "x-agent-lookout-hook";
|
|
5345
5585
|
var HOOK_HEADER_VALUE = "permission-request";
|
|
@@ -5412,7 +5652,7 @@ function createHookSocket(options) {
|
|
|
5412
5652
|
const maxBody = options.maxBodyBytes ?? MAX_HOOK_BODY_BYTES;
|
|
5413
5653
|
const bodyTimeoutMs = options.bodyTimeoutMs ?? HOOK_BODY_TIMEOUT_MS;
|
|
5414
5654
|
const uid = options.uid ?? process.getuid?.() ?? -1;
|
|
5415
|
-
const folder =
|
|
5655
|
+
const folder = path10.dirname(socketPath);
|
|
5416
5656
|
let server = null;
|
|
5417
5657
|
let made = null;
|
|
5418
5658
|
async function handle(req, res) {
|
|
@@ -5535,12 +5775,12 @@ function createHookSocket(options) {
|
|
|
5535
5775
|
|
|
5536
5776
|
// src/collector/answers/registryStatus.ts
|
|
5537
5777
|
import os4 from "node:os";
|
|
5538
|
-
import
|
|
5778
|
+
import path11 from "node:path";
|
|
5539
5779
|
function createRegistryStatus(options) {
|
|
5540
5780
|
const homeDir = options.homeDir ?? os4.homedir();
|
|
5541
5781
|
const readFile2 = options.readFile ?? readRegularFile;
|
|
5542
|
-
const home = options.env[CLAUDE_HOME_ENV]?.trim() ||
|
|
5543
|
-
const sessionsDir =
|
|
5782
|
+
const home = options.env[CLAUDE_HOME_ENV]?.trim() || path11.join(homeDir, ".claude");
|
|
5783
|
+
const sessionsDir = path11.join(path11.resolve(home), "sessions");
|
|
5544
5784
|
return {
|
|
5545
5785
|
async statusOf(sessionId) {
|
|
5546
5786
|
const unknown = { status: "unknown" };
|
|
@@ -5551,7 +5791,7 @@ function createRegistryStatus(options) {
|
|
|
5551
5791
|
const ownId = sessionId.slice("claude-code:".length);
|
|
5552
5792
|
let content;
|
|
5553
5793
|
try {
|
|
5554
|
-
content = await readFile2(
|
|
5794
|
+
content = await readFile2(path11.join(sessionsDir, `${pid}.json`));
|
|
5555
5795
|
} catch {
|
|
5556
5796
|
return unknown;
|
|
5557
5797
|
}
|
|
@@ -5825,18 +6065,18 @@ function namesOwnFiles(words, ownPaths = [], folder) {
|
|
|
5825
6065
|
(part) => from !== void 0 && !part.startsWith("/") && !part.startsWith("-") ? [part, `${from}/${part}`] : [part]
|
|
5826
6066
|
);
|
|
5827
6067
|
return read.some((part) => {
|
|
5828
|
-
const
|
|
5829
|
-
if (
|
|
5830
|
-
if (own.some((name) =>
|
|
5831
|
-
if (above.has(
|
|
5832
|
-
return ownNames.some((name) =>
|
|
6068
|
+
const path28 = plainPath(part);
|
|
6069
|
+
if (path28 === BY_NUMBER || path28.startsWith(`${BY_NUMBER}/`)) return true;
|
|
6070
|
+
if (own.some((name) => path28 === name || path28.startsWith(`${name}/`))) return true;
|
|
6071
|
+
if (above.has(path28)) return true;
|
|
6072
|
+
return ownNames.some((name) => path28 === name || path28.endsWith(`/${name}`));
|
|
5833
6073
|
});
|
|
5834
6074
|
});
|
|
5835
6075
|
}
|
|
5836
6076
|
function folderReachesOwnFiles(folder, ownPaths = []) {
|
|
5837
6077
|
const own = ownPaths.filter((name) => name.startsWith("/")).map(plainPath);
|
|
5838
|
-
const
|
|
5839
|
-
return own.some((name) => name ===
|
|
6078
|
+
const path28 = plainPath(folder);
|
|
6079
|
+
return own.some((name) => name === path28 || name.startsWith(`${path28 === "/" ? "" : path28}/`));
|
|
5840
6080
|
}
|
|
5841
6081
|
var PLAIN_WORD = /^[A-Za-z0-9_\-.,/:=+@%]+$/;
|
|
5842
6082
|
var ASSIGNMENT = /^[A-Za-z_][A-Za-z0-9_]*=/;
|
|
@@ -6218,7 +6458,7 @@ function createAnswering(options) {
|
|
|
6218
6458
|
let problem = null;
|
|
6219
6459
|
let listening = false;
|
|
6220
6460
|
const answerRoute = createAnswerRoute({ poller, events, asks, now: options.now });
|
|
6221
|
-
const
|
|
6461
|
+
const answer2 = createAnswerer({ poller, events, asks, now: options.now });
|
|
6222
6462
|
return {
|
|
6223
6463
|
async start() {
|
|
6224
6464
|
const started = await socket.start();
|
|
@@ -6252,7 +6492,7 @@ function createAnswering(options) {
|
|
|
6252
6492
|
return answerRefusalFor(req) ?? refusal(405, "This address only answers GET requests.", { Allow: "GET" });
|
|
6253
6493
|
},
|
|
6254
6494
|
// The same answer, from the host's own process. With no socket nothing is held.
|
|
6255
|
-
answer: async (sessionId, requestId, decision) => problem === null ?
|
|
6495
|
+
answer: async (sessionId, requestId, decision) => problem === null ? answer2(sessionId, requestId, decision) : "unavailable",
|
|
6256
6496
|
check: () => asks.check(),
|
|
6257
6497
|
ruleAnswers: () => ruleAnswers.recent()
|
|
6258
6498
|
};
|
|
@@ -6483,7 +6723,7 @@ function waitTotals(input) {
|
|
|
6483
6723
|
|
|
6484
6724
|
// src/collector/adapters/antigravity/index.ts
|
|
6485
6725
|
import os5 from "node:os";
|
|
6486
|
-
import
|
|
6726
|
+
import path15 from "node:path";
|
|
6487
6727
|
|
|
6488
6728
|
// src/core/mapping/antigravityLiveness.ts
|
|
6489
6729
|
function holdConversations(processes, conversations, listedAt = null) {
|
|
@@ -6522,14 +6762,15 @@ var SOURCE2 = {
|
|
|
6522
6762
|
backend: "server.go",
|
|
6523
6763
|
created: "server.go",
|
|
6524
6764
|
streaming: "conversation_manager.go",
|
|
6525
|
-
|
|
6765
|
+
asking: "tool_confirmation_manager.go",
|
|
6766
|
+
answered: "input_loop.go"
|
|
6526
6767
|
};
|
|
6527
6768
|
var UUID = "[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}";
|
|
6528
6769
|
var OPENED = new RegExp(`^(Created|Streaming) conversation (${UUID})$`);
|
|
6529
6770
|
var BACKEND = /^Creating CLI server backend: (?:.* )?workspaceDirs=\[([^\]\s]+)\](?: |$)/;
|
|
6530
6771
|
var ASKING = /^Surfacing tool confirmation: "([A-Za-z]{1,64})" at step (\d{1,9})$/;
|
|
6531
6772
|
var ANSWERED = new RegExp(
|
|
6532
|
-
`^Responding to tool confirmation: convID=(${UUID}), stepIdx=(\\d{1,9}), approved=(?:true|false)
|
|
6773
|
+
`^Responding to tool confirmation: convID=(${UUID}), stepIdx=(\\d{1,9}), approved=(?:true|false)(?:, .*)?$`
|
|
6533
6774
|
);
|
|
6534
6775
|
var ABSOLUTE = /^(?:\/|[A-Za-z]:[\\/])/;
|
|
6535
6776
|
var UNPRINTABLE = /[\p{Cc}\p{Cf}]/u;
|
|
@@ -6547,7 +6788,11 @@ function readAgyLogLine(line) {
|
|
|
6547
6788
|
const backend = BACKEND.exec(message);
|
|
6548
6789
|
return backend !== null && ABSOLUTE.test(backend[1]) && !UNPRINTABLE.test(backend[1]) ? { kind: "folder", folder: backend[1] } : null;
|
|
6549
6790
|
}
|
|
6550
|
-
if (source
|
|
6791
|
+
if (source === SOURCE2.answered) {
|
|
6792
|
+
const answered = ANSWERED.exec(message);
|
|
6793
|
+
return answered === null ? null : { kind: "answered", conversation: answered[1].toLowerCase(), step: Number(answered[2]) };
|
|
6794
|
+
}
|
|
6795
|
+
if (source !== SOURCE2.asking) return null;
|
|
6551
6796
|
const asking = ASKING.exec(message);
|
|
6552
6797
|
if (asking !== null) {
|
|
6553
6798
|
const fraction = (glog[6] ?? "0").padEnd(3, "0").slice(0, 3);
|
|
@@ -6565,10 +6810,6 @@ function readAgyLogLine(line) {
|
|
|
6565
6810
|
}
|
|
6566
6811
|
};
|
|
6567
6812
|
}
|
|
6568
|
-
const answered = ANSWERED.exec(message);
|
|
6569
|
-
if (answered !== null) {
|
|
6570
|
-
return { kind: "answered", conversation: answered[1].toLowerCase(), step: Number(answered[2]) };
|
|
6571
|
-
}
|
|
6572
6813
|
return null;
|
|
6573
6814
|
}
|
|
6574
6815
|
function emptyAgyLogState() {
|
|
@@ -6621,7 +6862,7 @@ function agyLogTimeAt(time, near) {
|
|
|
6621
6862
|
}
|
|
6622
6863
|
|
|
6623
6864
|
// src/collector/adapters/antigravity/agyLogs.ts
|
|
6624
|
-
import
|
|
6865
|
+
import path12 from "node:path";
|
|
6625
6866
|
var LOG_DIR = "log";
|
|
6626
6867
|
var LOG_LIMIT_BYTES = 4 * 1024 * 1024;
|
|
6627
6868
|
var LINE_LIMIT_BYTES = 64 * 1024;
|
|
@@ -6656,7 +6897,7 @@ function matchLogs(processes, logs) {
|
|
|
6656
6897
|
function createAgyLogReader(options) {
|
|
6657
6898
|
const { io } = options;
|
|
6658
6899
|
const now = options.now ?? Date.now;
|
|
6659
|
-
const logDir =
|
|
6900
|
+
const logDir = path12.join(options.home, LOG_DIR);
|
|
6660
6901
|
const cursors = /* @__PURE__ */ new Map();
|
|
6661
6902
|
let listed = /* @__PURE__ */ new Map();
|
|
6662
6903
|
let listedFor = "";
|
|
@@ -6753,7 +6994,7 @@ function createAgyLogReader(options) {
|
|
|
6753
6994
|
states.push(null);
|
|
6754
6995
|
continue;
|
|
6755
6996
|
}
|
|
6756
|
-
const file =
|
|
6997
|
+
const file = path12.join(logDir, name);
|
|
6757
6998
|
kept.add(file);
|
|
6758
6999
|
try {
|
|
6759
7000
|
const info = await io.lstat(file);
|
|
@@ -6871,12 +7112,12 @@ function createAgyProcessReader(options = {}) {
|
|
|
6871
7112
|
const unseen = rows.filter((row) => !known.has(keyOf(row)));
|
|
6872
7113
|
const ended = /* @__PURE__ */ new Set();
|
|
6873
7114
|
if (unseen.length > 0) {
|
|
6874
|
-
const
|
|
6875
|
-
const lines = parseCommandLines(
|
|
7115
|
+
const answer2 = await run(agyCommandLineArgs(unseen.map((row) => row.pid)));
|
|
7116
|
+
const lines = parseCommandLines(answer2.stdout);
|
|
6876
7117
|
for (const row of unseen) {
|
|
6877
7118
|
const line = lines.get(row.pid);
|
|
6878
7119
|
if (line !== void 0) known.set(keyOf(row), readAgyCommandLine(line));
|
|
6879
|
-
else if (
|
|
7120
|
+
else if (answer2.ok || lines.size > 0) ended.add(row.pid);
|
|
6880
7121
|
}
|
|
6881
7122
|
}
|
|
6882
7123
|
const running = /* @__PURE__ */ new Map();
|
|
@@ -6916,7 +7157,7 @@ function createAgyProcessReader(options = {}) {
|
|
|
6916
7157
|
}
|
|
6917
7158
|
|
|
6918
7159
|
// src/collector/adapters/antigravity/conversations.ts
|
|
6919
|
-
import
|
|
7160
|
+
import path13 from "node:path";
|
|
6920
7161
|
var BRAIN_DIR = "brain";
|
|
6921
7162
|
var CONVERSATIONS_DIR = "conversations";
|
|
6922
7163
|
var TRANSCRIPT_IN_FOLDER = [".system_generated", "logs", "transcript.jsonl"];
|
|
@@ -6932,15 +7173,15 @@ function conversationIdOfDatabase(name) {
|
|
|
6932
7173
|
}
|
|
6933
7174
|
function createConversationFinder(options) {
|
|
6934
7175
|
const { io } = options;
|
|
6935
|
-
const brainDir =
|
|
6936
|
-
const databaseDir =
|
|
7176
|
+
const brainDir = path13.join(options.home, BRAIN_DIR);
|
|
7177
|
+
const databaseDir = path13.join(options.home, CONVERSATIONS_DIR);
|
|
6937
7178
|
const oldRefreshMs = options.oldRefreshMs ?? OLD_REFRESH_MS;
|
|
6938
7179
|
const known = /* @__PURE__ */ new Map();
|
|
6939
7180
|
let oldCheckedAt = null;
|
|
6940
7181
|
async function lookAt(id) {
|
|
6941
|
-
const transcript =
|
|
7182
|
+
const transcript = path13.join(brainDir, id, ...TRANSCRIPT_IN_FOLDER);
|
|
6942
7183
|
const infos = await Promise.all(
|
|
6943
|
-
[transcript,
|
|
7184
|
+
[transcript, path13.join(databaseDir, `${id}.db`), path13.join(databaseDir, `${id}.db-wal`)].map(
|
|
6944
7185
|
(file) => io.lstat(file).catch(() => null)
|
|
6945
7186
|
)
|
|
6946
7187
|
);
|
|
@@ -6996,7 +7237,7 @@ function writtenRecently(files, now) {
|
|
|
6996
7237
|
}
|
|
6997
7238
|
|
|
6998
7239
|
// src/collector/adapters/antigravity/annotations.ts
|
|
6999
|
-
import
|
|
7240
|
+
import path14 from "node:path";
|
|
7000
7241
|
var ANNOTATIONS_DIR = "annotations";
|
|
7001
7242
|
var ANNOTATION_LIMIT_BYTES = 4 * 1024;
|
|
7002
7243
|
var TITLE_FIELD = /^title[ \t]*:[ \t]*"((?:[^"\\\n]|\\.)*)"[ \t]*$/m;
|
|
@@ -7068,10 +7309,10 @@ function readTitle(content) {
|
|
|
7068
7309
|
}
|
|
7069
7310
|
function createTitleReader(options) {
|
|
7070
7311
|
const { io } = options;
|
|
7071
|
-
const dir =
|
|
7312
|
+
const dir = path14.join(options.home, ANNOTATIONS_DIR);
|
|
7072
7313
|
const cache = /* @__PURE__ */ new Map();
|
|
7073
7314
|
async function read(id) {
|
|
7074
|
-
const file =
|
|
7315
|
+
const file = path14.join(dir, `${id}.pbtxt`);
|
|
7075
7316
|
const info = await io.lstat(file);
|
|
7076
7317
|
const known = cache.get(id);
|
|
7077
7318
|
if (known && known.ino === info.ino && known.size === info.size && known.mtimeMs === info.mtimeMs) {
|
|
@@ -7654,8 +7895,8 @@ function createAntigravityAdapter(options = {}) {
|
|
|
7654
7895
|
const processes = options.processes ?? createAgyProcessReader({ platform });
|
|
7655
7896
|
const override = env[ANTIGRAVITY_HOME_ENV]?.trim() || void 0;
|
|
7656
7897
|
const homeFrom = override ? "agent-lookout" : "default";
|
|
7657
|
-
const home =
|
|
7658
|
-
const brainDir =
|
|
7898
|
+
const home = path15.resolve(override ?? path15.join(homeDir, ...DEFAULT_HOME));
|
|
7899
|
+
const brainDir = path15.join(home, BRAIN_DIR);
|
|
7659
7900
|
const homeName = tildify(home, homeDir);
|
|
7660
7901
|
const brainName = tildify(brainDir, homeDir);
|
|
7661
7902
|
const finder = createConversationFinder({ home, io, oldRefreshMs: options.oldRefreshMs });
|
|
@@ -7820,8 +8061,8 @@ function createAntigravityAdapter(options = {}) {
|
|
|
7820
8061
|
basis = BASIS_UNMATCHED;
|
|
7821
8062
|
}
|
|
7822
8063
|
if (unknownStep) detail += ` ${UNKNOWN_STEP_NOTE}`;
|
|
7823
|
-
const
|
|
7824
|
-
return { ...
|
|
8064
|
+
const answer2 = result("ok", detail, every(pollIntervalMs), checkedAt, basis);
|
|
8065
|
+
return { ...answer2, sessions };
|
|
7825
8066
|
}
|
|
7826
8067
|
return {
|
|
7827
8068
|
id: SOURCE_ID2,
|
|
@@ -7845,7 +8086,7 @@ function createAntigravityAdapter(options = {}) {
|
|
|
7845
8086
|
|
|
7846
8087
|
// src/collector/adapters/codex/index.ts
|
|
7847
8088
|
import os6 from "node:os";
|
|
7848
|
-
import
|
|
8089
|
+
import path17 from "node:path";
|
|
7849
8090
|
|
|
7850
8091
|
// src/core/mapping/codexMapping.ts
|
|
7851
8092
|
var TURN_UNDER_WAY = /* @__PURE__ */ new Set(["task_started", "turn_started"]);
|
|
@@ -8162,7 +8403,7 @@ function createRolloutReader(io) {
|
|
|
8162
8403
|
}
|
|
8163
8404
|
|
|
8164
8405
|
// src/collector/adapters/codex/rollouts.ts
|
|
8165
|
-
import
|
|
8406
|
+
import path16 from "node:path";
|
|
8166
8407
|
var UUID4 = "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}";
|
|
8167
8408
|
var ROLLOUT_FILE_NAME = new RegExp(
|
|
8168
8409
|
`^rollout-\\d{4}-\\d{2}-\\d{2}T\\d{2}-\\d{2}-\\d{2}-(${UUID4})(?:_(${UUID4}))?\\.jsonl$`,
|
|
@@ -8180,7 +8421,7 @@ function twoDigits(value) {
|
|
|
8180
8421
|
function recentDayFolders(sessionsDir, now) {
|
|
8181
8422
|
const folders = [now, now - 24 * 60 * 60 * 1e3].map((at) => {
|
|
8182
8423
|
const date = new Date(at);
|
|
8183
|
-
return
|
|
8424
|
+
return path16.join(
|
|
8184
8425
|
sessionsDir,
|
|
8185
8426
|
String(date.getFullYear()),
|
|
8186
8427
|
twoDigits(date.getMonth() + 1),
|
|
@@ -8209,12 +8450,12 @@ function createRolloutFinder(options) {
|
|
|
8209
8450
|
const refs = [];
|
|
8210
8451
|
for (const name of (await names(dir)).sort()) {
|
|
8211
8452
|
const threadId = threadIdOfRollout(name);
|
|
8212
|
-
if (threadId !== null) refs.push({ path:
|
|
8453
|
+
if (threadId !== null) refs.push({ path: path16.join(dir, name), threadId });
|
|
8213
8454
|
}
|
|
8214
8455
|
return refs;
|
|
8215
8456
|
}
|
|
8216
8457
|
async function subfolders(dir, pattern) {
|
|
8217
|
-
return (await names(dir)).filter((name) => pattern.test(name)).sort().map((name) =>
|
|
8458
|
+
return (await names(dir)).filter((name) => pattern.test(name)).sort().map((name) => path16.join(dir, name));
|
|
8218
8459
|
}
|
|
8219
8460
|
async function buildIndex() {
|
|
8220
8461
|
const years = await subfolders(sessionsDir, YEAR);
|
|
@@ -8433,10 +8674,10 @@ function createCodexAdapter(options = {}) {
|
|
|
8433
8674
|
const ownOverride = env[CODEX_HOME_ENV]?.trim() || void 0;
|
|
8434
8675
|
const codexOverride = env[CODEX_OWN_HOME_ENV]?.trim() || void 0;
|
|
8435
8676
|
const homeFrom = ownOverride ? "agent-lookout" : codexOverride ? "codex" : "default";
|
|
8436
|
-
const codexHome =
|
|
8437
|
-
const sessionsDir =
|
|
8438
|
-
const locksDir =
|
|
8439
|
-
const indexFile =
|
|
8677
|
+
const codexHome = path17.resolve(ownOverride ?? codexOverride ?? path17.join(homeDir, ".codex"));
|
|
8678
|
+
const sessionsDir = path17.join(codexHome, "sessions");
|
|
8679
|
+
const locksDir = path17.join(codexHome, WRITER_LOCK_DIR);
|
|
8680
|
+
const indexFile = path17.join(codexHome, SESSION_INDEX_FILE);
|
|
8440
8681
|
const homeName = tildify(codexHome, homeDir);
|
|
8441
8682
|
const sessionsName = tildify(sessionsDir, homeDir);
|
|
8442
8683
|
const locksName = tildify(locksDir, homeDir);
|
|
@@ -8619,8 +8860,8 @@ function createCodexAdapter(options = {}) {
|
|
|
8619
8860
|
} else if (olderCodex) {
|
|
8620
8861
|
detail += ` ${OLDER_CODEX_NOTE}`;
|
|
8621
8862
|
}
|
|
8622
|
-
const
|
|
8623
|
-
return { ...
|
|
8863
|
+
const answer2 = result("ok", detail, every(pollIntervalMs), checkedAt);
|
|
8864
|
+
return { ...answer2, sessions, basis: locks.supported ? BASIS2 : BASIS_WITHOUT_LOCKS };
|
|
8624
8865
|
}
|
|
8625
8866
|
return {
|
|
8626
8867
|
id: SOURCE_ID3,
|
|
@@ -8644,7 +8885,7 @@ function createCodexAdapter(options = {}) {
|
|
|
8644
8885
|
|
|
8645
8886
|
// src/collector/adapters/status-files/index.ts
|
|
8646
8887
|
import os7 from "node:os";
|
|
8647
|
-
import
|
|
8888
|
+
import path18 from "node:path";
|
|
8648
8889
|
|
|
8649
8890
|
// src/core/mapping/statusFileMapping.ts
|
|
8650
8891
|
var STATUS = /* @__PURE__ */ new Map([
|
|
@@ -8793,7 +9034,7 @@ function createStatusFileAdapter(options = {}) {
|
|
|
8793
9034
|
const isAlive = options.isAlive ?? isProcessAlive;
|
|
8794
9035
|
const pollIntervalMs = options.pollIntervalMs ?? POLL_INTERVAL_MS;
|
|
8795
9036
|
const override = env[STATUS_DIR_ENV]?.trim() || void 0;
|
|
8796
|
-
const dir =
|
|
9037
|
+
const dir = path18.resolve(override ?? path18.join(homeDir, ".agent-lookout", "sessions"));
|
|
8797
9038
|
const dirName = tildify(dir, homeDir);
|
|
8798
9039
|
const decoder2 = new TextDecoder();
|
|
8799
9040
|
let lastRead = /* @__PURE__ */ new Map();
|
|
@@ -8853,7 +9094,7 @@ function createStatusFileAdapter(options = {}) {
|
|
|
8853
9094
|
if (names.length <= MAX_FILES) return names;
|
|
8854
9095
|
const written = await Promise.all(
|
|
8855
9096
|
names.map(async (name) => {
|
|
8856
|
-
const info = await io.lstat(
|
|
9097
|
+
const info = await io.lstat(path18.join(dir, name)).catch(() => null);
|
|
8857
9098
|
return { name, mtimeMs: info?.mtimeMs ?? -Infinity };
|
|
8858
9099
|
})
|
|
8859
9100
|
);
|
|
@@ -8874,14 +9115,14 @@ function createStatusFileAdapter(options = {}) {
|
|
|
8874
9115
|
names = await io.readdir(dir);
|
|
8875
9116
|
} catch (error) {
|
|
8876
9117
|
if (!isMissing2(error)) {
|
|
8877
|
-
const
|
|
9118
|
+
const answer2 = result(
|
|
8878
9119
|
"error",
|
|
8879
9120
|
`Status files could not be read: the folder ${dirName} could not be listed.`,
|
|
8880
9121
|
watching("cannot be read"),
|
|
8881
9122
|
checkedAt
|
|
8882
9123
|
);
|
|
8883
|
-
|
|
8884
|
-
return
|
|
9124
|
+
answer2.health.advice = "Check that your user account can open the folder.";
|
|
9125
|
+
return answer2;
|
|
8885
9126
|
}
|
|
8886
9127
|
contentsKnown = true;
|
|
8887
9128
|
lastRead = /* @__PURE__ */ new Map();
|
|
@@ -8903,7 +9144,7 @@ function createStatusFileAdapter(options = {}) {
|
|
|
8903
9144
|
const nextRead = /* @__PURE__ */ new Map();
|
|
8904
9145
|
const nextStatuses = /* @__PURE__ */ new Map();
|
|
8905
9146
|
for (const fileName2 of await toRead(listed)) {
|
|
8906
|
-
const reading = await readOne(
|
|
9147
|
+
const reading = await readOne(path18.join(dir, fileName2));
|
|
8907
9148
|
let kept;
|
|
8908
9149
|
if (reading.kind === "read") {
|
|
8909
9150
|
kept = reading;
|
|
@@ -9130,7 +9371,7 @@ function createEventStore(capacity = EVENT_CAPACITY) {
|
|
|
9130
9371
|
|
|
9131
9372
|
// src/collector/git/branchFinder.ts
|
|
9132
9373
|
import os8 from "node:os";
|
|
9133
|
-
import
|
|
9374
|
+
import path20 from "node:path";
|
|
9134
9375
|
|
|
9135
9376
|
// src/collector/git/gitHead.ts
|
|
9136
9377
|
var MAX_GIT_FILE_BYTES = 4 * 1024;
|
|
@@ -9170,18 +9411,18 @@ function parseCommonDir(content) {
|
|
|
9170
9411
|
|
|
9171
9412
|
// src/collector/git/repository.ts
|
|
9172
9413
|
import { createHash as createHash3 } from "node:crypto";
|
|
9173
|
-
import
|
|
9414
|
+
import path19 from "node:path";
|
|
9174
9415
|
var REPOSITORY_ID_LENGTH = 16;
|
|
9175
9416
|
function repositoryId(gitFolder) {
|
|
9176
9417
|
return createHash3("sha256").update(gitFolder).digest("hex").slice(0, REPOSITORY_ID_LENGTH);
|
|
9177
9418
|
}
|
|
9178
9419
|
function sharedName(common) {
|
|
9179
|
-
const base =
|
|
9180
|
-
if (base.startsWith(".")) return
|
|
9420
|
+
const base = path19.basename(common);
|
|
9421
|
+
if (base.startsWith(".")) return path19.basename(path19.dirname(common));
|
|
9181
9422
|
return base.endsWith(".git") ? base.slice(0, -".git".length) : base;
|
|
9182
9423
|
}
|
|
9183
9424
|
function repositoryOf({ dir, gitDir, commonDir }) {
|
|
9184
|
-
const name = sessionName(commonDir === null ?
|
|
9425
|
+
const name = sessionName(commonDir === null ? path19.basename(dir) : sharedName(commonDir));
|
|
9185
9426
|
return name === void 0 ? null : { id: repositoryId(commonDir ?? gitDir), name };
|
|
9186
9427
|
}
|
|
9187
9428
|
|
|
@@ -9210,13 +9451,13 @@ async function readGitFile(io, file, maxBytes = MAX_GIT_FILE_BYTES) {
|
|
|
9210
9451
|
}
|
|
9211
9452
|
}
|
|
9212
9453
|
async function commonDirOf(io, gitDir) {
|
|
9213
|
-
const content = await readGitFile(io,
|
|
9454
|
+
const content = await readGitFile(io, path20.join(gitDir, "commondir"));
|
|
9214
9455
|
if (content === void 0) return null;
|
|
9215
9456
|
const target = content === null ? null : parseCommonDir(content);
|
|
9216
|
-
return target === null ? void 0 :
|
|
9457
|
+
return target === null ? void 0 : path20.resolve(gitDir, target);
|
|
9217
9458
|
}
|
|
9218
9459
|
async function headIn(dir, kind, io) {
|
|
9219
|
-
const marker =
|
|
9460
|
+
const marker = path20.join(dir, ".git");
|
|
9220
9461
|
let gitDir;
|
|
9221
9462
|
if (kind === "directory") {
|
|
9222
9463
|
gitDir = marker;
|
|
@@ -9224,11 +9465,11 @@ async function headIn(dir, kind, io) {
|
|
|
9224
9465
|
const content2 = await readGitFile(io, marker);
|
|
9225
9466
|
const target = typeof content2 === "string" ? parseGitFile(content2) : null;
|
|
9226
9467
|
if (target === null) return null;
|
|
9227
|
-
gitDir =
|
|
9468
|
+
gitDir = path20.resolve(dir, target);
|
|
9228
9469
|
} else {
|
|
9229
9470
|
return null;
|
|
9230
9471
|
}
|
|
9231
|
-
const content = await readGitFile(io,
|
|
9472
|
+
const content = await readGitFile(io, path20.join(gitDir, "HEAD"));
|
|
9232
9473
|
const head = typeof content === "string" ? parseHead(content) : null;
|
|
9233
9474
|
if (head === null) return null;
|
|
9234
9475
|
const commonDir = kind === "file" ? await commonDirOf(io, gitDir) : null;
|
|
@@ -9237,20 +9478,20 @@ async function headIn(dir, kind, io) {
|
|
|
9237
9478
|
return { head: { ...head, repository }, gitFolder: commonDir ?? gitDir };
|
|
9238
9479
|
}
|
|
9239
9480
|
async function findHead(cwd, { io, homeDir }) {
|
|
9240
|
-
if (!
|
|
9241
|
-
const home =
|
|
9242
|
-
let dir =
|
|
9481
|
+
if (!path20.isAbsolute(cwd)) return null;
|
|
9482
|
+
const home = path20.resolve(homeDir);
|
|
9483
|
+
let dir = path20.resolve(cwd);
|
|
9243
9484
|
try {
|
|
9244
9485
|
if ((await io.stat(dir)).kind !== "directory") return null;
|
|
9245
9486
|
} catch {
|
|
9246
9487
|
return null;
|
|
9247
9488
|
}
|
|
9248
9489
|
for (let level = 0; level < MAX_LEVELS; level += 1) {
|
|
9249
|
-
const parent =
|
|
9490
|
+
const parent = path20.dirname(dir);
|
|
9250
9491
|
if (dir === home || dir === parent) return null;
|
|
9251
9492
|
let kind;
|
|
9252
9493
|
try {
|
|
9253
|
-
kind = (await io.lstat(
|
|
9494
|
+
kind = (await io.lstat(path20.join(dir, ".git"))).kind;
|
|
9254
9495
|
} catch (error) {
|
|
9255
9496
|
if (!isMissing2(error)) return null;
|
|
9256
9497
|
dir = parent;
|
|
@@ -9435,11 +9676,10 @@ function ghArguments(target) {
|
|
|
9435
9676
|
];
|
|
9436
9677
|
}
|
|
9437
9678
|
var LEFT_OUT = ["GH_FORCE_TTY", "GH_DEBUG", "DEBUG", "GH_PAGER", "PAGER"];
|
|
9438
|
-
var OWN_SETTING = /^AGENT_LOOKOUT_/;
|
|
9439
9679
|
function ghEnvironment(env) {
|
|
9440
9680
|
const given = {};
|
|
9441
|
-
for (const [name, value] of Object.entries(env)) {
|
|
9442
|
-
if (!
|
|
9681
|
+
for (const [name, value] of Object.entries(childEnvironment(env))) {
|
|
9682
|
+
if (!LEFT_OUT.includes(name)) given[name] = value;
|
|
9443
9683
|
}
|
|
9444
9684
|
return {
|
|
9445
9685
|
...given,
|
|
@@ -9528,7 +9768,7 @@ function createGhAsker(options) {
|
|
|
9528
9768
|
}
|
|
9529
9769
|
|
|
9530
9770
|
// src/collector/git/pullRequestTarget.ts
|
|
9531
|
-
import
|
|
9771
|
+
import path21 from "node:path";
|
|
9532
9772
|
|
|
9533
9773
|
// src/collector/git/gitConfig.ts
|
|
9534
9774
|
var MAX_CONFIG_BYTES = 64 * 1024;
|
|
@@ -9786,7 +10026,7 @@ function targetOf(config, branch, base, defaultBranch) {
|
|
|
9786
10026
|
async function readPullRequestTarget(gitFolder, branch, io = nodeIo2) {
|
|
9787
10027
|
try {
|
|
9788
10028
|
if (!isAskableBranch(branch)) return null;
|
|
9789
|
-
const text4 = await readGitFile(io,
|
|
10029
|
+
const text4 = await readGitFile(io, path21.join(gitFolder, "config"), MAX_CONFIG_BYTES);
|
|
9790
10030
|
if (typeof text4 !== "string") return null;
|
|
9791
10031
|
const config = parseGitConfig(text4);
|
|
9792
10032
|
const base = config === null ? null : baseRemoteOf(config);
|
|
@@ -9795,7 +10035,7 @@ async function readPullRequestTarget(gitFolder, branch, io = nodeIo2) {
|
|
|
9795
10035
|
if (base.name !== null && isSafeRemoteName(base.name)) {
|
|
9796
10036
|
const head = await readGitFile(
|
|
9797
10037
|
io,
|
|
9798
|
-
|
|
10038
|
+
path21.join(gitFolder, "refs", "remotes", base.name, "HEAD")
|
|
9799
10039
|
);
|
|
9800
10040
|
if (typeof head === "string") defaultBranch = parseRemoteHead(head, base.name);
|
|
9801
10041
|
}
|
|
@@ -9830,19 +10070,19 @@ function createPullRequestFinder(options) {
|
|
|
9830
10070
|
let gh = "unknown";
|
|
9831
10071
|
let last = null;
|
|
9832
10072
|
const due = (at, since) => at - (since ?? -Infinity) >= intervalMs;
|
|
9833
|
-
function take(question,
|
|
9834
|
-
switch (
|
|
10073
|
+
function take(question, answer2, at) {
|
|
10074
|
+
switch (answer2.kind) {
|
|
9835
10075
|
case "found":
|
|
9836
10076
|
case "none":
|
|
9837
10077
|
gh = "ready";
|
|
9838
10078
|
last = { at, ok: true };
|
|
9839
10079
|
answers2.set(question, {
|
|
9840
|
-
pullRequest:
|
|
10080
|
+
pullRequest: answer2.kind === "found" ? answer2.pullRequest : null,
|
|
9841
10081
|
askedAt: at
|
|
9842
10082
|
});
|
|
9843
10083
|
return true;
|
|
9844
10084
|
case "failed":
|
|
9845
|
-
last = { at, ok: false, reason:
|
|
10085
|
+
last = { at, ok: false, reason: answer2.reason };
|
|
9846
10086
|
answers2.set(question, {
|
|
9847
10087
|
pullRequest: answers2.get(question)?.pullRequest ?? null,
|
|
9848
10088
|
askedAt: at
|
|
@@ -9850,7 +10090,7 @@ function createPullRequestFinder(options) {
|
|
|
9850
10090
|
return true;
|
|
9851
10091
|
case "not-found":
|
|
9852
10092
|
case "signed-out":
|
|
9853
|
-
gh =
|
|
10093
|
+
gh = answer2.kind;
|
|
9854
10094
|
answers2.clear();
|
|
9855
10095
|
pausedUntil = at + intervalMs;
|
|
9856
10096
|
return false;
|
|
@@ -9870,14 +10110,14 @@ function createPullRequestFinder(options) {
|
|
|
9870
10110
|
const at = now();
|
|
9871
10111
|
if (at < pausedUntil) return;
|
|
9872
10112
|
if (!due(at, answers2.get(question)?.askedAt)) continue;
|
|
9873
|
-
const
|
|
10113
|
+
const answer2 = await ask(target).catch(() => ({
|
|
9874
10114
|
kind: "failed",
|
|
9875
10115
|
reason: "gh could not be run"
|
|
9876
10116
|
}));
|
|
9877
10117
|
if (![...reads.values()].some((read) => read.target && questionOf(read.target) === question)) {
|
|
9878
10118
|
continue;
|
|
9879
10119
|
}
|
|
9880
|
-
if (!take(question,
|
|
10120
|
+
if (!take(question, answer2, now())) return;
|
|
9881
10121
|
}
|
|
9882
10122
|
}
|
|
9883
10123
|
return {
|
|
@@ -9966,7 +10206,7 @@ function createClearHistoryRoute(options) {
|
|
|
9966
10206
|
}
|
|
9967
10207
|
|
|
9968
10208
|
// src/collector/history/historyKeeper.ts
|
|
9969
|
-
import
|
|
10209
|
+
import path22 from "node:path";
|
|
9970
10210
|
|
|
9971
10211
|
// src/collector/history/historyFiles.ts
|
|
9972
10212
|
import {
|
|
@@ -10266,7 +10506,7 @@ function createHistoryKeeper(options) {
|
|
|
10266
10506
|
});
|
|
10267
10507
|
const lock = createWriterLock({
|
|
10268
10508
|
fs,
|
|
10269
|
-
file:
|
|
10509
|
+
file: path22.join(dir, LOCK_FILE),
|
|
10270
10510
|
pid: options.pid,
|
|
10271
10511
|
now,
|
|
10272
10512
|
isAlive: options.isAlive,
|
|
@@ -10292,7 +10532,7 @@ function createHistoryKeeper(options) {
|
|
|
10292
10532
|
});
|
|
10293
10533
|
return next;
|
|
10294
10534
|
}
|
|
10295
|
-
const full = (name) =>
|
|
10535
|
+
const full = (name) => path22.join(dir, name);
|
|
10296
10536
|
function hold(record) {
|
|
10297
10537
|
if (unusable !== null || !running) return;
|
|
10298
10538
|
const line = encodeRecord(record);
|
|
@@ -10754,13 +10994,13 @@ function keptHistoryStore(store, keeper) {
|
|
|
10754
10994
|
// src/collector/history/historySettings.ts
|
|
10755
10995
|
import { createHash as createHash4 } from "node:crypto";
|
|
10756
10996
|
import os9 from "node:os";
|
|
10757
|
-
import
|
|
10997
|
+
import path23 from "node:path";
|
|
10758
10998
|
var HISTORY_ENV = "AGENT_LOOKOUT_HISTORY";
|
|
10759
10999
|
var HISTORY_DIR_ENV = "AGENT_LOOKOUT_HISTORY_DIR";
|
|
10760
11000
|
function readHistorySetup(env, homeDir = os9.homedir()) {
|
|
10761
11001
|
if (env[HISTORY_ENV]?.trim().toLowerCase() === "off") return { on: false };
|
|
10762
11002
|
const override = env[HISTORY_DIR_ENV]?.trim() || void 0;
|
|
10763
|
-
const dir =
|
|
11003
|
+
const dir = path23.resolve(override ?? path23.join(homeDir, ".agent-lookout", "history"));
|
|
10764
11004
|
return {
|
|
10765
11005
|
on: true,
|
|
10766
11006
|
dir,
|
|
@@ -10771,14 +11011,57 @@ function readHistorySetup(env, homeDir = os9.homedir()) {
|
|
|
10771
11011
|
function sourcesFingerprint(env, homeDir = os9.homedir()) {
|
|
10772
11012
|
const set = (name) => env[name]?.trim() || void 0;
|
|
10773
11013
|
const folders = [
|
|
10774
|
-
|
|
10775
|
-
|
|
10776
|
-
|
|
10777
|
-
|
|
11014
|
+
path23.resolve(set(CLAUDE_HOME_ENV) ?? path23.join(homeDir, ".claude")),
|
|
11015
|
+
path23.resolve(set(CODEX_HOME_ENV) ?? set(CODEX_OWN_HOME_ENV) ?? path23.join(homeDir, ".codex")),
|
|
11016
|
+
path23.resolve(set(STATUS_DIR_ENV) ?? path23.join(homeDir, ".agent-lookout", "sessions")),
|
|
11017
|
+
path23.resolve(set(ANTIGRAVITY_HOME_ENV) ?? path23.join(homeDir, ...DEFAULT_HOME))
|
|
10778
11018
|
];
|
|
10779
11019
|
return createHash4("sha256").update(JSON.stringify(folders)).digest("hex").slice(0, 32);
|
|
10780
11020
|
}
|
|
10781
11021
|
|
|
11022
|
+
// src/collector/messages/lastMessageRoute.ts
|
|
11023
|
+
var answer = (body) => ({ status: 200, body });
|
|
11024
|
+
var failed3 = (status, error, headers) => ({
|
|
11025
|
+
status,
|
|
11026
|
+
body: { error },
|
|
11027
|
+
...headers && { headers }
|
|
11028
|
+
});
|
|
11029
|
+
function idIn(query) {
|
|
11030
|
+
const pairs = [...query];
|
|
11031
|
+
if (pairs.length !== 1) return null;
|
|
11032
|
+
const [name, value] = pairs[0] ?? ["", ""];
|
|
11033
|
+
if (name !== "id" || value === "" || value.length > MAX_SESSION_ID_LENGTH) return null;
|
|
11034
|
+
return value;
|
|
11035
|
+
}
|
|
11036
|
+
function createLastMessageRoute(options) {
|
|
11037
|
+
const { poller, adapters } = options;
|
|
11038
|
+
const off = lastMessageOff(options.env);
|
|
11039
|
+
return async (req, url) => {
|
|
11040
|
+
const site = req.headers["sec-fetch-site"];
|
|
11041
|
+
if (site !== void 0 && site !== "same-origin") {
|
|
11042
|
+
return failed3(403, "This address only answers the dashboard page served from this machine.");
|
|
11043
|
+
}
|
|
11044
|
+
const id = idIn(url.searchParams);
|
|
11045
|
+
if (id === null) {
|
|
11046
|
+
return failed3(400, "Name one session as id, and nothing else.");
|
|
11047
|
+
}
|
|
11048
|
+
if (off) return answer({ message: null, reason: "off", setting: LAST_MESSAGE_ENV });
|
|
11049
|
+
const session = poller.getSnapshot().sessions.find((listed) => listed.id === id);
|
|
11050
|
+
const notListed = () => failed3(404, "No session with that id is listed.");
|
|
11051
|
+
if (session === void 0) return notListed();
|
|
11052
|
+
const adapter = adapters.find((candidate) => candidate.id === session.source);
|
|
11053
|
+
if (adapter?.lastMessage === void 0) return answer({ message: null, reason: "not-read" });
|
|
11054
|
+
const read = await adapter.lastMessage(session.id);
|
|
11055
|
+
if (read === null) return notListed();
|
|
11056
|
+
if (read === "busy") {
|
|
11057
|
+
return failed3(429, "Too many last messages were asked for at once. Ask again in a second.", {
|
|
11058
|
+
"Retry-After": "1"
|
|
11059
|
+
});
|
|
11060
|
+
}
|
|
11061
|
+
return answer(read);
|
|
11062
|
+
};
|
|
11063
|
+
}
|
|
11064
|
+
|
|
10782
11065
|
// src/collector/notifications/heldWait.ts
|
|
10783
11066
|
var HANDOVER_GRACE_MS = 3e3;
|
|
10784
11067
|
var PAGE_GONE_AFTER_MS = 5e3;
|
|
@@ -10944,6 +11227,7 @@ function runOsascript(args, options = {}) {
|
|
|
10944
11227
|
OSASCRIPT,
|
|
10945
11228
|
[...args],
|
|
10946
11229
|
{
|
|
11230
|
+
env: childEnvironment(process.env),
|
|
10947
11231
|
timeout: options.timeoutMs ?? TERMINAL_JUMP_TIMEOUT_MS,
|
|
10948
11232
|
maxBuffer: MAX_OUTPUT_BYTES4,
|
|
10949
11233
|
encoding: "utf8",
|
|
@@ -11266,7 +11550,7 @@ function channelIn(body) {
|
|
|
11266
11550
|
function asSentence(reason) {
|
|
11267
11551
|
return `${reason.charAt(0).toUpperCase()}${reason.slice(1)}.`;
|
|
11268
11552
|
}
|
|
11269
|
-
var
|
|
11553
|
+
var failed4 = (status, reason, error, more = {}) => ({
|
|
11270
11554
|
status,
|
|
11271
11555
|
body: {
|
|
11272
11556
|
error,
|
|
@@ -11298,21 +11582,21 @@ function createTestSendRoute(options) {
|
|
|
11298
11582
|
const { name, setting } = NAMED[channel];
|
|
11299
11583
|
const tester = channels[channel];
|
|
11300
11584
|
if (tester === null) {
|
|
11301
|
-
return
|
|
11585
|
+
return failed4(
|
|
11302
11586
|
409,
|
|
11303
11587
|
"off",
|
|
11304
11588
|
`${name} is not set up. Set ${setting} and start Agent Lookout again.`
|
|
11305
11589
|
);
|
|
11306
11590
|
}
|
|
11307
11591
|
if (!limiter.begin()) {
|
|
11308
|
-
return
|
|
11592
|
+
return failed4(429, "too-soon", "One test is sent a second. Try again in a moment.", {
|
|
11309
11593
|
retryAfter: 1
|
|
11310
11594
|
});
|
|
11311
11595
|
}
|
|
11312
11596
|
try {
|
|
11313
11597
|
const result = await tester.test();
|
|
11314
11598
|
if (!result.tried) {
|
|
11315
|
-
return
|
|
11599
|
+
return failed4(
|
|
11316
11600
|
429,
|
|
11317
11601
|
"limited",
|
|
11318
11602
|
`${SENDS_PER_HOUR} ${name} pushes were tried in the last hour, the most it tries.`,
|
|
@@ -11323,7 +11607,7 @@ function createTestSendRoute(options) {
|
|
|
11323
11607
|
);
|
|
11324
11608
|
}
|
|
11325
11609
|
if (!result.outcome.sent) {
|
|
11326
|
-
return
|
|
11610
|
+
return failed4(502, "not-sent", asSentence(result.outcome.reason));
|
|
11327
11611
|
}
|
|
11328
11612
|
return {
|
|
11329
11613
|
status: 200,
|
|
@@ -11423,12 +11707,12 @@ function askRemote(port, route, timeoutMs = REMOTE_ANSWER_TIMEOUT_MS) {
|
|
|
11423
11707
|
return new Promise((resolve) => {
|
|
11424
11708
|
let connected = false;
|
|
11425
11709
|
let settled = false;
|
|
11426
|
-
const finish = (
|
|
11710
|
+
const finish = (answer2) => {
|
|
11427
11711
|
if (settled) return;
|
|
11428
11712
|
settled = true;
|
|
11429
11713
|
clearTimeout(timer);
|
|
11430
11714
|
req.destroy();
|
|
11431
|
-
resolve(
|
|
11715
|
+
resolve(answer2);
|
|
11432
11716
|
};
|
|
11433
11717
|
const req = request({
|
|
11434
11718
|
host: "127.0.0.1",
|
|
@@ -11624,7 +11908,7 @@ function readRemoteSnapshot(value, machine, now, options = {}) {
|
|
|
11624
11908
|
}
|
|
11625
11909
|
|
|
11626
11910
|
// src/collector/remotes/sshProgram.ts
|
|
11627
|
-
import
|
|
11911
|
+
import path24 from "node:path";
|
|
11628
11912
|
var SSH_BIN_ENV = "AGENT_LOOKOUT_SSH_BIN";
|
|
11629
11913
|
var SSH_LOCATIONS = ["/usr/bin/ssh", "/opt/homebrew/bin/ssh", "/usr/local/bin/ssh"];
|
|
11630
11914
|
var SERVER_ALIVE_SECONDS = 15;
|
|
@@ -11646,7 +11930,7 @@ async function findSsh(env, isExecutable, platform = process.platform) {
|
|
|
11646
11930
|
}
|
|
11647
11931
|
return {
|
|
11648
11932
|
found: false,
|
|
11649
|
-
looked: `The ssh command was not found on PATH or in ${SSH_LOCATIONS.map((at) =>
|
|
11933
|
+
looked: `The ssh command was not found on PATH or in ${SSH_LOCATIONS.map((at) => path24.dirname(at)).join(", ")}`
|
|
11650
11934
|
};
|
|
11651
11935
|
}
|
|
11652
11936
|
function sshArguments(forward) {
|
|
@@ -11988,7 +12272,11 @@ function freeLoopbackPort() {
|
|
|
11988
12272
|
});
|
|
11989
12273
|
});
|
|
11990
12274
|
}
|
|
11991
|
-
var spawnSsh = (binary, args, env) => spawn2(binary, [...args], {
|
|
12275
|
+
var spawnSsh = (binary, args, env) => spawn2(binary, [...args], {
|
|
12276
|
+
env: childEnvironment(env),
|
|
12277
|
+
stdio: ["ignore", "ignore", "pipe"],
|
|
12278
|
+
windowsHide: true
|
|
12279
|
+
});
|
|
11992
12280
|
var live = /* @__PURE__ */ new Set();
|
|
11993
12281
|
var endedOnExit = false;
|
|
11994
12282
|
function endOnExit(child) {
|
|
@@ -12242,14 +12530,14 @@ import {
|
|
|
12242
12530
|
writeSync as writeSync2
|
|
12243
12531
|
} from "node:fs";
|
|
12244
12532
|
import os10 from "node:os";
|
|
12245
|
-
import
|
|
12533
|
+
import path25 from "node:path";
|
|
12246
12534
|
var SETTINGS_FILE_ENV = "AGENT_LOOKOUT_SETTINGS_FILE";
|
|
12247
12535
|
var SETTINGS_FILE_MODE = 384;
|
|
12248
12536
|
var SETTINGS_FOLDER_MODE = 448;
|
|
12249
12537
|
var MAX_SETTINGS_BYTES = 64 * 1024;
|
|
12250
12538
|
function readSettingsSetup(env, homeDir = os10.homedir()) {
|
|
12251
12539
|
const named = env[SETTINGS_FILE_ENV]?.trim() || void 0;
|
|
12252
|
-
const file =
|
|
12540
|
+
const file = path25.resolve(named ?? path25.join(homeDir, ".agent-lookout", "settings.json"));
|
|
12253
12541
|
return { file, shown: tildify(file, homeDir) };
|
|
12254
12542
|
}
|
|
12255
12543
|
function codeOf(error) {
|
|
@@ -12294,7 +12582,7 @@ function readSettingsStamp(setup) {
|
|
|
12294
12582
|
}
|
|
12295
12583
|
function readSettingsText(setup) {
|
|
12296
12584
|
const { file, shown } = setup;
|
|
12297
|
-
const folder =
|
|
12585
|
+
const folder = path25.dirname(file);
|
|
12298
12586
|
try {
|
|
12299
12587
|
const folderKind = kindAt(folder);
|
|
12300
12588
|
if (folderKind === null) return { kind: "missing" };
|
|
@@ -12353,7 +12641,7 @@ function readSettingsText(setup) {
|
|
|
12353
12641
|
}
|
|
12354
12642
|
function writeSettingsText(setup, text4) {
|
|
12355
12643
|
const { file, shown } = setup;
|
|
12356
|
-
const folder =
|
|
12644
|
+
const folder = path25.dirname(file);
|
|
12357
12645
|
const notSaved = {
|
|
12358
12646
|
ok: false,
|
|
12359
12647
|
problem: `${shown} could not be written, so the change was not saved.`
|
|
@@ -12374,9 +12662,9 @@ function writeSettingsText(setup, text4) {
|
|
|
12374
12662
|
problem: `${shown} is a link or not an ordinary file, which Agent Lookout does not write through, so the change was not saved.`
|
|
12375
12663
|
};
|
|
12376
12664
|
}
|
|
12377
|
-
temporary =
|
|
12665
|
+
temporary = path25.join(
|
|
12378
12666
|
folder,
|
|
12379
|
-
`.${
|
|
12667
|
+
`.${path25.basename(file)}.${process.pid}.${randomBytes3(6).toString("hex")}.tmp`
|
|
12380
12668
|
);
|
|
12381
12669
|
const fd = openWithoutFollowingNow(
|
|
12382
12670
|
temporary,
|
|
@@ -12655,7 +12943,7 @@ function applyRulesChange(rules, change, newId) {
|
|
|
12655
12943
|
|
|
12656
12944
|
// src/collector/settings/permissionRulesRoute.ts
|
|
12657
12945
|
var MAX_PERMISSION_RULES_BODY_BYTES = 1024;
|
|
12658
|
-
var
|
|
12946
|
+
var failed5 = (status, reason, error) => ({
|
|
12659
12947
|
status,
|
|
12660
12948
|
body: { error, reason }
|
|
12661
12949
|
});
|
|
@@ -12682,14 +12970,14 @@ function createPermissionRulesRoute(options) {
|
|
|
12682
12970
|
try {
|
|
12683
12971
|
value = JSON.parse(body.text);
|
|
12684
12972
|
} catch {
|
|
12685
|
-
return
|
|
12973
|
+
return failed5(400, "invalid", "The body must be JSON: one change to the permission rules.");
|
|
12686
12974
|
}
|
|
12687
12975
|
const asked = rulesChangeIn(value);
|
|
12688
|
-
if (!asked.ok) return
|
|
12976
|
+
if (!asked.ok) return failed5(400, "invalid", asked.problem);
|
|
12689
12977
|
const made = settings.changePermissionRules(
|
|
12690
12978
|
(rules) => applyRulesChange(rules, asked.change, newId)
|
|
12691
12979
|
);
|
|
12692
|
-
if (!made.ok) return
|
|
12980
|
+
if (!made.ok) return failed5(STATUS_OF[made.reason], made.reason, made.problem);
|
|
12693
12981
|
return {
|
|
12694
12982
|
status: 200,
|
|
12695
12983
|
body: { ok: true, permissionRules: made.rules }
|
|
@@ -12699,7 +12987,7 @@ function createPermissionRulesRoute(options) {
|
|
|
12699
12987
|
|
|
12700
12988
|
// src/collector/settings/timeRulesRoute.ts
|
|
12701
12989
|
var MAX_TIME_RULES_BODY_BYTES = 2048;
|
|
12702
|
-
var
|
|
12990
|
+
var failed6 = (status, reason, error) => ({
|
|
12703
12991
|
status,
|
|
12704
12992
|
body: { error, reason }
|
|
12705
12993
|
});
|
|
@@ -12723,12 +13011,12 @@ function createTimeRulesRoute(options) {
|
|
|
12723
13011
|
try {
|
|
12724
13012
|
value = JSON.parse(body.text);
|
|
12725
13013
|
} catch {
|
|
12726
|
-
return
|
|
13014
|
+
return failed6(400, "invalid", "The body must be JSON: the three time rules.");
|
|
12727
13015
|
}
|
|
12728
13016
|
const asked = timeRulesIn(value);
|
|
12729
|
-
if (!asked.ok) return
|
|
13017
|
+
if (!asked.ok) return failed6(400, "invalid", asked.problem);
|
|
12730
13018
|
const saved = settings.changeTimeRules(asked.rules);
|
|
12731
|
-
if (!saved.ok) return
|
|
13019
|
+
if (!saved.ok) return failed6(500, "not-saved", saved.problem);
|
|
12732
13020
|
return {
|
|
12733
13021
|
status: 200,
|
|
12734
13022
|
body: { ok: true, timeRules: asked.rules }
|
|
@@ -12751,8 +13039,8 @@ function parseProcessTable(stdout) {
|
|
|
12751
13039
|
}
|
|
12752
13040
|
var readProcessTableWithPs = async () => {
|
|
12753
13041
|
if (process.platform !== "darwin") return /* @__PURE__ */ new Map();
|
|
12754
|
-
const
|
|
12755
|
-
return
|
|
13042
|
+
const answer2 = await runPs(PS_TABLE_ARGS);
|
|
13043
|
+
return answer2.ok ? parseProcessTable(answer2.stdout) : /* @__PURE__ */ new Map();
|
|
12756
13044
|
};
|
|
12757
13045
|
function parentsIn(table) {
|
|
12758
13046
|
const parents = /* @__PURE__ */ new Map();
|
|
@@ -12896,7 +13184,7 @@ function runTmuxBinary(binary, args, options) {
|
|
|
12896
13184
|
binary,
|
|
12897
13185
|
[...args],
|
|
12898
13186
|
{
|
|
12899
|
-
env: options.env,
|
|
13187
|
+
env: childEnvironment(options.env),
|
|
12900
13188
|
timeout: options.timeoutMs ?? TMUX_TIMEOUT_MS,
|
|
12901
13189
|
maxBuffer: MAX_OUTPUT_BYTES5,
|
|
12902
13190
|
encoding: "utf8",
|
|
@@ -13304,6 +13592,10 @@ function createCollector(options) {
|
|
|
13304
13592
|
}),
|
|
13305
13593
|
pullRequests: () => pullRequests?.status() ?? pullRequestsOff,
|
|
13306
13594
|
jump: createJumpRoute({ poller, panes, run: tmux, tabs, osascript, now }),
|
|
13595
|
+
// Only this computer's own adapters read what a session last said. A
|
|
13596
|
+
// session on another machine is answered as not read, and nothing is
|
|
13597
|
+
// asked of that machine.
|
|
13598
|
+
lastMessage: createLastMessageRoute({ env, poller, adapters: localAdapters }),
|
|
13307
13599
|
stop: stopRoutes && createStopRoute(stopRoutes),
|
|
13308
13600
|
cleanUp: stopRoutes && createCleanUpRoute(stopRoutes),
|
|
13309
13601
|
settings: () => ({
|
|
@@ -13433,7 +13725,7 @@ async function runStandalone(options) {
|
|
|
13433
13725
|
} catch (error) {
|
|
13434
13726
|
return stopWith(error.message);
|
|
13435
13727
|
}
|
|
13436
|
-
if (!existsSync(
|
|
13728
|
+
if (!existsSync(path26.join(distDir, "index.html"))) {
|
|
13437
13729
|
return stopWith(
|
|
13438
13730
|
"The dashboard has not been built yet. Run `npm run build`, then start it again."
|
|
13439
13731
|
);
|
|
@@ -13476,12 +13768,12 @@ async function runStandalone(options) {
|
|
|
13476
13768
|
|
|
13477
13769
|
// src/cli/start/openBrowser.ts
|
|
13478
13770
|
import { spawn as spawn3 } from "node:child_process";
|
|
13479
|
-
import
|
|
13771
|
+
import path27 from "node:path";
|
|
13480
13772
|
var OPEN_WAIT_MS = 1e4;
|
|
13481
13773
|
var WINDOWS_FOLDER = "C:\\Windows";
|
|
13482
13774
|
function windowsFolder(env) {
|
|
13483
13775
|
const named = env.SystemRoot?.trim() || env.SYSTEMROOT?.trim();
|
|
13484
|
-
return named &&
|
|
13776
|
+
return named && path27.win32.isAbsolute(named) ? named : WINDOWS_FOLDER;
|
|
13485
13777
|
}
|
|
13486
13778
|
function openCommand(platform, url, env = process.env) {
|
|
13487
13779
|
const address = loopbackAddress(url);
|
|
@@ -13490,7 +13782,7 @@ function openCommand(platform, url, env = process.env) {
|
|
|
13490
13782
|
if (platform === "linux") return { file: "xdg-open", args: [address] };
|
|
13491
13783
|
if (platform === "win32") {
|
|
13492
13784
|
return {
|
|
13493
|
-
file:
|
|
13785
|
+
file: path27.win32.join(windowsFolder(env), "System32", "rundll32.exe"),
|
|
13494
13786
|
args: ["url.dll,FileProtocolHandler", address]
|
|
13495
13787
|
};
|
|
13496
13788
|
}
|
|
@@ -13504,7 +13796,12 @@ function runProgram2(file, args, waitMs = OPEN_WAIT_MS) {
|
|
|
13504
13796
|
resolve(opened);
|
|
13505
13797
|
};
|
|
13506
13798
|
try {
|
|
13507
|
-
const child = spawn3(file, [...args], {
|
|
13799
|
+
const child = spawn3(file, [...args], {
|
|
13800
|
+
env: childEnvironment(process.env),
|
|
13801
|
+
stdio: "ignore",
|
|
13802
|
+
detached: true,
|
|
13803
|
+
windowsHide: true
|
|
13804
|
+
});
|
|
13508
13805
|
child.once("error", () => settle(false));
|
|
13509
13806
|
child.once("exit", (code) => settle(code === 0));
|
|
13510
13807
|
child.unref();
|