@fml-inc/fml 0.0.26 → 0.0.28
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/.claude-plugin/plugin.json +1 -0
- package/dist/chunk-2CRFCFE4.js +11 -0
- package/dist/chunk-2CRFCFE4.js.map +1 -0
- package/dist/{chunk-WXDGVKXK.js → chunk-ECXIFGGY.js} +146 -6
- package/dist/chunk-ECXIFGGY.js.map +1 -0
- package/dist/chunk-NLFKYSN7.js +640 -0
- package/dist/chunk-NLFKYSN7.js.map +1 -0
- package/dist/chunk-UALBZA5X.js +123 -0
- package/dist/chunk-UALBZA5X.js.map +1 -0
- package/dist/{chunk-FS55W24B.js → chunk-USJO3G5V.js} +29 -3
- package/dist/chunk-USJO3G5V.js.map +1 -0
- package/dist/chunk-YIWT7BWN.js +33 -0
- package/dist/chunk-YIWT7BWN.js.map +1 -0
- package/dist/{chunk-EL2WSQBR.js → chunk-ZA4RG25C.js} +6 -33
- package/dist/chunk-ZA4RG25C.js.map +1 -0
- package/dist/cli.js +2052 -87
- package/dist/cli.js.map +1 -1
- package/dist/{daemon-J4WSZXVY.js → daemon-2YGD43RJ.js} +3 -2
- package/dist/daemon-utils-LIG6K2MK.js +17 -0
- package/dist/doctor-YV6KQEGW.js +2019 -0
- package/dist/doctor-YV6KQEGW.js.map +1 -0
- package/dist/esm-GTDP7OMQ-EP6UYLQG.js +14442 -0
- package/dist/esm-GTDP7OMQ-EP6UYLQG.js.map +1 -0
- package/dist/mcp/server.js +7 -4
- package/dist/mcp/server.js.map +1 -1
- package/dist/postinstall.js +0 -69
- package/dist/postinstall.js.map +1 -1
- package/dist/{token-store-KQSMR6XA.js → token-store-Y35HF3FJ.js} +2 -2
- package/dist/token-store-Y35HF3FJ.js.map +1 -0
- package/package.json +4 -4
- package/dist/chunk-EL2WSQBR.js.map +0 -1
- package/dist/chunk-FS55W24B.js.map +0 -1
- package/dist/chunk-WXDGVKXK.js.map +0 -1
- /package/dist/{daemon-J4WSZXVY.js.map → daemon-2YGD43RJ.js.map} +0 -0
- /package/dist/{token-store-KQSMR6XA.js.map → daemon-utils-LIG6K2MK.js.map} +0 -0
|
@@ -0,0 +1,2019 @@
|
|
|
1
|
+
import {
|
|
2
|
+
closeDb,
|
|
3
|
+
config,
|
|
4
|
+
getDb,
|
|
5
|
+
loadUnifiedConfig,
|
|
6
|
+
readWatermark,
|
|
7
|
+
watermarkKey
|
|
8
|
+
} from "./chunk-NLFKYSN7.js";
|
|
9
|
+
import "./chunk-UALBZA5X.js";
|
|
10
|
+
import "./chunk-2CRFCFE4.js";
|
|
11
|
+
import "./chunk-PZ5AY32C.js";
|
|
12
|
+
|
|
13
|
+
// node_modules/.pnpm/@fml-inc+panopticon@0.2.2/node_modules/@fml-inc/panopticon/dist/chunk-QVK6VGCV.js
|
|
14
|
+
import fs2 from "fs";
|
|
15
|
+
import os from "os";
|
|
16
|
+
import path from "path";
|
|
17
|
+
import fs from "fs";
|
|
18
|
+
import fs3 from "fs";
|
|
19
|
+
import os2 from "os";
|
|
20
|
+
import path2 from "path";
|
|
21
|
+
import fs4 from "fs";
|
|
22
|
+
import os3 from "os";
|
|
23
|
+
import path3 from "path";
|
|
24
|
+
var targets = /* @__PURE__ */ new Map();
|
|
25
|
+
function registerTarget(adapter) {
|
|
26
|
+
if (targets.has(adapter.id)) {
|
|
27
|
+
throw new Error(`Target "${adapter.id}" is already registered`);
|
|
28
|
+
}
|
|
29
|
+
targets.set(adapter.id, adapter);
|
|
30
|
+
}
|
|
31
|
+
function allTargets() {
|
|
32
|
+
return [...targets.values()];
|
|
33
|
+
}
|
|
34
|
+
function defaultToolCategory(_toolName) {
|
|
35
|
+
return "";
|
|
36
|
+
}
|
|
37
|
+
function readNewLines(filePath, fromByteOffset) {
|
|
38
|
+
let fd;
|
|
39
|
+
try {
|
|
40
|
+
fd = fs.openSync(filePath, "r");
|
|
41
|
+
} catch {
|
|
42
|
+
return { lines: [], newByteOffset: fromByteOffset };
|
|
43
|
+
}
|
|
44
|
+
try {
|
|
45
|
+
const size = fs.fstatSync(fd).size;
|
|
46
|
+
if (size <= fromByteOffset) {
|
|
47
|
+
return { lines: [], newByteOffset: fromByteOffset };
|
|
48
|
+
}
|
|
49
|
+
const bytesToRead = size - fromByteOffset;
|
|
50
|
+
const buf = Buffer.alloc(bytesToRead);
|
|
51
|
+
fs.readSync(fd, buf, 0, bytesToRead, fromByteOffset);
|
|
52
|
+
const text = buf.toString("utf-8");
|
|
53
|
+
const lastNewline = text.lastIndexOf("\n");
|
|
54
|
+
if (lastNewline === -1) {
|
|
55
|
+
return { lines: [], newByteOffset: fromByteOffset };
|
|
56
|
+
}
|
|
57
|
+
const complete = text.slice(0, lastNewline);
|
|
58
|
+
const lines = complete.split("\n").filter((l) => l.length > 0);
|
|
59
|
+
const bytesConsumed = Buffer.byteLength(
|
|
60
|
+
text.slice(0, lastNewline + 1),
|
|
61
|
+
"utf-8"
|
|
62
|
+
);
|
|
63
|
+
return {
|
|
64
|
+
lines,
|
|
65
|
+
newByteOffset: fromByteOffset + bytesConsumed
|
|
66
|
+
};
|
|
67
|
+
} finally {
|
|
68
|
+
fs.closeSync(fd);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
var CLAUDE_TOOL_CATEGORIES = {
|
|
72
|
+
Read: "Read",
|
|
73
|
+
read_file: "Read",
|
|
74
|
+
ReadNotebook: "Read",
|
|
75
|
+
Edit: "Edit",
|
|
76
|
+
StrReplace: "Edit",
|
|
77
|
+
MultiEdit: "Edit",
|
|
78
|
+
Write: "Write",
|
|
79
|
+
create_file: "Write",
|
|
80
|
+
NotebookEdit: "Write",
|
|
81
|
+
Bash: "Bash",
|
|
82
|
+
Grep: "Grep",
|
|
83
|
+
Glob: "Glob",
|
|
84
|
+
list_dir: "Glob",
|
|
85
|
+
Task: "Task",
|
|
86
|
+
Agent: "Task",
|
|
87
|
+
TaskCreate: "Task",
|
|
88
|
+
TaskUpdate: "Task",
|
|
89
|
+
Skill: "Tool",
|
|
90
|
+
WebSearch: "Web",
|
|
91
|
+
WebFetch: "Web",
|
|
92
|
+
ToolSearch: "Web"
|
|
93
|
+
};
|
|
94
|
+
function claudeToolCategory(toolName) {
|
|
95
|
+
const mapped = CLAUDE_TOOL_CATEGORIES[toolName];
|
|
96
|
+
if (mapped) return mapped;
|
|
97
|
+
if (toolName.startsWith("mcp__")) return "MCP";
|
|
98
|
+
if (toolName.toLowerCase().includes("subagent")) return "Task";
|
|
99
|
+
return defaultToolCategory(toolName);
|
|
100
|
+
}
|
|
101
|
+
var SYSTEM_MESSAGE_PREFIXES = [
|
|
102
|
+
"This session is being continued",
|
|
103
|
+
"[Request interrupted",
|
|
104
|
+
"<task-notification>",
|
|
105
|
+
"<local-command-",
|
|
106
|
+
"Stop hook feedback:"
|
|
107
|
+
];
|
|
108
|
+
function isSystemMessage(content) {
|
|
109
|
+
const trimmed = content.trimStart();
|
|
110
|
+
return SYSTEM_MESSAGE_PREFIXES.some((p) => trimmed.startsWith(p));
|
|
111
|
+
}
|
|
112
|
+
var CMD_NAME_RE = /<command-name>([^<]+)<\/command-name>/;
|
|
113
|
+
var CMD_ARGS_RE = /<command-args>([^<]*)<\/command-args>/;
|
|
114
|
+
var CMD_MSG_RE = /<command-message>([^<]+)<\/command-message>/;
|
|
115
|
+
var CMD_STRIP_RE = /<\/?(?:command-name|command-message|command-args)>[^<]*<\/(?:command-name|command-message|command-args)>|<\/?(?:command-name|command-message|command-args)>/g;
|
|
116
|
+
function extractCommandText(content) {
|
|
117
|
+
const trimmed = content.replace(/^\uFEFF/, "").trimStart();
|
|
118
|
+
if (!trimmed.startsWith("<command-message>") && !trimmed.startsWith("<command-name>")) {
|
|
119
|
+
return [content, false];
|
|
120
|
+
}
|
|
121
|
+
const stripped = trimmed.replace(CMD_STRIP_RE, "");
|
|
122
|
+
if (stripped.trim() !== "") {
|
|
123
|
+
return [content, false];
|
|
124
|
+
}
|
|
125
|
+
const nameMatch = CMD_NAME_RE.exec(content);
|
|
126
|
+
if (!nameMatch) {
|
|
127
|
+
const msgMatch = CMD_MSG_RE.exec(content);
|
|
128
|
+
if (msgMatch) return [`/${msgMatch[1]}`, true];
|
|
129
|
+
return [content, false];
|
|
130
|
+
}
|
|
131
|
+
let name = nameMatch[1];
|
|
132
|
+
if (!name.startsWith("/")) name = `/${name}`;
|
|
133
|
+
const argsMatch = CMD_ARGS_RE.exec(content);
|
|
134
|
+
const args = argsMatch?.[1]?.trim();
|
|
135
|
+
return [args ? `${name} ${args}` : name, true];
|
|
136
|
+
}
|
|
137
|
+
function extractToolResultTextLength(content) {
|
|
138
|
+
if (typeof content === "string") return content.length;
|
|
139
|
+
if (Array.isArray(content)) {
|
|
140
|
+
let len = 0;
|
|
141
|
+
for (const b of content) {
|
|
142
|
+
if (typeof b === "object" && b !== null && b.type === "text") {
|
|
143
|
+
const text = b.text;
|
|
144
|
+
if (typeof text === "string") len += text.length;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return len;
|
|
148
|
+
}
|
|
149
|
+
return 0;
|
|
150
|
+
}
|
|
151
|
+
var FORK_THRESHOLD = 3;
|
|
152
|
+
function hasDAGFork(entries) {
|
|
153
|
+
for (let i = 1; i < entries.length; i++) {
|
|
154
|
+
if (entries[i].parentUuid !== entries[i - 1].uuid) return true;
|
|
155
|
+
}
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
function detectForks(entries, sessionId) {
|
|
159
|
+
const children = /* @__PURE__ */ new Map();
|
|
160
|
+
const roots = [];
|
|
161
|
+
const uuidSet = /* @__PURE__ */ new Set();
|
|
162
|
+
for (let i = 0; i < entries.length; i++) {
|
|
163
|
+
const e = entries[i];
|
|
164
|
+
uuidSet.add(e.uuid);
|
|
165
|
+
if (!e.parentUuid) {
|
|
166
|
+
roots.push(i);
|
|
167
|
+
} else {
|
|
168
|
+
const kids = children.get(e.parentUuid) ?? [];
|
|
169
|
+
kids.push(i);
|
|
170
|
+
children.set(e.parentUuid, kids);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
if (roots.length !== 1) {
|
|
174
|
+
return { mainDagIndices: entries.map((_, i) => i), forkBranches: [] };
|
|
175
|
+
}
|
|
176
|
+
for (const e of entries) {
|
|
177
|
+
if (e.parentUuid && !uuidSet.has(e.parentUuid)) {
|
|
178
|
+
return { mainDagIndices: entries.map((_, i) => i), forkBranches: [] };
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
const forkBranches = [];
|
|
182
|
+
function countUserTurns(startIdx) {
|
|
183
|
+
const stack = [startIdx];
|
|
184
|
+
let count = 0;
|
|
185
|
+
while (stack.length > 0) {
|
|
186
|
+
const idx = stack.pop();
|
|
187
|
+
if (entries[idx].type === "user") count++;
|
|
188
|
+
for (const k of children.get(entries[idx].uuid) ?? []) stack.push(k);
|
|
189
|
+
}
|
|
190
|
+
return count;
|
|
191
|
+
}
|
|
192
|
+
function walkBranch(startIdx, ownerId) {
|
|
193
|
+
const pathIndices = [];
|
|
194
|
+
let current = startIdx;
|
|
195
|
+
while (current !== null) {
|
|
196
|
+
pathIndices.push(current);
|
|
197
|
+
const kids = children.get(entries[current].uuid) ?? [];
|
|
198
|
+
if (kids.length === 0) {
|
|
199
|
+
current = null;
|
|
200
|
+
} else if (kids.length === 1) {
|
|
201
|
+
current = kids[0];
|
|
202
|
+
} else {
|
|
203
|
+
const firstChildTurns = countUserTurns(kids[0]);
|
|
204
|
+
if (firstChildTurns <= FORK_THRESHOLD) {
|
|
205
|
+
current = kids[kids.length - 1];
|
|
206
|
+
} else {
|
|
207
|
+
for (let k = 1; k < kids.length; k++) {
|
|
208
|
+
const branchIndices = walkBranch(kids[k], ownerId);
|
|
209
|
+
forkBranches.push({
|
|
210
|
+
parentId: ownerId,
|
|
211
|
+
dagIndices: branchIndices
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
current = kids[0];
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return pathIndices;
|
|
219
|
+
}
|
|
220
|
+
const mainDagIndices = walkBranch(roots[0], sessionId);
|
|
221
|
+
return { mainDagIndices, forkBranches };
|
|
222
|
+
}
|
|
223
|
+
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
224
|
+
var CLAUDE_DIR = path.join(os.homedir(), ".claude");
|
|
225
|
+
var claude = {
|
|
226
|
+
id: "claude",
|
|
227
|
+
config: {
|
|
228
|
+
dir: CLAUDE_DIR,
|
|
229
|
+
configPath: path.join(CLAUDE_DIR, "settings.json"),
|
|
230
|
+
configFormat: "json"
|
|
231
|
+
},
|
|
232
|
+
hooks: {
|
|
233
|
+
// Claude Code uses plugin marketplace, not direct hook registration.
|
|
234
|
+
// Marketplace setup is handled separately in the install command;
|
|
235
|
+
// this method handles the settings.json portion only.
|
|
236
|
+
events: [],
|
|
237
|
+
applyInstallConfig(existing, _opts) {
|
|
238
|
+
const settings = { ...existing };
|
|
239
|
+
settings.extraKnownMarketplaces = settings.extraKnownMarketplaces ?? {};
|
|
240
|
+
settings.extraKnownMarketplaces["local-plugins"] = {
|
|
241
|
+
source: { source: "directory", path: config.marketplaceDir }
|
|
242
|
+
};
|
|
243
|
+
settings.enabledPlugins = settings.enabledPlugins ?? {};
|
|
244
|
+
settings.enabledPlugins["panopticon@local-plugins"] = true;
|
|
245
|
+
const hooks = settings.hooks;
|
|
246
|
+
if (hooks) {
|
|
247
|
+
for (const event of Object.keys(hooks)) {
|
|
248
|
+
const entries = hooks[event];
|
|
249
|
+
if (!Array.isArray(entries)) continue;
|
|
250
|
+
hooks[event] = entries.filter(
|
|
251
|
+
(h) => !(typeof h === "object" && h !== null && JSON.stringify(h).includes("hook-handler"))
|
|
252
|
+
);
|
|
253
|
+
if (hooks[event].length === 0) delete hooks[event];
|
|
254
|
+
}
|
|
255
|
+
if (Object.keys(hooks).length === 0) delete settings.hooks;
|
|
256
|
+
}
|
|
257
|
+
return settings;
|
|
258
|
+
},
|
|
259
|
+
removeInstallConfig(existing) {
|
|
260
|
+
const settings = { ...existing };
|
|
261
|
+
const marketplaces = settings.extraKnownMarketplaces;
|
|
262
|
+
if (marketplaces) {
|
|
263
|
+
delete marketplaces["local-plugins"];
|
|
264
|
+
if (Object.keys(marketplaces).length === 0)
|
|
265
|
+
delete settings.extraKnownMarketplaces;
|
|
266
|
+
}
|
|
267
|
+
const plugins = settings.enabledPlugins;
|
|
268
|
+
if (plugins) {
|
|
269
|
+
delete plugins["panopticon@local-plugins"];
|
|
270
|
+
delete plugins["fml@local-plugins"];
|
|
271
|
+
if (Object.keys(plugins).length === 0) delete settings.enabledPlugins;
|
|
272
|
+
}
|
|
273
|
+
return settings;
|
|
274
|
+
}
|
|
275
|
+
},
|
|
276
|
+
shellEnv: {
|
|
277
|
+
envVars(port, proxy) {
|
|
278
|
+
const vars = [
|
|
279
|
+
["CLAUDE_CODE_ENABLE_TELEMETRY", "1"]
|
|
280
|
+
];
|
|
281
|
+
if (proxy) {
|
|
282
|
+
vars.push([
|
|
283
|
+
"ANTHROPIC_BASE_URL",
|
|
284
|
+
`http://localhost:${port}/proxy/anthropic`
|
|
285
|
+
]);
|
|
286
|
+
}
|
|
287
|
+
return vars;
|
|
288
|
+
}
|
|
289
|
+
},
|
|
290
|
+
events: {
|
|
291
|
+
// Claude Code already sends canonical event names
|
|
292
|
+
eventMap: {},
|
|
293
|
+
formatPermissionResponse({ allow, reason }) {
|
|
294
|
+
return {
|
|
295
|
+
hookSpecificOutput: {
|
|
296
|
+
hookEventName: "PreToolUse",
|
|
297
|
+
permissionDecision: allow ? "allow" : "deny",
|
|
298
|
+
permissionDecisionReason: reason
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
},
|
|
303
|
+
detect: {
|
|
304
|
+
displayName: "Claude Code",
|
|
305
|
+
isInstalled: () => fs2.existsSync(CLAUDE_DIR),
|
|
306
|
+
isConfigured() {
|
|
307
|
+
try {
|
|
308
|
+
const settings = JSON.parse(
|
|
309
|
+
fs2.readFileSync(path.join(CLAUDE_DIR, "settings.json"), "utf-8")
|
|
310
|
+
);
|
|
311
|
+
const plugins = settings.enabledPlugins ?? {};
|
|
312
|
+
return !!plugins["panopticon@local-plugins"] || !!plugins["fml@local-plugins"];
|
|
313
|
+
} catch {
|
|
314
|
+
return false;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
},
|
|
318
|
+
proxy: {
|
|
319
|
+
upstreamHost: "api.anthropic.com",
|
|
320
|
+
accumulatorType: "anthropic"
|
|
321
|
+
},
|
|
322
|
+
otel: {
|
|
323
|
+
metrics: {
|
|
324
|
+
metricNames: ["claude_code.token.usage"],
|
|
325
|
+
aggregation: "SUM",
|
|
326
|
+
tokenTypeAttrs: ["$.type"],
|
|
327
|
+
modelAttrs: ["$.model"]
|
|
328
|
+
}
|
|
329
|
+
},
|
|
330
|
+
ident: {
|
|
331
|
+
modelPatterns: [/^claude-/]
|
|
332
|
+
},
|
|
333
|
+
scanner: {
|
|
334
|
+
normalizeToolCategory: claudeToolCategory,
|
|
335
|
+
discover() {
|
|
336
|
+
const projectsDir = path.join(CLAUDE_DIR, "projects");
|
|
337
|
+
const files = [];
|
|
338
|
+
const safeReaddir = (d) => {
|
|
339
|
+
try {
|
|
340
|
+
return fs2.readdirSync(d);
|
|
341
|
+
} catch {
|
|
342
|
+
return [];
|
|
343
|
+
}
|
|
344
|
+
};
|
|
345
|
+
const safeIsDir = (d) => {
|
|
346
|
+
try {
|
|
347
|
+
return fs2.statSync(d).isDirectory();
|
|
348
|
+
} catch {
|
|
349
|
+
return false;
|
|
350
|
+
}
|
|
351
|
+
};
|
|
352
|
+
try {
|
|
353
|
+
for (const slug of fs2.readdirSync(projectsDir)) {
|
|
354
|
+
const slugDir = path.join(projectsDir, slug);
|
|
355
|
+
if (!safeIsDir(slugDir)) continue;
|
|
356
|
+
for (const entry of fs2.readdirSync(slugDir)) {
|
|
357
|
+
const entryPath = path.join(slugDir, entry);
|
|
358
|
+
if (entry.endsWith(".jsonl")) {
|
|
359
|
+
files.push({ filePath: entryPath });
|
|
360
|
+
}
|
|
361
|
+
if (safeIsDir(entryPath)) {
|
|
362
|
+
const subagentsDir = path.join(entryPath, "subagents");
|
|
363
|
+
for (const sub of safeReaddir(subagentsDir)) {
|
|
364
|
+
if (sub.endsWith(".jsonl")) {
|
|
365
|
+
files.push({
|
|
366
|
+
filePath: path.join(subagentsDir, sub)
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
} catch {
|
|
374
|
+
}
|
|
375
|
+
return files;
|
|
376
|
+
},
|
|
377
|
+
parseFile(filePath, fromByteOffset) {
|
|
378
|
+
const { lines, newByteOffset } = readNewLines(filePath, fromByteOffset);
|
|
379
|
+
if (lines.length === 0) return null;
|
|
380
|
+
const fileUuid = path.basename(filePath, ".jsonl");
|
|
381
|
+
const isSubagentPath = filePath.includes("/subagents/");
|
|
382
|
+
let meta;
|
|
383
|
+
const turns = [];
|
|
384
|
+
const events = [];
|
|
385
|
+
const messages = [];
|
|
386
|
+
let turnIndex = 0;
|
|
387
|
+
let ordinal = 0;
|
|
388
|
+
let firstPrompt;
|
|
389
|
+
const subagentMap = /* @__PURE__ */ new Map();
|
|
390
|
+
const orphanedToolResults = /* @__PURE__ */ new Map();
|
|
391
|
+
const dagEntries = [];
|
|
392
|
+
const msgLineIdx = [];
|
|
393
|
+
const turnLineIdx = [];
|
|
394
|
+
for (let lineIdx = 0; lineIdx < lines.length; lineIdx++) {
|
|
395
|
+
const line = lines[lineIdx];
|
|
396
|
+
let obj;
|
|
397
|
+
try {
|
|
398
|
+
obj = JSON.parse(line);
|
|
399
|
+
} catch {
|
|
400
|
+
continue;
|
|
401
|
+
}
|
|
402
|
+
const type = obj.type;
|
|
403
|
+
const sessionId = obj.sessionId;
|
|
404
|
+
const agentId = obj.agentId;
|
|
405
|
+
const tsMs = obj.timestamp ? new Date(obj.timestamp).getTime() : Date.now();
|
|
406
|
+
const uuid = obj.uuid;
|
|
407
|
+
const parentUuid = obj.parentUuid;
|
|
408
|
+
if (uuid && (type === "user" || type === "assistant")) {
|
|
409
|
+
dagEntries.push({
|
|
410
|
+
uuid,
|
|
411
|
+
parentUuid: parentUuid ?? "",
|
|
412
|
+
type,
|
|
413
|
+
lineIndex: lineIdx,
|
|
414
|
+
timestampMs: tsMs
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
if (!meta && sessionId) {
|
|
418
|
+
const common = {
|
|
419
|
+
cliVersion: obj.version,
|
|
420
|
+
cwd: obj.cwd,
|
|
421
|
+
startedAtMs: tsMs
|
|
422
|
+
};
|
|
423
|
+
if (agentId) {
|
|
424
|
+
meta = {
|
|
425
|
+
sessionId: `agent-${agentId}`,
|
|
426
|
+
parentSessionId: sessionId,
|
|
427
|
+
relationshipType: "subagent",
|
|
428
|
+
...common
|
|
429
|
+
};
|
|
430
|
+
} else if (!isSubagentPath && UUID_RE.test(fileUuid) && sessionId !== fileUuid) {
|
|
431
|
+
meta = {
|
|
432
|
+
sessionId: fileUuid,
|
|
433
|
+
parentSessionId: sessionId,
|
|
434
|
+
relationshipType: "continuation",
|
|
435
|
+
...common
|
|
436
|
+
};
|
|
437
|
+
} else {
|
|
438
|
+
meta = { sessionId, ...common };
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
const sid = meta?.sessionId ?? sessionId ?? "";
|
|
442
|
+
if (type === "user") {
|
|
443
|
+
const msg = obj.message;
|
|
444
|
+
const content = msg?.content;
|
|
445
|
+
let preview;
|
|
446
|
+
const textParts = [];
|
|
447
|
+
const toolResults = /* @__PURE__ */ new Map();
|
|
448
|
+
if (typeof content === "string") {
|
|
449
|
+
preview = content.slice(0, 200);
|
|
450
|
+
textParts.push(content);
|
|
451
|
+
} else if (Array.isArray(content)) {
|
|
452
|
+
for (const block of content) {
|
|
453
|
+
if (typeof block !== "object" || block === null) continue;
|
|
454
|
+
const b = block;
|
|
455
|
+
if (b.type === "text" && typeof b.text === "string") {
|
|
456
|
+
textParts.push(b.text);
|
|
457
|
+
} else if (b.type === "tool_result") {
|
|
458
|
+
const textLen = extractToolResultTextLength(b.content);
|
|
459
|
+
const raw = typeof b.content === "string" ? b.content : JSON.stringify(b.content ?? "");
|
|
460
|
+
toolResults.set(b.tool_use_id, {
|
|
461
|
+
contentLength: textLen,
|
|
462
|
+
contentRaw: raw,
|
|
463
|
+
timestampMs: tsMs
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
if (textParts.length > 0) {
|
|
468
|
+
preview = textParts[0].slice(0, 200);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
if (!firstPrompt && preview) firstPrompt = preview;
|
|
472
|
+
let fullContent = textParts.join("\n");
|
|
473
|
+
const isMeta = obj.isMeta === true;
|
|
474
|
+
const isCompact = obj.isCompactSummary === true;
|
|
475
|
+
if (isMeta || isCompact) {
|
|
476
|
+
} else if (fullContent.length === 0 && toolResults.size > 0) {
|
|
477
|
+
for (const [id, result] of toolResults) {
|
|
478
|
+
orphanedToolResults.set(id, result);
|
|
479
|
+
}
|
|
480
|
+
} else if (fullContent.length > 0) {
|
|
481
|
+
const [converted, wasCommand] = extractCommandText(fullContent);
|
|
482
|
+
if (wasCommand) {
|
|
483
|
+
fullContent = converted;
|
|
484
|
+
}
|
|
485
|
+
const isSystem = isSystemMessage(fullContent);
|
|
486
|
+
messages.push({
|
|
487
|
+
sessionId: sid,
|
|
488
|
+
ordinal: ordinal++,
|
|
489
|
+
role: "user",
|
|
490
|
+
content: fullContent,
|
|
491
|
+
timestampMs: tsMs,
|
|
492
|
+
hasThinking: false,
|
|
493
|
+
hasToolUse: false,
|
|
494
|
+
isSystem,
|
|
495
|
+
contentLength: fullContent.length,
|
|
496
|
+
hasContextTokens: false,
|
|
497
|
+
hasOutputTokens: false,
|
|
498
|
+
uuid,
|
|
499
|
+
parentUuid: parentUuid ?? void 0,
|
|
500
|
+
toolCalls: [],
|
|
501
|
+
toolResults
|
|
502
|
+
});
|
|
503
|
+
msgLineIdx.push(lineIdx);
|
|
504
|
+
}
|
|
505
|
+
turns.push({
|
|
506
|
+
sessionId: sid,
|
|
507
|
+
turnIndex: turnIndex++,
|
|
508
|
+
timestampMs: tsMs,
|
|
509
|
+
role: "user",
|
|
510
|
+
contentPreview: preview,
|
|
511
|
+
inputTokens: 0,
|
|
512
|
+
outputTokens: 0,
|
|
513
|
+
cacheReadTokens: 0,
|
|
514
|
+
cacheCreationTokens: 0,
|
|
515
|
+
reasoningTokens: 0
|
|
516
|
+
});
|
|
517
|
+
turnLineIdx.push(lineIdx);
|
|
518
|
+
}
|
|
519
|
+
if (type === "assistant") {
|
|
520
|
+
const msg = obj.message;
|
|
521
|
+
const usage = msg?.usage;
|
|
522
|
+
const model = msg?.model;
|
|
523
|
+
if (meta && model && !meta.model) meta.model = model;
|
|
524
|
+
const textParts = [];
|
|
525
|
+
let hasThinking = false;
|
|
526
|
+
let hasToolUse = false;
|
|
527
|
+
const toolCalls = [];
|
|
528
|
+
const content = msg?.content;
|
|
529
|
+
if (Array.isArray(content)) {
|
|
530
|
+
for (const block of content) {
|
|
531
|
+
if (typeof block !== "object" || block === null) continue;
|
|
532
|
+
const b = block;
|
|
533
|
+
if (b.type === "text" && typeof b.text === "string") {
|
|
534
|
+
textParts.push(b.text);
|
|
535
|
+
}
|
|
536
|
+
if (b.type === "thinking") {
|
|
537
|
+
hasThinking = true;
|
|
538
|
+
if (typeof b.thinking === "string") {
|
|
539
|
+
textParts.push(`[Thinking]
|
|
540
|
+
${b.thinking}
|
|
541
|
+
[/Thinking]`);
|
|
542
|
+
}
|
|
543
|
+
events.push({
|
|
544
|
+
sessionId: sid,
|
|
545
|
+
eventType: "thinking",
|
|
546
|
+
timestampMs: tsMs,
|
|
547
|
+
content: typeof b.thinking === "string" ? b.thinking.slice(0, 2e3) : void 0,
|
|
548
|
+
metadata: {
|
|
549
|
+
has_signature: !!b.signature
|
|
550
|
+
}
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
if (b.type === "tool_use") {
|
|
554
|
+
hasToolUse = true;
|
|
555
|
+
const toolName = b.name ?? "";
|
|
556
|
+
const input = b.input;
|
|
557
|
+
const inputJson = input ? JSON.stringify(input) : void 0;
|
|
558
|
+
let skillName;
|
|
559
|
+
if (toolName === "Skill" && input) {
|
|
560
|
+
skillName = input.skill ?? input.name;
|
|
561
|
+
}
|
|
562
|
+
toolCalls.push({
|
|
563
|
+
toolUseId: b.id ?? "",
|
|
564
|
+
toolName,
|
|
565
|
+
category: claudeToolCategory(toolName),
|
|
566
|
+
inputJson,
|
|
567
|
+
skillName,
|
|
568
|
+
timestampMs: tsMs
|
|
569
|
+
});
|
|
570
|
+
events.push({
|
|
571
|
+
sessionId: sid,
|
|
572
|
+
eventType: "tool_call",
|
|
573
|
+
timestampMs: tsMs,
|
|
574
|
+
toolName,
|
|
575
|
+
toolInput: inputJson?.slice(0, 1e4),
|
|
576
|
+
metadata: { tool_use_id: b.id }
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
const fullContent = textParts.join("\n");
|
|
582
|
+
const inputTokens = usage?.input_tokens ?? 0;
|
|
583
|
+
const outTokens = usage?.output_tokens ?? 0;
|
|
584
|
+
const cacheRead = usage?.cache_read_input_tokens ?? 0;
|
|
585
|
+
const cacheCreation = usage?.cache_creation_input_tokens ?? 0;
|
|
586
|
+
const ctxTokens = inputTokens + cacheRead + cacheCreation;
|
|
587
|
+
const hasCtx = inputTokens > 0 || cacheRead > 0 || cacheCreation > 0;
|
|
588
|
+
messages.push({
|
|
589
|
+
sessionId: sid,
|
|
590
|
+
ordinal: ordinal++,
|
|
591
|
+
role: "assistant",
|
|
592
|
+
content: fullContent,
|
|
593
|
+
timestampMs: tsMs,
|
|
594
|
+
hasThinking,
|
|
595
|
+
hasToolUse,
|
|
596
|
+
isSystem: false,
|
|
597
|
+
contentLength: fullContent.length,
|
|
598
|
+
model,
|
|
599
|
+
tokenUsage: usage ? JSON.stringify(usage) : void 0,
|
|
600
|
+
contextTokens: hasCtx ? ctxTokens : void 0,
|
|
601
|
+
outputTokens: outTokens > 0 ? outTokens : void 0,
|
|
602
|
+
hasContextTokens: hasCtx,
|
|
603
|
+
hasOutputTokens: outTokens > 0,
|
|
604
|
+
uuid,
|
|
605
|
+
parentUuid: parentUuid ?? void 0,
|
|
606
|
+
toolCalls,
|
|
607
|
+
toolResults: /* @__PURE__ */ new Map()
|
|
608
|
+
});
|
|
609
|
+
msgLineIdx.push(lineIdx);
|
|
610
|
+
turns.push({
|
|
611
|
+
sessionId: sid,
|
|
612
|
+
turnIndex: turnIndex++,
|
|
613
|
+
timestampMs: tsMs,
|
|
614
|
+
model,
|
|
615
|
+
role: "assistant",
|
|
616
|
+
inputTokens,
|
|
617
|
+
outputTokens: outTokens,
|
|
618
|
+
cacheReadTokens: cacheRead,
|
|
619
|
+
cacheCreationTokens: cacheCreation,
|
|
620
|
+
reasoningTokens: 0
|
|
621
|
+
});
|
|
622
|
+
turnLineIdx.push(lineIdx);
|
|
623
|
+
}
|
|
624
|
+
if (type === "user") {
|
|
625
|
+
const msg = obj.message;
|
|
626
|
+
const content = msg?.content;
|
|
627
|
+
if (Array.isArray(content)) {
|
|
628
|
+
for (const block of content) {
|
|
629
|
+
if (typeof block !== "object" || block === null) continue;
|
|
630
|
+
const b = block;
|
|
631
|
+
if (b.type === "tool_result") {
|
|
632
|
+
const resultContent = b.content;
|
|
633
|
+
events.push({
|
|
634
|
+
sessionId: sid,
|
|
635
|
+
eventType: "tool_result",
|
|
636
|
+
timestampMs: tsMs,
|
|
637
|
+
toolOutput: typeof resultContent === "string" ? resultContent.slice(0, 500) : void 0,
|
|
638
|
+
metadata: {
|
|
639
|
+
tool_use_id: b.tool_use_id,
|
|
640
|
+
is_error: b.is_error
|
|
641
|
+
}
|
|
642
|
+
});
|
|
643
|
+
}
|
|
644
|
+
if (b.type === "image") {
|
|
645
|
+
const src = b.source;
|
|
646
|
+
events.push({
|
|
647
|
+
sessionId: sid,
|
|
648
|
+
eventType: "image",
|
|
649
|
+
timestampMs: tsMs,
|
|
650
|
+
metadata: {
|
|
651
|
+
media_type: src?.media_type,
|
|
652
|
+
source_type: src?.type
|
|
653
|
+
}
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
if (type === "system") {
|
|
660
|
+
const data = obj.data;
|
|
661
|
+
const level = obj.level;
|
|
662
|
+
const subtype = obj.subtype;
|
|
663
|
+
if (data?.type === "api_error" || level === "error") {
|
|
664
|
+
events.push({
|
|
665
|
+
sessionId: sid,
|
|
666
|
+
eventType: "error",
|
|
667
|
+
timestampMs: tsMs,
|
|
668
|
+
content: typeof data?.message === "string" ? data.message : void 0,
|
|
669
|
+
metadata: {
|
|
670
|
+
level,
|
|
671
|
+
retryAttempt: data?.retryAttempt,
|
|
672
|
+
maxRetries: data?.maxRetries,
|
|
673
|
+
retryInMs: data?.retryInMs
|
|
674
|
+
}
|
|
675
|
+
});
|
|
676
|
+
} else if (subtype === "stop_hook_summary" || level === "suggestion") {
|
|
677
|
+
events.push({
|
|
678
|
+
sessionId: sid,
|
|
679
|
+
eventType: subtype ?? "system",
|
|
680
|
+
timestampMs: tsMs,
|
|
681
|
+
metadata: {
|
|
682
|
+
subtype,
|
|
683
|
+
level,
|
|
684
|
+
hookCount: obj.hookCount,
|
|
685
|
+
hookInfos: obj.hookInfos,
|
|
686
|
+
stopReason: obj.stopReason,
|
|
687
|
+
preventedContinuation: obj.preventedContinuation
|
|
688
|
+
}
|
|
689
|
+
});
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
if (type === "file-history-snapshot") {
|
|
693
|
+
const data = obj.data;
|
|
694
|
+
const messageId = obj.messageId;
|
|
695
|
+
events.push({
|
|
696
|
+
sessionId: sid,
|
|
697
|
+
eventType: "file_snapshot",
|
|
698
|
+
timestampMs: tsMs,
|
|
699
|
+
metadata: { messageId, ...data ?? {} }
|
|
700
|
+
});
|
|
701
|
+
}
|
|
702
|
+
if (type === "progress") {
|
|
703
|
+
const data = obj.data;
|
|
704
|
+
const hookEvent = data?.hookEvent;
|
|
705
|
+
const progressType = data?.type;
|
|
706
|
+
if (hookEvent || data?.durationMs) {
|
|
707
|
+
events.push({
|
|
708
|
+
sessionId: sid,
|
|
709
|
+
eventType: hookEvent ? `progress:${hookEvent}` : "progress",
|
|
710
|
+
timestampMs: tsMs,
|
|
711
|
+
toolName: data?.hookName ?? data?.toolName,
|
|
712
|
+
metadata: {
|
|
713
|
+
hookEvent,
|
|
714
|
+
durationMs: data?.durationMs
|
|
715
|
+
}
|
|
716
|
+
});
|
|
717
|
+
} else if (progressType === "agent_progress") {
|
|
718
|
+
const tuid = data?.parentToolUseID ?? obj.parentToolUseID;
|
|
719
|
+
const agentId2 = data?.agentId ?? obj.agentId;
|
|
720
|
+
if (tuid && agentId2) {
|
|
721
|
+
subagentMap.set(tuid, `agent-${agentId2}`);
|
|
722
|
+
}
|
|
723
|
+
events.push({
|
|
724
|
+
sessionId: sid,
|
|
725
|
+
eventType: "agent_progress",
|
|
726
|
+
timestampMs: tsMs,
|
|
727
|
+
metadata: {
|
|
728
|
+
parentToolUseID: tuid,
|
|
729
|
+
toolUseID: obj.toolUseID
|
|
730
|
+
}
|
|
731
|
+
});
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
if (type === "queue-operation") {
|
|
735
|
+
const operation = obj.operation;
|
|
736
|
+
if (operation === "enqueue" && typeof obj.content === "string") {
|
|
737
|
+
try {
|
|
738
|
+
const qc = JSON.parse(obj.content);
|
|
739
|
+
const tuid = qc.tool_use_id;
|
|
740
|
+
const taskId = qc.task_id;
|
|
741
|
+
if (tuid && taskId) {
|
|
742
|
+
subagentMap.set(tuid, `agent-${taskId}`);
|
|
743
|
+
}
|
|
744
|
+
} catch {
|
|
745
|
+
const tuidMatch = obj.content.match(
|
|
746
|
+
/<tool-use-id>([^<]+)<\/tool-use-id>/
|
|
747
|
+
);
|
|
748
|
+
const taskMatch = obj.content.match(
|
|
749
|
+
/<task-id>([^<]+)<\/task-id>/
|
|
750
|
+
);
|
|
751
|
+
if (tuidMatch?.[1] && taskMatch?.[1]) {
|
|
752
|
+
subagentMap.set(tuidMatch[1], `agent-${taskMatch[1]}`);
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
events.push({
|
|
757
|
+
sessionId: sid,
|
|
758
|
+
eventType: `queue:${operation ?? "unknown"}`,
|
|
759
|
+
timestampMs: tsMs,
|
|
760
|
+
content: typeof obj.content === "string" ? obj.content.slice(0, 500) : void 0
|
|
761
|
+
});
|
|
762
|
+
}
|
|
763
|
+
if (type === "last-prompt") {
|
|
764
|
+
events.push({
|
|
765
|
+
sessionId: sid,
|
|
766
|
+
eventType: "last_prompt",
|
|
767
|
+
timestampMs: tsMs,
|
|
768
|
+
content: typeof obj.lastPrompt === "string" ? obj.lastPrompt.slice(0, 500) : void 0
|
|
769
|
+
});
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
if (meta && firstPrompt && !meta.firstPrompt)
|
|
773
|
+
meta.firstPrompt = firstPrompt;
|
|
774
|
+
if (subagentMap.size > 0) {
|
|
775
|
+
for (const msg of messages) {
|
|
776
|
+
for (const tc of msg.toolCalls) {
|
|
777
|
+
const agentSid = subagentMap.get(tc.toolUseId);
|
|
778
|
+
if (agentSid && (tc.category === "Task" || tc.toolName === "Agent"))
|
|
779
|
+
tc.subagentSessionId = agentSid;
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
if (fromByteOffset > 0 && dagEntries.length > 1 && hasDAGFork(dagEntries)) {
|
|
784
|
+
return {
|
|
785
|
+
meta,
|
|
786
|
+
turns: [],
|
|
787
|
+
events: [],
|
|
788
|
+
messages: [],
|
|
789
|
+
newByteOffset: fromByteOffset,
|
|
790
|
+
// don't advance watermark
|
|
791
|
+
needsFullReparse: true
|
|
792
|
+
};
|
|
793
|
+
}
|
|
794
|
+
if (fromByteOffset === 0 && meta && dagEntries.length > 1 && hasDAGFork(dagEntries)) {
|
|
795
|
+
const { mainDagIndices, forkBranches } = detectForks(
|
|
796
|
+
dagEntries,
|
|
797
|
+
meta.sessionId
|
|
798
|
+
);
|
|
799
|
+
if (forkBranches.length > 0) {
|
|
800
|
+
const mainLineSet = new Set(
|
|
801
|
+
mainDagIndices.map((i) => dagEntries[i].lineIndex)
|
|
802
|
+
);
|
|
803
|
+
const mainMessages = messages.filter(
|
|
804
|
+
(_, i) => mainLineSet.has(msgLineIdx[i])
|
|
805
|
+
);
|
|
806
|
+
const mainTurns = turns.filter(
|
|
807
|
+
(_, i) => mainLineSet.has(turnLineIdx[i])
|
|
808
|
+
);
|
|
809
|
+
for (let i = 0; i < mainMessages.length; i++) {
|
|
810
|
+
mainMessages[i] = { ...mainMessages[i], ordinal: i };
|
|
811
|
+
}
|
|
812
|
+
for (let i = 0; i < mainTurns.length; i++) {
|
|
813
|
+
mainTurns[i] = { ...mainTurns[i], turnIndex: i };
|
|
814
|
+
}
|
|
815
|
+
const forks = [];
|
|
816
|
+
for (const branch of forkBranches) {
|
|
817
|
+
const branchLineSet = new Set(
|
|
818
|
+
branch.dagIndices.map((i) => dagEntries[i].lineIndex)
|
|
819
|
+
);
|
|
820
|
+
const forkUuid = dagEntries[branch.dagIndices[0]].uuid;
|
|
821
|
+
const forkSessionId = `${meta.sessionId}-${forkUuid}`;
|
|
822
|
+
const forkStartMs = dagEntries[branch.dagIndices[0]].timestampMs;
|
|
823
|
+
const forkMessages = messages.filter((_, i) => branchLineSet.has(msgLineIdx[i])).map((m, i) => ({
|
|
824
|
+
...m,
|
|
825
|
+
sessionId: forkSessionId,
|
|
826
|
+
ordinal: i
|
|
827
|
+
}));
|
|
828
|
+
const forkTurns = turns.filter((_, i) => branchLineSet.has(turnLineIdx[i])).map((t, i) => ({
|
|
829
|
+
...t,
|
|
830
|
+
sessionId: forkSessionId,
|
|
831
|
+
turnIndex: i
|
|
832
|
+
}));
|
|
833
|
+
forks.push({
|
|
834
|
+
meta: {
|
|
835
|
+
sessionId: forkSessionId,
|
|
836
|
+
parentSessionId: branch.parentId,
|
|
837
|
+
relationshipType: "fork",
|
|
838
|
+
model: meta.model,
|
|
839
|
+
cwd: meta.cwd,
|
|
840
|
+
cliVersion: meta.cliVersion,
|
|
841
|
+
startedAtMs: forkStartMs
|
|
842
|
+
},
|
|
843
|
+
turns: forkTurns,
|
|
844
|
+
events: [],
|
|
845
|
+
// events stay in main result
|
|
846
|
+
messages: forkMessages,
|
|
847
|
+
newByteOffset
|
|
848
|
+
});
|
|
849
|
+
}
|
|
850
|
+
return {
|
|
851
|
+
meta,
|
|
852
|
+
turns: mainTurns,
|
|
853
|
+
events,
|
|
854
|
+
// all events stay in main
|
|
855
|
+
messages: mainMessages,
|
|
856
|
+
newByteOffset,
|
|
857
|
+
forks,
|
|
858
|
+
orphanedToolResults: orphanedToolResults.size > 0 ? orphanedToolResults : void 0
|
|
859
|
+
};
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
return {
|
|
863
|
+
meta,
|
|
864
|
+
turns,
|
|
865
|
+
events,
|
|
866
|
+
messages,
|
|
867
|
+
newByteOffset,
|
|
868
|
+
orphanedToolResults: orphanedToolResults.size > 0 ? orphanedToolResults : void 0
|
|
869
|
+
};
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
};
|
|
873
|
+
registerTarget(claude);
|
|
874
|
+
var GEMINI_TOOL_CATEGORIES = {
|
|
875
|
+
read_file: "Read",
|
|
876
|
+
read_many_files: "Read",
|
|
877
|
+
write_file: "Write",
|
|
878
|
+
edit_file: "Edit",
|
|
879
|
+
run_shell_command: "Bash",
|
|
880
|
+
shell: "Bash",
|
|
881
|
+
glob: "Glob",
|
|
882
|
+
list_directory: "Glob",
|
|
883
|
+
grep: "Grep",
|
|
884
|
+
search_files: "Grep",
|
|
885
|
+
web_search: "Web"
|
|
886
|
+
};
|
|
887
|
+
function geminiToolCategory(toolName) {
|
|
888
|
+
return GEMINI_TOOL_CATEGORIES[toolName] ?? defaultToolCategory(toolName);
|
|
889
|
+
}
|
|
890
|
+
var GEMINI_DIR = path2.join(os2.homedir(), ".gemini");
|
|
891
|
+
var gemini = {
|
|
892
|
+
id: "gemini",
|
|
893
|
+
config: {
|
|
894
|
+
dir: GEMINI_DIR,
|
|
895
|
+
configPath: path2.join(GEMINI_DIR, "settings.json"),
|
|
896
|
+
configFormat: "json"
|
|
897
|
+
},
|
|
898
|
+
hooks: {
|
|
899
|
+
// Gemini's native event names
|
|
900
|
+
events: ["SessionStart", "BeforeModel", "BeforeTool", "AfterTool"],
|
|
901
|
+
applyInstallConfig(existing, opts) {
|
|
902
|
+
const settings = { ...existing };
|
|
903
|
+
const hookBin = path2.join(opts.pluginRoot, "bin", "hook-handler");
|
|
904
|
+
const mcpBin = path2.join(opts.pluginRoot, "bin", "mcp-server");
|
|
905
|
+
const hooks = structuredClone(
|
|
906
|
+
settings.hooks ?? {}
|
|
907
|
+
);
|
|
908
|
+
for (const event of this.events) {
|
|
909
|
+
hooks[event] = hooks[event] || [];
|
|
910
|
+
hooks[event] = hooks[event].map((group) => ({
|
|
911
|
+
...group,
|
|
912
|
+
hooks: (group.hooks || []).filter(
|
|
913
|
+
(h) => !h.command?.includes("panopticon") && !h.path?.includes("panopticon")
|
|
914
|
+
)
|
|
915
|
+
})).filter((group) => group.hooks.length > 0);
|
|
916
|
+
hooks[event].push({
|
|
917
|
+
hooks: [
|
|
918
|
+
{
|
|
919
|
+
type: "command",
|
|
920
|
+
command: `node ${hookBin} gemini ${opts.port}${opts.proxy ? " --proxy" : ""}`
|
|
921
|
+
}
|
|
922
|
+
]
|
|
923
|
+
});
|
|
924
|
+
}
|
|
925
|
+
settings.hooks = hooks;
|
|
926
|
+
settings.mcpServers = settings.mcpServers || {};
|
|
927
|
+
settings.mcpServers.panopticon = {
|
|
928
|
+
command: "node",
|
|
929
|
+
args: [mcpBin]
|
|
930
|
+
};
|
|
931
|
+
settings.telemetry = settings.telemetry || {};
|
|
932
|
+
Object.assign(settings.telemetry, {
|
|
933
|
+
enabled: true,
|
|
934
|
+
target: "local",
|
|
935
|
+
otlpProtocol: "http",
|
|
936
|
+
otlpEndpoint: `http://localhost:${opts.port}`
|
|
937
|
+
});
|
|
938
|
+
return settings;
|
|
939
|
+
},
|
|
940
|
+
removeInstallConfig(existing) {
|
|
941
|
+
const settings = { ...existing };
|
|
942
|
+
const hooks = settings.hooks;
|
|
943
|
+
if (hooks) {
|
|
944
|
+
for (const event of Object.keys(hooks)) {
|
|
945
|
+
hooks[event] = hooks[event].map((group) => ({
|
|
946
|
+
...group,
|
|
947
|
+
hooks: (group.hooks || []).filter(
|
|
948
|
+
(h) => !h.command?.includes("panopticon") && !h.path?.includes("panopticon")
|
|
949
|
+
)
|
|
950
|
+
})).filter((group) => group.hooks.length > 0);
|
|
951
|
+
if (hooks[event].length === 0) delete hooks[event];
|
|
952
|
+
}
|
|
953
|
+
if (Object.keys(hooks).length === 0) delete settings.hooks;
|
|
954
|
+
}
|
|
955
|
+
delete settings.hooksConfig;
|
|
956
|
+
const servers = settings.mcpServers;
|
|
957
|
+
if (servers) {
|
|
958
|
+
delete servers.panopticon;
|
|
959
|
+
if (Object.keys(servers).length === 0) delete settings.mcpServers;
|
|
960
|
+
}
|
|
961
|
+
delete settings.telemetry;
|
|
962
|
+
return settings;
|
|
963
|
+
}
|
|
964
|
+
},
|
|
965
|
+
shellEnv: {
|
|
966
|
+
envVars(port) {
|
|
967
|
+
return [
|
|
968
|
+
["GEMINI_TELEMETRY_ENABLED", "true"],
|
|
969
|
+
["GEMINI_TELEMETRY_TARGET", "local"],
|
|
970
|
+
["GEMINI_TELEMETRY_USE_COLLECTOR", "true"],
|
|
971
|
+
["GEMINI_TELEMETRY_OTLP_ENDPOINT", `http://localhost:${port}`],
|
|
972
|
+
["GEMINI_TELEMETRY_OTLP_PROTOCOL", "http"],
|
|
973
|
+
["GEMINI_TELEMETRY_LOG_PROMPTS", "true"]
|
|
974
|
+
];
|
|
975
|
+
}
|
|
976
|
+
},
|
|
977
|
+
events: {
|
|
978
|
+
eventMap: {
|
|
979
|
+
BeforeTool: "PreToolUse",
|
|
980
|
+
AfterTool: "PostToolUse",
|
|
981
|
+
BeforeModel: "UserPromptSubmit"
|
|
982
|
+
},
|
|
983
|
+
normalizePayload(data) {
|
|
984
|
+
const messages = data.llm_request;
|
|
985
|
+
if (messages?.messages && Array.isArray(messages.messages)) {
|
|
986
|
+
const lastUser = [...messages.messages].reverse().find((m) => m.role === "user");
|
|
987
|
+
if (lastUser?.content) {
|
|
988
|
+
const text = typeof lastUser.content === "string" ? lastUser.content : Array.isArray(lastUser.content) ? lastUser.content.filter((p) => p.type === "text").map((p) => p.text).join("\n") : "";
|
|
989
|
+
if (text) {
|
|
990
|
+
data.user_prompt = text;
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
return data;
|
|
995
|
+
},
|
|
996
|
+
formatPermissionResponse({ allow, reason }) {
|
|
997
|
+
return { decision: allow ? "allow" : "deny", reason };
|
|
998
|
+
}
|
|
999
|
+
},
|
|
1000
|
+
detect: {
|
|
1001
|
+
displayName: "Gemini CLI",
|
|
1002
|
+
isInstalled: () => fs3.existsSync(GEMINI_DIR),
|
|
1003
|
+
isConfigured() {
|
|
1004
|
+
try {
|
|
1005
|
+
const settings = JSON.parse(
|
|
1006
|
+
fs3.readFileSync(path2.join(GEMINI_DIR, "settings.json"), "utf-8")
|
|
1007
|
+
);
|
|
1008
|
+
return !!settings.telemetry?.enabled;
|
|
1009
|
+
} catch {
|
|
1010
|
+
return false;
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
},
|
|
1014
|
+
proxy: {
|
|
1015
|
+
upstreamHost: "generativelanguage.googleapis.com",
|
|
1016
|
+
accumulatorType: "openai"
|
|
1017
|
+
},
|
|
1018
|
+
otel: {
|
|
1019
|
+
serviceName: "gemini-cli",
|
|
1020
|
+
metrics: {
|
|
1021
|
+
metricNames: ["gemini_cli.token.usage", "gen_ai.client.token.usage"],
|
|
1022
|
+
aggregation: "MAX",
|
|
1023
|
+
tokenTypeAttrs: ['$."gen_ai.token.type"'],
|
|
1024
|
+
modelAttrs: ['$."gen_ai.response.model"']
|
|
1025
|
+
}
|
|
1026
|
+
},
|
|
1027
|
+
scanner: {
|
|
1028
|
+
normalizeToolCategory: geminiToolCategory,
|
|
1029
|
+
discover() {
|
|
1030
|
+
const tmpDir = path2.join(GEMINI_DIR, "tmp");
|
|
1031
|
+
const files = [];
|
|
1032
|
+
const safeReaddir = (d) => {
|
|
1033
|
+
try {
|
|
1034
|
+
return fs3.readdirSync(d);
|
|
1035
|
+
} catch {
|
|
1036
|
+
return [];
|
|
1037
|
+
}
|
|
1038
|
+
};
|
|
1039
|
+
for (const project of safeReaddir(tmpDir)) {
|
|
1040
|
+
const chatsDir = path2.join(tmpDir, project, "chats");
|
|
1041
|
+
for (const entry of safeReaddir(chatsDir)) {
|
|
1042
|
+
if (entry.startsWith("session-") && entry.endsWith(".json"))
|
|
1043
|
+
files.push({ filePath: path2.join(chatsDir, entry) });
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
return files;
|
|
1047
|
+
},
|
|
1048
|
+
parseFile(filePath, fromByteOffset) {
|
|
1049
|
+
let size;
|
|
1050
|
+
try {
|
|
1051
|
+
size = fs3.statSync(filePath).size;
|
|
1052
|
+
} catch {
|
|
1053
|
+
return null;
|
|
1054
|
+
}
|
|
1055
|
+
if (size <= fromByteOffset) return null;
|
|
1056
|
+
let session;
|
|
1057
|
+
try {
|
|
1058
|
+
session = JSON.parse(fs3.readFileSync(filePath, "utf-8"));
|
|
1059
|
+
} catch {
|
|
1060
|
+
return null;
|
|
1061
|
+
}
|
|
1062
|
+
const sessionId = session.sessionId;
|
|
1063
|
+
if (!sessionId) return null;
|
|
1064
|
+
const messages = session.messages;
|
|
1065
|
+
if (!messages?.length) return null;
|
|
1066
|
+
const meta = {
|
|
1067
|
+
sessionId,
|
|
1068
|
+
startedAtMs: session.startTime ? new Date(session.startTime).getTime() : void 0
|
|
1069
|
+
};
|
|
1070
|
+
const turns = [];
|
|
1071
|
+
const events = [];
|
|
1072
|
+
const parsedMessages = [];
|
|
1073
|
+
let turnIndex = 0;
|
|
1074
|
+
let ordinal = 0;
|
|
1075
|
+
let firstPrompt;
|
|
1076
|
+
for (const msg of messages) {
|
|
1077
|
+
const type = msg.type;
|
|
1078
|
+
const timestamp = msg.timestamp;
|
|
1079
|
+
const timestampMs = timestamp ? new Date(timestamp).getTime() : Date.now();
|
|
1080
|
+
if (type === "user") {
|
|
1081
|
+
const content = msg.content;
|
|
1082
|
+
const textParts = [];
|
|
1083
|
+
if (content) {
|
|
1084
|
+
for (const block of content) {
|
|
1085
|
+
if (block.text) textParts.push(block.text);
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
const fullContent = textParts.join("\n");
|
|
1089
|
+
const preview = textParts[0]?.slice(0, 200);
|
|
1090
|
+
if (!firstPrompt && preview) firstPrompt = preview;
|
|
1091
|
+
turns.push({
|
|
1092
|
+
sessionId,
|
|
1093
|
+
turnIndex: turnIndex++,
|
|
1094
|
+
timestampMs,
|
|
1095
|
+
role: "user",
|
|
1096
|
+
contentPreview: preview,
|
|
1097
|
+
inputTokens: 0,
|
|
1098
|
+
outputTokens: 0,
|
|
1099
|
+
cacheReadTokens: 0,
|
|
1100
|
+
cacheCreationTokens: 0,
|
|
1101
|
+
reasoningTokens: 0
|
|
1102
|
+
});
|
|
1103
|
+
if (fullContent.length > 0) {
|
|
1104
|
+
parsedMessages.push({
|
|
1105
|
+
sessionId,
|
|
1106
|
+
ordinal: ordinal++,
|
|
1107
|
+
role: "user",
|
|
1108
|
+
content: fullContent,
|
|
1109
|
+
timestampMs,
|
|
1110
|
+
hasThinking: false,
|
|
1111
|
+
hasToolUse: false,
|
|
1112
|
+
isSystem: false,
|
|
1113
|
+
contentLength: fullContent.length,
|
|
1114
|
+
hasContextTokens: false,
|
|
1115
|
+
hasOutputTokens: false,
|
|
1116
|
+
toolCalls: [],
|
|
1117
|
+
toolResults: /* @__PURE__ */ new Map()
|
|
1118
|
+
});
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
if (type === "gemini") {
|
|
1122
|
+
const model = msg.model;
|
|
1123
|
+
const tokens = msg.tokens;
|
|
1124
|
+
if (model && !meta.model) meta.model = model;
|
|
1125
|
+
const inputTokens = tokens?.input ?? 0;
|
|
1126
|
+
const outTokens = tokens?.output ?? 0;
|
|
1127
|
+
const cacheRead = tokens?.cached ?? 0;
|
|
1128
|
+
const reasoning = tokens?.thoughts ?? 0;
|
|
1129
|
+
turns.push({
|
|
1130
|
+
sessionId,
|
|
1131
|
+
turnIndex: turnIndex++,
|
|
1132
|
+
timestampMs,
|
|
1133
|
+
model,
|
|
1134
|
+
role: "assistant",
|
|
1135
|
+
contentPreview: typeof msg.content === "string" ? msg.content.slice(0, 200) : void 0,
|
|
1136
|
+
inputTokens,
|
|
1137
|
+
outputTokens: outTokens,
|
|
1138
|
+
cacheReadTokens: cacheRead,
|
|
1139
|
+
cacheCreationTokens: 0,
|
|
1140
|
+
reasoningTokens: reasoning
|
|
1141
|
+
});
|
|
1142
|
+
const textParts = [];
|
|
1143
|
+
if (typeof msg.content === "string") textParts.push(msg.content);
|
|
1144
|
+
const msgToolCalls = msg.toolCalls;
|
|
1145
|
+
const toolCalls = [];
|
|
1146
|
+
const toolResults = /* @__PURE__ */ new Map();
|
|
1147
|
+
if (msgToolCalls) {
|
|
1148
|
+
for (let tcIdx = 0; tcIdx < msgToolCalls.length; tcIdx++) {
|
|
1149
|
+
const tc = msgToolCalls[tcIdx];
|
|
1150
|
+
const toolName = tc.name ?? tc.displayName ?? "";
|
|
1151
|
+
const inputJson = tc.args ? JSON.stringify(tc.args) : void 0;
|
|
1152
|
+
const toolUseId = tc.id || `${toolName}-${timestampMs}-${tcIdx}`;
|
|
1153
|
+
toolCalls.push({
|
|
1154
|
+
toolUseId,
|
|
1155
|
+
toolName,
|
|
1156
|
+
category: geminiToolCategory(toolName),
|
|
1157
|
+
inputJson
|
|
1158
|
+
});
|
|
1159
|
+
const result = tc.result;
|
|
1160
|
+
const fnResponse = result?.[0]?.functionResponse;
|
|
1161
|
+
const responseObj = fnResponse?.response;
|
|
1162
|
+
const output = typeof responseObj?.output === "string" ? responseObj.output : fnResponse ? JSON.stringify(fnResponse) : void 0;
|
|
1163
|
+
if (output) {
|
|
1164
|
+
toolResults.set(toolUseId, {
|
|
1165
|
+
contentLength: output.length,
|
|
1166
|
+
contentRaw: output
|
|
1167
|
+
});
|
|
1168
|
+
}
|
|
1169
|
+
events.push({
|
|
1170
|
+
sessionId,
|
|
1171
|
+
eventType: "tool_call",
|
|
1172
|
+
timestampMs,
|
|
1173
|
+
toolName,
|
|
1174
|
+
toolInput: inputJson?.slice(0, 1e3),
|
|
1175
|
+
toolOutput: output?.slice(0, 1e3)
|
|
1176
|
+
});
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
let hasThinking = false;
|
|
1180
|
+
const thoughts = msg.thoughts;
|
|
1181
|
+
if (thoughts) {
|
|
1182
|
+
hasThinking = true;
|
|
1183
|
+
for (const t of thoughts) {
|
|
1184
|
+
const text = t.description ?? t.subject;
|
|
1185
|
+
if (text) textParts.push(`[Thinking]
|
|
1186
|
+
${text}
|
|
1187
|
+
[/Thinking]`);
|
|
1188
|
+
events.push({
|
|
1189
|
+
sessionId,
|
|
1190
|
+
eventType: "reasoning",
|
|
1191
|
+
timestampMs,
|
|
1192
|
+
content: text?.slice(0, 500)
|
|
1193
|
+
});
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
const fullContent = textParts.join("\n");
|
|
1197
|
+
const ctxTokens = inputTokens + cacheRead;
|
|
1198
|
+
const hasCtx = inputTokens > 0 || cacheRead > 0;
|
|
1199
|
+
parsedMessages.push({
|
|
1200
|
+
sessionId,
|
|
1201
|
+
ordinal: ordinal++,
|
|
1202
|
+
role: "assistant",
|
|
1203
|
+
content: fullContent,
|
|
1204
|
+
timestampMs,
|
|
1205
|
+
hasThinking,
|
|
1206
|
+
hasToolUse: toolCalls.length > 0,
|
|
1207
|
+
isSystem: false,
|
|
1208
|
+
contentLength: fullContent.length,
|
|
1209
|
+
model,
|
|
1210
|
+
tokenUsage: tokens ? JSON.stringify(tokens) : void 0,
|
|
1211
|
+
contextTokens: hasCtx ? ctxTokens : void 0,
|
|
1212
|
+
outputTokens: outTokens > 0 ? outTokens : void 0,
|
|
1213
|
+
hasContextTokens: hasCtx,
|
|
1214
|
+
hasOutputTokens: outTokens > 0,
|
|
1215
|
+
toolCalls,
|
|
1216
|
+
toolResults
|
|
1217
|
+
});
|
|
1218
|
+
}
|
|
1219
|
+
if (type === "info") {
|
|
1220
|
+
events.push({
|
|
1221
|
+
sessionId,
|
|
1222
|
+
eventType: "info",
|
|
1223
|
+
timestampMs,
|
|
1224
|
+
content: typeof msg.content === "string" ? msg.content.slice(0, 500) : void 0
|
|
1225
|
+
});
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
if (firstPrompt) meta.firstPrompt = firstPrompt;
|
|
1229
|
+
return {
|
|
1230
|
+
meta,
|
|
1231
|
+
turns,
|
|
1232
|
+
events,
|
|
1233
|
+
messages: parsedMessages,
|
|
1234
|
+
newByteOffset: size,
|
|
1235
|
+
absoluteIndices: true
|
|
1236
|
+
};
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
};
|
|
1240
|
+
registerTarget(gemini);
|
|
1241
|
+
var CODEX_TOOL_CATEGORIES = {
|
|
1242
|
+
shell_command: "Bash",
|
|
1243
|
+
shell: "Bash",
|
|
1244
|
+
exec_command: "Bash",
|
|
1245
|
+
write_stdin: "Bash",
|
|
1246
|
+
run_command: "Bash",
|
|
1247
|
+
read_file: "Read",
|
|
1248
|
+
write_file: "Write",
|
|
1249
|
+
create_file: "Write",
|
|
1250
|
+
edit_file: "Edit",
|
|
1251
|
+
list_dir: "Glob",
|
|
1252
|
+
grep_search: "Grep",
|
|
1253
|
+
finder: "Grep",
|
|
1254
|
+
spawn_agent: "Task"
|
|
1255
|
+
};
|
|
1256
|
+
function codexToolCategory(toolName) {
|
|
1257
|
+
return CODEX_TOOL_CATEGORIES[toolName] ?? defaultToolCategory(toolName);
|
|
1258
|
+
}
|
|
1259
|
+
var CODEX_SYSTEM_PREFIXES = [
|
|
1260
|
+
"# AGENTS.md",
|
|
1261
|
+
"<environment_context>",
|
|
1262
|
+
"<INSTRUCTIONS>",
|
|
1263
|
+
"<subagent_notification>"
|
|
1264
|
+
];
|
|
1265
|
+
function isCodexSystemMessage(content) {
|
|
1266
|
+
return CODEX_SYSTEM_PREFIXES.some((p) => content.startsWith(p));
|
|
1267
|
+
}
|
|
1268
|
+
var CODEX_DIR = path3.join(os3.homedir(), ".codex");
|
|
1269
|
+
var CODEX_HOOKS_JSON = path3.join(CODEX_DIR, "hooks.json");
|
|
1270
|
+
var HOOK_EVENTS = [
|
|
1271
|
+
"SessionStart",
|
|
1272
|
+
"UserPromptSubmit",
|
|
1273
|
+
"PreToolUse",
|
|
1274
|
+
"PostToolUse",
|
|
1275
|
+
"Stop"
|
|
1276
|
+
];
|
|
1277
|
+
function readHooksJson() {
|
|
1278
|
+
try {
|
|
1279
|
+
return JSON.parse(fs4.readFileSync(CODEX_HOOKS_JSON, "utf-8"));
|
|
1280
|
+
} catch {
|
|
1281
|
+
return {};
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
function writeHooksJson(data) {
|
|
1285
|
+
fs4.mkdirSync(CODEX_DIR, { recursive: true });
|
|
1286
|
+
fs4.writeFileSync(CODEX_HOOKS_JSON, `${JSON.stringify(data, null, 2)}
|
|
1287
|
+
`);
|
|
1288
|
+
}
|
|
1289
|
+
var codex = {
|
|
1290
|
+
id: "codex",
|
|
1291
|
+
config: {
|
|
1292
|
+
dir: CODEX_DIR,
|
|
1293
|
+
configPath: path3.join(CODEX_DIR, "config.toml"),
|
|
1294
|
+
configFormat: "toml"
|
|
1295
|
+
},
|
|
1296
|
+
hooks: {
|
|
1297
|
+
events: HOOK_EVENTS,
|
|
1298
|
+
applyInstallConfig(existing, opts) {
|
|
1299
|
+
const codexConfig = { ...existing };
|
|
1300
|
+
const hookBin = path3.join(opts.pluginRoot, "bin", "hook-handler");
|
|
1301
|
+
const mcpBin = path3.join(opts.pluginRoot, "bin", "mcp-server");
|
|
1302
|
+
codexConfig.features = codexConfig.features ?? {};
|
|
1303
|
+
codexConfig.features.codex_hooks = true;
|
|
1304
|
+
codexConfig.suppress_unstable_features_warning = true;
|
|
1305
|
+
delete codexConfig.hooks;
|
|
1306
|
+
const hooksFile = readHooksJson();
|
|
1307
|
+
const hooks = hooksFile.hooks ?? {};
|
|
1308
|
+
const proxyFlag = opts.proxy ? " --proxy" : "";
|
|
1309
|
+
const command = `node ${hookBin} codex ${opts.port}${proxyFlag}`;
|
|
1310
|
+
for (const event of HOOK_EVENTS) {
|
|
1311
|
+
const groups = hooks[event] ?? [];
|
|
1312
|
+
hooks[event] = groups.filter((g) => {
|
|
1313
|
+
const h = g.hooks ?? [];
|
|
1314
|
+
return !h.some(
|
|
1315
|
+
(entry) => entry.command?.includes("panopticon")
|
|
1316
|
+
);
|
|
1317
|
+
});
|
|
1318
|
+
hooks[event].push({
|
|
1319
|
+
hooks: [{ type: "command", command, timeout: 10 }]
|
|
1320
|
+
});
|
|
1321
|
+
}
|
|
1322
|
+
hooksFile.hooks = hooks;
|
|
1323
|
+
writeHooksJson(hooksFile);
|
|
1324
|
+
if (opts.proxy) {
|
|
1325
|
+
codexConfig.openai_base_url = `http://localhost:${opts.port}/proxy/codex`;
|
|
1326
|
+
} else {
|
|
1327
|
+
delete codexConfig.openai_base_url;
|
|
1328
|
+
}
|
|
1329
|
+
delete codexConfig.telemetry;
|
|
1330
|
+
const endpoint = `http://localhost:${opts.port}`;
|
|
1331
|
+
const exporterConfig = { endpoint, protocol: "binary" };
|
|
1332
|
+
codexConfig.otel = {
|
|
1333
|
+
...codexConfig.otel ?? {},
|
|
1334
|
+
log_user_prompt: true,
|
|
1335
|
+
exporter: { "otlp-http": exporterConfig },
|
|
1336
|
+
trace_exporter: { "otlp-http": exporterConfig },
|
|
1337
|
+
metrics_exporter: { "otlp-http": exporterConfig }
|
|
1338
|
+
};
|
|
1339
|
+
const mcpServers = codexConfig.mcp_servers ?? {};
|
|
1340
|
+
mcpServers.panopticon = { command: "node", args: [mcpBin] };
|
|
1341
|
+
codexConfig.mcp_servers = mcpServers;
|
|
1342
|
+
return codexConfig;
|
|
1343
|
+
},
|
|
1344
|
+
removeInstallConfig(existing) {
|
|
1345
|
+
const cfg = { ...existing };
|
|
1346
|
+
const features = cfg.features;
|
|
1347
|
+
if (features) {
|
|
1348
|
+
delete features.codex_hooks;
|
|
1349
|
+
if (Object.keys(features).length === 0) delete cfg.features;
|
|
1350
|
+
}
|
|
1351
|
+
delete cfg.suppress_unstable_features_warning;
|
|
1352
|
+
delete cfg.hooks;
|
|
1353
|
+
const hooksFile = readHooksJson();
|
|
1354
|
+
const hooks = hooksFile.hooks;
|
|
1355
|
+
if (hooks) {
|
|
1356
|
+
for (const event of Object.keys(hooks)) {
|
|
1357
|
+
hooks[event] = hooks[event].filter((g) => {
|
|
1358
|
+
const h = g.hooks ?? [];
|
|
1359
|
+
return !h.some(
|
|
1360
|
+
(entry) => entry.command?.includes("panopticon")
|
|
1361
|
+
);
|
|
1362
|
+
});
|
|
1363
|
+
if (hooks[event].length === 0) delete hooks[event];
|
|
1364
|
+
}
|
|
1365
|
+
if (Object.keys(hooks).length === 0) {
|
|
1366
|
+
try {
|
|
1367
|
+
fs4.unlinkSync(CODEX_HOOKS_JSON);
|
|
1368
|
+
} catch {
|
|
1369
|
+
}
|
|
1370
|
+
} else {
|
|
1371
|
+
hooksFile.hooks = hooks;
|
|
1372
|
+
writeHooksJson(hooksFile);
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
if (typeof cfg.openai_base_url === "string" && cfg.openai_base_url.includes("panopticon")) {
|
|
1376
|
+
delete cfg.openai_base_url;
|
|
1377
|
+
}
|
|
1378
|
+
delete cfg.telemetry;
|
|
1379
|
+
const otel = cfg.otel;
|
|
1380
|
+
if (otel) {
|
|
1381
|
+
delete otel.log_user_prompt;
|
|
1382
|
+
delete otel.exporter;
|
|
1383
|
+
delete otel.trace_exporter;
|
|
1384
|
+
delete otel.metrics_exporter;
|
|
1385
|
+
if (Object.keys(otel).length === 0) delete cfg.otel;
|
|
1386
|
+
}
|
|
1387
|
+
const servers = cfg.mcp_servers;
|
|
1388
|
+
if (servers) {
|
|
1389
|
+
delete servers.panopticon;
|
|
1390
|
+
if (Object.keys(servers).length === 0) delete cfg.mcp_servers;
|
|
1391
|
+
}
|
|
1392
|
+
return cfg;
|
|
1393
|
+
}
|
|
1394
|
+
},
|
|
1395
|
+
shellEnv: {
|
|
1396
|
+
// Codex CLI reads its config from TOML, no shell env vars needed
|
|
1397
|
+
envVars() {
|
|
1398
|
+
return [];
|
|
1399
|
+
}
|
|
1400
|
+
},
|
|
1401
|
+
events: {
|
|
1402
|
+
// Codex uses snake_case but the hook handler already accepts both cases;
|
|
1403
|
+
// no mapping needed since ingest.ts normalizes at storage time
|
|
1404
|
+
eventMap: {},
|
|
1405
|
+
formatPermissionResponse({ allow, reason }) {
|
|
1406
|
+
return {
|
|
1407
|
+
hookSpecificOutput: {
|
|
1408
|
+
hookEventName: "PreToolUse",
|
|
1409
|
+
permissionDecision: allow ? "allow" : "deny",
|
|
1410
|
+
permissionDecisionReason: reason
|
|
1411
|
+
}
|
|
1412
|
+
};
|
|
1413
|
+
}
|
|
1414
|
+
},
|
|
1415
|
+
detect: {
|
|
1416
|
+
displayName: "Codex CLI",
|
|
1417
|
+
isInstalled: () => fs4.existsSync(CODEX_DIR),
|
|
1418
|
+
isConfigured() {
|
|
1419
|
+
try {
|
|
1420
|
+
const content = fs4.readFileSync(CODEX_HOOKS_JSON, "utf-8");
|
|
1421
|
+
return content.includes("panopticon");
|
|
1422
|
+
} catch {
|
|
1423
|
+
return false;
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
},
|
|
1427
|
+
otel: {
|
|
1428
|
+
serviceName: "codex_cli_rs",
|
|
1429
|
+
metrics: {
|
|
1430
|
+
metricNames: ["codex.turn.token_usage"],
|
|
1431
|
+
aggregation: "SUM",
|
|
1432
|
+
tokenTypeAttrs: ["$.token_type"],
|
|
1433
|
+
modelAttrs: ["$.model"],
|
|
1434
|
+
tokenTypeMap: {
|
|
1435
|
+
cached_input: "cacheRead",
|
|
1436
|
+
reasoning_output: "output"
|
|
1437
|
+
},
|
|
1438
|
+
excludeTokenTypes: ["total"]
|
|
1439
|
+
},
|
|
1440
|
+
logFields: {
|
|
1441
|
+
eventTypeExprs: ["body", `json_extract(attributes, '$."event.name"')`],
|
|
1442
|
+
timestampMsExprs: [
|
|
1443
|
+
"CAST(timestamp_ns / 1000000 AS INTEGER)",
|
|
1444
|
+
`CAST(strftime('%s', json_extract(attributes, '$."event.timestamp"')) AS INTEGER) * 1000`
|
|
1445
|
+
]
|
|
1446
|
+
}
|
|
1447
|
+
},
|
|
1448
|
+
ident: {
|
|
1449
|
+
modelPatterns: [/^(gpt-|o[1-9]|chatgpt-)/]
|
|
1450
|
+
},
|
|
1451
|
+
proxy: {
|
|
1452
|
+
upstreamHost(headers) {
|
|
1453
|
+
const auth = headers.authorization ?? "";
|
|
1454
|
+
return auth.startsWith("Bearer eyJ") ? "chatgpt.com" : "api.openai.com";
|
|
1455
|
+
},
|
|
1456
|
+
rewritePath(requestPath, headers) {
|
|
1457
|
+
const auth = headers.authorization ?? "";
|
|
1458
|
+
const isChatGptOAuth = auth.startsWith("Bearer eyJ");
|
|
1459
|
+
return isChatGptOAuth ? `/backend-api/codex${requestPath}` : `/v1${requestPath}`;
|
|
1460
|
+
},
|
|
1461
|
+
accumulatorType: "openai"
|
|
1462
|
+
},
|
|
1463
|
+
scanner: {
|
|
1464
|
+
normalizeToolCategory: codexToolCategory,
|
|
1465
|
+
discover() {
|
|
1466
|
+
const sessionsDir = path3.join(CODEX_DIR, "sessions");
|
|
1467
|
+
const files = [];
|
|
1468
|
+
const safeReaddir = (d) => {
|
|
1469
|
+
try {
|
|
1470
|
+
return fs4.readdirSync(d);
|
|
1471
|
+
} catch {
|
|
1472
|
+
return [];
|
|
1473
|
+
}
|
|
1474
|
+
};
|
|
1475
|
+
for (const year of safeReaddir(sessionsDir)) {
|
|
1476
|
+
for (const month of safeReaddir(path3.join(sessionsDir, year))) {
|
|
1477
|
+
for (const day of safeReaddir(path3.join(sessionsDir, year, month))) {
|
|
1478
|
+
const dayDir = path3.join(sessionsDir, year, month, day);
|
|
1479
|
+
for (const entry of safeReaddir(dayDir)) {
|
|
1480
|
+
if (entry.endsWith(".jsonl"))
|
|
1481
|
+
files.push({ filePath: path3.join(dayDir, entry) });
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1485
|
+
}
|
|
1486
|
+
return files;
|
|
1487
|
+
},
|
|
1488
|
+
parseFile(filePath, fromByteOffset) {
|
|
1489
|
+
const { lines, newByteOffset } = readNewLines(filePath, fromByteOffset);
|
|
1490
|
+
if (lines.length === 0) return null;
|
|
1491
|
+
let meta;
|
|
1492
|
+
const turns = [];
|
|
1493
|
+
const events = [];
|
|
1494
|
+
const messages = [];
|
|
1495
|
+
let turnIndex = 0;
|
|
1496
|
+
let ordinal = 0;
|
|
1497
|
+
let currentModel;
|
|
1498
|
+
let firstPrompt;
|
|
1499
|
+
let pendingToolCalls = [];
|
|
1500
|
+
let pendingAssistantContent = "";
|
|
1501
|
+
const toolResultsByCallId = /* @__PURE__ */ new Map();
|
|
1502
|
+
for (const line of lines) {
|
|
1503
|
+
let obj;
|
|
1504
|
+
try {
|
|
1505
|
+
obj = JSON.parse(line);
|
|
1506
|
+
} catch {
|
|
1507
|
+
continue;
|
|
1508
|
+
}
|
|
1509
|
+
const type = obj.type;
|
|
1510
|
+
const timestamp = obj.timestamp;
|
|
1511
|
+
const tsMs = timestamp ? new Date(timestamp).getTime() : Date.now();
|
|
1512
|
+
const payload = obj.payload;
|
|
1513
|
+
if (type === "session_meta" && payload) {
|
|
1514
|
+
meta = {
|
|
1515
|
+
sessionId: payload.id,
|
|
1516
|
+
cwd: payload.cwd,
|
|
1517
|
+
cliVersion: payload.cli_version,
|
|
1518
|
+
startedAtMs: payload.timestamp ? new Date(payload.timestamp).getTime() : tsMs
|
|
1519
|
+
};
|
|
1520
|
+
}
|
|
1521
|
+
if (type === "turn_context" && payload) {
|
|
1522
|
+
currentModel = payload.model;
|
|
1523
|
+
if (meta && currentModel && !meta.model) meta.model = currentModel;
|
|
1524
|
+
}
|
|
1525
|
+
const sid = meta?.sessionId ?? "";
|
|
1526
|
+
if (type === "event_msg" && payload) {
|
|
1527
|
+
const eventType = payload.type;
|
|
1528
|
+
if (eventType === "user_message") {
|
|
1529
|
+
const message = payload.message;
|
|
1530
|
+
if (!firstPrompt && message) firstPrompt = message.slice(0, 200);
|
|
1531
|
+
turns.push({
|
|
1532
|
+
sessionId: sid,
|
|
1533
|
+
turnIndex: turnIndex++,
|
|
1534
|
+
timestampMs: tsMs,
|
|
1535
|
+
model: currentModel,
|
|
1536
|
+
role: "user",
|
|
1537
|
+
contentPreview: message?.slice(0, 200),
|
|
1538
|
+
inputTokens: 0,
|
|
1539
|
+
outputTokens: 0,
|
|
1540
|
+
cacheReadTokens: 0,
|
|
1541
|
+
cacheCreationTokens: 0,
|
|
1542
|
+
reasoningTokens: 0
|
|
1543
|
+
});
|
|
1544
|
+
if (message && !isCodexSystemMessage(message)) {
|
|
1545
|
+
messages.push({
|
|
1546
|
+
sessionId: sid,
|
|
1547
|
+
ordinal: ordinal++,
|
|
1548
|
+
role: "user",
|
|
1549
|
+
content: message,
|
|
1550
|
+
timestampMs: tsMs,
|
|
1551
|
+
hasThinking: false,
|
|
1552
|
+
hasToolUse: false,
|
|
1553
|
+
isSystem: false,
|
|
1554
|
+
contentLength: message.length,
|
|
1555
|
+
hasContextTokens: false,
|
|
1556
|
+
hasOutputTokens: false,
|
|
1557
|
+
toolCalls: [],
|
|
1558
|
+
toolResults: /* @__PURE__ */ new Map()
|
|
1559
|
+
});
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1562
|
+
if (eventType === "token_count") {
|
|
1563
|
+
const info = payload.info;
|
|
1564
|
+
if (!info) continue;
|
|
1565
|
+
const lastUsage = info.last_token_usage;
|
|
1566
|
+
if (!lastUsage) continue;
|
|
1567
|
+
const inputTokens = lastUsage.input_tokens ?? 0;
|
|
1568
|
+
const outTokens = lastUsage.output_tokens ?? 0;
|
|
1569
|
+
const cacheRead = lastUsage.cached_input_tokens ?? 0;
|
|
1570
|
+
const reasoning = lastUsage.reasoning_output_tokens ?? 0;
|
|
1571
|
+
const ctxTokens = inputTokens + cacheRead;
|
|
1572
|
+
const hasCtx = inputTokens > 0 || cacheRead > 0;
|
|
1573
|
+
turns.push({
|
|
1574
|
+
sessionId: sid,
|
|
1575
|
+
turnIndex: turnIndex++,
|
|
1576
|
+
timestampMs: tsMs,
|
|
1577
|
+
model: currentModel,
|
|
1578
|
+
role: "assistant",
|
|
1579
|
+
inputTokens,
|
|
1580
|
+
outputTokens: outTokens,
|
|
1581
|
+
cacheReadTokens: cacheRead,
|
|
1582
|
+
cacheCreationTokens: 0,
|
|
1583
|
+
reasoningTokens: reasoning
|
|
1584
|
+
});
|
|
1585
|
+
const toolResults = /* @__PURE__ */ new Map();
|
|
1586
|
+
for (const tc of pendingToolCalls) {
|
|
1587
|
+
const result = toolResultsByCallId.get(tc.toolUseId);
|
|
1588
|
+
if (result) toolResults.set(tc.toolUseId, result);
|
|
1589
|
+
}
|
|
1590
|
+
messages.push({
|
|
1591
|
+
sessionId: sid,
|
|
1592
|
+
ordinal: ordinal++,
|
|
1593
|
+
role: "assistant",
|
|
1594
|
+
content: pendingAssistantContent,
|
|
1595
|
+
timestampMs: tsMs,
|
|
1596
|
+
hasThinking: false,
|
|
1597
|
+
hasToolUse: pendingToolCalls.length > 0,
|
|
1598
|
+
isSystem: false,
|
|
1599
|
+
contentLength: pendingAssistantContent.length,
|
|
1600
|
+
model: currentModel,
|
|
1601
|
+
tokenUsage: JSON.stringify(lastUsage),
|
|
1602
|
+
contextTokens: hasCtx ? ctxTokens : void 0,
|
|
1603
|
+
outputTokens: outTokens > 0 ? outTokens : void 0,
|
|
1604
|
+
hasContextTokens: hasCtx,
|
|
1605
|
+
hasOutputTokens: outTokens > 0,
|
|
1606
|
+
toolCalls: pendingToolCalls,
|
|
1607
|
+
toolResults
|
|
1608
|
+
});
|
|
1609
|
+
pendingToolCalls = [];
|
|
1610
|
+
pendingAssistantContent = "";
|
|
1611
|
+
}
|
|
1612
|
+
if (eventType === "agent_message") {
|
|
1613
|
+
events.push({
|
|
1614
|
+
sessionId: sid,
|
|
1615
|
+
eventType: "agent_message",
|
|
1616
|
+
timestampMs: tsMs,
|
|
1617
|
+
content: payload.message?.slice(0, 500),
|
|
1618
|
+
metadata: { phase: payload.phase }
|
|
1619
|
+
});
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
if (type === "response_item") {
|
|
1623
|
+
const p = obj.payload;
|
|
1624
|
+
const itemType = p?.type;
|
|
1625
|
+
if (itemType === "message" || itemType === "text") {
|
|
1626
|
+
const text = p?.text ?? p?.content ?? p?.output_text;
|
|
1627
|
+
if (text) {
|
|
1628
|
+
pendingAssistantContent += (pendingAssistantContent ? "\n" : "") + text;
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
if (itemType === "function_call") {
|
|
1632
|
+
const toolName = p?.name ?? "";
|
|
1633
|
+
const callId = p?.call_id ?? "";
|
|
1634
|
+
const inputJson = typeof p?.arguments === "string" ? p.arguments : JSON.stringify(p?.arguments);
|
|
1635
|
+
pendingToolCalls.push({
|
|
1636
|
+
toolUseId: callId,
|
|
1637
|
+
toolName,
|
|
1638
|
+
category: codexToolCategory(toolName),
|
|
1639
|
+
inputJson
|
|
1640
|
+
});
|
|
1641
|
+
events.push({
|
|
1642
|
+
sessionId: sid,
|
|
1643
|
+
eventType: "tool_call",
|
|
1644
|
+
timestampMs: tsMs,
|
|
1645
|
+
toolName,
|
|
1646
|
+
toolInput: inputJson?.slice(0, 1e3),
|
|
1647
|
+
metadata: { call_id: callId }
|
|
1648
|
+
});
|
|
1649
|
+
}
|
|
1650
|
+
if (itemType === "function_call_output") {
|
|
1651
|
+
const callId = p?.call_id ?? "";
|
|
1652
|
+
const output = typeof p?.output === "string" ? p.output : void 0;
|
|
1653
|
+
if (output) {
|
|
1654
|
+
toolResultsByCallId.set(callId, {
|
|
1655
|
+
contentLength: output.length,
|
|
1656
|
+
contentRaw: output
|
|
1657
|
+
});
|
|
1658
|
+
}
|
|
1659
|
+
events.push({
|
|
1660
|
+
sessionId: sid,
|
|
1661
|
+
eventType: "tool_result",
|
|
1662
|
+
timestampMs: tsMs,
|
|
1663
|
+
toolOutput: output?.slice(0, 1e3),
|
|
1664
|
+
metadata: { call_id: callId }
|
|
1665
|
+
});
|
|
1666
|
+
}
|
|
1667
|
+
}
|
|
1668
|
+
}
|
|
1669
|
+
if (meta && firstPrompt && !meta.firstPrompt)
|
|
1670
|
+
meta.firstPrompt = firstPrompt;
|
|
1671
|
+
return { meta, turns, events, messages, newByteOffset };
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1674
|
+
};
|
|
1675
|
+
registerTarget(codex);
|
|
1676
|
+
|
|
1677
|
+
// node_modules/.pnpm/@fml-inc+panopticon@0.2.2/node_modules/@fml-inc/panopticon/dist/chunk-SHXOVUQD.js
|
|
1678
|
+
function dbStats() {
|
|
1679
|
+
const db = getDb();
|
|
1680
|
+
const logs = db.prepare("SELECT COUNT(*) as count FROM otel_logs").get();
|
|
1681
|
+
const metrics = db.prepare("SELECT COUNT(*) as count FROM otel_metrics").get();
|
|
1682
|
+
const hooks = db.prepare("SELECT COUNT(*) as count FROM hook_events").get();
|
|
1683
|
+
const spans = db.prepare("SELECT COUNT(*) as count FROM otel_spans").get();
|
|
1684
|
+
const sessions = db.prepare("SELECT COUNT(*) as count FROM sessions").get();
|
|
1685
|
+
const scannerTurns = db.prepare("SELECT COUNT(*) as count FROM scanner_turns").get();
|
|
1686
|
+
const scannerEvents = db.prepare("SELECT COUNT(*) as count FROM scanner_events").get();
|
|
1687
|
+
const messages = db.prepare("SELECT COUNT(*) as count FROM messages").get();
|
|
1688
|
+
const toolCalls = db.prepare("SELECT COUNT(*) as count FROM tool_calls").get();
|
|
1689
|
+
return {
|
|
1690
|
+
sessions: sessions.count,
|
|
1691
|
+
messages: messages.count,
|
|
1692
|
+
tool_calls: toolCalls.count,
|
|
1693
|
+
scanner_turns: scannerTurns.count,
|
|
1694
|
+
scanner_events: scannerEvents.count,
|
|
1695
|
+
hook_events: hooks.count,
|
|
1696
|
+
otel_logs: logs.count,
|
|
1697
|
+
otel_metrics: metrics.count,
|
|
1698
|
+
otel_spans: spans.count
|
|
1699
|
+
};
|
|
1700
|
+
}
|
|
1701
|
+
|
|
1702
|
+
// node_modules/.pnpm/@fml-inc+panopticon@0.2.2/node_modules/@fml-inc/panopticon/dist/chunk-N4KQ56HJ.js
|
|
1703
|
+
import fs5 from "fs";
|
|
1704
|
+
import http from "http";
|
|
1705
|
+
import os4 from "os";
|
|
1706
|
+
import path4 from "path";
|
|
1707
|
+
function checkHealth(port, host) {
|
|
1708
|
+
return new Promise((resolve) => {
|
|
1709
|
+
const req = http.request(
|
|
1710
|
+
{ hostname: host, port, path: "/health", method: "GET", timeout: 2e3 },
|
|
1711
|
+
(res) => {
|
|
1712
|
+
res.resume();
|
|
1713
|
+
resolve(res.statusCode === 200);
|
|
1714
|
+
}
|
|
1715
|
+
);
|
|
1716
|
+
req.on("error", () => resolve(false));
|
|
1717
|
+
req.on("timeout", () => {
|
|
1718
|
+
req.destroy();
|
|
1719
|
+
resolve(false);
|
|
1720
|
+
});
|
|
1721
|
+
req.end();
|
|
1722
|
+
});
|
|
1723
|
+
}
|
|
1724
|
+
async function doctor() {
|
|
1725
|
+
const checks = [];
|
|
1726
|
+
const isSandbox = process.env.SANDBOX !== void 0 || fs5.existsSync(path4.join(os4.homedir(), ".sandbox-home"));
|
|
1727
|
+
if (!fs5.existsSync(config.dbPath)) {
|
|
1728
|
+
checks.push({
|
|
1729
|
+
label: "Database",
|
|
1730
|
+
status: "fail",
|
|
1731
|
+
detail: `Not found at ${config.dbPath}. Run install to initialize.`
|
|
1732
|
+
});
|
|
1733
|
+
} else {
|
|
1734
|
+
try {
|
|
1735
|
+
const stats = dbStats();
|
|
1736
|
+
const total = stats.hook_events + stats.otel_logs + stats.otel_metrics;
|
|
1737
|
+
checks.push({
|
|
1738
|
+
label: "Database",
|
|
1739
|
+
status: "ok",
|
|
1740
|
+
detail: `${total} rows (${stats.hook_events} hooks, ${stats.otel_logs} logs, ${stats.otel_metrics} metrics)`
|
|
1741
|
+
});
|
|
1742
|
+
closeDb();
|
|
1743
|
+
} catch (err) {
|
|
1744
|
+
checks.push({
|
|
1745
|
+
label: "Database",
|
|
1746
|
+
status: "fail",
|
|
1747
|
+
detail: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
1748
|
+
});
|
|
1749
|
+
}
|
|
1750
|
+
}
|
|
1751
|
+
const serverUp = await checkHealth(config.port, "127.0.0.1");
|
|
1752
|
+
if (serverUp) {
|
|
1753
|
+
checks.push({
|
|
1754
|
+
label: "Server",
|
|
1755
|
+
status: "ok",
|
|
1756
|
+
detail: `Listening on 127.0.0.1:${config.port}`
|
|
1757
|
+
});
|
|
1758
|
+
} else {
|
|
1759
|
+
checks.push({
|
|
1760
|
+
label: "Server",
|
|
1761
|
+
status: "warn",
|
|
1762
|
+
detail: `Not responding on port ${config.port}`
|
|
1763
|
+
});
|
|
1764
|
+
}
|
|
1765
|
+
const shellRc = path4.join(
|
|
1766
|
+
os4.homedir(),
|
|
1767
|
+
process.env.SHELL?.includes("zsh") ? ".zshrc" : ".bashrc"
|
|
1768
|
+
);
|
|
1769
|
+
const rcContent = fs5.existsSync(shellRc) ? fs5.readFileSync(shellRc, "utf-8") : "";
|
|
1770
|
+
const hasTelemetry = rcContent.includes("CLAUDE_CODE_ENABLE_TELEMETRY");
|
|
1771
|
+
const hasEndpoint = rcContent.includes("OTEL_EXPORTER_OTLP_ENDPOINT");
|
|
1772
|
+
if (hasTelemetry && hasEndpoint) {
|
|
1773
|
+
checks.push({
|
|
1774
|
+
label: "Shell Env",
|
|
1775
|
+
status: "ok",
|
|
1776
|
+
detail: `Telemetry configured in ${path4.basename(shellRc)}`
|
|
1777
|
+
});
|
|
1778
|
+
} else if (hasTelemetry || hasEndpoint) {
|
|
1779
|
+
checks.push({
|
|
1780
|
+
label: "Shell Env",
|
|
1781
|
+
status: "warn",
|
|
1782
|
+
detail: `Partial config in ${path4.basename(shellRc)} \u2014 re-run install`
|
|
1783
|
+
});
|
|
1784
|
+
} else {
|
|
1785
|
+
checks.push({
|
|
1786
|
+
label: "Shell Env",
|
|
1787
|
+
status: "warn",
|
|
1788
|
+
detail: `Not configured. Run install to set up telemetry.`
|
|
1789
|
+
});
|
|
1790
|
+
}
|
|
1791
|
+
const tools = [];
|
|
1792
|
+
for (const t of allTargets()) {
|
|
1793
|
+
if (t.detect.isInstalled()) {
|
|
1794
|
+
tools.push({
|
|
1795
|
+
name: t.detect.displayName,
|
|
1796
|
+
dir: t.config.dir,
|
|
1797
|
+
configured: t.detect.isConfigured()
|
|
1798
|
+
});
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1801
|
+
if (tools.length === 0) {
|
|
1802
|
+
checks.push({
|
|
1803
|
+
label: "Tools",
|
|
1804
|
+
status: "warn",
|
|
1805
|
+
detail: "No supported coding tools found"
|
|
1806
|
+
});
|
|
1807
|
+
} else {
|
|
1808
|
+
const configured = tools.filter((t) => t.configured);
|
|
1809
|
+
const unconfigured = tools.filter((t) => !t.configured);
|
|
1810
|
+
if (unconfigured.length === 0) {
|
|
1811
|
+
checks.push({
|
|
1812
|
+
label: "Tools",
|
|
1813
|
+
status: "ok",
|
|
1814
|
+
detail: configured.map((t) => t.name).join(", ")
|
|
1815
|
+
});
|
|
1816
|
+
} else {
|
|
1817
|
+
const detail = tools.map((t) => `${t.name} ${t.configured ? "\u2713" : "(not configured)"}`).join(", ");
|
|
1818
|
+
checks.push({
|
|
1819
|
+
label: "Tools",
|
|
1820
|
+
status: "warn",
|
|
1821
|
+
detail: `${detail} \u2014 re-run install`
|
|
1822
|
+
});
|
|
1823
|
+
}
|
|
1824
|
+
}
|
|
1825
|
+
if (fs5.existsSync(config.dbPath)) {
|
|
1826
|
+
try {
|
|
1827
|
+
const db = getDb();
|
|
1828
|
+
const latest = db.prepare("SELECT MAX(timestamp_ms) as ts FROM hook_events").get();
|
|
1829
|
+
closeDb();
|
|
1830
|
+
if (latest?.ts) {
|
|
1831
|
+
const ago = Date.now() - latest.ts;
|
|
1832
|
+
const minutes = Math.floor(ago / 6e4);
|
|
1833
|
+
if (minutes < 60) {
|
|
1834
|
+
checks.push({
|
|
1835
|
+
label: "Data Flow",
|
|
1836
|
+
status: "ok",
|
|
1837
|
+
detail: `Last event ${minutes}m ago`
|
|
1838
|
+
});
|
|
1839
|
+
} else if (minutes < 1440) {
|
|
1840
|
+
checks.push({
|
|
1841
|
+
label: "Data Flow",
|
|
1842
|
+
status: "ok",
|
|
1843
|
+
detail: `Last event ${Math.floor(minutes / 60)}h ago`
|
|
1844
|
+
});
|
|
1845
|
+
} else {
|
|
1846
|
+
checks.push({
|
|
1847
|
+
label: "Data Flow",
|
|
1848
|
+
status: "warn",
|
|
1849
|
+
detail: `Last event ${Math.floor(minutes / 1440)}d ago`
|
|
1850
|
+
});
|
|
1851
|
+
}
|
|
1852
|
+
} else {
|
|
1853
|
+
checks.push({
|
|
1854
|
+
label: "Data Flow",
|
|
1855
|
+
status: "warn",
|
|
1856
|
+
detail: "No events recorded yet"
|
|
1857
|
+
});
|
|
1858
|
+
}
|
|
1859
|
+
} catch {
|
|
1860
|
+
}
|
|
1861
|
+
}
|
|
1862
|
+
try {
|
|
1863
|
+
const cfg = loadUnifiedConfig();
|
|
1864
|
+
const targets2 = cfg.sync.targets;
|
|
1865
|
+
if (targets2.length === 0) {
|
|
1866
|
+
checks.push({
|
|
1867
|
+
label: "Sync",
|
|
1868
|
+
status: "ok",
|
|
1869
|
+
detail: "No targets configured"
|
|
1870
|
+
});
|
|
1871
|
+
} else {
|
|
1872
|
+
const tables = ["hook_events", "otel_logs", "otel_metrics"];
|
|
1873
|
+
const targetDetails = [];
|
|
1874
|
+
for (const t of targets2) {
|
|
1875
|
+
const watermarks = tables.map(
|
|
1876
|
+
(table) => readWatermark(watermarkKey(table, t.name))
|
|
1877
|
+
);
|
|
1878
|
+
const minWm = Math.min(...watermarks);
|
|
1879
|
+
const wmLabel = minWm > 0 ? `synced to #${minWm}` : "not synced yet";
|
|
1880
|
+
targetDetails.push(`${t.name} \u2192 ${t.url} (${wmLabel})`);
|
|
1881
|
+
}
|
|
1882
|
+
checks.push({
|
|
1883
|
+
label: "Sync",
|
|
1884
|
+
status: "ok",
|
|
1885
|
+
detail: `${targets2.length} target${targets2.length > 1 ? "s" : ""}: ${targetDetails.join("; ")}`
|
|
1886
|
+
});
|
|
1887
|
+
}
|
|
1888
|
+
} catch {
|
|
1889
|
+
checks.push({
|
|
1890
|
+
label: "Sync",
|
|
1891
|
+
status: "warn",
|
|
1892
|
+
detail: "Could not read sync config"
|
|
1893
|
+
});
|
|
1894
|
+
}
|
|
1895
|
+
try {
|
|
1896
|
+
const cfg = loadUnifiedConfig();
|
|
1897
|
+
const dsn = process.env.PANOPTICON_SENTRY_DSN ?? cfg.sentryDsn;
|
|
1898
|
+
checks.push({
|
|
1899
|
+
label: "Sentry",
|
|
1900
|
+
status: dsn ? "ok" : "ok",
|
|
1901
|
+
detail: dsn ? "Configured" : "Not configured (optional)"
|
|
1902
|
+
});
|
|
1903
|
+
} catch {
|
|
1904
|
+
}
|
|
1905
|
+
let recentEvents = [];
|
|
1906
|
+
let recentErrors = [];
|
|
1907
|
+
if (fs5.existsSync(config.dbPath)) {
|
|
1908
|
+
try {
|
|
1909
|
+
const db = getDb();
|
|
1910
|
+
const events = db.prepare(
|
|
1911
|
+
"SELECT event_type, tool_name, timestamp_ms FROM hook_events ORDER BY id DESC LIMIT 3"
|
|
1912
|
+
).all();
|
|
1913
|
+
recentEvents = events.map((e) => ({
|
|
1914
|
+
eventType: e.event_type,
|
|
1915
|
+
toolName: e.tool_name,
|
|
1916
|
+
timestamp: new Date(e.timestamp_ms).toISOString()
|
|
1917
|
+
}));
|
|
1918
|
+
const errors = db.prepare(
|
|
1919
|
+
"SELECT id, body FROM otel_logs WHERE severity_text = 'ERROR' ORDER BY id DESC LIMIT 3"
|
|
1920
|
+
).all();
|
|
1921
|
+
recentErrors = errors.map((e) => ({
|
|
1922
|
+
id: e.id,
|
|
1923
|
+
body: (e.body ?? "").slice(0, 200)
|
|
1924
|
+
}));
|
|
1925
|
+
closeDb();
|
|
1926
|
+
} catch {
|
|
1927
|
+
}
|
|
1928
|
+
}
|
|
1929
|
+
return {
|
|
1930
|
+
checks,
|
|
1931
|
+
system: {
|
|
1932
|
+
os: `${os4.platform()} (${os4.release()} ${os4.arch()})`,
|
|
1933
|
+
node: process.version,
|
|
1934
|
+
sandbox: isSandbox
|
|
1935
|
+
},
|
|
1936
|
+
recentEvents,
|
|
1937
|
+
recentErrors
|
|
1938
|
+
};
|
|
1939
|
+
}
|
|
1940
|
+
|
|
1941
|
+
// node_modules/.pnpm/@fml-inc+panopticon@0.2.2/node_modules/@fml-inc/panopticon/dist/chunk-ZEC4LRKS.js
|
|
1942
|
+
import fs6 from "fs";
|
|
1943
|
+
import os5 from "os";
|
|
1944
|
+
import path5 from "path";
|
|
1945
|
+
var CLAUDE_DESKTOP_DIR = path5.join(
|
|
1946
|
+
os5.homedir(),
|
|
1947
|
+
"Library",
|
|
1948
|
+
"Application Support",
|
|
1949
|
+
"Claude"
|
|
1950
|
+
);
|
|
1951
|
+
var CLAUDE_DESKTOP_CONFIG = path5.join(
|
|
1952
|
+
CLAUDE_DESKTOP_DIR,
|
|
1953
|
+
"claude_desktop_config.json"
|
|
1954
|
+
);
|
|
1955
|
+
var claudeDesktop = {
|
|
1956
|
+
id: "claude-desktop",
|
|
1957
|
+
config: {
|
|
1958
|
+
dir: CLAUDE_DESKTOP_DIR,
|
|
1959
|
+
configPath: CLAUDE_DESKTOP_CONFIG,
|
|
1960
|
+
configFormat: "json"
|
|
1961
|
+
},
|
|
1962
|
+
hooks: {
|
|
1963
|
+
// Claude Desktop uses MCP servers, not hooks
|
|
1964
|
+
events: [],
|
|
1965
|
+
applyInstallConfig(existing, opts) {
|
|
1966
|
+
const cfg = { ...existing };
|
|
1967
|
+
const serverBin = path5.join(opts.pluginRoot, "bin", "mcp-server");
|
|
1968
|
+
cfg.mcpServers = cfg.mcpServers ?? {};
|
|
1969
|
+
cfg.mcpServers.panopticon = {
|
|
1970
|
+
command: "node",
|
|
1971
|
+
args: [serverBin]
|
|
1972
|
+
};
|
|
1973
|
+
return cfg;
|
|
1974
|
+
},
|
|
1975
|
+
removeInstallConfig(existing) {
|
|
1976
|
+
const cfg = { ...existing };
|
|
1977
|
+
const servers = cfg.mcpServers;
|
|
1978
|
+
if (servers) {
|
|
1979
|
+
delete servers.panopticon;
|
|
1980
|
+
if (Object.keys(servers).length === 0) delete cfg.mcpServers;
|
|
1981
|
+
}
|
|
1982
|
+
return cfg;
|
|
1983
|
+
}
|
|
1984
|
+
},
|
|
1985
|
+
shellEnv: {
|
|
1986
|
+
envVars() {
|
|
1987
|
+
return [];
|
|
1988
|
+
}
|
|
1989
|
+
},
|
|
1990
|
+
events: {
|
|
1991
|
+
eventMap: {},
|
|
1992
|
+
formatPermissionResponse({ allow, reason }) {
|
|
1993
|
+
return {
|
|
1994
|
+
hookSpecificOutput: {
|
|
1995
|
+
hookEventName: "PreToolUse",
|
|
1996
|
+
permissionDecision: allow ? "allow" : "deny",
|
|
1997
|
+
permissionDecisionReason: reason
|
|
1998
|
+
}
|
|
1999
|
+
};
|
|
2000
|
+
}
|
|
2001
|
+
},
|
|
2002
|
+
detect: {
|
|
2003
|
+
displayName: "Claude Desktop",
|
|
2004
|
+
isInstalled: () => fs6.existsSync(CLAUDE_DESKTOP_DIR),
|
|
2005
|
+
isConfigured() {
|
|
2006
|
+
try {
|
|
2007
|
+
const cfg = JSON.parse(fs6.readFileSync(CLAUDE_DESKTOP_CONFIG, "utf-8"));
|
|
2008
|
+
return !!cfg.mcpServers?.panopticon;
|
|
2009
|
+
} catch {
|
|
2010
|
+
return false;
|
|
2011
|
+
}
|
|
2012
|
+
}
|
|
2013
|
+
}
|
|
2014
|
+
};
|
|
2015
|
+
registerTarget(claudeDesktop);
|
|
2016
|
+
export {
|
|
2017
|
+
doctor
|
|
2018
|
+
};
|
|
2019
|
+
//# sourceMappingURL=doctor-YV6KQEGW.js.map
|