@taewooopark/agent-blackbox 0.44.0 → 0.46.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/dist/cli.js
CHANGED
|
@@ -61,6 +61,9 @@ function validateTraceEvent(event) {
|
|
|
61
61
|
requireEnum(event, "host", traceHosts, errors);
|
|
62
62
|
requireString(event, "runId", errors);
|
|
63
63
|
requireString(event, "sessionId", errors);
|
|
64
|
+
if (event.cwd !== void 0 && typeof event.cwd !== "string") {
|
|
65
|
+
errors.push("cwd must be a string when present");
|
|
66
|
+
}
|
|
64
67
|
requireEnum(event, "kind", traceEventKinds, errors);
|
|
65
68
|
requireEnum(event, "sensitivity", dataSensitivities, errors);
|
|
66
69
|
if (!isRecord(event.payload)) {
|
|
@@ -134,6 +137,9 @@ function replayWorkflowGraphAtSeq(events, seq) {
|
|
|
134
137
|
}
|
|
135
138
|
function replayWorkflowGraphAtTime(events, at) {
|
|
136
139
|
const atTime = new Date(at).getTime();
|
|
140
|
+
if (Number.isNaN(atTime)) {
|
|
141
|
+
throw new Error("replayWorkflowGraphAtTime: invalid time");
|
|
142
|
+
}
|
|
137
143
|
return materializeWorkflowGraph(events.filter((event) => new Date(event.ts).getTime() <= atTime));
|
|
138
144
|
}
|
|
139
145
|
function applyTraceEvent(graph, event) {
|
|
@@ -248,7 +254,8 @@ function ensureSession(graph, event) {
|
|
|
248
254
|
status: "ACTIVE",
|
|
249
255
|
at: event.ts,
|
|
250
256
|
eventId: event.id,
|
|
251
|
-
data: { sessionId: event.sessionId, parentSessionId: event.parentSessionId ?? null }
|
|
257
|
+
data: { sessionId: event.sessionId, parentSessionId: event.parentSessionId ?? null },
|
|
258
|
+
keepStatusIfExists: true
|
|
252
259
|
});
|
|
253
260
|
ensureEdge(graph, {
|
|
254
261
|
from: runNodeId(event.runId),
|
|
@@ -270,7 +277,8 @@ function ensureAgent(graph, event) {
|
|
|
270
277
|
status: "ACTIVE",
|
|
271
278
|
at: event.ts,
|
|
272
279
|
eventId: event.id,
|
|
273
|
-
data: { agentId: event.agentId, agentRole: event.agentRole ?? "unknown" }
|
|
280
|
+
data: { agentId: event.agentId, agentRole: event.agentRole ?? "unknown" },
|
|
281
|
+
keepStatusIfExists: true
|
|
274
282
|
});
|
|
275
283
|
ensureEdge(graph, {
|
|
276
284
|
from: sessionNodeId(event.sessionId),
|
|
@@ -292,7 +300,8 @@ function ensureTurn(graph, event) {
|
|
|
292
300
|
status: "ACTIVE",
|
|
293
301
|
at: event.ts,
|
|
294
302
|
eventId: event.id,
|
|
295
|
-
data: { turnId: event.turnId }
|
|
303
|
+
data: { turnId: event.turnId },
|
|
304
|
+
keepStatusIfExists: true
|
|
296
305
|
});
|
|
297
306
|
ensureEdge(graph, {
|
|
298
307
|
from: event.agentId ? agentNodeId(event.agentId) : sessionNodeId(event.sessionId),
|
|
@@ -360,7 +369,9 @@ function ensureNode(graph, input) {
|
|
|
360
369
|
const existing = graph.nodes.get(input.id);
|
|
361
370
|
if (existing) {
|
|
362
371
|
existing.updatedAt = input.at;
|
|
363
|
-
|
|
372
|
+
if (!input.keepStatusIfExists) {
|
|
373
|
+
existing.status = input.status;
|
|
374
|
+
}
|
|
364
375
|
existing.data = { ...existing.data, ...input.data ?? {} };
|
|
365
376
|
if (input.eventId && !existing.eventIds.includes(input.eventId)) {
|
|
366
377
|
existing.eventIds.push(input.eventId);
|
|
@@ -514,7 +525,8 @@ function eventNodeIdFromEventId(eventId) {
|
|
|
514
525
|
return `event:${stablePart(eventId)}`;
|
|
515
526
|
}
|
|
516
527
|
function workflowEdgeId(from, to, type) {
|
|
517
|
-
|
|
528
|
+
const enc = (s) => s.replace(/:/g, "__");
|
|
529
|
+
return `edge:${type}:${enc(stablePart(from))}:${enc(stablePart(to))}`;
|
|
518
530
|
}
|
|
519
531
|
function stablePart(value) {
|
|
520
532
|
return value.replace(/[^a-zA-Z0-9_.:-]/g, "_");
|
|
@@ -842,7 +854,7 @@ function computeEfficiencyReport(events) {
|
|
|
842
854
|
label: "Large context injections",
|
|
843
855
|
value: biggest,
|
|
844
856
|
unit: "tokens",
|
|
845
|
-
display: biggest >=
|
|
857
|
+
display: biggest >= 5e3 ? formatTokens(biggest) : "none",
|
|
846
858
|
score,
|
|
847
859
|
status,
|
|
848
860
|
detail: over5k.length === 0 ? "No single tool output flooded the context." : `${over5k.length} output(s) added 5k+ tokens (largest ${formatTokens(biggest)}) \u2014 scope greps/reads or summarise.`,
|
|
@@ -979,7 +991,7 @@ function stringAt(event, key) {
|
|
|
979
991
|
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
980
992
|
}
|
|
981
993
|
function readTokenSnapshot(event) {
|
|
982
|
-
const present =
|
|
994
|
+
const present = isRecordAtPath(event.payload, "properties.info.tokens") || isRecordAtPath(event.payload, "properties.tokens") || isRecordAtPath(event.payload, "tokens");
|
|
983
995
|
if (!present) return void 0;
|
|
984
996
|
return {
|
|
985
997
|
input: deepNumber(event.payload, ["properties.info.tokens.input", "properties.tokens.input", "tokens.input"]) ?? 0,
|
|
@@ -1082,13 +1094,13 @@ function buildEfficiencyMemory(report, options = {}) {
|
|
|
1082
1094
|
].join("\n");
|
|
1083
1095
|
}
|
|
1084
1096
|
var escapeRegExp = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1085
|
-
var managedBlockRegExp = () => new RegExp(`${escapeRegExp(EFFICIENCY_MEMORY_START)}[\\s\\S]*?${escapeRegExp(EFFICIENCY_MEMORY_END)}
|
|
1097
|
+
var managedBlockRegExp = () => new RegExp(`${escapeRegExp(EFFICIENCY_MEMORY_START)}[\\s\\S]*?${escapeRegExp(EFFICIENCY_MEMORY_END)}`, "g");
|
|
1086
1098
|
function hasManagedBlock(content) {
|
|
1087
1099
|
return managedBlockRegExp().test(content);
|
|
1088
1100
|
}
|
|
1089
1101
|
function upsertManagedBlock(content, block) {
|
|
1090
1102
|
if (hasManagedBlock(content)) {
|
|
1091
|
-
return content.replace(managedBlockRegExp(), block);
|
|
1103
|
+
return content.replace(managedBlockRegExp(), () => block);
|
|
1092
1104
|
}
|
|
1093
1105
|
const base = content.trimEnd();
|
|
1094
1106
|
return base.length === 0 ? `${block}
|
|
@@ -1107,8 +1119,9 @@ function removeManagedBlock(content) {
|
|
|
1107
1119
|
// apps/daemon/dist/cli.js
|
|
1108
1120
|
import { spawn as spawn2 } from "node:child_process";
|
|
1109
1121
|
import { existsSync } from "node:fs";
|
|
1122
|
+
import { homedir as homedir2 } from "node:os";
|
|
1110
1123
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
1111
|
-
import { dirname as
|
|
1124
|
+
import { dirname as dirname5, join as join6, resolve } from "node:path";
|
|
1112
1125
|
|
|
1113
1126
|
// apps/daemon/dist/dashboardServer.js
|
|
1114
1127
|
import { createReadStream } from "node:fs";
|
|
@@ -1138,7 +1151,14 @@ async function startDashboardServer(options) {
|
|
|
1138
1151
|
const injected = indexHtml.replace("</head>", ` <script>window.AGENT_BLACKBOX_DAEMON_URL=${JSON.stringify(options.daemonUrl)};</script>
|
|
1139
1152
|
</head>`);
|
|
1140
1153
|
const server = createServer((request, response) => {
|
|
1141
|
-
|
|
1154
|
+
let rawPath;
|
|
1155
|
+
try {
|
|
1156
|
+
rawPath = decodeURIComponent((request.url ?? "/").split("?")[0] ?? "/");
|
|
1157
|
+
} catch {
|
|
1158
|
+
response.writeHead(400);
|
|
1159
|
+
response.end("Bad Request");
|
|
1160
|
+
return;
|
|
1161
|
+
}
|
|
1142
1162
|
if (rawPath === "/" || rawPath === "/index.html") {
|
|
1143
1163
|
response.writeHead(200, { "content-type": "text/html; charset=utf-8" });
|
|
1144
1164
|
response.end(injected);
|
|
@@ -1156,15 +1176,21 @@ async function startDashboardServer(options) {
|
|
|
1156
1176
|
throw new Error("not a file");
|
|
1157
1177
|
}
|
|
1158
1178
|
response.writeHead(200, { "content-type": mimeTypes[extname(filePath)] ?? "application/octet-stream" });
|
|
1159
|
-
createReadStream(filePath)
|
|
1179
|
+
const rs = createReadStream(filePath);
|
|
1180
|
+
rs.on("error", () => response.destroy());
|
|
1181
|
+
rs.pipe(response);
|
|
1160
1182
|
}).catch(() => {
|
|
1161
1183
|
response.writeHead(200, { "content-type": "text/html; charset=utf-8" });
|
|
1162
1184
|
response.end(injected);
|
|
1163
1185
|
});
|
|
1164
1186
|
});
|
|
1165
1187
|
const port = options.port ?? 5173;
|
|
1166
|
-
await new Promise((resolve2) => {
|
|
1167
|
-
server.
|
|
1188
|
+
await new Promise((resolve2, reject) => {
|
|
1189
|
+
server.once("error", reject);
|
|
1190
|
+
server.listen(port, "127.0.0.1", () => {
|
|
1191
|
+
server.off("error", reject);
|
|
1192
|
+
resolve2();
|
|
1193
|
+
});
|
|
1168
1194
|
});
|
|
1169
1195
|
const address = server.address();
|
|
1170
1196
|
const actualPort = typeof address === "object" && address ? address.port : port;
|
|
@@ -1179,7 +1205,7 @@ async function startDashboardServer(options) {
|
|
|
1179
1205
|
|
|
1180
1206
|
// apps/daemon/dist/index.js
|
|
1181
1207
|
import { readFileSync } from "node:fs";
|
|
1182
|
-
import { dirname as
|
|
1208
|
+
import { dirname as dirname4, join as join5 } from "node:path";
|
|
1183
1209
|
import { fileURLToPath } from "node:url";
|
|
1184
1210
|
|
|
1185
1211
|
// packages/storage/src/ndjson.ts
|
|
@@ -1196,7 +1222,19 @@ function parseTraceEventLine(line) {
|
|
|
1196
1222
|
return parsed;
|
|
1197
1223
|
}
|
|
1198
1224
|
function parseTraceEvents(input) {
|
|
1199
|
-
|
|
1225
|
+
const lines = input.split(/\r?\n/).filter((line) => line.trim().length > 0);
|
|
1226
|
+
const lastIsPossiblyTorn = lines.length > 0 && !/\r?\n$/.test(input);
|
|
1227
|
+
const completeCount = lastIsPossiblyTorn ? lines.length - 1 : lines.length;
|
|
1228
|
+
const events = lines.slice(0, completeCount).map((line) => parseTraceEventLine(line));
|
|
1229
|
+
if (lastIsPossiblyTorn) {
|
|
1230
|
+
const lastLine = lines[lines.length - 1];
|
|
1231
|
+
try {
|
|
1232
|
+
events.push(parseTraceEventLine(lastLine));
|
|
1233
|
+
} catch (error) {
|
|
1234
|
+
if (!(error instanceof SyntaxError)) throw error;
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
return events;
|
|
1200
1238
|
}
|
|
1201
1239
|
async function appendTraceEvent(filePath, event) {
|
|
1202
1240
|
await mkdir(dirname(filePath), { recursive: true });
|
|
@@ -1225,10 +1263,11 @@ async function runOptimize(options) {
|
|
|
1225
1263
|
}
|
|
1226
1264
|
async function computeOptimize(options) {
|
|
1227
1265
|
const eventsFile = options.eventsFile ?? join2(options.projectDir, ".agent-blackbox", "events.ndjson");
|
|
1228
|
-
const agentsMdPath = join2(options.projectDir, "AGENTS.md");
|
|
1229
|
-
const statePath = join2(options.projectDir, ".agent-blackbox", "optimization.json");
|
|
1230
1266
|
const events = await loadTraceEvents(eventsFile);
|
|
1231
1267
|
const { runId, events: runEvents } = latestRun(events);
|
|
1268
|
+
const targetDir = runEvents.find((e) => typeof e.cwd === "string" && e.cwd.length > 0)?.cwd ?? options.projectDir;
|
|
1269
|
+
const agentsMdPath = join2(targetDir, "AGENTS.md");
|
|
1270
|
+
const statePath = join2(targetDir, ".agent-blackbox", "optimization.json");
|
|
1232
1271
|
const latestTs = runEvents.reduce((max, e) => e.ts > max ? e.ts : max, "");
|
|
1233
1272
|
const report = runEvents.length > 0 ? computeEfficiencyReport(runEvents) : null;
|
|
1234
1273
|
const score = report ? report.overallScore : null;
|
|
@@ -1254,15 +1293,17 @@ async function computeOptimize(options) {
|
|
|
1254
1293
|
}
|
|
1255
1294
|
const prior = await readMaybe(agentsMdPath);
|
|
1256
1295
|
const next = upsertManagedBlock(prior ?? "", block);
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1296
|
+
if (prior === null || next !== prior) {
|
|
1297
|
+
await writeFile(agentsMdPath, next, "utf8");
|
|
1298
|
+
await writeState(statePath, {
|
|
1299
|
+
runId: runId ?? "",
|
|
1300
|
+
baselineScore: score,
|
|
1301
|
+
baselineLatestTs: latestTs,
|
|
1302
|
+
baselineFlagged: flaggedIds(report),
|
|
1303
|
+
fileExisted: prior !== null,
|
|
1304
|
+
appliedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1305
|
+
});
|
|
1306
|
+
}
|
|
1266
1307
|
return {
|
|
1267
1308
|
mode: "apply",
|
|
1268
1309
|
action: `Wrote efficiency memory to AGENTS.md \u2014 targets ~${report.reclaimableTokens} reclaimable tokens on similar future runs (no re-run needed). Optional: re-run the same task + \`optimize --check\` to benchmark the gain.`,
|
|
@@ -1469,7 +1510,7 @@ function buildDigest(report) {
|
|
|
1469
1510
|
id: m.id,
|
|
1470
1511
|
label: m.label,
|
|
1471
1512
|
status: m.status,
|
|
1472
|
-
value: Number(m.value.toFixed(3)),
|
|
1513
|
+
value: Number((typeof m.value === "number" && Number.isFinite(m.value) ? m.value : 0).toFixed(3)),
|
|
1473
1514
|
display: m.display,
|
|
1474
1515
|
detail: m.detail,
|
|
1475
1516
|
...m.reclaimableTokens ? { reclaimableTokens: m.reclaimableTokens } : {},
|
|
@@ -1491,6 +1532,8 @@ var freeCooldownUntil = /* @__PURE__ */ new Map();
|
|
|
1491
1532
|
function orderFreePool(pool, cooldownUntil, cursor, now) {
|
|
1492
1533
|
const fresh = pool.filter((entry) => (cooldownUntil.get(entry.model) ?? 0) <= now);
|
|
1493
1534
|
const list = fresh.length > 0 ? fresh : pool;
|
|
1535
|
+
if (list.length === 0)
|
|
1536
|
+
return [];
|
|
1494
1537
|
const start = (cursor % list.length + list.length) % list.length;
|
|
1495
1538
|
return [...list.slice(start), ...list.slice(0, start)];
|
|
1496
1539
|
}
|
|
@@ -1635,7 +1678,10 @@ async function fetchJson(url, body, extraHeaders = {}) {
|
|
|
1635
1678
|
}
|
|
1636
1679
|
function runCommand(command, args2) {
|
|
1637
1680
|
return new Promise((resolve2, reject) => {
|
|
1638
|
-
const child = spawn(command, args2, {
|
|
1681
|
+
const child = spawn(command, args2, {
|
|
1682
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
1683
|
+
env: { ...process.env, AGENT_BLACKBOX_DISABLE: "1" }
|
|
1684
|
+
});
|
|
1639
1685
|
let out = "";
|
|
1640
1686
|
const timer = setTimeout(() => {
|
|
1641
1687
|
child.kill("SIGKILL");
|
|
@@ -1662,10 +1708,24 @@ function extractJsonObject(text) {
|
|
|
1662
1708
|
if (start === -1)
|
|
1663
1709
|
return void 0;
|
|
1664
1710
|
let depth = 0;
|
|
1711
|
+
let inString = false;
|
|
1712
|
+
let escaped = false;
|
|
1665
1713
|
for (let i = start; i < text.length; i += 1) {
|
|
1666
|
-
|
|
1714
|
+
const ch = text[i];
|
|
1715
|
+
if (inString) {
|
|
1716
|
+
if (escaped)
|
|
1717
|
+
escaped = false;
|
|
1718
|
+
else if (ch === "\\")
|
|
1719
|
+
escaped = true;
|
|
1720
|
+
else if (ch === '"')
|
|
1721
|
+
inString = false;
|
|
1722
|
+
continue;
|
|
1723
|
+
}
|
|
1724
|
+
if (ch === '"')
|
|
1725
|
+
inString = true;
|
|
1726
|
+
else if (ch === "{")
|
|
1667
1727
|
depth += 1;
|
|
1668
|
-
else if (
|
|
1728
|
+
else if (ch === "}") {
|
|
1669
1729
|
depth -= 1;
|
|
1670
1730
|
if (depth === 0) {
|
|
1671
1731
|
try {
|
|
@@ -1703,8 +1763,12 @@ async function startTraceDaemon(options) {
|
|
|
1703
1763
|
});
|
|
1704
1764
|
});
|
|
1705
1765
|
const port = options.port ?? 47831;
|
|
1706
|
-
await new Promise((resolve2) => {
|
|
1707
|
-
server.
|
|
1766
|
+
await new Promise((resolve2, reject) => {
|
|
1767
|
+
server.once("error", reject);
|
|
1768
|
+
server.listen(port, "127.0.0.1", () => {
|
|
1769
|
+
server.off("error", reject);
|
|
1770
|
+
resolve2();
|
|
1771
|
+
});
|
|
1708
1772
|
});
|
|
1709
1773
|
const address = server.address();
|
|
1710
1774
|
const actualPort = typeof address === "object" && address ? address.port : port;
|
|
@@ -1713,8 +1777,12 @@ async function startTraceDaemon(options) {
|
|
|
1713
1777
|
port: actualPort,
|
|
1714
1778
|
eventsFile,
|
|
1715
1779
|
close: () => new Promise((resolve2, reject) => {
|
|
1780
|
+
for (const client of clients) {
|
|
1781
|
+
client.terminate();
|
|
1782
|
+
}
|
|
1783
|
+
clients.clear();
|
|
1784
|
+
streamServer.close();
|
|
1716
1785
|
server.close((error) => {
|
|
1717
|
-
streamServer.close();
|
|
1718
1786
|
if (error) {
|
|
1719
1787
|
reject(error);
|
|
1720
1788
|
} else {
|
|
@@ -1768,11 +1836,11 @@ async function buildTraceSnapshot(eventsFile, replay = {}) {
|
|
|
1768
1836
|
async function handleRequest(request, response, eventsFile, clients, suggestConfig, projectDir) {
|
|
1769
1837
|
try {
|
|
1770
1838
|
const url = new URL(request.url ?? "/", "http://127.0.0.1");
|
|
1771
|
-
const replay = parseReplayQuery(url);
|
|
1772
1839
|
if (request.method === "OPTIONS") {
|
|
1773
1840
|
sendEmpty(response, 204);
|
|
1774
1841
|
return;
|
|
1775
1842
|
}
|
|
1843
|
+
const replay = parseReplayQuery(url);
|
|
1776
1844
|
if (request.method === "GET" && url.pathname === "/health") {
|
|
1777
1845
|
sendJson(response, 200, { ok: true, data: { status: "ok", eventsFile } });
|
|
1778
1846
|
return;
|
|
@@ -1867,13 +1935,27 @@ async function sendSnapshot(client, eventsFile) {
|
|
|
1867
1935
|
}
|
|
1868
1936
|
}
|
|
1869
1937
|
}
|
|
1938
|
+
var MAX_BODY_BYTES = 5e7;
|
|
1870
1939
|
async function readJsonBody(request) {
|
|
1871
1940
|
const chunks = [];
|
|
1941
|
+
let total = 0;
|
|
1872
1942
|
for await (const chunk of request) {
|
|
1873
|
-
|
|
1943
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
1944
|
+
total += buffer.length;
|
|
1945
|
+
if (total > MAX_BODY_BYTES) {
|
|
1946
|
+
throw new BadRequestError("Request body too large");
|
|
1947
|
+
}
|
|
1948
|
+
chunks.push(buffer);
|
|
1874
1949
|
}
|
|
1875
1950
|
const raw = Buffer.concat(chunks).toString("utf8");
|
|
1876
|
-
|
|
1951
|
+
if (raw.length === 0) {
|
|
1952
|
+
return {};
|
|
1953
|
+
}
|
|
1954
|
+
try {
|
|
1955
|
+
return JSON.parse(raw);
|
|
1956
|
+
} catch {
|
|
1957
|
+
throw new BadRequestError("Invalid JSON body");
|
|
1958
|
+
}
|
|
1877
1959
|
}
|
|
1878
1960
|
function sendJson(response, statusCode, payload) {
|
|
1879
1961
|
response.writeHead(statusCode, {
|
|
@@ -1917,10 +1999,39 @@ function isNodeError(error) {
|
|
|
1917
1999
|
}
|
|
1918
2000
|
|
|
1919
2001
|
// apps/daemon/dist/initOpenCode.js
|
|
1920
|
-
import { mkdir as mkdir3, readFile as readFile4, writeFile as writeFile2 } from "node:fs/promises";
|
|
1921
|
-
import {
|
|
2002
|
+
import { mkdir as mkdir3, readFile as readFile4, rm as rm2, writeFile as writeFile2 } from "node:fs/promises";
|
|
2003
|
+
import { homedir } from "node:os";
|
|
2004
|
+
import { dirname as dirname3, join as join4 } from "node:path";
|
|
1922
2005
|
var defaultAdapterPackage = "@agent-blackbox/opencode-adapter";
|
|
1923
2006
|
var defaultDaemonUrl = "http://127.0.0.1:47831";
|
|
2007
|
+
function globalOpenCodeDir() {
|
|
2008
|
+
const xdg = process.env.XDG_CONFIG_HOME;
|
|
2009
|
+
return xdg && xdg.length > 0 ? join4(xdg, "opencode") : join4(homedir(), ".config", "opencode");
|
|
2010
|
+
}
|
|
2011
|
+
function globalRecorderPath() {
|
|
2012
|
+
return join4(globalOpenCodeDir(), "plugins", "agent-blackbox.js");
|
|
2013
|
+
}
|
|
2014
|
+
async function installGlobalRecorder(options) {
|
|
2015
|
+
if (!await pathExists(options.pluginBundlePath)) {
|
|
2016
|
+
throw new Error("Self-contained recorder bundle not found. Use the published npx package, or build it from source with `npm run build:cli`.");
|
|
2017
|
+
}
|
|
2018
|
+
const pluginPath = globalRecorderPath();
|
|
2019
|
+
const bundle = (await readFile4(options.pluginBundlePath, "utf8")).replaceAll("__ABB_DAEMON_URL__", options.daemonUrl);
|
|
2020
|
+
await mkdir3(dirname3(pluginPath), { recursive: true });
|
|
2021
|
+
await writeFile2(pluginPath, bundle, "utf8");
|
|
2022
|
+
return { pluginPath };
|
|
2023
|
+
}
|
|
2024
|
+
async function uninstallGlobalRecorder() {
|
|
2025
|
+
const pluginPath = globalRecorderPath();
|
|
2026
|
+
try {
|
|
2027
|
+
await rm2(pluginPath);
|
|
2028
|
+
return { pluginPath, removed: true };
|
|
2029
|
+
} catch (error) {
|
|
2030
|
+
if (isNodeError2(error) && error.code === "ENOENT")
|
|
2031
|
+
return { pluginPath, removed: false };
|
|
2032
|
+
throw error;
|
|
2033
|
+
}
|
|
2034
|
+
}
|
|
1924
2035
|
async function initOpenCodeProject(options) {
|
|
1925
2036
|
const adapterPackage = options.adapterPackage ?? defaultAdapterPackage;
|
|
1926
2037
|
const adapterImport = inferAdapterImport(adapterPackage);
|
|
@@ -1998,7 +2109,7 @@ function isNodeError2(error) {
|
|
|
1998
2109
|
|
|
1999
2110
|
// apps/daemon/dist/index.js
|
|
2000
2111
|
function resolvePackageVersion() {
|
|
2001
|
-
let dir =
|
|
2112
|
+
let dir = dirname4(fileURLToPath(import.meta.url));
|
|
2002
2113
|
for (let i = 0; i < 6; i += 1) {
|
|
2003
2114
|
try {
|
|
2004
2115
|
const pkg = JSON.parse(readFileSync(join5(dir, "package.json"), "utf8"));
|
|
@@ -2006,7 +2117,7 @@ function resolvePackageVersion() {
|
|
|
2006
2117
|
return pkg.version;
|
|
2007
2118
|
} catch {
|
|
2008
2119
|
}
|
|
2009
|
-
const parent =
|
|
2120
|
+
const parent = dirname4(dir);
|
|
2010
2121
|
if (parent === dir)
|
|
2011
2122
|
break;
|
|
2012
2123
|
dir = parent;
|
|
@@ -2020,12 +2131,19 @@ function describeDaemon() {
|
|
|
2020
2131
|
|
|
2021
2132
|
// apps/daemon/dist/cli.js
|
|
2022
2133
|
var args = process.argv.slice(2);
|
|
2023
|
-
var cliDir =
|
|
2134
|
+
var cliDir = dirname5(fileURLToPath2(import.meta.url));
|
|
2024
2135
|
var repoRoot = resolve(cliDir, "../../..");
|
|
2025
2136
|
var firstExisting = (paths) => paths.find((p) => existsSync(p));
|
|
2026
2137
|
var dashboardDistDir = firstExisting([resolve(cliDir, "dashboard"), resolve(repoRoot, "apps/dashboard/dist")]) ?? resolve(repoRoot, "apps/dashboard/dist");
|
|
2027
2138
|
var pluginBundlePath = firstExisting([resolve(cliDir, "agent-blackbox.plugin.mjs")]);
|
|
2028
|
-
|
|
2139
|
+
function globalDataDir() {
|
|
2140
|
+
const xdg = process.env.XDG_DATA_HOME;
|
|
2141
|
+
return xdg && xdg.length > 0 ? join6(xdg, "agent-blackbox") : join6(homedir2(), ".local", "share", "agent-blackbox");
|
|
2142
|
+
}
|
|
2143
|
+
void main(args).catch((error) => {
|
|
2144
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
2145
|
+
process.exitCode = 1;
|
|
2146
|
+
});
|
|
2029
2147
|
async function main(argv) {
|
|
2030
2148
|
if (argv.includes("--version") || argv.includes("-v")) {
|
|
2031
2149
|
console.log(AGENT_BLACKBOX_DAEMON_VERSION);
|
|
@@ -2034,19 +2152,48 @@ async function main(argv) {
|
|
|
2034
2152
|
const command = argv[0] ?? "help";
|
|
2035
2153
|
if (command === "daemon") {
|
|
2036
2154
|
const projectDir = readFlag(argv, "--project") ?? process.cwd();
|
|
2037
|
-
const port =
|
|
2155
|
+
const port = portArg(readFlag(argv, "--port"), 47831);
|
|
2038
2156
|
const daemon = await startTraceDaemon({ projectDir, port });
|
|
2039
2157
|
console.log(`Agent-Blackbox daemon listening on http://127.0.0.1:${daemon.port}`);
|
|
2040
2158
|
console.log(`Trace file: ${daemon.eventsFile}`);
|
|
2041
2159
|
return;
|
|
2042
2160
|
}
|
|
2043
2161
|
if (command === "up") {
|
|
2044
|
-
const
|
|
2045
|
-
const
|
|
2046
|
-
const
|
|
2162
|
+
const projectFlag = readFlag(argv, "--project");
|
|
2163
|
+
const global = projectFlag === void 0;
|
|
2164
|
+
const port = portArg(readFlag(argv, "--port"), 47831);
|
|
2165
|
+
const uiPort = portArg(readFlag(argv, "--ui-port"), 5173);
|
|
2047
2166
|
const daemonUrl = `http://127.0.0.1:${port}`;
|
|
2048
|
-
const adapterPackage = readFlag(argv, "--adapter-package") ?? `file:${resolve(repoRoot, "packages/opencode-adapter")}`;
|
|
2049
2167
|
const suggest = readSuggestConfig(argv);
|
|
2168
|
+
let daemon;
|
|
2169
|
+
if (global) {
|
|
2170
|
+
if (!pluginBundlePath) {
|
|
2171
|
+
throw new Error("Global install needs the self-contained recorder bundle. Use the published npx package, or `npm run build:cli` then `node packages/cli/dist/cli.js up`.\n(Or scope to one project with: agent-blackbox up --project <dir>.)");
|
|
2172
|
+
}
|
|
2173
|
+
const { pluginPath } = await installGlobalRecorder({ daemonUrl, pluginBundlePath });
|
|
2174
|
+
const dataDir = globalDataDir();
|
|
2175
|
+
const eventsFile = join6(dataDir, "events.ndjson");
|
|
2176
|
+
daemon = await startTraceDaemon({ projectDir: dataDir, port, eventsFile, suggest });
|
|
2177
|
+
const ui2 = await startDashboardServer({ distDir: dashboardDistDir, port: uiPort, daemonUrl });
|
|
2178
|
+
const dashboardUrl2 = `http://127.0.0.1:${ui2.port}`;
|
|
2179
|
+
console.log(`\u2713 Global OpenCode recorder installed: ${pluginPath}`);
|
|
2180
|
+
console.log(`\u2713 Agent-Blackbox is up (recording all OpenCode sessions)`);
|
|
2181
|
+
console.log(` Dashboard: ${dashboardUrl2}`);
|
|
2182
|
+
console.log(` Daemon API: ${daemonUrl} (trace: ${daemon.eventsFile})`);
|
|
2183
|
+
console.log(` Suggestions: ${suggest.mode}${suggest.model ? ` (${suggest.model})` : ""}`);
|
|
2184
|
+
console.log("");
|
|
2185
|
+
if (!argv.includes("--no-open"))
|
|
2186
|
+
openInBrowser(dashboardUrl2);
|
|
2187
|
+
console.log("Now use OpenCode however you already do \u2014 the dashboard fills in live:");
|
|
2188
|
+
console.log(" opencode # in any folder (terminal), or");
|
|
2189
|
+
console.log(" the OpenCode desktop app # open any project");
|
|
2190
|
+
console.log("");
|
|
2191
|
+
console.log("Stop recording any time with: agent-blackbox uninstall");
|
|
2192
|
+
console.log("Press Ctrl+C to stop the daemon + dashboard.");
|
|
2193
|
+
return;
|
|
2194
|
+
}
|
|
2195
|
+
const projectDir = resolve(projectFlag);
|
|
2196
|
+
const adapterPackage = readFlag(argv, "--adapter-package") ?? `file:${resolve(repoRoot, "packages/opencode-adapter")}`;
|
|
2050
2197
|
try {
|
|
2051
2198
|
const result = await initOpenCodeProject({
|
|
2052
2199
|
projectDir,
|
|
@@ -2064,7 +2211,7 @@ async function main(argv) {
|
|
|
2064
2211
|
throw error;
|
|
2065
2212
|
}
|
|
2066
2213
|
}
|
|
2067
|
-
|
|
2214
|
+
daemon = await startTraceDaemon({ projectDir, port, suggest });
|
|
2068
2215
|
const ui = await startDashboardServer({ distDir: dashboardDistDir, port: uiPort, daemonUrl });
|
|
2069
2216
|
const dashboardUrl = `http://127.0.0.1:${ui.port}`;
|
|
2070
2217
|
console.log("");
|
|
@@ -2076,11 +2223,29 @@ async function main(argv) {
|
|
|
2076
2223
|
if (!argv.includes("--no-open"))
|
|
2077
2224
|
openInBrowser(dashboardUrl);
|
|
2078
2225
|
console.log("Now run your agent in that project, e.g.:");
|
|
2079
|
-
console.log(`
|
|
2226
|
+
console.log(` opencode # in ${projectDir} (the project-local recorder streams here)`);
|
|
2080
2227
|
console.log("");
|
|
2081
2228
|
console.log("Press Ctrl+C to stop.");
|
|
2082
2229
|
return;
|
|
2083
2230
|
}
|
|
2231
|
+
if (command === "install") {
|
|
2232
|
+
const port = portArg(readFlag(argv, "--port"), 47831);
|
|
2233
|
+
const daemonUrl = `http://127.0.0.1:${port}`;
|
|
2234
|
+
if (!pluginBundlePath) {
|
|
2235
|
+
throw new Error("Global install needs the self-contained recorder bundle. Use the published npx package, or `npm run build:cli` first.");
|
|
2236
|
+
}
|
|
2237
|
+
const { pluginPath } = await installGlobalRecorder({ daemonUrl, pluginBundlePath });
|
|
2238
|
+
console.log(`\u2713 Global OpenCode recorder installed: ${pluginPath}`);
|
|
2239
|
+
console.log(` Every OpenCode session (any folder, terminal, or the app) now streams to ${daemonUrl}.`);
|
|
2240
|
+
console.log(` Start the dashboard with: agent-blackbox up`);
|
|
2241
|
+
console.log(` Remove with: agent-blackbox uninstall`);
|
|
2242
|
+
return;
|
|
2243
|
+
}
|
|
2244
|
+
if (command === "uninstall") {
|
|
2245
|
+
const { pluginPath, removed } = await uninstallGlobalRecorder();
|
|
2246
|
+
console.log(removed ? `\u2713 Removed global OpenCode recorder: ${pluginPath}` : `Nothing to remove \u2014 ${pluginPath} is not present.`);
|
|
2247
|
+
return;
|
|
2248
|
+
}
|
|
2084
2249
|
if (command === "replay") {
|
|
2085
2250
|
const eventsFile = argv[1];
|
|
2086
2251
|
if (!eventsFile) {
|
|
@@ -2138,8 +2303,11 @@ function printHelp() {
|
|
|
2138
2303
|
console.log(describeDaemon());
|
|
2139
2304
|
console.log("");
|
|
2140
2305
|
console.log("Usage:");
|
|
2141
|
-
console.log(" agent-blackbox up
|
|
2142
|
-
console.log("
|
|
2306
|
+
console.log(" agent-blackbox up # GLOBAL: record every OpenCode session (any folder / the app) + daemon + dashboard");
|
|
2307
|
+
console.log(" agent-blackbox up --project <dir> # scope the recorder to one project instead");
|
|
2308
|
+
console.log(" [--port <port>] [--ui-port <port>] [--suggest auto|free|off|ollama|opencode|openai-compat] [--suggest-model <id>] [--optimize] [--no-open]");
|
|
2309
|
+
console.log(" agent-blackbox install [--port <port>] # install the global recorder only (no daemon)");
|
|
2310
|
+
console.log(" agent-blackbox uninstall # remove the global recorder");
|
|
2143
2311
|
console.log(" agent-blackbox daemon [--project <dir>] [--port <port>]");
|
|
2144
2312
|
console.log(" agent-blackbox init-opencode [--project <dir>] [--daemon-url <url>] [--adapter-package <specifier>] [--force] [--optimize]");
|
|
2145
2313
|
console.log(" agent-blackbox optimize [--project <dir>] [--apply | --check | --revert] # write/measure/rollback AGENTS.md efficiency memory");
|
|
@@ -2163,6 +2331,10 @@ function readFlag(argv, flag) {
|
|
|
2163
2331
|
}
|
|
2164
2332
|
return argv[index + 1];
|
|
2165
2333
|
}
|
|
2334
|
+
function portArg(raw, fallback) {
|
|
2335
|
+
const n = Number(raw);
|
|
2336
|
+
return Number.isInteger(n) && n >= 0 && n <= 65535 ? n : fallback;
|
|
2337
|
+
}
|
|
2166
2338
|
function readSuggestConfig(argv) {
|
|
2167
2339
|
const modes = ["auto", "off", "free", "ollama", "opencode", "openai-compat"];
|
|
2168
2340
|
const raw = readFlag(argv, "--suggest") ?? process.env.AGENT_BLACKBOX_SUGGEST ?? "auto";
|