sessionpipe-conformance 0.1.0
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/LICENSE +202 -0
- package/NOTICE +41 -0
- package/README.md +5 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +1775 -0
- package/dist/cli.js.map +7 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1753 -0
- package/dist/index.js.map +7 -0
- package/package.json +54 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1753 @@
|
|
|
1
|
+
// sessionpipe-conformance 0.1.0 · Apache-2.0 · https://sessionpipe.org
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import { readFileSync as readFileSync6 } from "node:fs";
|
|
5
|
+
|
|
6
|
+
// ../core/src/adapters/antigravity.ts
|
|
7
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readdirSync as readdirSync2, readFileSync as readFileSync2, realpathSync, statSync as statSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
8
|
+
import path3 from "node:path";
|
|
9
|
+
|
|
10
|
+
// ../core/src/paths.ts
|
|
11
|
+
import os from "node:os";
|
|
12
|
+
var HOME = os.homedir();
|
|
13
|
+
|
|
14
|
+
// ../core/src/readers/files.ts
|
|
15
|
+
import { closeSync, fstatSync, openSync, readdirSync, readSync, statSync } from "node:fs";
|
|
16
|
+
import path from "node:path";
|
|
17
|
+
function ends(file, headBytes, tailBytes) {
|
|
18
|
+
let fd;
|
|
19
|
+
try {
|
|
20
|
+
fd = openSync(file, "r");
|
|
21
|
+
const size = fstatSync(fd).size;
|
|
22
|
+
const read = (pos, len) => {
|
|
23
|
+
const b = Buffer.alloc(len);
|
|
24
|
+
const n = readSync(fd, b, 0, len, pos);
|
|
25
|
+
return b.subarray(0, n).toString("utf8");
|
|
26
|
+
};
|
|
27
|
+
const head = read(0, Math.min(size, headBytes));
|
|
28
|
+
const tail = size > headBytes ? read(Math.max(0, size - tailBytes), Math.min(size, tailBytes)) : head;
|
|
29
|
+
return { head: head.split("\n").slice(0, -1), tail: tail.split("\n").slice(size > headBytes ? 1 : 0) };
|
|
30
|
+
} catch {
|
|
31
|
+
return { head: [], tail: [] };
|
|
32
|
+
} finally {
|
|
33
|
+
if (fd !== void 0)
|
|
34
|
+
try {
|
|
35
|
+
closeSync(fd);
|
|
36
|
+
} catch {
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
var parseLines = (lines) => lines.map((l) => {
|
|
41
|
+
try {
|
|
42
|
+
return JSON.parse(l);
|
|
43
|
+
} catch {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
}).filter((x) => !!x);
|
|
47
|
+
function scanLines(file, visit, cap = 16 * 1024 * 1024) {
|
|
48
|
+
let fd;
|
|
49
|
+
try {
|
|
50
|
+
fd = openSync(file, "r");
|
|
51
|
+
const buf = Buffer.alloc(1024 * 1024);
|
|
52
|
+
let pos = 0;
|
|
53
|
+
let rest = Buffer.alloc(0);
|
|
54
|
+
while (pos < cap) {
|
|
55
|
+
const n = readSync(fd, buf, 0, buf.length, pos);
|
|
56
|
+
if (!n) break;
|
|
57
|
+
pos += n;
|
|
58
|
+
let chunk = Buffer.concat([rest, buf.subarray(0, n)]);
|
|
59
|
+
let nl = chunk.indexOf(10);
|
|
60
|
+
while (nl >= 0) {
|
|
61
|
+
const r = visit(chunk.subarray(0, nl).toString("utf8"));
|
|
62
|
+
if (r !== null) return r;
|
|
63
|
+
chunk = chunk.subarray(nl + 1);
|
|
64
|
+
nl = chunk.indexOf(10);
|
|
65
|
+
}
|
|
66
|
+
rest = chunk;
|
|
67
|
+
}
|
|
68
|
+
} catch {
|
|
69
|
+
} finally {
|
|
70
|
+
if (fd !== void 0)
|
|
71
|
+
try {
|
|
72
|
+
closeSync(fd);
|
|
73
|
+
} catch {
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
function recentFiles(root, depth, sinceMs, match) {
|
|
79
|
+
const out = [];
|
|
80
|
+
const walk = (dir, d) => {
|
|
81
|
+
let names = [];
|
|
82
|
+
try {
|
|
83
|
+
names = readdirSync(dir, { withFileTypes: true });
|
|
84
|
+
} catch {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
for (const e of names) {
|
|
88
|
+
const p = path.join(dir, e.name);
|
|
89
|
+
if (e.isDirectory()) {
|
|
90
|
+
if (d < depth) walk(p, d + 1);
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
if (!match(e.name)) continue;
|
|
94
|
+
try {
|
|
95
|
+
if (statSync(p).mtimeMs >= sinceMs) out.push(p);
|
|
96
|
+
} catch {
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
walk(root, 0);
|
|
101
|
+
return out;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ../core/src/readers/transcripts.ts
|
|
105
|
+
function pairTurns(msgs, endLine) {
|
|
106
|
+
const turns = [];
|
|
107
|
+
let cur = null;
|
|
108
|
+
for (const m of msgs) {
|
|
109
|
+
if (m.role === "user") {
|
|
110
|
+
if (cur && !cur.assistant) {
|
|
111
|
+
cur.user += `
|
|
112
|
+
|
|
113
|
+
${m.text}`;
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
if (cur) turns.push(cur);
|
|
117
|
+
cur = { user: m.text, assistant: "", at: m.at, startLine: m.line, endLine: m.line };
|
|
118
|
+
} else {
|
|
119
|
+
if (!cur) cur = { user: "", assistant: "", at: m.at, startLine: m.line, endLine: m.line };
|
|
120
|
+
cur.assistant += (cur.assistant ? "\n\n" : "") + m.text;
|
|
121
|
+
cur.endLine = m.line;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
if (cur?.assistant) turns.push(cur);
|
|
125
|
+
const line = cur && !cur.assistant ? cur.startLine : endLine;
|
|
126
|
+
return { turns: turns.map((t) => ({ user: t.user, assistant: t.assistant, at: t.at, endLine: t.endLine })), line };
|
|
127
|
+
}
|
|
128
|
+
var isInjected = (text) => /^<[a-z_-]+>/i.test(text) && /<\/[a-z_-]+>\s*$/i.test(text) || /^<!--\s/.test(text) || /^<command-/.test(text);
|
|
129
|
+
var endOf = (lines) => lines[lines.length - 1] === "" ? lines.length - 1 : lines.length;
|
|
130
|
+
var parse = (raw) => {
|
|
131
|
+
try {
|
|
132
|
+
return JSON.parse(raw);
|
|
133
|
+
} catch {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
var textParts = (c, kinds) => {
|
|
138
|
+
if (typeof c === "string") return c;
|
|
139
|
+
if (!Array.isArray(c)) return "";
|
|
140
|
+
return c.filter(
|
|
141
|
+
(b) => b && typeof b === "object" && kinds.includes(b.type ?? "") && typeof b.text === "string"
|
|
142
|
+
).map((b) => b.text).join("\n");
|
|
143
|
+
};
|
|
144
|
+
function readClaude(lines, from) {
|
|
145
|
+
const msgs = [];
|
|
146
|
+
for (let i = from; i < lines.length; i++) {
|
|
147
|
+
const j = lines[i] ? parse(lines[i]) : null;
|
|
148
|
+
if (!j || j.isSidechain) continue;
|
|
149
|
+
const origin = j.origin;
|
|
150
|
+
if (j.type === "user" && (j.isMeta || origin?.kind && origin.kind !== "human")) continue;
|
|
151
|
+
const at = Date.parse(String(j.timestamp ?? "")) || Date.now();
|
|
152
|
+
const c = j.message?.content;
|
|
153
|
+
if (j.type === "user") {
|
|
154
|
+
const text = textParts(c, ["text"]).trim();
|
|
155
|
+
if (!text || isInjected(text)) continue;
|
|
156
|
+
msgs.push({ role: "user", text, at, line: i });
|
|
157
|
+
} else if (j.type === "assistant" && Array.isArray(c)) {
|
|
158
|
+
const text = textParts(c, ["text"]).trim();
|
|
159
|
+
if (text) msgs.push({ role: "assistant", text, at, line: i });
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return pairTurns(msgs, endOf(lines));
|
|
163
|
+
}
|
|
164
|
+
function readCodex(lines, from) {
|
|
165
|
+
const msgs = [];
|
|
166
|
+
for (let i = from; i < lines.length; i++) {
|
|
167
|
+
const j = lines[i] ? parse(lines[i]) : null;
|
|
168
|
+
if (!j || j.type !== "response_item") continue;
|
|
169
|
+
const p = j.payload;
|
|
170
|
+
if (!p || p.type !== "message") continue;
|
|
171
|
+
const at = Date.parse(String(j.timestamp ?? "")) || Date.now();
|
|
172
|
+
const text = textParts(p.content, ["input_text", "output_text"]).trim();
|
|
173
|
+
if (!text) continue;
|
|
174
|
+
if (p.role === "user") {
|
|
175
|
+
if (/^<[a-z_-]+>/i.test(text)) continue;
|
|
176
|
+
msgs.push({ role: "user", text, at, line: i });
|
|
177
|
+
} else if (p.role === "assistant") msgs.push({ role: "assistant", text, at, line: i });
|
|
178
|
+
}
|
|
179
|
+
return pairTurns(msgs, endOf(lines));
|
|
180
|
+
}
|
|
181
|
+
function readGemini(lines, from) {
|
|
182
|
+
const msgs = [];
|
|
183
|
+
for (let i = from; i < lines.length; i++) {
|
|
184
|
+
const j = lines[i] ? parse(lines[i]) : null;
|
|
185
|
+
if (!j) continue;
|
|
186
|
+
const role = j.type === "user" ? "user" : j.type === "gemini" || j.type === "model" ? "assistant" : null;
|
|
187
|
+
if (!role) continue;
|
|
188
|
+
const raw = j.content ?? j.message ?? j.text;
|
|
189
|
+
const text = (typeof raw === "string" ? raw : textParts(raw, ["text"])).trim();
|
|
190
|
+
if (!text) continue;
|
|
191
|
+
const at = Date.parse(String(j.timestamp ?? "")) || Date.now();
|
|
192
|
+
msgs.push({ role, text, at, line: i });
|
|
193
|
+
}
|
|
194
|
+
return pairTurns(msgs, endOf(lines));
|
|
195
|
+
}
|
|
196
|
+
function readAntigravity(lines, from) {
|
|
197
|
+
const msgs = [];
|
|
198
|
+
for (let i = from; i < lines.length; i++) {
|
|
199
|
+
const j = lines[i] ? parse(lines[i]) : null;
|
|
200
|
+
if (!j || typeof j.content !== "string") continue;
|
|
201
|
+
const at = Date.parse(String(j.created_at ?? j.timestamp ?? "")) || Date.now();
|
|
202
|
+
if (j.type === "USER_INPUT") {
|
|
203
|
+
const m = /<USER_REQUEST>([\s\S]*?)(?:<\/USER_REQUEST>|$)/.exec(j.content);
|
|
204
|
+
const text = (m ? m[1] ?? "" : j.content).trim();
|
|
205
|
+
if (text) msgs.push({ role: "user", text, at, line: i });
|
|
206
|
+
} else if (j.type === "PLANNER_RESPONSE") {
|
|
207
|
+
const text = j.content.trim();
|
|
208
|
+
if (text) msgs.push({ role: "assistant", text, at, line: i });
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return pairTurns(msgs, endOf(lines));
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// ../core/src/adapters/claude-shaped.ts
|
|
215
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
216
|
+
import path2 from "node:path";
|
|
217
|
+
var OURS = /sessionpipe[^"\n]*[\\/]hook\.js"?\s+[a-z][a-z-]*\s+[A-Za-z]+\s*$/;
|
|
218
|
+
var isOurs = (h) => Array.isArray(h?.hooks) && h.hooks.some((x) => typeof x.command === "string" && OURS.test(x.command));
|
|
219
|
+
function readJson(file) {
|
|
220
|
+
try {
|
|
221
|
+
return JSON.parse(readFileSync(file, "utf8"));
|
|
222
|
+
} catch {
|
|
223
|
+
return {};
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
function entryFor(event, harness, cmd, o) {
|
|
227
|
+
const matcher = o.matcherFor(event);
|
|
228
|
+
return {
|
|
229
|
+
...matcher !== void 0 ? { matcher } : {},
|
|
230
|
+
hooks: [{ type: "command", command: cmd([harness, event]).command, timeout: o.timeout }]
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
function installClaudeShaped(file, harness, events, cmd, o) {
|
|
234
|
+
const settings2 = readJson(file);
|
|
235
|
+
const hooks = settings2.hooks && typeof settings2.hooks === "object" ? settings2.hooks : {};
|
|
236
|
+
let changed = false;
|
|
237
|
+
for (const ev of events) {
|
|
238
|
+
const list = Array.isArray(hooks[ev]) ? hooks[ev] : [];
|
|
239
|
+
const entry2 = entryFor(ev, harness, cmd, o);
|
|
240
|
+
const mine = list.filter((h) => isOurs(h));
|
|
241
|
+
if (mine.length === 1 && JSON.stringify(mine[0]) === JSON.stringify(entry2)) continue;
|
|
242
|
+
hooks[ev] = list.filter((h) => !isOurs(h)).concat([entry2]);
|
|
243
|
+
changed = true;
|
|
244
|
+
}
|
|
245
|
+
if (o.top) {
|
|
246
|
+
for (const [k, v] of Object.entries(o.top))
|
|
247
|
+
if (settings2[k] !== v) {
|
|
248
|
+
settings2[k] = v;
|
|
249
|
+
changed = true;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
if (changed) {
|
|
253
|
+
settings2.hooks = hooks;
|
|
254
|
+
mkdirSync(path2.dirname(file), { recursive: true });
|
|
255
|
+
writeFileSync(file, `${JSON.stringify(settings2, null, 2)}
|
|
256
|
+
`);
|
|
257
|
+
}
|
|
258
|
+
return { file, changed };
|
|
259
|
+
}
|
|
260
|
+
function uninstallClaudeShaped(file, events) {
|
|
261
|
+
if (!existsSync(file)) return { file, changed: false };
|
|
262
|
+
const raw = readFileSync(file, "utf8");
|
|
263
|
+
const settings2 = readJson(file);
|
|
264
|
+
const hooks = settings2.hooks;
|
|
265
|
+
if (!hooks || typeof hooks !== "object") return { file, changed: false };
|
|
266
|
+
let changed = false;
|
|
267
|
+
for (const ev of events) {
|
|
268
|
+
const list = Array.isArray(hooks[ev]) ? hooks[ev] : [];
|
|
269
|
+
const kept = list.filter((h) => !isOurs(h));
|
|
270
|
+
if (kept.length !== list.length) {
|
|
271
|
+
changed = true;
|
|
272
|
+
if (kept.length) hooks[ev] = kept;
|
|
273
|
+
else delete hooks[ev];
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
if (!changed) return { file, changed: false };
|
|
277
|
+
if (!Object.keys(hooks).length) delete settings2.hooks;
|
|
278
|
+
const next = `${JSON.stringify(settings2, null, 2)}
|
|
279
|
+
`;
|
|
280
|
+
if (next !== raw) writeFileSync(file, next);
|
|
281
|
+
return { file, changed: true };
|
|
282
|
+
}
|
|
283
|
+
function installedClaudeShaped(file, harness, events, cmd, o) {
|
|
284
|
+
const hooks = readJson(file).hooks ?? {};
|
|
285
|
+
let present = 0;
|
|
286
|
+
let current = 0;
|
|
287
|
+
for (const ev of events) {
|
|
288
|
+
const list = Array.isArray(hooks[ev]) ? hooks[ev] : [];
|
|
289
|
+
const mine = list.filter((h) => isOurs(h));
|
|
290
|
+
if (mine.length) present++;
|
|
291
|
+
if (mine.length === 1 && JSON.stringify(mine[0]) === JSON.stringify(entryFor(ev, harness, cmd, o))) current++;
|
|
292
|
+
}
|
|
293
|
+
if (!present) return "missing";
|
|
294
|
+
return current === events.length ? "current" : "stale";
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// ../core/src/adapters/antigravity.ts
|
|
298
|
+
var NAME = "antigravity";
|
|
299
|
+
var HOOK_NAME = "sessionpipe";
|
|
300
|
+
var AG_EVENTS = ["PreInvocation", "PostInvocation", "PreToolUse", "PostToolUse", "Stop"];
|
|
301
|
+
var gemini = (env) => env.GEMINI_CLI_HOME || path3.join(HOME, ".gemini");
|
|
302
|
+
var hooksFile = (env) => path3.join(gemini(env), "config", "hooks.json");
|
|
303
|
+
var projectsDir = (env) => path3.join(gemini(env), "config", "projects");
|
|
304
|
+
var dataDirs = (env) => ["antigravity", "antigravity-cli", "antigravity-ide"].map((d) => path3.join(gemini(env), d));
|
|
305
|
+
var ID_RE = /^[A-Za-z0-9_-]{1,128}$/;
|
|
306
|
+
var validTool = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
|
|
307
|
+
function entry(cmd) {
|
|
308
|
+
const handler = (e) => ({ type: "command", command: cmd([NAME, e]).command, timeout: 5 });
|
|
309
|
+
const out = { enabled: true };
|
|
310
|
+
for (const e of AG_EVENTS) out[e] = /Tool/.test(e) ? [{ matcher: "*", hooks: [handler(e)] }] : [handler(e)];
|
|
311
|
+
return out;
|
|
312
|
+
}
|
|
313
|
+
var antigravity = {
|
|
314
|
+
name: NAME,
|
|
315
|
+
events: AG_EVENTS,
|
|
316
|
+
detect: (env = process.env) => existsSync2(path3.join(gemini(env), "config")) || dataDirs(env).some((d) => existsSync2(d)),
|
|
317
|
+
configFiles: (env = process.env) => [hooksFile(env)],
|
|
318
|
+
install(cmd, env = process.env) {
|
|
319
|
+
const file = hooksFile(env);
|
|
320
|
+
const hooks = readJson(file);
|
|
321
|
+
const e = entry(cmd);
|
|
322
|
+
if (JSON.stringify(hooks[HOOK_NAME]) === JSON.stringify(e)) return [{ file, changed: false }];
|
|
323
|
+
hooks[HOOK_NAME] = e;
|
|
324
|
+
mkdirSync2(path3.dirname(file), { recursive: true });
|
|
325
|
+
writeFileSync2(file, `${JSON.stringify(hooks, null, 2)}
|
|
326
|
+
`);
|
|
327
|
+
return [
|
|
328
|
+
{ file, changed: true, note: "hooks.json is read when a conversation starts; open ones report after a restart" }
|
|
329
|
+
];
|
|
330
|
+
},
|
|
331
|
+
uninstall(env = process.env) {
|
|
332
|
+
const file = hooksFile(env);
|
|
333
|
+
if (!existsSync2(file)) return [{ file, changed: false }];
|
|
334
|
+
const hooks = readJson(file);
|
|
335
|
+
if (!(HOOK_NAME in hooks)) return [{ file, changed: false }];
|
|
336
|
+
delete hooks[HOOK_NAME];
|
|
337
|
+
writeFileSync2(file, `${JSON.stringify(hooks, null, 2)}
|
|
338
|
+
`);
|
|
339
|
+
return [{ file, changed: true }];
|
|
340
|
+
},
|
|
341
|
+
installed(cmd, env = process.env) {
|
|
342
|
+
const file = hooksFile(env);
|
|
343
|
+
const have = readJson(file)[HOOK_NAME];
|
|
344
|
+
return [
|
|
345
|
+
{ file, state: !have ? "missing" : JSON.stringify(have) === JSON.stringify(entry(cmd)) ? "current" : "stale" }
|
|
346
|
+
];
|
|
347
|
+
},
|
|
348
|
+
fromHook(input) {
|
|
349
|
+
const event = input.argv[1] ?? "";
|
|
350
|
+
let s = {};
|
|
351
|
+
try {
|
|
352
|
+
s = input.stdin ? JSON.parse(input.stdin) : {};
|
|
353
|
+
} catch {
|
|
354
|
+
}
|
|
355
|
+
const id = s.conversationId ?? s.session_id ?? s.sessionId;
|
|
356
|
+
if (!id) return { session: { id: "" }, events: [], stdout: "{}\n" };
|
|
357
|
+
const workspaces = agWorkspaces(s.workspacePaths);
|
|
358
|
+
const session = { id: String(id), cwd: workspaces[0] ?? input.cwd };
|
|
359
|
+
if (typeof s.modelName === "string" && s.modelName !== "auto") session.model = s.modelName;
|
|
360
|
+
const toolCall = s.toolCall;
|
|
361
|
+
const tool = validTool(toolCall?.name) ? toolCall?.name : validTool(s.toolName) ? s.toolName : void 0;
|
|
362
|
+
const ev = (type, data) => ({ type, data: prune(data), harnessEvent: event });
|
|
363
|
+
const events = [];
|
|
364
|
+
switch (event) {
|
|
365
|
+
case "PreInvocation":
|
|
366
|
+
if (Number(s.invocationNum) === 0 || s.invocationNum === void 0) {
|
|
367
|
+
events.push(ev("session.started", { source: "unknown" }));
|
|
368
|
+
events.push(ev("turn.started", {}));
|
|
369
|
+
} else events.push(ev("session.heartbeat", {}));
|
|
370
|
+
break;
|
|
371
|
+
case "PostInvocation":
|
|
372
|
+
break;
|
|
373
|
+
case "PreToolUse":
|
|
374
|
+
if (tool) events.push(ev("tool.started", { tool, input: toolCall?.args }));
|
|
375
|
+
break;
|
|
376
|
+
case "PostToolUse":
|
|
377
|
+
if (tool)
|
|
378
|
+
events.push(
|
|
379
|
+
ev("tool.ended", {
|
|
380
|
+
tool,
|
|
381
|
+
ok: !s.error,
|
|
382
|
+
error: typeof s.error === "string" ? s.error.slice(0, 2e3) : void 0,
|
|
383
|
+
input: toolCall?.args
|
|
384
|
+
})
|
|
385
|
+
);
|
|
386
|
+
break;
|
|
387
|
+
case "Stop":
|
|
388
|
+
events.push(ev("turn.ended", { reason: s.error ? "error" : "stop" }));
|
|
389
|
+
break;
|
|
390
|
+
default:
|
|
391
|
+
return { session, events: [], stdout: "{}\n" };
|
|
392
|
+
}
|
|
393
|
+
const r = { session, events, stdout: "{}\n", hints: { workspaces } };
|
|
394
|
+
if (typeof s.transcriptPath === "string") r.transcript = s.transcriptPath;
|
|
395
|
+
return r;
|
|
396
|
+
},
|
|
397
|
+
readTranscript(file, fromLine) {
|
|
398
|
+
let text = "";
|
|
399
|
+
try {
|
|
400
|
+
text = readFileSync2(file, "utf8");
|
|
401
|
+
} catch {
|
|
402
|
+
return { turns: [], line: fromLine };
|
|
403
|
+
}
|
|
404
|
+
return readAntigravity(text.split("\n"), fromLine);
|
|
405
|
+
},
|
|
406
|
+
facts(session, transcript, hints, state) {
|
|
407
|
+
if (!transcript) return {};
|
|
408
|
+
return antigravityFacts(transcript, {
|
|
409
|
+
conversation: session.id,
|
|
410
|
+
workspaces: hints.workspaces ?? (session.cwd ? [session.cwd] : []),
|
|
411
|
+
state,
|
|
412
|
+
env: process.env
|
|
413
|
+
});
|
|
414
|
+
},
|
|
415
|
+
backfill(sinceMs, env = process.env) {
|
|
416
|
+
const rows = [];
|
|
417
|
+
const noState = { get: () => ({}), save: () => {
|
|
418
|
+
} };
|
|
419
|
+
for (const data of dataDirs(env)) {
|
|
420
|
+
let ids = [];
|
|
421
|
+
try {
|
|
422
|
+
ids = readdirSync2(path3.join(data, "brain")).filter((n) => /^[0-9a-f-]{36}$/i.test(n));
|
|
423
|
+
} catch {
|
|
424
|
+
}
|
|
425
|
+
for (const id of ids) {
|
|
426
|
+
const f = path3.join(data, "brain", id, ".system_generated", "logs", "transcript.jsonl");
|
|
427
|
+
try {
|
|
428
|
+
if (statSync2(f).mtimeMs < sinceMs) continue;
|
|
429
|
+
} catch {
|
|
430
|
+
continue;
|
|
431
|
+
}
|
|
432
|
+
const facts = antigravityFacts(f, { conversation: id, workspaces: [], state: noState, env });
|
|
433
|
+
if (!facts.started_at || !facts.last_at) continue;
|
|
434
|
+
const { started_at, last_at, first_ask: _fa, harness_version: _hv, ...rest } = facts;
|
|
435
|
+
rows.push({ session: { id, ...rest }, started_at, last_at });
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
return rows;
|
|
439
|
+
}
|
|
440
|
+
};
|
|
441
|
+
function prune(o) {
|
|
442
|
+
for (const k of Object.keys(o)) if (o[k] === void 0) delete o[k];
|
|
443
|
+
return o;
|
|
444
|
+
}
|
|
445
|
+
function agPath(p) {
|
|
446
|
+
if (typeof p !== "string" || !p) return null;
|
|
447
|
+
let s = p;
|
|
448
|
+
if (/^file:/i.test(s)) {
|
|
449
|
+
try {
|
|
450
|
+
s = decodeURIComponent(new URL(s).pathname);
|
|
451
|
+
} catch {
|
|
452
|
+
return null;
|
|
453
|
+
}
|
|
454
|
+
if (process.platform === "win32" && /^\/[A-Za-z]:/.test(s)) s = s.slice(1);
|
|
455
|
+
} else if (/^[a-z][a-z0-9+.-]*:\/\//i.test(s)) return null;
|
|
456
|
+
if (!path3.isAbsolute(s)) return null;
|
|
457
|
+
return path3.resolve(s);
|
|
458
|
+
}
|
|
459
|
+
var agWorkspaces = (paths) => (Array.isArray(paths) ? paths : []).map(agPath).filter((p) => !!p).filter((p, i, a) => a.indexOf(p) === i);
|
|
460
|
+
function agVariants(p) {
|
|
461
|
+
const out = [p];
|
|
462
|
+
try {
|
|
463
|
+
const r = realpathSync.native(p);
|
|
464
|
+
if (r !== p) out.push(r);
|
|
465
|
+
} catch {
|
|
466
|
+
}
|
|
467
|
+
return out;
|
|
468
|
+
}
|
|
469
|
+
function agProjects(dir) {
|
|
470
|
+
const out = [];
|
|
471
|
+
let names = [];
|
|
472
|
+
try {
|
|
473
|
+
names = readdirSync2(dir).filter((n) => n.endsWith(".json"));
|
|
474
|
+
} catch {
|
|
475
|
+
return out;
|
|
476
|
+
}
|
|
477
|
+
for (const n of names) {
|
|
478
|
+
const id = n.slice(0, -5);
|
|
479
|
+
if (!ID_RE.test(id)) continue;
|
|
480
|
+
let j;
|
|
481
|
+
try {
|
|
482
|
+
j = JSON.parse(readFileSync2(path3.join(dir, n), "utf8"));
|
|
483
|
+
} catch {
|
|
484
|
+
continue;
|
|
485
|
+
}
|
|
486
|
+
const folders = [];
|
|
487
|
+
for (const r of j?.projectResources?.resources ?? []) {
|
|
488
|
+
const f = agPath(r?.folderUri);
|
|
489
|
+
if (f) folders.push(...agVariants(f));
|
|
490
|
+
}
|
|
491
|
+
if (folders.length) out.push({ id, folders });
|
|
492
|
+
}
|
|
493
|
+
return out;
|
|
494
|
+
}
|
|
495
|
+
function agMatchProject(projects, workspaces) {
|
|
496
|
+
const ws = [];
|
|
497
|
+
for (const w of workspaces) {
|
|
498
|
+
const p = agPath(w);
|
|
499
|
+
if (p) ws.push(...agVariants(p));
|
|
500
|
+
}
|
|
501
|
+
let best = null;
|
|
502
|
+
let bestLen = -1;
|
|
503
|
+
for (const pr of projects)
|
|
504
|
+
for (const f of pr.folders) {
|
|
505
|
+
const within = ws.some((w) => w === f || w.startsWith(f.endsWith(path3.sep) ? f : f + path3.sep));
|
|
506
|
+
if (within && f.length > bestLen) {
|
|
507
|
+
best = pr.id;
|
|
508
|
+
bestLen = f.length;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
return best;
|
|
512
|
+
}
|
|
513
|
+
function antigravityRemoteUrl(transcript, opts) {
|
|
514
|
+
const i = transcript.indexOf(`${path3.sep}brain${path3.sep}`);
|
|
515
|
+
const dirs = (i > 0 ? [transcript.slice(0, i)] : []).concat(dataDirs(opts.env));
|
|
516
|
+
for (const d of dirs) {
|
|
517
|
+
try {
|
|
518
|
+
const m = /installation_uuid:\s*"([0-9a-f-]{36})"/i.exec(
|
|
519
|
+
readFileSync2(path3.join(d, "antigravity_state.pbtxt"), "utf8")
|
|
520
|
+
);
|
|
521
|
+
if (!m) continue;
|
|
522
|
+
const base = `https://antigravity.google.com/r/${m[1]}-v2`;
|
|
523
|
+
const conv = opts.conversation ?? "";
|
|
524
|
+
if (!ID_RE.test(conv) || !opts.workspaces?.length) return base;
|
|
525
|
+
const project = agMatchProject(agProjects(opts.projectsDir ?? projectsDir(opts.env)), opts.workspaces);
|
|
526
|
+
return project ? `${base}?p=${encodeURIComponent(`c/${conv}?section=${project}`)}` : base;
|
|
527
|
+
} catch {
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
return null;
|
|
531
|
+
}
|
|
532
|
+
function antigravityFacts(file, opts) {
|
|
533
|
+
const { head, tail } = ends(file, 128 * 1024, 64 * 1024);
|
|
534
|
+
const H = parseLines(head);
|
|
535
|
+
const T = parseLines(tail);
|
|
536
|
+
const out = {};
|
|
537
|
+
const url = antigravityRemoteUrl(file, opts);
|
|
538
|
+
if (url) out.url = url;
|
|
539
|
+
for (const j of H) {
|
|
540
|
+
if (j.type !== "USER_INPUT" || typeof j.content !== "string") continue;
|
|
541
|
+
const m = /<USER_REQUEST>([\s\S]*?)(?:<\/USER_REQUEST>|$)/.exec(j.content);
|
|
542
|
+
const text = (m ? m[1] ?? "" : j.content).trim();
|
|
543
|
+
const line = (text.split("\n").find((l) => l.trim()) ?? "").trim();
|
|
544
|
+
if (!line || /^<[a-z_-]+>/i.test(line)) continue;
|
|
545
|
+
out.title = line.length > 80 ? `${line.slice(0, 80).replace(/\s+\S*$/, "")}\u2026` : line;
|
|
546
|
+
out.title_source = "first-ask";
|
|
547
|
+
break;
|
|
548
|
+
}
|
|
549
|
+
const times = H.concat(T).map((j) => Date.parse(String(j.created_at ?? j.timestamp ?? ""))).filter((n) => n > 0);
|
|
550
|
+
if (times.length) {
|
|
551
|
+
out.started_at = Math.min(...times);
|
|
552
|
+
out.last_at = Math.max(...times);
|
|
553
|
+
}
|
|
554
|
+
return out;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
// ../core/src/adapters/claude-code.ts
|
|
558
|
+
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
559
|
+
import { createHash } from "node:crypto";
|
|
560
|
+
import { existsSync as existsSync3, readdirSync as readdirSync3, readFileSync as readFileSync3, statSync as statSync3 } from "node:fs";
|
|
561
|
+
import path4 from "node:path";
|
|
562
|
+
|
|
563
|
+
// ../core/src/readers/git.ts
|
|
564
|
+
import { execFileSync } from "node:child_process";
|
|
565
|
+
var cache = /* @__PURE__ */ new Map();
|
|
566
|
+
function gitFacts(cwd) {
|
|
567
|
+
if (!cwd) return {};
|
|
568
|
+
const hit = cache.get(cwd);
|
|
569
|
+
if (hit) return hit;
|
|
570
|
+
const run = (args) => {
|
|
571
|
+
try {
|
|
572
|
+
return execFileSync("git", ["-C", cwd, ...args], {
|
|
573
|
+
encoding: "utf8",
|
|
574
|
+
timeout: 1500,
|
|
575
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
576
|
+
}).trim();
|
|
577
|
+
} catch {
|
|
578
|
+
return "";
|
|
579
|
+
}
|
|
580
|
+
};
|
|
581
|
+
const out = {};
|
|
582
|
+
const repo = run(["remote", "get-url", "origin"]);
|
|
583
|
+
if (repo) out.repo = stripCredentials(repo);
|
|
584
|
+
const branch = run(["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
585
|
+
if (branch && branch !== "HEAD") out.branch = branch;
|
|
586
|
+
cache.set(cwd, out);
|
|
587
|
+
return out;
|
|
588
|
+
}
|
|
589
|
+
function stripCredentials(url) {
|
|
590
|
+
let u = url.replace(/\/\/[^/@]*@/, "//");
|
|
591
|
+
const ssh = /^(?:ssh:\/\/)?git@([^:/]+)[:/](.+?)(?:\.git)?$/.exec(u);
|
|
592
|
+
if (ssh) u = `${ssh[1]}/${ssh[2]}`;
|
|
593
|
+
return u.replace(/^https?:\/\//, "").replace(/\.git$/, "");
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
// ../core/src/adapters/claude-code.ts
|
|
597
|
+
var NAME2 = "claude-code";
|
|
598
|
+
var CLAUDE_EVENTS = [
|
|
599
|
+
"SessionStart",
|
|
600
|
+
"UserPromptSubmit",
|
|
601
|
+
"PreToolUse",
|
|
602
|
+
"PostToolUse",
|
|
603
|
+
"PostToolUseFailure",
|
|
604
|
+
"PermissionRequest",
|
|
605
|
+
"Notification",
|
|
606
|
+
"Stop",
|
|
607
|
+
"SubagentStart",
|
|
608
|
+
"SubagentStop",
|
|
609
|
+
"PreCompact",
|
|
610
|
+
"PostCompact",
|
|
611
|
+
"SessionEnd"
|
|
612
|
+
];
|
|
613
|
+
var OPTS = { matcherFor: (e) => /Tool|Permission/.test(e) ? "" : void 0, timeout: 5 };
|
|
614
|
+
var DESKTOP_ID = /^local_[A-Za-z0-9-]{1,64}$/;
|
|
615
|
+
function claudeDirs(env = process.env, home = HOME) {
|
|
616
|
+
const out = [];
|
|
617
|
+
const add = (d) => {
|
|
618
|
+
if (!d) return;
|
|
619
|
+
const r = path4.resolve(d);
|
|
620
|
+
if (!out.includes(r)) out.push(r);
|
|
621
|
+
};
|
|
622
|
+
add(path4.join(home, ".claude"));
|
|
623
|
+
add(env.CLAUDE_CONFIG_DIR);
|
|
624
|
+
try {
|
|
625
|
+
for (const e of readdirSync3(home, { withFileTypes: true })) {
|
|
626
|
+
if (!e.isDirectory() || !/^\.claude[-_.]/.test(e.name)) continue;
|
|
627
|
+
const d = path4.join(home, e.name);
|
|
628
|
+
if (existsSync3(path4.join(d, "projects")) || existsSync3(path4.join(d, "settings.json"))) add(d);
|
|
629
|
+
}
|
|
630
|
+
} catch {
|
|
631
|
+
}
|
|
632
|
+
return out.filter((d, i) => i === 0 || existsSync3(d));
|
|
633
|
+
}
|
|
634
|
+
var settingsIn = (dir) => path4.join(dir, "settings.json");
|
|
635
|
+
var configDirOf = (transcript) => path4.dirname(path4.dirname(path4.dirname(transcript)));
|
|
636
|
+
var validTool2 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
|
|
637
|
+
var endReason = (r) => r === "clear" || r === "logout" || r === "exit" ? r : r === "prompt_input_exit" ? "exit" : "other";
|
|
638
|
+
var notifKind = (t) => t === "permission_prompt" ? "permission" : t === "idle_prompt" ? "idle" : t === "elicitation_dialog" ? "elicitation" : "question";
|
|
639
|
+
var claudeCode = {
|
|
640
|
+
name: NAME2,
|
|
641
|
+
events: CLAUDE_EVENTS,
|
|
642
|
+
detect: (env = process.env) => claudeDirs(env).some((d) => existsSync3(d)),
|
|
643
|
+
configFiles: (env = process.env) => claudeDirs(env).map(settingsIn),
|
|
644
|
+
install: (cmd, env = process.env) => claudeDirs(env).map((d) => installClaudeShaped(settingsIn(d), NAME2, CLAUDE_EVENTS, cmd, OPTS)),
|
|
645
|
+
uninstall: (env = process.env) => claudeDirs(env).map((d) => uninstallClaudeShaped(settingsIn(d), CLAUDE_EVENTS)),
|
|
646
|
+
installed: (cmd, env = process.env) => claudeDirs(env).map((d) => ({
|
|
647
|
+
file: settingsIn(d),
|
|
648
|
+
state: installedClaudeShaped(settingsIn(d), NAME2, CLAUDE_EVENTS, cmd, OPTS)
|
|
649
|
+
})),
|
|
650
|
+
fromHook(input) {
|
|
651
|
+
const event = input.argv[1] ?? "";
|
|
652
|
+
let s = {};
|
|
653
|
+
try {
|
|
654
|
+
s = input.stdin ? JSON.parse(input.stdin) : {};
|
|
655
|
+
} catch {
|
|
656
|
+
}
|
|
657
|
+
const id = s.session_id ?? s.sessionId;
|
|
658
|
+
if (!id) return null;
|
|
659
|
+
const session = { id: String(id), cwd: typeof s.cwd === "string" ? s.cwd : input.cwd };
|
|
660
|
+
if (typeof s.model === "string") session.model = s.model;
|
|
661
|
+
if (typeof s.agent_id === "string" && event.startsWith("Subagent") === false && s.agent_id)
|
|
662
|
+
session.parent_id = null;
|
|
663
|
+
const turn_id = typeof s.prompt_id === "string" ? s.prompt_id : void 0;
|
|
664
|
+
const tool = validTool2(s.tool_name) ? s.tool_name : void 0;
|
|
665
|
+
const call_id = typeof s.tool_use_id === "string" ? s.tool_use_id : void 0;
|
|
666
|
+
const ms = typeof s.duration_ms === "number" ? Math.round(s.duration_ms) : void 0;
|
|
667
|
+
const agent_id = typeof s.agent_id === "string" ? s.agent_id : void 0;
|
|
668
|
+
const permissionId = () => `perm-${createHash("sha256").update(`${turn_id ?? ""}|${tool ?? ""}|${JSON.stringify(s.tool_input ?? null)}`).digest("hex").slice(0, 16)}`;
|
|
669
|
+
const ev = (type, data) => ({ type, data: prune2(data), harnessEvent: event });
|
|
670
|
+
const events = [];
|
|
671
|
+
switch (event) {
|
|
672
|
+
case "SessionStart":
|
|
673
|
+
events.push(ev("session.started", { source: pickSource(s.source ?? s.trigger) }));
|
|
674
|
+
break;
|
|
675
|
+
case "UserPromptSubmit":
|
|
676
|
+
events.push(
|
|
677
|
+
ev("turn.started", {
|
|
678
|
+
turn_id,
|
|
679
|
+
prompt_chars: typeof s.prompt === "string" ? [...s.prompt].length : void 0
|
|
680
|
+
})
|
|
681
|
+
);
|
|
682
|
+
break;
|
|
683
|
+
case "PreToolUse":
|
|
684
|
+
if (tool) events.push(ev("tool.started", { tool, call_id, turn_id, agent_id, input: s.tool_input }));
|
|
685
|
+
break;
|
|
686
|
+
case "PostToolUse":
|
|
687
|
+
if (tool)
|
|
688
|
+
events.push(
|
|
689
|
+
ev("tool.ended", {
|
|
690
|
+
tool,
|
|
691
|
+
call_id,
|
|
692
|
+
turn_id,
|
|
693
|
+
agent_id,
|
|
694
|
+
ms,
|
|
695
|
+
ok: true,
|
|
696
|
+
input: s.tool_input,
|
|
697
|
+
output: s.tool_response
|
|
698
|
+
})
|
|
699
|
+
);
|
|
700
|
+
break;
|
|
701
|
+
case "PostToolUseFailure":
|
|
702
|
+
if (tool)
|
|
703
|
+
events.push(
|
|
704
|
+
ev("tool.ended", {
|
|
705
|
+
tool,
|
|
706
|
+
call_id,
|
|
707
|
+
turn_id,
|
|
708
|
+
agent_id,
|
|
709
|
+
ms,
|
|
710
|
+
ok: false,
|
|
711
|
+
error: errText(s.error ?? s.tool_response),
|
|
712
|
+
input: s.tool_input,
|
|
713
|
+
output: s.tool_response
|
|
714
|
+
})
|
|
715
|
+
);
|
|
716
|
+
break;
|
|
717
|
+
case "PermissionRequest":
|
|
718
|
+
events.push(
|
|
719
|
+
ev("attention.needed", {
|
|
720
|
+
attention_id: call_id ?? permissionId(),
|
|
721
|
+
kind: "permission",
|
|
722
|
+
tool,
|
|
723
|
+
message: tool ? `${tool}${describe(s.tool_input)}` : void 0
|
|
724
|
+
})
|
|
725
|
+
);
|
|
726
|
+
break;
|
|
727
|
+
case "Notification":
|
|
728
|
+
events.push(
|
|
729
|
+
ev("attention.needed", {
|
|
730
|
+
attention_id: `notif-${Date.now()}`,
|
|
731
|
+
kind: notifKind(s.notification_type),
|
|
732
|
+
message: typeof s.message === "string" ? s.message.slice(0, 200) : void 0
|
|
733
|
+
})
|
|
734
|
+
);
|
|
735
|
+
break;
|
|
736
|
+
case "Stop":
|
|
737
|
+
events.push(ev("turn.ended", { turn_id, reason: "stop" }));
|
|
738
|
+
break;
|
|
739
|
+
case "SubagentStart":
|
|
740
|
+
events.push(
|
|
741
|
+
ev("subagent.started", {
|
|
742
|
+
agent_id: String(s.agent_id ?? ""),
|
|
743
|
+
agent_type: typeof s.agent_type === "string" ? s.agent_type : void 0
|
|
744
|
+
})
|
|
745
|
+
);
|
|
746
|
+
break;
|
|
747
|
+
case "SubagentStop":
|
|
748
|
+
events.push(
|
|
749
|
+
ev("subagent.ended", {
|
|
750
|
+
agent_id: String(s.agent_id ?? ""),
|
|
751
|
+
agent_type: typeof s.agent_type === "string" ? s.agent_type : void 0
|
|
752
|
+
})
|
|
753
|
+
);
|
|
754
|
+
break;
|
|
755
|
+
case "PreCompact":
|
|
756
|
+
events.push(ev("context.compacted", { trigger: s.trigger === "manual" ? "manual" : "auto" }));
|
|
757
|
+
break;
|
|
758
|
+
case "PostCompact":
|
|
759
|
+
break;
|
|
760
|
+
// PreCompact already said it; a second event would double-count
|
|
761
|
+
case "SessionEnd":
|
|
762
|
+
events.push(ev("session.ended", { reason: endReason(s.reason) }));
|
|
763
|
+
break;
|
|
764
|
+
default:
|
|
765
|
+
return null;
|
|
766
|
+
}
|
|
767
|
+
const hints = {};
|
|
768
|
+
const host = input.env.CLAUDE_CODE_HOST_SESSION_ID;
|
|
769
|
+
if (host && DESKTOP_ID.test(host)) hints.desktop_id = host;
|
|
770
|
+
if (event !== "PreToolUse" && event !== "PostToolUse") hints.claude_pid = claudePid(input.env);
|
|
771
|
+
const r = { session, events, hints };
|
|
772
|
+
if (typeof s.transcript_path === "string") r.transcript = s.transcript_path;
|
|
773
|
+
return r;
|
|
774
|
+
},
|
|
775
|
+
readTranscript(file, fromLine) {
|
|
776
|
+
let text = "";
|
|
777
|
+
try {
|
|
778
|
+
text = readFileSync3(file, "utf8");
|
|
779
|
+
} catch {
|
|
780
|
+
return { turns: [], line: fromLine };
|
|
781
|
+
}
|
|
782
|
+
return readClaude(text.split("\n"), fromLine);
|
|
783
|
+
},
|
|
784
|
+
facts(session, transcript, hints, state) {
|
|
785
|
+
const out = {};
|
|
786
|
+
if (transcript) Object.assign(out, claudeFacts(transcript, session.id, hints, state));
|
|
787
|
+
const cwd = out.cwd ?? session.cwd;
|
|
788
|
+
Object.assign(out, gitFacts(cwd), out.branch ? { branch: out.branch } : {});
|
|
789
|
+
if (!out.url && typeof hints.desktop_id === "string") out.url = `claude://code/continue/${hints.desktop_id}`;
|
|
790
|
+
return out;
|
|
791
|
+
},
|
|
792
|
+
backfill(sinceMs, env = process.env) {
|
|
793
|
+
const rows = [];
|
|
794
|
+
const files = [];
|
|
795
|
+
for (const d of claudeDirs(env))
|
|
796
|
+
files.push(...recentFiles(path4.join(d, "projects"), 1, sinceMs, (n) => /^[0-9a-f-]{36}\.jsonl$/.test(n)));
|
|
797
|
+
const noState = { get: () => ({}), save: () => {
|
|
798
|
+
} };
|
|
799
|
+
for (const f of files) {
|
|
800
|
+
const id = path4.basename(f, ".jsonl");
|
|
801
|
+
const facts = claudeFacts(f, id, {}, noState);
|
|
802
|
+
if (!facts.started_at || !facts.last_at) continue;
|
|
803
|
+
const { started_at, last_at, first_ask: _fa, harness_version: _hv, ...rest } = facts;
|
|
804
|
+
const cwd = rest.cwd;
|
|
805
|
+
rows.push({
|
|
806
|
+
session: { id, ...rest, ...gitFacts(cwd), ...rest.branch ? { branch: rest.branch } : {} },
|
|
807
|
+
started_at,
|
|
808
|
+
last_at
|
|
809
|
+
});
|
|
810
|
+
}
|
|
811
|
+
return rows;
|
|
812
|
+
}
|
|
813
|
+
};
|
|
814
|
+
function prune2(o) {
|
|
815
|
+
for (const k of Object.keys(o)) if (o[k] === void 0) delete o[k];
|
|
816
|
+
return o;
|
|
817
|
+
}
|
|
818
|
+
function pickSource(v) {
|
|
819
|
+
return typeof v === "string" && ["startup", "resume", "clear", "compact", "fork"].includes(v) ? v : "unknown";
|
|
820
|
+
}
|
|
821
|
+
function errText(v) {
|
|
822
|
+
if (typeof v === "string") return v.slice(0, 2e3);
|
|
823
|
+
if (v && typeof v === "object") {
|
|
824
|
+
const e = v;
|
|
825
|
+
if (typeof e.error === "string") return e.error.slice(0, 2e3);
|
|
826
|
+
if (typeof e.message === "string") return e.message.slice(0, 2e3);
|
|
827
|
+
}
|
|
828
|
+
return void 0;
|
|
829
|
+
}
|
|
830
|
+
function describe(input) {
|
|
831
|
+
if (!input || typeof input !== "object") return "";
|
|
832
|
+
const i = input;
|
|
833
|
+
const v = i.command ?? i.file_path ?? i.path ?? i.url ?? i.pattern;
|
|
834
|
+
return typeof v === "string" ? ` ${v.slice(0, 120)}` : "";
|
|
835
|
+
}
|
|
836
|
+
var NAME_RANK = {
|
|
837
|
+
user: "custom",
|
|
838
|
+
auto: "harness",
|
|
839
|
+
hook: "harness",
|
|
840
|
+
peer: "harness"
|
|
841
|
+
};
|
|
842
|
+
function registryEntry(j) {
|
|
843
|
+
if (!j || typeof j !== "object") return null;
|
|
844
|
+
const r = j;
|
|
845
|
+
const source = r.nameSource ? NAME_RANK[r.nameSource] : void 0;
|
|
846
|
+
return {
|
|
847
|
+
sessionId: r.sessionId ? String(r.sessionId) : null,
|
|
848
|
+
name: source && typeof r.name === "string" && r.name.trim() ? r.name.trim() : null,
|
|
849
|
+
name_source: source ?? null,
|
|
850
|
+
bridge: typeof r.bridgeSessionId === "string" ? r.bridgeSessionId : null
|
|
851
|
+
};
|
|
852
|
+
}
|
|
853
|
+
var liveCache = /* @__PURE__ */ new Map();
|
|
854
|
+
function claudeLive(dir) {
|
|
855
|
+
const hit = liveCache.get(dir);
|
|
856
|
+
if (hit) return hit;
|
|
857
|
+
const map = /* @__PURE__ */ new Map();
|
|
858
|
+
let names = [];
|
|
859
|
+
try {
|
|
860
|
+
names = readdirSync3(path4.join(dir, "sessions")).filter((n) => n.endsWith(".json"));
|
|
861
|
+
} catch {
|
|
862
|
+
}
|
|
863
|
+
for (const n of names) {
|
|
864
|
+
try {
|
|
865
|
+
const e = registryEntry(JSON.parse(readFileSync3(path4.join(dir, "sessions", n), "utf8")));
|
|
866
|
+
if (e?.sessionId) map.set(e.sessionId, e);
|
|
867
|
+
} catch {
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
liveCache.set(dir, map);
|
|
871
|
+
return map;
|
|
872
|
+
}
|
|
873
|
+
function claudeLiveByPid(dir, pid) {
|
|
874
|
+
if (!pid) return null;
|
|
875
|
+
try {
|
|
876
|
+
return registryEntry(JSON.parse(readFileSync3(path4.join(dir, "sessions", `${pid}.json`), "utf8")));
|
|
877
|
+
} catch {
|
|
878
|
+
return null;
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
function claudePid(env = process.env) {
|
|
882
|
+
try {
|
|
883
|
+
const dirs = claudeDirs(env);
|
|
884
|
+
const has = (pid2) => dirs.some((d) => existsSync3(path4.join(d, "sessions", `${pid2}.json`)));
|
|
885
|
+
let pid = process.ppid;
|
|
886
|
+
if (has(pid)) return pid;
|
|
887
|
+
const parent = parentOf();
|
|
888
|
+
for (let hop = 0; hop < 6 && pid > 1; hop++) {
|
|
889
|
+
pid = parent(pid);
|
|
890
|
+
if (pid > 1 && has(pid)) return pid;
|
|
891
|
+
}
|
|
892
|
+
} catch {
|
|
893
|
+
}
|
|
894
|
+
return null;
|
|
895
|
+
}
|
|
896
|
+
function parentOf() {
|
|
897
|
+
if (process.platform === "linux") {
|
|
898
|
+
return (pid) => {
|
|
899
|
+
try {
|
|
900
|
+
const st = readFileSync3(`/proc/${pid}/stat`, "utf8");
|
|
901
|
+
return Number(st.slice(st.lastIndexOf(")") + 2).split(" ")[1]) || 0;
|
|
902
|
+
} catch {
|
|
903
|
+
return 0;
|
|
904
|
+
}
|
|
905
|
+
};
|
|
906
|
+
}
|
|
907
|
+
const table = /* @__PURE__ */ new Map();
|
|
908
|
+
try {
|
|
909
|
+
const out = execFileSync2("ps", ["-axo", "pid=,ppid="], {
|
|
910
|
+
encoding: "utf8",
|
|
911
|
+
timeout: 2e3,
|
|
912
|
+
maxBuffer: 8 * 1024 * 1024,
|
|
913
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
914
|
+
});
|
|
915
|
+
for (const l of out.split("\n")) {
|
|
916
|
+
const m = /^\s*(\d+)\s+(\d+)/.exec(l);
|
|
917
|
+
if (m) table.set(Number(m[1]), Number(m[2]));
|
|
918
|
+
}
|
|
919
|
+
} catch {
|
|
920
|
+
}
|
|
921
|
+
return (pid) => table.get(pid) || 0;
|
|
922
|
+
}
|
|
923
|
+
var accountCache = /* @__PURE__ */ new Map();
|
|
924
|
+
function claudeAccount(dir) {
|
|
925
|
+
const hit = accountCache.get(dir);
|
|
926
|
+
if (hit !== void 0) return hit;
|
|
927
|
+
const files = [path4.join(dir, ".claude.json")];
|
|
928
|
+
if (path4.resolve(dir) === path4.join(HOME, ".claude")) files.unshift(path4.join(HOME, ".claude.json"));
|
|
929
|
+
let acct = null;
|
|
930
|
+
for (const f of files) {
|
|
931
|
+
try {
|
|
932
|
+
const a = JSON.parse(readFileSync3(f, "utf8")).oauthAccount;
|
|
933
|
+
if (a?.accountUuid) {
|
|
934
|
+
acct = String(a.accountUuid);
|
|
935
|
+
break;
|
|
936
|
+
}
|
|
937
|
+
} catch {
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
accountCache.set(dir, acct);
|
|
941
|
+
return acct;
|
|
942
|
+
}
|
|
943
|
+
var LINK_RE = /^https:\/\/claude\.ai\/code\/session_[A-Za-z0-9]+$/;
|
|
944
|
+
function transcriptLink(records) {
|
|
945
|
+
for (let i = records.length - 1; i >= 0; i--) {
|
|
946
|
+
const j = records[i];
|
|
947
|
+
if (j.type === "system" && j.subtype === "bridge_status" && typeof j.url === "string" && LINK_RE.test(j.url))
|
|
948
|
+
return j.url;
|
|
949
|
+
if (j.type === "bridge-session" && typeof j.bridgeSessionId === "string") {
|
|
950
|
+
const m = /^(?:cse|session)_([A-Za-z0-9]+)$/.exec(j.bridgeSessionId);
|
|
951
|
+
if (m) return `https://claude.ai/code/session_${m[1]}`;
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
return null;
|
|
955
|
+
}
|
|
956
|
+
function appTitle(text) {
|
|
957
|
+
const t = String(text).replace(/\s+/g, " ").trim();
|
|
958
|
+
return t.length > 50 ? `${t.slice(0, 50)}...` : t;
|
|
959
|
+
}
|
|
960
|
+
function askText(j) {
|
|
961
|
+
if (!j || j.type !== "user" || j.isMeta || j.isSidechain) return null;
|
|
962
|
+
const origin = j.origin;
|
|
963
|
+
if (origin?.kind && origin.kind !== "human") return null;
|
|
964
|
+
const c = j.message?.content;
|
|
965
|
+
const text = (typeof c === "string" ? c : Array.isArray(c) ? c.filter((b) => b && b.type === "text" && typeof b.text === "string").map((b) => b.text).join("\n") : "").trim();
|
|
966
|
+
if (!text || isInjected(text)) return null;
|
|
967
|
+
return text;
|
|
968
|
+
}
|
|
969
|
+
function firstAsk(records) {
|
|
970
|
+
for (const j of records) {
|
|
971
|
+
const t = askText(j);
|
|
972
|
+
if (t) return appTitle(t);
|
|
973
|
+
}
|
|
974
|
+
return null;
|
|
975
|
+
}
|
|
976
|
+
function scanTitles(file, from, found) {
|
|
977
|
+
const out = { custom: found.custom ?? null, ai: found.ai ?? null, upto: from };
|
|
978
|
+
let size = 0;
|
|
979
|
+
try {
|
|
980
|
+
size = statSync3(file).size;
|
|
981
|
+
} catch {
|
|
982
|
+
return out;
|
|
983
|
+
}
|
|
984
|
+
if (size < from) {
|
|
985
|
+
out.upto = 0;
|
|
986
|
+
out.custom = out.ai = null;
|
|
987
|
+
from = 0;
|
|
988
|
+
}
|
|
989
|
+
let pos = 0;
|
|
990
|
+
scanLines(
|
|
991
|
+
file,
|
|
992
|
+
(l) => {
|
|
993
|
+
pos += Buffer.byteLength(l, "utf8") + 1;
|
|
994
|
+
if (pos <= from || l.indexOf('-title"') < 0) return null;
|
|
995
|
+
try {
|
|
996
|
+
const j = JSON.parse(l);
|
|
997
|
+
if (j.type === "custom-title" && j.customTitle) out.custom = j.customTitle;
|
|
998
|
+
if (j.type === "ai-title" && j.aiTitle) out.ai = j.aiTitle;
|
|
999
|
+
} catch {
|
|
1000
|
+
}
|
|
1001
|
+
return null;
|
|
1002
|
+
},
|
|
1003
|
+
512 * 1024 * 1024
|
|
1004
|
+
);
|
|
1005
|
+
out.upto = size;
|
|
1006
|
+
return out;
|
|
1007
|
+
}
|
|
1008
|
+
function claudeFacts(file, sessionId, hints, state) {
|
|
1009
|
+
const { head, tail } = ends(file, 64 * 1024, 256 * 1024);
|
|
1010
|
+
const H = parseLines(head);
|
|
1011
|
+
const T = parseLines(tail);
|
|
1012
|
+
const out = {};
|
|
1013
|
+
const id = sessionId || path4.basename(file, ".jsonl");
|
|
1014
|
+
const mem = state.get(NAME2, id);
|
|
1015
|
+
let custom = null;
|
|
1016
|
+
let ai = null;
|
|
1017
|
+
for (const j of H.concat(T)) {
|
|
1018
|
+
if (j.type === "custom-title" && typeof j.customTitle === "string") custom = j.customTitle;
|
|
1019
|
+
if (j.type === "ai-title" && typeof j.aiTitle === "string") ai = j.aiTitle;
|
|
1020
|
+
}
|
|
1021
|
+
let size = 0;
|
|
1022
|
+
try {
|
|
1023
|
+
size = statSync3(file).size;
|
|
1024
|
+
} catch {
|
|
1025
|
+
}
|
|
1026
|
+
if (!custom && !ai && size > 64 * 1024 + 256 * 1024) {
|
|
1027
|
+
const s = scanTitles(file, Number(mem.scan_upto) || 0, {
|
|
1028
|
+
custom: mem.scan_custom,
|
|
1029
|
+
ai: mem.scan_ai
|
|
1030
|
+
});
|
|
1031
|
+
custom = s.custom;
|
|
1032
|
+
ai = s.ai;
|
|
1033
|
+
state.save(NAME2, id, { scan_upto: s.upto, scan_custom: s.custom, scan_ai: s.ai });
|
|
1034
|
+
}
|
|
1035
|
+
const dir = configDirOf(file);
|
|
1036
|
+
const live = claudeLive(dir).get(id) ?? claudeLiveByPid(dir, hints.claude_pid);
|
|
1037
|
+
if (live?.name) state.save(NAME2, id, { name: live.name, name_source: live.name_source });
|
|
1038
|
+
const bridge = live?.bridge && /^session_[A-Za-z0-9]+$/.test(live.bridge) ? `https://claude.ai/code/${live.bridge}` : null;
|
|
1039
|
+
const url = bridge ?? transcriptLink(T) ?? transcriptLink(H) ?? (typeof mem.url === "string" ? mem.url : null);
|
|
1040
|
+
if (url) {
|
|
1041
|
+
out.url = url;
|
|
1042
|
+
state.save(NAME2, id, { url });
|
|
1043
|
+
}
|
|
1044
|
+
const name = live?.name ? { title: live.name, source: live.name_source } : typeof mem.name === "string" ? { title: mem.name, source: mem.name_source } : null;
|
|
1045
|
+
const pick = name && name.source === "custom" && name || custom && { title: custom, source: "custom" } || name || ai && { title: ai, source: "harness" };
|
|
1046
|
+
let ask = typeof mem.first_ask === "string" ? mem.first_ask : firstAsk(H);
|
|
1047
|
+
if (!ask && size > 64 * 1024 && !mem.first_ask_none) {
|
|
1048
|
+
ask = scanLines(file, (l) => {
|
|
1049
|
+
try {
|
|
1050
|
+
const t = askText(JSON.parse(l));
|
|
1051
|
+
return t ? appTitle(t) : null;
|
|
1052
|
+
} catch {
|
|
1053
|
+
return null;
|
|
1054
|
+
}
|
|
1055
|
+
});
|
|
1056
|
+
if (!ask && size > 16 * 1024 * 1024) state.save(NAME2, id, { first_ask_none: true });
|
|
1057
|
+
}
|
|
1058
|
+
if (ask) {
|
|
1059
|
+
out.first_ask = ask;
|
|
1060
|
+
if (!mem.first_ask) state.save(NAME2, id, { first_ask: ask });
|
|
1061
|
+
}
|
|
1062
|
+
if (pick) {
|
|
1063
|
+
out.title = String(pick.title).slice(0, 120);
|
|
1064
|
+
out.title_source = pick.source;
|
|
1065
|
+
} else if (ask) {
|
|
1066
|
+
out.title = ask;
|
|
1067
|
+
out.title_source = "first-ask";
|
|
1068
|
+
}
|
|
1069
|
+
const acct = claudeAccount(dir);
|
|
1070
|
+
if (acct) out.account_id = acct;
|
|
1071
|
+
for (let i = T.length - 1; i >= 0; i--) {
|
|
1072
|
+
const m = T[i].message?.model;
|
|
1073
|
+
if (T[i]?.type === "assistant" && m && m !== "<synthetic>") {
|
|
1074
|
+
out.model = m;
|
|
1075
|
+
break;
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
const meta = [...T].reverse().find((j) => j.version) ?? H.find((j) => j.version);
|
|
1079
|
+
if (meta?.version) out.harness_version = String(meta.version);
|
|
1080
|
+
const times = H.concat(T).map((j) => Date.parse(String(j.timestamp ?? ""))).filter((n) => n > 0);
|
|
1081
|
+
if (times.length) {
|
|
1082
|
+
out.started_at = Math.min(...times);
|
|
1083
|
+
out.last_at = Math.max(...times);
|
|
1084
|
+
}
|
|
1085
|
+
const withCwd = H.find((j) => typeof j.cwd === "string");
|
|
1086
|
+
if (withCwd) {
|
|
1087
|
+
out.cwd = withCwd.cwd;
|
|
1088
|
+
if (typeof withCwd.gitBranch === "string" && withCwd.gitBranch !== "HEAD") out.branch = withCwd.gitBranch;
|
|
1089
|
+
}
|
|
1090
|
+
return out;
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
// ../core/src/adapters/codex.ts
|
|
1094
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readdirSync as readdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "node:fs";
|
|
1095
|
+
import path5 from "node:path";
|
|
1096
|
+
|
|
1097
|
+
// ../core/src/adapters/codex-config.ts
|
|
1098
|
+
var NOTIFY_LINE = /^[ \t]*notify[ \t]*=[ \t]*(\[.*\])[ \t]*(?:#.*)?$/m;
|
|
1099
|
+
var NOTIFY_KEY = /^[ \t]*notify[ \t]*=/m;
|
|
1100
|
+
var OURS2 = /hook\.js",\s*"codex",\s*"notify"/;
|
|
1101
|
+
function scan(line, st) {
|
|
1102
|
+
let i = 0;
|
|
1103
|
+
while (i < line.length) {
|
|
1104
|
+
if (st.str) {
|
|
1105
|
+
const j = line.indexOf(st.str, i);
|
|
1106
|
+
if (j < 0) return;
|
|
1107
|
+
i = j + 3;
|
|
1108
|
+
st.str = null;
|
|
1109
|
+
continue;
|
|
1110
|
+
}
|
|
1111
|
+
const ch = line[i];
|
|
1112
|
+
if (ch === "#") return;
|
|
1113
|
+
if (line.startsWith('"""', i) || line.startsWith("'''", i)) {
|
|
1114
|
+
st.str = line.slice(i, i + 3);
|
|
1115
|
+
i += 3;
|
|
1116
|
+
continue;
|
|
1117
|
+
}
|
|
1118
|
+
if (ch === '"') {
|
|
1119
|
+
i++;
|
|
1120
|
+
while (i < line.length && line[i] !== '"') i += line[i] === "\\" ? 2 : 1;
|
|
1121
|
+
i++;
|
|
1122
|
+
continue;
|
|
1123
|
+
}
|
|
1124
|
+
if (ch === "'") {
|
|
1125
|
+
const j = line.indexOf("'", i + 1);
|
|
1126
|
+
i = j < 0 ? line.length : j + 1;
|
|
1127
|
+
continue;
|
|
1128
|
+
}
|
|
1129
|
+
if (ch === "[") st.depth++;
|
|
1130
|
+
else if (ch === "]") st.depth = Math.max(0, st.depth - 1);
|
|
1131
|
+
i++;
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
function firstTableAt(text) {
|
|
1135
|
+
const st = { depth: 0, str: null };
|
|
1136
|
+
let pos = 0;
|
|
1137
|
+
for (const line of text.split("\n")) {
|
|
1138
|
+
if (!st.str && st.depth === 0 && /^[ \t]*\[/.test(line)) return pos;
|
|
1139
|
+
scan(line, st);
|
|
1140
|
+
pos += line.length + 1;
|
|
1141
|
+
}
|
|
1142
|
+
return -1;
|
|
1143
|
+
}
|
|
1144
|
+
function split(text) {
|
|
1145
|
+
const at = firstTableAt(text);
|
|
1146
|
+
return at < 0 ? { head: text, tail: "", at: text.length } : { head: text.slice(0, at), tail: text.slice(at), at };
|
|
1147
|
+
}
|
|
1148
|
+
function parseArgv(raw) {
|
|
1149
|
+
try {
|
|
1150
|
+
const a = JSON.parse(raw);
|
|
1151
|
+
return Array.isArray(a) && a.every((x) => typeof x === "string") ? a : null;
|
|
1152
|
+
} catch {
|
|
1153
|
+
return null;
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
function readNotify(text) {
|
|
1157
|
+
const { head, tail, at } = split(text);
|
|
1158
|
+
const m = NOTIFY_LINE.exec(head);
|
|
1159
|
+
if (m)
|
|
1160
|
+
return {
|
|
1161
|
+
raw: m[1] ?? "",
|
|
1162
|
+
argv: parseArgv(m[1] ?? ""),
|
|
1163
|
+
index: m.index,
|
|
1164
|
+
length: m[0].length,
|
|
1165
|
+
misplaced: false,
|
|
1166
|
+
other: !OURS2.test(m[1] ?? "")
|
|
1167
|
+
};
|
|
1168
|
+
const k = NOTIFY_KEY.exec(head);
|
|
1169
|
+
if (k) {
|
|
1170
|
+
const rest = head.slice(k.index).split("\n")[0] ?? "";
|
|
1171
|
+
return {
|
|
1172
|
+
raw: rest.replace(NOTIFY_KEY, "").trim(),
|
|
1173
|
+
argv: null,
|
|
1174
|
+
index: k.index,
|
|
1175
|
+
length: 0,
|
|
1176
|
+
misplaced: false,
|
|
1177
|
+
other: true
|
|
1178
|
+
};
|
|
1179
|
+
}
|
|
1180
|
+
const t = NOTIFY_LINE.exec(tail);
|
|
1181
|
+
if (t && OURS2.test(t[1] ?? ""))
|
|
1182
|
+
return {
|
|
1183
|
+
raw: t[1] ?? "",
|
|
1184
|
+
argv: parseArgv(t[1] ?? ""),
|
|
1185
|
+
index: at + t.index,
|
|
1186
|
+
length: t[0].length,
|
|
1187
|
+
misplaced: true,
|
|
1188
|
+
other: false
|
|
1189
|
+
};
|
|
1190
|
+
return null;
|
|
1191
|
+
}
|
|
1192
|
+
function dropLine(text, index, length) {
|
|
1193
|
+
return text.slice(0, index) + text.slice(index + length).replace(/^\r?\n/, "");
|
|
1194
|
+
}
|
|
1195
|
+
function insertTopLevel(text, line) {
|
|
1196
|
+
const { head, tail } = split(text);
|
|
1197
|
+
if (!tail) return (head.trim() ? head.replace(/\s*$/, "\n") : "") + line + "\n";
|
|
1198
|
+
return `${(head.trim() ? head.replace(/\s*$/, "\n") : "") + line}
|
|
1199
|
+
|
|
1200
|
+
${tail}`;
|
|
1201
|
+
}
|
|
1202
|
+
function withNotify(text, line) {
|
|
1203
|
+
const n = readNotify(text);
|
|
1204
|
+
if (n && !n.misplaced && !n.other) return text.slice(0, n.index) + line + text.slice(n.index + n.length);
|
|
1205
|
+
if (n?.misplaced) return insertTopLevel(dropLine(text, n.index, n.length), line);
|
|
1206
|
+
return insertTopLevel(text, line);
|
|
1207
|
+
}
|
|
1208
|
+
function replaceNotify(text, line) {
|
|
1209
|
+
const n = readNotify(text);
|
|
1210
|
+
if (!n || n.misplaced || !n.length) return null;
|
|
1211
|
+
return text.slice(0, n.index) + line + text.slice(n.index + n.length);
|
|
1212
|
+
}
|
|
1213
|
+
function withoutNotify(text) {
|
|
1214
|
+
const n = readNotify(text);
|
|
1215
|
+
if (!n || n.other || !n.length) return text;
|
|
1216
|
+
return dropLine(text, n.index, n.length);
|
|
1217
|
+
}
|
|
1218
|
+
var PREVIOUS = "--previous-notify";
|
|
1219
|
+
function previousNotify(argv) {
|
|
1220
|
+
if (!Array.isArray(argv)) return null;
|
|
1221
|
+
const i = argv.indexOf(PREVIOUS);
|
|
1222
|
+
return i >= 0 && typeof argv[i + 1] === "string" ? parseArgv(argv[i + 1]) : null;
|
|
1223
|
+
}
|
|
1224
|
+
function stripPreviousRaw(raw) {
|
|
1225
|
+
return raw.replace(/,\s*"--previous-notify",\s*"(?:[^"\\]|\\.)*"\s*(?=\])/, "");
|
|
1226
|
+
}
|
|
1227
|
+
function withPreviousRaw(raw, prev) {
|
|
1228
|
+
const base = stripPreviousRaw(raw).replace(/\s*\]\s*$/, "");
|
|
1229
|
+
return `${base}, "--previous-notify", ${JSON.stringify(JSON.stringify(prev))}]`;
|
|
1230
|
+
}
|
|
1231
|
+
var isComputerUse = (argv) => Array.isArray(argv) && typeof argv[0] === "string" && /(^|\/)SkyComputerUseClient$/.test(argv[0]);
|
|
1232
|
+
|
|
1233
|
+
// ../core/src/adapters/codex.ts
|
|
1234
|
+
var NAME3 = "codex";
|
|
1235
|
+
var CODEX_EVENTS = [
|
|
1236
|
+
"SessionStart",
|
|
1237
|
+
"UserPromptSubmit",
|
|
1238
|
+
"PreToolUse",
|
|
1239
|
+
"PostToolUse",
|
|
1240
|
+
"PermissionRequest",
|
|
1241
|
+
"Stop",
|
|
1242
|
+
"Interrupt",
|
|
1243
|
+
"SubagentStart",
|
|
1244
|
+
"SubagentStop",
|
|
1245
|
+
"PreCompact",
|
|
1246
|
+
"PostCompact",
|
|
1247
|
+
"SessionEnd"
|
|
1248
|
+
];
|
|
1249
|
+
var OPTS2 = { matcherFor: (e) => /Tool|Permission/.test(e) ? "" : void 0, timeout: 5 };
|
|
1250
|
+
var codexHome = (env) => env.CODEX_HOME || path5.join(HOME, ".codex");
|
|
1251
|
+
var hooksFile2 = (env) => path5.join(codexHome(env), "hooks.json");
|
|
1252
|
+
var configFile = (env) => path5.join(codexHome(env), "config.toml");
|
|
1253
|
+
var sessionsDir = (env) => path5.join(codexHome(env), "sessions");
|
|
1254
|
+
var validTool3 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
|
|
1255
|
+
var endReason2 = (r) => r === "clear" || r === "logout" || r === "exit" ? r : "other";
|
|
1256
|
+
function installNotify(cmd, env) {
|
|
1257
|
+
const file = configFile(env);
|
|
1258
|
+
let text = "";
|
|
1259
|
+
try {
|
|
1260
|
+
text = readFileSync4(file, "utf8");
|
|
1261
|
+
} catch {
|
|
1262
|
+
}
|
|
1263
|
+
const argv = cmd([NAME3, "notify"]).argv;
|
|
1264
|
+
const line = `notify = ${JSON.stringify(argv)}`;
|
|
1265
|
+
const n = readNotify(text);
|
|
1266
|
+
const write = (next) => {
|
|
1267
|
+
mkdirSync3(path5.dirname(file), { recursive: true });
|
|
1268
|
+
writeFileSync3(file, next);
|
|
1269
|
+
};
|
|
1270
|
+
if (n && !n.other) {
|
|
1271
|
+
if (!n.misplaced && JSON.stringify(n.argv) === JSON.stringify(argv))
|
|
1272
|
+
return { file, changed: false, note: "notify current" };
|
|
1273
|
+
write(withNotify(text, line));
|
|
1274
|
+
return {
|
|
1275
|
+
file,
|
|
1276
|
+
changed: true,
|
|
1277
|
+
note: n.misplaced ? "notify moved to the top level (it sat inside a [table], where Codex never reads it)" : "notify updated"
|
|
1278
|
+
};
|
|
1279
|
+
}
|
|
1280
|
+
if (n?.other) {
|
|
1281
|
+
const prev = previousNotify(n.argv);
|
|
1282
|
+
if (isComputerUse(n.argv)) {
|
|
1283
|
+
if (prev && OURS2.test(JSON.stringify(prev))) {
|
|
1284
|
+
if (JSON.stringify(prev) === JSON.stringify(argv))
|
|
1285
|
+
return { file, changed: false, note: "notify current (after Codex Computer Use)" };
|
|
1286
|
+
}
|
|
1287
|
+
if (!prev || OURS2.test(JSON.stringify(prev))) {
|
|
1288
|
+
const next = replaceNotify(text, `notify = ${withPreviousRaw(n.raw, argv)}`);
|
|
1289
|
+
if (next) {
|
|
1290
|
+
write(next);
|
|
1291
|
+
return { file, changed: true, note: "chained after Codex Computer Use (--previous-notify)" };
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
return {
|
|
1296
|
+
file,
|
|
1297
|
+
changed: false,
|
|
1298
|
+
note: `left alone: notify runs ${n.raw.slice(0, 80)} (hooks.json carries the events; the notify fallback is off)`
|
|
1299
|
+
};
|
|
1300
|
+
}
|
|
1301
|
+
write(withNotify(text, line));
|
|
1302
|
+
return { file, changed: true, note: "notify set at the top level" };
|
|
1303
|
+
}
|
|
1304
|
+
function uninstallNotify(env) {
|
|
1305
|
+
const file = configFile(env);
|
|
1306
|
+
let text;
|
|
1307
|
+
try {
|
|
1308
|
+
text = readFileSync4(file, "utf8");
|
|
1309
|
+
} catch {
|
|
1310
|
+
return { file, changed: false };
|
|
1311
|
+
}
|
|
1312
|
+
const n = readNotify(text);
|
|
1313
|
+
if (!n) return { file, changed: false };
|
|
1314
|
+
if (!n.other) {
|
|
1315
|
+
writeFileSync3(file, withoutNotify(text));
|
|
1316
|
+
return { file, changed: true };
|
|
1317
|
+
}
|
|
1318
|
+
const prev = previousNotify(n.argv);
|
|
1319
|
+
if (isComputerUse(n.argv) && prev && OURS2.test(JSON.stringify(prev))) {
|
|
1320
|
+
const next = replaceNotify(text, `notify = ${stripPreviousRaw(n.raw)}`);
|
|
1321
|
+
if (next) writeFileSync3(file, next);
|
|
1322
|
+
return { file, changed: true };
|
|
1323
|
+
}
|
|
1324
|
+
return { file, changed: false };
|
|
1325
|
+
}
|
|
1326
|
+
function findRollout(threadId, env) {
|
|
1327
|
+
let best = null;
|
|
1328
|
+
const walk = (dir, depth) => {
|
|
1329
|
+
let names = [];
|
|
1330
|
+
try {
|
|
1331
|
+
names = readdirSync4(dir);
|
|
1332
|
+
} catch {
|
|
1333
|
+
return;
|
|
1334
|
+
}
|
|
1335
|
+
for (const n of names.sort().reverse()) {
|
|
1336
|
+
const p = path5.join(dir, n);
|
|
1337
|
+
if (n.endsWith(".jsonl") && n.includes(threadId)) {
|
|
1338
|
+
best = p;
|
|
1339
|
+
return;
|
|
1340
|
+
}
|
|
1341
|
+
if (depth < 3) {
|
|
1342
|
+
walk(p, depth + 1);
|
|
1343
|
+
if (best) return;
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
};
|
|
1347
|
+
walk(sessionsDir(env), 0);
|
|
1348
|
+
return best;
|
|
1349
|
+
}
|
|
1350
|
+
var codex = {
|
|
1351
|
+
name: NAME3,
|
|
1352
|
+
events: CODEX_EVENTS,
|
|
1353
|
+
detect: (env = process.env) => existsSync4(codexHome(env)),
|
|
1354
|
+
configFiles: (env = process.env) => [hooksFile2(env), configFile(env)],
|
|
1355
|
+
install: (cmd, env = process.env) => [
|
|
1356
|
+
installClaudeShaped(hooksFile2(env), NAME3, CODEX_EVENTS, cmd, OPTS2),
|
|
1357
|
+
installNotify(cmd, env)
|
|
1358
|
+
],
|
|
1359
|
+
uninstall: (env = process.env) => [uninstallClaudeShaped(hooksFile2(env), CODEX_EVENTS), uninstallNotify(env)],
|
|
1360
|
+
installed: (cmd, env = process.env) => {
|
|
1361
|
+
const out = [
|
|
1362
|
+
{ file: hooksFile2(env), state: installedClaudeShaped(hooksFile2(env), NAME3, CODEX_EVENTS, cmd, OPTS2) }
|
|
1363
|
+
];
|
|
1364
|
+
let text = "";
|
|
1365
|
+
try {
|
|
1366
|
+
text = readFileSync4(configFile(env), "utf8");
|
|
1367
|
+
} catch {
|
|
1368
|
+
}
|
|
1369
|
+
const n = readNotify(text);
|
|
1370
|
+
const argv = cmd([NAME3, "notify"]).argv;
|
|
1371
|
+
const prev = n ? previousNotify(n.argv) : null;
|
|
1372
|
+
const ours = n && (!n.other || prev && JSON.stringify(prev) === JSON.stringify(argv));
|
|
1373
|
+
out.push({
|
|
1374
|
+
file: configFile(env),
|
|
1375
|
+
state: !n || n.other && !ours ? "missing" : n.misplaced ? "misplaced" : JSON.stringify(n.argv) === JSON.stringify(argv) || prev && JSON.stringify(prev) === JSON.stringify(argv) ? "current" : "stale"
|
|
1376
|
+
});
|
|
1377
|
+
return out;
|
|
1378
|
+
},
|
|
1379
|
+
fromHook(input) {
|
|
1380
|
+
const event = input.argv[1] ?? "";
|
|
1381
|
+
let s = {};
|
|
1382
|
+
if (event === "notify") {
|
|
1383
|
+
const raw = input.argv.find((a) => a.startsWith("{")) ?? input.stdin;
|
|
1384
|
+
try {
|
|
1385
|
+
s = raw ? JSON.parse(raw) : {};
|
|
1386
|
+
} catch {
|
|
1387
|
+
}
|
|
1388
|
+
const id2 = s["thread-id"] ?? s.thread_id ?? s["session-id"] ?? s.session_id;
|
|
1389
|
+
if (!id2) return null;
|
|
1390
|
+
const r2 = {
|
|
1391
|
+
session: { id: String(id2), cwd: typeof s.cwd === "string" ? s.cwd : input.cwd },
|
|
1392
|
+
events: [
|
|
1393
|
+
{
|
|
1394
|
+
type: "turn.ended",
|
|
1395
|
+
data: { reason: "stop", ...typeof s["turn-id"] === "string" ? { turn_id: s["turn-id"] } : {} },
|
|
1396
|
+
harnessEvent: "notify"
|
|
1397
|
+
}
|
|
1398
|
+
],
|
|
1399
|
+
hints: {}
|
|
1400
|
+
};
|
|
1401
|
+
const file = findRollout(String(id2), input.env);
|
|
1402
|
+
if (file) r2.transcript = file;
|
|
1403
|
+
return r2;
|
|
1404
|
+
}
|
|
1405
|
+
try {
|
|
1406
|
+
s = input.stdin ? JSON.parse(input.stdin) : {};
|
|
1407
|
+
} catch {
|
|
1408
|
+
}
|
|
1409
|
+
const id = s.session_id;
|
|
1410
|
+
if (!id) return null;
|
|
1411
|
+
const session = { id: String(id), cwd: typeof s.cwd === "string" ? s.cwd : input.cwd };
|
|
1412
|
+
if (typeof s.model === "string") session.model = s.model;
|
|
1413
|
+
const turn_id = typeof s.turn_id === "string" ? s.turn_id : void 0;
|
|
1414
|
+
const tool = validTool3(s.tool_name) ? s.tool_name : void 0;
|
|
1415
|
+
const call_id = typeof s.tool_use_id === "string" ? s.tool_use_id : void 0;
|
|
1416
|
+
const ev = (type, data) => ({ type, data: prune3(data), harnessEvent: event });
|
|
1417
|
+
const events = [];
|
|
1418
|
+
switch (event) {
|
|
1419
|
+
case "SessionStart":
|
|
1420
|
+
events.push(
|
|
1421
|
+
ev("session.started", {
|
|
1422
|
+
source: typeof s.source === "string" && ["startup", "resume", "clear", "compact", "fork"].includes(s.source) ? s.source : "unknown"
|
|
1423
|
+
})
|
|
1424
|
+
);
|
|
1425
|
+
break;
|
|
1426
|
+
case "UserPromptSubmit":
|
|
1427
|
+
events.push(
|
|
1428
|
+
ev("turn.started", {
|
|
1429
|
+
turn_id,
|
|
1430
|
+
prompt_chars: typeof s.prompt === "string" ? [...s.prompt].length : void 0
|
|
1431
|
+
})
|
|
1432
|
+
);
|
|
1433
|
+
break;
|
|
1434
|
+
case "PreToolUse":
|
|
1435
|
+
if (tool) events.push(ev("tool.started", { tool, call_id, turn_id, input: s.tool_input }));
|
|
1436
|
+
break;
|
|
1437
|
+
case "PostToolUse":
|
|
1438
|
+
if (tool)
|
|
1439
|
+
events.push(
|
|
1440
|
+
ev("tool.ended", { tool, call_id, turn_id, ok: true, input: s.tool_input, output: s.tool_response })
|
|
1441
|
+
);
|
|
1442
|
+
break;
|
|
1443
|
+
case "PermissionRequest":
|
|
1444
|
+
events.push(
|
|
1445
|
+
ev("attention.needed", { attention_id: call_id ?? `perm-${Date.now()}`, kind: "permission", tool })
|
|
1446
|
+
);
|
|
1447
|
+
break;
|
|
1448
|
+
case "Stop":
|
|
1449
|
+
events.push(ev("turn.ended", { turn_id, reason: "stop" }));
|
|
1450
|
+
break;
|
|
1451
|
+
case "Interrupt":
|
|
1452
|
+
events.push(ev("turn.ended", { turn_id, reason: "interrupt" }));
|
|
1453
|
+
break;
|
|
1454
|
+
case "SubagentStart":
|
|
1455
|
+
events.push(
|
|
1456
|
+
ev("subagent.started", {
|
|
1457
|
+
agent_id: String(s.agent_id ?? s.subagent_id ?? ""),
|
|
1458
|
+
agent_type: typeof s.agent_type === "string" ? s.agent_type : void 0
|
|
1459
|
+
})
|
|
1460
|
+
);
|
|
1461
|
+
break;
|
|
1462
|
+
case "SubagentStop":
|
|
1463
|
+
events.push(
|
|
1464
|
+
ev("subagent.ended", {
|
|
1465
|
+
agent_id: String(s.agent_id ?? s.subagent_id ?? ""),
|
|
1466
|
+
agent_type: typeof s.agent_type === "string" ? s.agent_type : void 0
|
|
1467
|
+
})
|
|
1468
|
+
);
|
|
1469
|
+
break;
|
|
1470
|
+
case "PreCompact":
|
|
1471
|
+
events.push(ev("context.compacted", { trigger: s.trigger === "manual" ? "manual" : "auto" }));
|
|
1472
|
+
break;
|
|
1473
|
+
case "PostCompact":
|
|
1474
|
+
break;
|
|
1475
|
+
case "SessionEnd":
|
|
1476
|
+
events.push(ev("session.ended", { reason: endReason2(s.reason) }));
|
|
1477
|
+
break;
|
|
1478
|
+
default:
|
|
1479
|
+
return null;
|
|
1480
|
+
}
|
|
1481
|
+
const r = { session, events, hints: {} };
|
|
1482
|
+
if (typeof s.transcript_path === "string") r.transcript = s.transcript_path;
|
|
1483
|
+
return r;
|
|
1484
|
+
},
|
|
1485
|
+
readTranscript(file, fromLine) {
|
|
1486
|
+
let text = "";
|
|
1487
|
+
try {
|
|
1488
|
+
text = readFileSync4(file, "utf8");
|
|
1489
|
+
} catch {
|
|
1490
|
+
return { turns: [], line: fromLine };
|
|
1491
|
+
}
|
|
1492
|
+
return readCodex(text.split("\n"), fromLine);
|
|
1493
|
+
},
|
|
1494
|
+
facts(session, transcript) {
|
|
1495
|
+
const out = {};
|
|
1496
|
+
if (transcript) Object.assign(out, codexFacts(transcript));
|
|
1497
|
+
return out;
|
|
1498
|
+
},
|
|
1499
|
+
backfill(sinceMs, env = process.env) {
|
|
1500
|
+
const rows = [];
|
|
1501
|
+
for (const f of recentFiles(sessionsDir(env), 3, sinceMs, (n) => /^rollout-.*\.jsonl$/.test(n))) {
|
|
1502
|
+
const m = /([0-9a-f]{8}-[0-9a-f-]{27,})\.jsonl$/i.exec(path5.basename(f));
|
|
1503
|
+
if (!m?.[1]) continue;
|
|
1504
|
+
const facts = codexFacts(f);
|
|
1505
|
+
if (!facts.started_at || !facts.last_at) continue;
|
|
1506
|
+
const { started_at, last_at, first_ask: _fa, harness_version: _hv, ...rest } = facts;
|
|
1507
|
+
rows.push({ session: { id: m[1], ...rest }, started_at, last_at });
|
|
1508
|
+
}
|
|
1509
|
+
return rows;
|
|
1510
|
+
}
|
|
1511
|
+
};
|
|
1512
|
+
function prune3(o) {
|
|
1513
|
+
for (const k of Object.keys(o)) if (o[k] === void 0) delete o[k];
|
|
1514
|
+
return o;
|
|
1515
|
+
}
|
|
1516
|
+
function codexFacts(file) {
|
|
1517
|
+
const { head, tail } = ends(file, 128 * 1024, 64 * 1024);
|
|
1518
|
+
const H = parseLines(head);
|
|
1519
|
+
const T = parseLines(tail);
|
|
1520
|
+
const out = {};
|
|
1521
|
+
const meta = H.find((j) => j.type === "session_meta" && j.payload);
|
|
1522
|
+
if (meta?.payload) {
|
|
1523
|
+
const p = meta.payload;
|
|
1524
|
+
if (p.cwd) out.cwd = p.cwd;
|
|
1525
|
+
if (p.cli_version) out.harness_version = String(p.cli_version);
|
|
1526
|
+
if (p.git?.branch) out.branch = p.git.branch;
|
|
1527
|
+
if (p.git?.repository_url) out.repo = String(p.git.repository_url).replace(/\/\/[^/@]*@/, "//");
|
|
1528
|
+
}
|
|
1529
|
+
const ctx = H.concat(T).reverse().find((j) => j.type === "turn_context" && j.payload?.model);
|
|
1530
|
+
if (ctx?.payload?.model) out.model = ctx.payload.model;
|
|
1531
|
+
for (const j of H) {
|
|
1532
|
+
const p = j.payload;
|
|
1533
|
+
if (j.type !== "response_item" || !p || p.type !== "message" || p.role !== "user") continue;
|
|
1534
|
+
const text = (p.content ?? []).filter((b) => b && b.type === "input_text" && typeof b.text === "string").map((b) => b.text).join("\n").trim();
|
|
1535
|
+
if (!text || /^<[a-z_-]+>/i.test(text)) continue;
|
|
1536
|
+
const line = text.split("\n").find((l) => l.trim()) ?? "";
|
|
1537
|
+
out.title = line.length > 80 ? `${line.slice(0, 80).replace(/\s+\S*$/, "")}\u2026` : line;
|
|
1538
|
+
out.title_source = "first-ask";
|
|
1539
|
+
break;
|
|
1540
|
+
}
|
|
1541
|
+
const times = H.concat(T).map((j) => Date.parse(String(j.timestamp ?? ""))).filter((n) => n > 0);
|
|
1542
|
+
if (times.length) {
|
|
1543
|
+
out.started_at = Math.min(...times);
|
|
1544
|
+
out.last_at = Math.max(...times);
|
|
1545
|
+
}
|
|
1546
|
+
return out;
|
|
1547
|
+
}
|
|
1548
|
+
|
|
1549
|
+
// ../core/src/adapters/gemini-cli.ts
|
|
1550
|
+
import { existsSync as existsSync5, readFileSync as readFileSync5 } from "node:fs";
|
|
1551
|
+
import path6 from "node:path";
|
|
1552
|
+
var NAME4 = "gemini-cli";
|
|
1553
|
+
var GEMINI_EVENTS = [
|
|
1554
|
+
"SessionStart",
|
|
1555
|
+
"BeforeAgent",
|
|
1556
|
+
"BeforeTool",
|
|
1557
|
+
"AfterTool",
|
|
1558
|
+
"Notification",
|
|
1559
|
+
"AfterAgent",
|
|
1560
|
+
"PreCompress",
|
|
1561
|
+
"SessionEnd"
|
|
1562
|
+
];
|
|
1563
|
+
var OPTS3 = { matcherFor: (e) => /Tool/.test(e) ? ".*" : void 0, timeout: 5e3 };
|
|
1564
|
+
var geminiDir = (env) => env.GEMINI_CLI_HOME || path6.join(HOME, ".gemini");
|
|
1565
|
+
var settings = (env) => path6.join(geminiDir(env), "settings.json");
|
|
1566
|
+
var validTool4 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
|
|
1567
|
+
var geminiCli = {
|
|
1568
|
+
name: NAME4,
|
|
1569
|
+
events: GEMINI_EVENTS,
|
|
1570
|
+
// ~/.gemini also belongs to Antigravity; Gemini CLI is here when its settings or its chat logs exist.
|
|
1571
|
+
detect: (env = process.env) => existsSync5(settings(env)) || existsSync5(path6.join(geminiDir(env), "tmp")),
|
|
1572
|
+
configFiles: (env = process.env) => [settings(env)],
|
|
1573
|
+
install: (cmd, env = process.env) => [installClaudeShaped(settings(env), NAME4, GEMINI_EVENTS, cmd, OPTS3)],
|
|
1574
|
+
uninstall: (env = process.env) => [uninstallClaudeShaped(settings(env), GEMINI_EVENTS)],
|
|
1575
|
+
installed: (cmd, env = process.env) => [
|
|
1576
|
+
{ file: settings(env), state: installedClaudeShaped(settings(env), NAME4, GEMINI_EVENTS, cmd, OPTS3) }
|
|
1577
|
+
],
|
|
1578
|
+
fromHook(input) {
|
|
1579
|
+
const event = input.argv[1] ?? "";
|
|
1580
|
+
let s = {};
|
|
1581
|
+
try {
|
|
1582
|
+
s = input.stdin ? JSON.parse(input.stdin) : {};
|
|
1583
|
+
} catch {
|
|
1584
|
+
}
|
|
1585
|
+
const id = s.session_id;
|
|
1586
|
+
if (!id) return null;
|
|
1587
|
+
const session = { id: String(id), cwd: typeof s.cwd === "string" ? s.cwd : input.cwd };
|
|
1588
|
+
const tool = validTool4(s.tool_name) ? s.tool_name : void 0;
|
|
1589
|
+
const ev = (type, data) => ({ type, data: prune4(data), harnessEvent: event });
|
|
1590
|
+
const events = [];
|
|
1591
|
+
switch (event) {
|
|
1592
|
+
case "SessionStart":
|
|
1593
|
+
events.push(
|
|
1594
|
+
ev("session.started", {
|
|
1595
|
+
source: typeof s.source === "string" && ["startup", "resume", "clear"].includes(s.source) ? s.source : "unknown"
|
|
1596
|
+
})
|
|
1597
|
+
);
|
|
1598
|
+
break;
|
|
1599
|
+
case "BeforeAgent":
|
|
1600
|
+
events.push(
|
|
1601
|
+
ev("turn.started", { prompt_chars: typeof s.prompt === "string" ? [...s.prompt].length : void 0 })
|
|
1602
|
+
);
|
|
1603
|
+
break;
|
|
1604
|
+
case "BeforeTool":
|
|
1605
|
+
if (tool) events.push(ev("tool.started", { tool, input: s.tool_input }));
|
|
1606
|
+
break;
|
|
1607
|
+
case "AfterTool": {
|
|
1608
|
+
const resp = s.tool_response;
|
|
1609
|
+
const err = resp && typeof resp === "object" && resp.error ? String(typeof resp.error === "string" ? resp.error : JSON.stringify(resp.error)).slice(0, 2e3) : void 0;
|
|
1610
|
+
if (tool)
|
|
1611
|
+
events.push(
|
|
1612
|
+
ev("tool.ended", {
|
|
1613
|
+
tool,
|
|
1614
|
+
ok: !err,
|
|
1615
|
+
error: err,
|
|
1616
|
+
input: s.tool_input,
|
|
1617
|
+
output: resp?.llmContent ?? s.tool_response
|
|
1618
|
+
})
|
|
1619
|
+
);
|
|
1620
|
+
break;
|
|
1621
|
+
}
|
|
1622
|
+
case "Notification":
|
|
1623
|
+
events.push(
|
|
1624
|
+
ev("attention.needed", {
|
|
1625
|
+
attention_id: `notif-${Date.now()}`,
|
|
1626
|
+
kind: s.notification_type === "ToolPermission" ? "permission" : "question",
|
|
1627
|
+
message: typeof s.message === "string" ? s.message.slice(0, 200) : void 0
|
|
1628
|
+
})
|
|
1629
|
+
);
|
|
1630
|
+
break;
|
|
1631
|
+
case "AfterAgent":
|
|
1632
|
+
events.push(ev("turn.ended", { reason: "stop" }));
|
|
1633
|
+
break;
|
|
1634
|
+
case "PreCompress":
|
|
1635
|
+
events.push(ev("context.compacted", { trigger: s.trigger === "manual" ? "manual" : "auto" }));
|
|
1636
|
+
break;
|
|
1637
|
+
case "SessionEnd":
|
|
1638
|
+
events.push(
|
|
1639
|
+
ev("session.ended", {
|
|
1640
|
+
reason: s.reason === "clear" || s.reason === "logout" || s.reason === "exit" ? s.reason : s.reason === "prompt_input_exit" ? "exit" : "other"
|
|
1641
|
+
})
|
|
1642
|
+
);
|
|
1643
|
+
break;
|
|
1644
|
+
default:
|
|
1645
|
+
return null;
|
|
1646
|
+
}
|
|
1647
|
+
const r = { session, events, hints: {} };
|
|
1648
|
+
if (typeof s.transcript_path === "string") r.transcript = s.transcript_path;
|
|
1649
|
+
return r;
|
|
1650
|
+
},
|
|
1651
|
+
readTranscript(file, fromLine) {
|
|
1652
|
+
let text = "";
|
|
1653
|
+
try {
|
|
1654
|
+
text = readFileSync5(file, "utf8");
|
|
1655
|
+
} catch {
|
|
1656
|
+
return { turns: [], line: fromLine };
|
|
1657
|
+
}
|
|
1658
|
+
return readGemini(text.split("\n"), fromLine);
|
|
1659
|
+
},
|
|
1660
|
+
facts(_session, transcript) {
|
|
1661
|
+
const out = {};
|
|
1662
|
+
if (!transcript) return out;
|
|
1663
|
+
const { head, tail } = ends(transcript, 64 * 1024, 64 * 1024);
|
|
1664
|
+
const H = parseLines(head);
|
|
1665
|
+
const T = parseLines(tail);
|
|
1666
|
+
const first = H.find((j) => j.type === "user");
|
|
1667
|
+
const raw = first ? first.content ?? first.message ?? first.text : void 0;
|
|
1668
|
+
const text = typeof raw === "string" ? raw.trim() : "";
|
|
1669
|
+
if (text) {
|
|
1670
|
+
const line = text.split("\n").find((l) => l.trim()) ?? "";
|
|
1671
|
+
out.title = line.length > 80 ? `${line.slice(0, 80).replace(/\s+\S*$/, "")}\u2026` : line;
|
|
1672
|
+
out.title_source = "first-ask";
|
|
1673
|
+
}
|
|
1674
|
+
const times = H.concat(T).map((j) => Date.parse(String(j.timestamp ?? ""))).filter((n) => n > 0);
|
|
1675
|
+
if (times.length) {
|
|
1676
|
+
out.started_at = Math.min(...times);
|
|
1677
|
+
out.last_at = Math.max(...times);
|
|
1678
|
+
}
|
|
1679
|
+
return out;
|
|
1680
|
+
},
|
|
1681
|
+
backfill: () => []
|
|
1682
|
+
// chat logs are keyed by a project hash, not a session id we can name; the first hook of a resumed session reports it
|
|
1683
|
+
};
|
|
1684
|
+
function prune4(o) {
|
|
1685
|
+
for (const k of Object.keys(o)) if (o[k] === void 0) delete o[k];
|
|
1686
|
+
return o;
|
|
1687
|
+
}
|
|
1688
|
+
|
|
1689
|
+
// ../core/src/adapters/index.ts
|
|
1690
|
+
var ADAPTERS = [claudeCode, codex, geminiCli, antigravity];
|
|
1691
|
+
var adapterByName = (name) => ADAPTERS.find((a) => a.name === name);
|
|
1692
|
+
|
|
1693
|
+
// src/index.ts
|
|
1694
|
+
function matches(expect, got, pathStr = "") {
|
|
1695
|
+
if (expect === "*") return null;
|
|
1696
|
+
if (Array.isArray(expect)) {
|
|
1697
|
+
if (!Array.isArray(got) || got.length !== expect.length) return `${pathStr}: expected ${expect.length} items`;
|
|
1698
|
+
for (let i = 0; i < expect.length; i++) {
|
|
1699
|
+
const r = matches(expect[i], got[i], `${pathStr}[${i}]`);
|
|
1700
|
+
if (r) return r;
|
|
1701
|
+
}
|
|
1702
|
+
return null;
|
|
1703
|
+
}
|
|
1704
|
+
if (expect && typeof expect === "object") {
|
|
1705
|
+
if (!got || typeof got !== "object") return `${pathStr}: expected an object`;
|
|
1706
|
+
for (const [k, v] of Object.entries(expect)) {
|
|
1707
|
+
const r = matches(v, got[k], `${pathStr}.${k}`);
|
|
1708
|
+
if (r) return r;
|
|
1709
|
+
}
|
|
1710
|
+
return null;
|
|
1711
|
+
}
|
|
1712
|
+
return expect === got ? null : `${pathStr}: expected ${JSON.stringify(expect)}, got ${JSON.stringify(got)}`;
|
|
1713
|
+
}
|
|
1714
|
+
async function runAdapterFixtures(files) {
|
|
1715
|
+
const results = [];
|
|
1716
|
+
for (const file of files) {
|
|
1717
|
+
const fx = JSON.parse(readFileSync6(file, "utf8"));
|
|
1718
|
+
const adapter = adapterByName(fx.harness);
|
|
1719
|
+
if (!adapter) {
|
|
1720
|
+
results.push({ file, ok: false, why: `no adapter ${fx.harness}` });
|
|
1721
|
+
continue;
|
|
1722
|
+
}
|
|
1723
|
+
const r = adapter.fromHook({
|
|
1724
|
+
argv: fx.input.argv,
|
|
1725
|
+
stdin: fx.input.stdin,
|
|
1726
|
+
env: fx.input.env ?? {},
|
|
1727
|
+
cwd: fx.input.cwd ?? "/"
|
|
1728
|
+
});
|
|
1729
|
+
if (!r) {
|
|
1730
|
+
results.push({ file, ok: false, why: "fromHook returned null" });
|
|
1731
|
+
continue;
|
|
1732
|
+
}
|
|
1733
|
+
let why = matches(
|
|
1734
|
+
fx.expect.map((e) => e.type),
|
|
1735
|
+
r.events.map((e) => e.type),
|
|
1736
|
+
"types"
|
|
1737
|
+
);
|
|
1738
|
+
if (!why)
|
|
1739
|
+
for (const [i, e] of fx.expect.entries()) {
|
|
1740
|
+
const got = r.events[i];
|
|
1741
|
+
why = matches(e.harness.event, got?.harnessEvent, `[${i}].harness.event`) ?? matches(e.data, got?.data, `[${i}].data`) ?? matches({ ...e.session, seq: "*" }, r.session, `[${i}].session`);
|
|
1742
|
+
if (why) break;
|
|
1743
|
+
}
|
|
1744
|
+
results.push(why ? { file, ok: false, why } : { file, ok: true });
|
|
1745
|
+
}
|
|
1746
|
+
const failed = results.filter((r) => !r.ok).length;
|
|
1747
|
+
const table = results.map((r) => `${r.ok ? "ok " : "FAIL"} ${r.file}${r.why ? ` \u2014 ${r.why}` : ""}`).join("\n");
|
|
1748
|
+
return { results, failed, table };
|
|
1749
|
+
}
|
|
1750
|
+
export {
|
|
1751
|
+
runAdapterFixtures
|
|
1752
|
+
};
|
|
1753
|
+
//# sourceMappingURL=index.js.map
|