@taewooopark/agent-blackbox 0.45.0 → 0.46.1
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.
|
@@ -81,6 +81,7 @@ function createTraceEvent(seq, input) {
|
|
|
81
81
|
runId: input.runId,
|
|
82
82
|
sessionId: input.sessionId,
|
|
83
83
|
...input.parentSessionId ? { parentSessionId: input.parentSessionId } : {},
|
|
84
|
+
...input.cwd ? { cwd: input.cwd } : {},
|
|
84
85
|
...input.agentId ? { agentId: input.agentId } : {},
|
|
85
86
|
...input.agentRole ? { agentRole: input.agentRole } : {},
|
|
86
87
|
...input.turnId ? { turnId: input.turnId } : {},
|
|
@@ -111,6 +112,9 @@ function validateTraceEvent(event) {
|
|
|
111
112
|
requireEnum(event, "host", traceHosts, errors);
|
|
112
113
|
requireString(event, "runId", errors);
|
|
113
114
|
requireString(event, "sessionId", errors);
|
|
115
|
+
if (event.cwd !== void 0 && typeof event.cwd !== "string") {
|
|
116
|
+
errors.push("cwd must be a string when present");
|
|
117
|
+
}
|
|
114
118
|
requireEnum(event, "kind", traceEventKinds, errors);
|
|
115
119
|
requireEnum(event, "sensitivity", dataSensitivities, errors);
|
|
116
120
|
if (!isRecord(event.payload)) {
|
|
@@ -168,16 +172,16 @@ var defaultRedactionRules = [
|
|
|
168
172
|
pattern: /\b(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9_]{20,}\b/g,
|
|
169
173
|
replacement: "[REDACTED_GITHUB_TOKEN]"
|
|
170
174
|
},
|
|
171
|
-
{
|
|
172
|
-
name: "openai-key",
|
|
173
|
-
pattern: /\bsk-[A-Za-z0-9_-]{20,}\b/g,
|
|
174
|
-
replacement: "[REDACTED_OPENAI_KEY]"
|
|
175
|
-
},
|
|
176
175
|
{
|
|
177
176
|
name: "anthropic-key",
|
|
178
177
|
pattern: /\bsk-ant-[A-Za-z0-9_-]{20,}\b/g,
|
|
179
178
|
replacement: "[REDACTED_ANTHROPIC_KEY]"
|
|
180
179
|
},
|
|
180
|
+
{
|
|
181
|
+
name: "openai-key",
|
|
182
|
+
pattern: /\bsk-[A-Za-z0-9_-]{20,}\b/g,
|
|
183
|
+
replacement: "[REDACTED_OPENAI_KEY]"
|
|
184
|
+
},
|
|
181
185
|
{
|
|
182
186
|
name: "private-key",
|
|
183
187
|
pattern: /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g,
|
|
@@ -195,12 +199,12 @@ function redactJsonValue(value, options = {}) {
|
|
|
195
199
|
const visit = (current) => {
|
|
196
200
|
if (typeof current === "string") {
|
|
197
201
|
let next = current;
|
|
198
|
-
if (options.homeDir) {
|
|
199
|
-
next = replaceLiteral(next, options.homeDir, "~", "home-dir", applied);
|
|
200
|
-
}
|
|
201
202
|
if (options.projectDir) {
|
|
202
203
|
next = replaceLiteral(next, options.projectDir, "$PROJECT", "project-dir", applied);
|
|
203
204
|
}
|
|
205
|
+
if (options.homeDir) {
|
|
206
|
+
next = replaceLiteral(next, options.homeDir, "~", "home-dir", applied);
|
|
207
|
+
}
|
|
204
208
|
for (const rule of rules) {
|
|
205
209
|
if (rule.pattern.test(next)) {
|
|
206
210
|
applied.add(rule.name);
|
|
@@ -429,6 +433,7 @@ function normalizeToolBefore(input, output, context) {
|
|
|
429
433
|
const agentId = extractAgentId(input);
|
|
430
434
|
if (agentId) {
|
|
431
435
|
traceInput.agentId = agentId;
|
|
436
|
+
traceInput.agentRole = extractAgentRole(input);
|
|
432
437
|
}
|
|
433
438
|
return createTraceEvent(context.seq, traceInput);
|
|
434
439
|
}
|
|
@@ -462,6 +467,7 @@ function normalizeToolAfter(input, output, context) {
|
|
|
462
467
|
const agentId = extractAgentId(input);
|
|
463
468
|
if (agentId) {
|
|
464
469
|
traceInput.agentId = agentId;
|
|
470
|
+
traceInput.agentRole = extractAgentRole(input);
|
|
465
471
|
}
|
|
466
472
|
}
|
|
467
473
|
return createTraceEvent(context.seq, traceInput);
|
|
@@ -618,7 +624,9 @@ function sanitizeJson(value, seen) {
|
|
|
618
624
|
return "[Circular]";
|
|
619
625
|
}
|
|
620
626
|
seen.add(value);
|
|
621
|
-
|
|
627
|
+
const mapped = value.map((item) => sanitizeJson(item, seen));
|
|
628
|
+
seen.delete(value);
|
|
629
|
+
return mapped;
|
|
622
630
|
}
|
|
623
631
|
if (typeof value === "object") {
|
|
624
632
|
if (seen.has(value)) {
|
|
@@ -632,6 +640,7 @@ function sanitizeJson(value, seen) {
|
|
|
632
640
|
}
|
|
633
641
|
output[key] = sanitizeJson(nested, seen);
|
|
634
642
|
}
|
|
643
|
+
seen.delete(value);
|
|
635
644
|
return output;
|
|
636
645
|
}
|
|
637
646
|
return String(value);
|
|
@@ -843,26 +852,48 @@ function serializeTraceEvent(event) {
|
|
|
843
852
|
return `${JSON.stringify(event)}
|
|
844
853
|
`;
|
|
845
854
|
}
|
|
855
|
+
var writeChains = /* @__PURE__ */ new Map();
|
|
846
856
|
async function appendTraceEvent(filePath, event) {
|
|
847
|
-
|
|
848
|
-
|
|
857
|
+
const line = serializeTraceEvent(event);
|
|
858
|
+
const run = async () => {
|
|
859
|
+
await mkdir(dirname(filePath), { recursive: true });
|
|
860
|
+
await appendFile(filePath, line, "utf8");
|
|
861
|
+
};
|
|
862
|
+
const prev = writeChains.get(filePath) ?? Promise.resolve();
|
|
863
|
+
const next = prev.then(run, run);
|
|
864
|
+
const tail = next.then(
|
|
865
|
+
() => void 0,
|
|
866
|
+
() => void 0
|
|
867
|
+
);
|
|
868
|
+
writeChains.set(filePath, tail);
|
|
869
|
+
try {
|
|
870
|
+
await next;
|
|
871
|
+
} finally {
|
|
872
|
+
if (writeChains.get(filePath) === tail) writeChains.delete(filePath);
|
|
873
|
+
}
|
|
849
874
|
}
|
|
850
875
|
|
|
851
876
|
// packages/opencode-adapter/dist/sink.js
|
|
852
877
|
import { join } from "node:path";
|
|
853
878
|
function createTraceSink(options) {
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
879
|
+
const base = options.sink ? options.sink : options.daemonUrl ? createHttpTraceSink(options.daemonUrl) : createFileTraceSink(options.eventsFile ?? join(options.directory, ".agent-blackbox", "events.ndjson"));
|
|
880
|
+
return {
|
|
881
|
+
async write(event) {
|
|
882
|
+
if (!event.cwd && options.directory) {
|
|
883
|
+
event.cwd = options.directory;
|
|
884
|
+
}
|
|
885
|
+
await base.write(event);
|
|
886
|
+
}
|
|
887
|
+
};
|
|
861
888
|
}
|
|
862
889
|
function createFileTraceSink(eventsFile) {
|
|
863
890
|
return {
|
|
864
891
|
async write(event) {
|
|
865
|
-
|
|
892
|
+
try {
|
|
893
|
+
await appendTraceEvent(eventsFile, event);
|
|
894
|
+
} catch (error) {
|
|
895
|
+
console.warn(`[agent-blackbox] could not write event ${event.id}: ${error instanceof Error ? error.message : String(error)}`);
|
|
896
|
+
}
|
|
866
897
|
}
|
|
867
898
|
};
|
|
868
899
|
}
|
|
@@ -933,7 +964,24 @@ function resolveRecorderOptions(options) {
|
|
|
933
964
|
}
|
|
934
965
|
|
|
935
966
|
// packages/opencode-adapter/dist/index.js
|
|
967
|
+
function noopRecorderHooks() {
|
|
968
|
+
return {
|
|
969
|
+
event: async () => {
|
|
970
|
+
},
|
|
971
|
+
"tool.execute.before": async () => {
|
|
972
|
+
},
|
|
973
|
+
"tool.execute.after": async () => {
|
|
974
|
+
},
|
|
975
|
+
"experimental.session.compacting": async () => {
|
|
976
|
+
},
|
|
977
|
+
"experimental.chat.system.transform": async () => {
|
|
978
|
+
}
|
|
979
|
+
};
|
|
980
|
+
}
|
|
936
981
|
async function createOpenCodeRecorder(context, options = {}) {
|
|
982
|
+
if (process.env.AGENT_BLACKBOX_DISABLE === "1") {
|
|
983
|
+
return noopRecorderHooks();
|
|
984
|
+
}
|
|
937
985
|
const resolved = resolveRecorderOptions(options);
|
|
938
986
|
const directory = context.directory ?? process.cwd();
|
|
939
987
|
const runId = resolved.runId ?? `opencode-${Date.now()}`;
|
|
@@ -1021,7 +1069,7 @@ function createOpenCodeEventFactory(options) {
|
|
|
1021
1069
|
const decision = decideReadServe(readCache.get(key), { hash, content: current }, compactionGen, path);
|
|
1022
1070
|
readCache.set(key, { hash, content: current, gen: compactionGen });
|
|
1023
1071
|
bumpFile(path, "read", hash);
|
|
1024
|
-
if (decision.mode !== "full" && typeof decision.output === "string") {
|
|
1072
|
+
if (decision.mode !== "full" && typeof decision.output === "string" && decision.saved > 0) {
|
|
1025
1073
|
output.output = decision.output;
|
|
1026
1074
|
}
|
|
1027
1075
|
};
|
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}
|
|
@@ -1139,7 +1151,14 @@ async function startDashboardServer(options) {
|
|
|
1139
1151
|
const injected = indexHtml.replace("</head>", ` <script>window.AGENT_BLACKBOX_DAEMON_URL=${JSON.stringify(options.daemonUrl)};</script>
|
|
1140
1152
|
</head>`);
|
|
1141
1153
|
const server = createServer((request, response) => {
|
|
1142
|
-
|
|
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
|
+
}
|
|
1143
1162
|
if (rawPath === "/" || rawPath === "/index.html") {
|
|
1144
1163
|
response.writeHead(200, { "content-type": "text/html; charset=utf-8" });
|
|
1145
1164
|
response.end(injected);
|
|
@@ -1157,15 +1176,21 @@ async function startDashboardServer(options) {
|
|
|
1157
1176
|
throw new Error("not a file");
|
|
1158
1177
|
}
|
|
1159
1178
|
response.writeHead(200, { "content-type": mimeTypes[extname(filePath)] ?? "application/octet-stream" });
|
|
1160
|
-
createReadStream(filePath)
|
|
1179
|
+
const rs = createReadStream(filePath);
|
|
1180
|
+
rs.on("error", () => response.destroy());
|
|
1181
|
+
rs.pipe(response);
|
|
1161
1182
|
}).catch(() => {
|
|
1162
1183
|
response.writeHead(200, { "content-type": "text/html; charset=utf-8" });
|
|
1163
1184
|
response.end(injected);
|
|
1164
1185
|
});
|
|
1165
1186
|
});
|
|
1166
1187
|
const port = options.port ?? 5173;
|
|
1167
|
-
await new Promise((resolve2) => {
|
|
1168
|
-
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
|
+
});
|
|
1169
1194
|
});
|
|
1170
1195
|
const address = server.address();
|
|
1171
1196
|
const actualPort = typeof address === "object" && address ? address.port : port;
|
|
@@ -1197,11 +1222,39 @@ function parseTraceEventLine(line) {
|
|
|
1197
1222
|
return parsed;
|
|
1198
1223
|
}
|
|
1199
1224
|
function parseTraceEvents(input) {
|
|
1200
|
-
|
|
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;
|
|
1201
1238
|
}
|
|
1239
|
+
var writeChains = /* @__PURE__ */ new Map();
|
|
1202
1240
|
async function appendTraceEvent(filePath, event) {
|
|
1203
|
-
|
|
1204
|
-
|
|
1241
|
+
const line = serializeTraceEvent(event);
|
|
1242
|
+
const run = async () => {
|
|
1243
|
+
await mkdir(dirname(filePath), { recursive: true });
|
|
1244
|
+
await appendFile(filePath, line, "utf8");
|
|
1245
|
+
};
|
|
1246
|
+
const prev = writeChains.get(filePath) ?? Promise.resolve();
|
|
1247
|
+
const next = prev.then(run, run);
|
|
1248
|
+
const tail = next.then(
|
|
1249
|
+
() => void 0,
|
|
1250
|
+
() => void 0
|
|
1251
|
+
);
|
|
1252
|
+
writeChains.set(filePath, tail);
|
|
1253
|
+
try {
|
|
1254
|
+
await next;
|
|
1255
|
+
} finally {
|
|
1256
|
+
if (writeChains.get(filePath) === tail) writeChains.delete(filePath);
|
|
1257
|
+
}
|
|
1205
1258
|
}
|
|
1206
1259
|
async function readTraceEvents(filePath) {
|
|
1207
1260
|
const input = await readFile2(filePath, "utf8");
|
|
@@ -1215,7 +1268,7 @@ import { WebSocket, WebSocketServer } from "ws";
|
|
|
1215
1268
|
|
|
1216
1269
|
// apps/daemon/dist/optimize.js
|
|
1217
1270
|
import { mkdir as mkdir2, readFile as readFile3, rm, writeFile } from "node:fs/promises";
|
|
1218
|
-
import { dirname as dirname2, join as join2 } from "node:path";
|
|
1271
|
+
import { dirname as dirname2, isAbsolute, join as join2 } from "node:path";
|
|
1219
1272
|
var flaggedIds = (report) => report.metrics.filter((m) => m.status !== "good").map((m) => m.id);
|
|
1220
1273
|
var joinIds = (ids) => ids.join(", ");
|
|
1221
1274
|
var REVERT_MARGIN = 3;
|
|
@@ -1226,10 +1279,12 @@ async function runOptimize(options) {
|
|
|
1226
1279
|
}
|
|
1227
1280
|
async function computeOptimize(options) {
|
|
1228
1281
|
const eventsFile = options.eventsFile ?? join2(options.projectDir, ".agent-blackbox", "events.ndjson");
|
|
1229
|
-
const agentsMdPath = join2(options.projectDir, "AGENTS.md");
|
|
1230
|
-
const statePath = join2(options.projectDir, ".agent-blackbox", "optimization.json");
|
|
1231
1282
|
const events = await loadTraceEvents(eventsFile);
|
|
1232
1283
|
const { runId, events: runEvents } = latestRun(events);
|
|
1284
|
+
const runCwd = runEvents.find((e) => typeof e.cwd === "string" && e.cwd.length > 0)?.cwd;
|
|
1285
|
+
const targetDir = runCwd && isAbsolute(runCwd) ? runCwd : options.projectDir;
|
|
1286
|
+
const agentsMdPath = join2(targetDir, "AGENTS.md");
|
|
1287
|
+
const statePath = join2(targetDir, ".agent-blackbox", "optimization.json");
|
|
1233
1288
|
const latestTs = runEvents.reduce((max, e) => e.ts > max ? e.ts : max, "");
|
|
1234
1289
|
const report = runEvents.length > 0 ? computeEfficiencyReport(runEvents) : null;
|
|
1235
1290
|
const score = report ? report.overallScore : null;
|
|
@@ -1255,15 +1310,17 @@ async function computeOptimize(options) {
|
|
|
1255
1310
|
}
|
|
1256
1311
|
const prior = await readMaybe(agentsMdPath);
|
|
1257
1312
|
const next = upsertManagedBlock(prior ?? "", block);
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1313
|
+
if (prior === null || next !== prior) {
|
|
1314
|
+
await writeFile(agentsMdPath, next, "utf8");
|
|
1315
|
+
await writeState(statePath, {
|
|
1316
|
+
runId: runId ?? "",
|
|
1317
|
+
baselineScore: score,
|
|
1318
|
+
baselineLatestTs: latestTs,
|
|
1319
|
+
baselineFlagged: flaggedIds(report),
|
|
1320
|
+
fileExisted: prior !== null,
|
|
1321
|
+
appliedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1322
|
+
});
|
|
1323
|
+
}
|
|
1267
1324
|
return {
|
|
1268
1325
|
mode: "apply",
|
|
1269
1326
|
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.`,
|
|
@@ -1470,7 +1527,7 @@ function buildDigest(report) {
|
|
|
1470
1527
|
id: m.id,
|
|
1471
1528
|
label: m.label,
|
|
1472
1529
|
status: m.status,
|
|
1473
|
-
value: Number(m.value.toFixed(3)),
|
|
1530
|
+
value: Number((typeof m.value === "number" && Number.isFinite(m.value) ? m.value : 0).toFixed(3)),
|
|
1474
1531
|
display: m.display,
|
|
1475
1532
|
detail: m.detail,
|
|
1476
1533
|
...m.reclaimableTokens ? { reclaimableTokens: m.reclaimableTokens } : {},
|
|
@@ -1492,6 +1549,8 @@ var freeCooldownUntil = /* @__PURE__ */ new Map();
|
|
|
1492
1549
|
function orderFreePool(pool, cooldownUntil, cursor, now) {
|
|
1493
1550
|
const fresh = pool.filter((entry) => (cooldownUntil.get(entry.model) ?? 0) <= now);
|
|
1494
1551
|
const list = fresh.length > 0 ? fresh : pool;
|
|
1552
|
+
if (list.length === 0)
|
|
1553
|
+
return [];
|
|
1495
1554
|
const start = (cursor % list.length + list.length) % list.length;
|
|
1496
1555
|
return [...list.slice(start), ...list.slice(0, start)];
|
|
1497
1556
|
}
|
|
@@ -1636,7 +1695,10 @@ async function fetchJson(url, body, extraHeaders = {}) {
|
|
|
1636
1695
|
}
|
|
1637
1696
|
function runCommand(command, args2) {
|
|
1638
1697
|
return new Promise((resolve2, reject) => {
|
|
1639
|
-
const child = spawn(command, args2, {
|
|
1698
|
+
const child = spawn(command, args2, {
|
|
1699
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
1700
|
+
env: { ...process.env, AGENT_BLACKBOX_DISABLE: "1" }
|
|
1701
|
+
});
|
|
1640
1702
|
let out = "";
|
|
1641
1703
|
const timer = setTimeout(() => {
|
|
1642
1704
|
child.kill("SIGKILL");
|
|
@@ -1663,10 +1725,24 @@ function extractJsonObject(text) {
|
|
|
1663
1725
|
if (start === -1)
|
|
1664
1726
|
return void 0;
|
|
1665
1727
|
let depth = 0;
|
|
1728
|
+
let inString = false;
|
|
1729
|
+
let escaped = false;
|
|
1666
1730
|
for (let i = start; i < text.length; i += 1) {
|
|
1667
|
-
|
|
1731
|
+
const ch = text[i];
|
|
1732
|
+
if (inString) {
|
|
1733
|
+
if (escaped)
|
|
1734
|
+
escaped = false;
|
|
1735
|
+
else if (ch === "\\")
|
|
1736
|
+
escaped = true;
|
|
1737
|
+
else if (ch === '"')
|
|
1738
|
+
inString = false;
|
|
1739
|
+
continue;
|
|
1740
|
+
}
|
|
1741
|
+
if (ch === '"')
|
|
1742
|
+
inString = true;
|
|
1743
|
+
else if (ch === "{")
|
|
1668
1744
|
depth += 1;
|
|
1669
|
-
else if (
|
|
1745
|
+
else if (ch === "}") {
|
|
1670
1746
|
depth -= 1;
|
|
1671
1747
|
if (depth === 0) {
|
|
1672
1748
|
try {
|
|
@@ -1704,8 +1780,12 @@ async function startTraceDaemon(options) {
|
|
|
1704
1780
|
});
|
|
1705
1781
|
});
|
|
1706
1782
|
const port = options.port ?? 47831;
|
|
1707
|
-
await new Promise((resolve2) => {
|
|
1708
|
-
server.
|
|
1783
|
+
await new Promise((resolve2, reject) => {
|
|
1784
|
+
server.once("error", reject);
|
|
1785
|
+
server.listen(port, "127.0.0.1", () => {
|
|
1786
|
+
server.off("error", reject);
|
|
1787
|
+
resolve2();
|
|
1788
|
+
});
|
|
1709
1789
|
});
|
|
1710
1790
|
const address = server.address();
|
|
1711
1791
|
const actualPort = typeof address === "object" && address ? address.port : port;
|
|
@@ -1714,8 +1794,12 @@ async function startTraceDaemon(options) {
|
|
|
1714
1794
|
port: actualPort,
|
|
1715
1795
|
eventsFile,
|
|
1716
1796
|
close: () => new Promise((resolve2, reject) => {
|
|
1797
|
+
for (const client of clients) {
|
|
1798
|
+
client.terminate();
|
|
1799
|
+
}
|
|
1800
|
+
clients.clear();
|
|
1801
|
+
streamServer.close();
|
|
1717
1802
|
server.close((error) => {
|
|
1718
|
-
streamServer.close();
|
|
1719
1803
|
if (error) {
|
|
1720
1804
|
reject(error);
|
|
1721
1805
|
} else {
|
|
@@ -1768,12 +1852,13 @@ async function buildTraceSnapshot(eventsFile, replay = {}) {
|
|
|
1768
1852
|
}
|
|
1769
1853
|
async function handleRequest(request, response, eventsFile, clients, suggestConfig, projectDir) {
|
|
1770
1854
|
try {
|
|
1855
|
+
applyCors(request, response);
|
|
1771
1856
|
const url = new URL(request.url ?? "/", "http://127.0.0.1");
|
|
1772
|
-
const replay = parseReplayQuery(url);
|
|
1773
1857
|
if (request.method === "OPTIONS") {
|
|
1774
1858
|
sendEmpty(response, 204);
|
|
1775
1859
|
return;
|
|
1776
1860
|
}
|
|
1861
|
+
const replay = parseReplayQuery(url);
|
|
1777
1862
|
if (request.method === "GET" && url.pathname === "/health") {
|
|
1778
1863
|
sendJson(response, 200, { ok: true, data: { status: "ok", eventsFile } });
|
|
1779
1864
|
return;
|
|
@@ -1868,19 +1953,47 @@ async function sendSnapshot(client, eventsFile) {
|
|
|
1868
1953
|
}
|
|
1869
1954
|
}
|
|
1870
1955
|
}
|
|
1956
|
+
var MAX_BODY_BYTES = 5e7;
|
|
1871
1957
|
async function readJsonBody(request) {
|
|
1872
1958
|
const chunks = [];
|
|
1959
|
+
let total = 0;
|
|
1873
1960
|
for await (const chunk of request) {
|
|
1874
|
-
|
|
1961
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
1962
|
+
total += buffer.length;
|
|
1963
|
+
if (total > MAX_BODY_BYTES) {
|
|
1964
|
+
throw new BadRequestError("Request body too large");
|
|
1965
|
+
}
|
|
1966
|
+
chunks.push(buffer);
|
|
1875
1967
|
}
|
|
1876
1968
|
const raw = Buffer.concat(chunks).toString("utf8");
|
|
1877
|
-
|
|
1969
|
+
if (raw.length === 0) {
|
|
1970
|
+
return {};
|
|
1971
|
+
}
|
|
1972
|
+
try {
|
|
1973
|
+
return JSON.parse(raw);
|
|
1974
|
+
} catch {
|
|
1975
|
+
throw new BadRequestError("Invalid JSON body");
|
|
1976
|
+
}
|
|
1977
|
+
}
|
|
1978
|
+
function applyCors(request, response) {
|
|
1979
|
+
const origin = request.headers.origin;
|
|
1980
|
+
if (typeof origin === "string" && isLoopbackOrigin(origin)) {
|
|
1981
|
+
response.setHeader("access-control-allow-origin", origin);
|
|
1982
|
+
response.setHeader("vary", "Origin");
|
|
1983
|
+
}
|
|
1984
|
+
}
|
|
1985
|
+
function isLoopbackOrigin(origin) {
|
|
1986
|
+
try {
|
|
1987
|
+
const { hostname } = new URL(origin);
|
|
1988
|
+
return hostname === "127.0.0.1" || hostname === "localhost" || hostname === "[::1]" || hostname === "::1";
|
|
1989
|
+
} catch {
|
|
1990
|
+
return false;
|
|
1991
|
+
}
|
|
1878
1992
|
}
|
|
1879
1993
|
function sendJson(response, statusCode, payload) {
|
|
1880
1994
|
response.writeHead(statusCode, {
|
|
1881
1995
|
"access-control-allow-headers": "content-type",
|
|
1882
1996
|
"access-control-allow-methods": "GET,POST,OPTIONS",
|
|
1883
|
-
"access-control-allow-origin": "*",
|
|
1884
1997
|
"content-type": "application/json; charset=utf-8"
|
|
1885
1998
|
});
|
|
1886
1999
|
response.end(JSON.stringify(payload));
|
|
@@ -1888,8 +2001,7 @@ function sendJson(response, statusCode, payload) {
|
|
|
1888
2001
|
function sendEmpty(response, statusCode) {
|
|
1889
2002
|
response.writeHead(statusCode, {
|
|
1890
2003
|
"access-control-allow-headers": "content-type",
|
|
1891
|
-
"access-control-allow-methods": "GET,POST,OPTIONS"
|
|
1892
|
-
"access-control-allow-origin": "*"
|
|
2004
|
+
"access-control-allow-methods": "GET,POST,OPTIONS"
|
|
1893
2005
|
});
|
|
1894
2006
|
response.end();
|
|
1895
2007
|
}
|
|
@@ -2059,7 +2171,10 @@ function globalDataDir() {
|
|
|
2059
2171
|
const xdg = process.env.XDG_DATA_HOME;
|
|
2060
2172
|
return xdg && xdg.length > 0 ? join6(xdg, "agent-blackbox") : join6(homedir2(), ".local", "share", "agent-blackbox");
|
|
2061
2173
|
}
|
|
2062
|
-
void main(args)
|
|
2174
|
+
void main(args).catch((error) => {
|
|
2175
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
2176
|
+
process.exitCode = 1;
|
|
2177
|
+
});
|
|
2063
2178
|
async function main(argv) {
|
|
2064
2179
|
if (argv.includes("--version") || argv.includes("-v")) {
|
|
2065
2180
|
console.log(AGENT_BLACKBOX_DAEMON_VERSION);
|
|
@@ -2068,7 +2183,7 @@ async function main(argv) {
|
|
|
2068
2183
|
const command = argv[0] ?? "help";
|
|
2069
2184
|
if (command === "daemon") {
|
|
2070
2185
|
const projectDir = readFlag(argv, "--project") ?? process.cwd();
|
|
2071
|
-
const port =
|
|
2186
|
+
const port = portArg(readFlag(argv, "--port"), 47831);
|
|
2072
2187
|
const daemon = await startTraceDaemon({ projectDir, port });
|
|
2073
2188
|
console.log(`Agent-Blackbox daemon listening on http://127.0.0.1:${daemon.port}`);
|
|
2074
2189
|
console.log(`Trace file: ${daemon.eventsFile}`);
|
|
@@ -2077,8 +2192,8 @@ async function main(argv) {
|
|
|
2077
2192
|
if (command === "up") {
|
|
2078
2193
|
const projectFlag = readFlag(argv, "--project");
|
|
2079
2194
|
const global = projectFlag === void 0;
|
|
2080
|
-
const port =
|
|
2081
|
-
const uiPort =
|
|
2195
|
+
const port = portArg(readFlag(argv, "--port"), 47831);
|
|
2196
|
+
const uiPort = portArg(readFlag(argv, "--ui-port"), 5173);
|
|
2082
2197
|
const daemonUrl = `http://127.0.0.1:${port}`;
|
|
2083
2198
|
const suggest = readSuggestConfig(argv);
|
|
2084
2199
|
let daemon;
|
|
@@ -2145,7 +2260,7 @@ async function main(argv) {
|
|
|
2145
2260
|
return;
|
|
2146
2261
|
}
|
|
2147
2262
|
if (command === "install") {
|
|
2148
|
-
const port =
|
|
2263
|
+
const port = portArg(readFlag(argv, "--port"), 47831);
|
|
2149
2264
|
const daemonUrl = `http://127.0.0.1:${port}`;
|
|
2150
2265
|
if (!pluginBundlePath) {
|
|
2151
2266
|
throw new Error("Global install needs the self-contained recorder bundle. Use the published npx package, or `npm run build:cli` first.");
|
|
@@ -2247,6 +2362,10 @@ function readFlag(argv, flag) {
|
|
|
2247
2362
|
}
|
|
2248
2363
|
return argv[index + 1];
|
|
2249
2364
|
}
|
|
2365
|
+
function portArg(raw, fallback) {
|
|
2366
|
+
const n = Number(raw);
|
|
2367
|
+
return Number.isInteger(n) && n >= 0 && n <= 65535 ? n : fallback;
|
|
2368
|
+
}
|
|
2250
2369
|
function readSuggestConfig(argv) {
|
|
2251
2370
|
const modes = ["auto", "off", "free", "ollama", "opencode", "openai-compat"];
|
|
2252
2371
|
const raw = readFlag(argv, "--suggest") ?? process.env.AGENT_BLACKBOX_SUGGEST ?? "auto";
|