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