@taewooopark/agent-blackbox 0.42.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.
@@ -0,0 +1,1162 @@
1
+ // packages/core/src/json.ts
2
+ function isJsonObject(value) {
3
+ return typeof value === "object" && value !== null && !Array.isArray(value);
4
+ }
5
+
6
+ // packages/core/src/events.ts
7
+ var traceHosts = [
8
+ "opencode",
9
+ "pi",
10
+ "codex",
11
+ "claude-code",
12
+ "hermes",
13
+ "custom"
14
+ ];
15
+ var traceEventKinds = [
16
+ "session_created",
17
+ "session_updated",
18
+ "session_idle",
19
+ "session_error",
20
+ "agent_start",
21
+ "agent_end",
22
+ "turn_start",
23
+ "turn_end",
24
+ "message",
25
+ "tool_call",
26
+ "tool_result",
27
+ "file_read",
28
+ "file_edit",
29
+ "file_created",
30
+ "file_deleted",
31
+ "search",
32
+ "bash",
33
+ "permission_asked",
34
+ "permission_replied",
35
+ "todo_updated",
36
+ "subagent_spawned",
37
+ "decision_extracted",
38
+ "blocker_detected",
39
+ "handoff_generated",
40
+ "git_status",
41
+ "git_commit",
42
+ "git_push",
43
+ "context_compacted",
44
+ "command_run",
45
+ "agent_switched",
46
+ "model_switched",
47
+ "host_event"
48
+ ];
49
+ var dataSensitivities = [
50
+ "public",
51
+ "internal",
52
+ "private",
53
+ "secret",
54
+ "student_sensitive"
55
+ ];
56
+ var observedKinds = /* @__PURE__ */ new Set([
57
+ "tool_call",
58
+ "tool_result",
59
+ "file_read",
60
+ "file_edit",
61
+ "file_created",
62
+ "file_deleted",
63
+ "search",
64
+ "bash",
65
+ "permission_asked",
66
+ "permission_replied",
67
+ "todo_updated",
68
+ "git_status",
69
+ "git_commit",
70
+ "git_push"
71
+ ]);
72
+ var claimKinds = /* @__PURE__ */ new Set(["message", "decision_extracted", "handoff_generated"]);
73
+ function createTraceEvent(seq, input) {
74
+ const id = makeTraceEventId(input.runId, seq);
75
+ const evidence = inferEvidence(input.kind, input.evidence);
76
+ return {
77
+ id,
78
+ ts: input.ts ?? (/* @__PURE__ */ new Date()).toISOString(),
79
+ seq,
80
+ host: input.host,
81
+ runId: input.runId,
82
+ sessionId: input.sessionId,
83
+ ...input.parentSessionId ? { parentSessionId: input.parentSessionId } : {},
84
+ ...input.agentId ? { agentId: input.agentId } : {},
85
+ ...input.agentRole ? { agentRole: input.agentRole } : {},
86
+ ...input.turnId ? { turnId: input.turnId } : {},
87
+ kind: input.kind,
88
+ ...input.summary ? { summary: input.summary } : {},
89
+ payload: input.payload ?? {},
90
+ sensitivity: input.sensitivity ?? "private",
91
+ redaction: {
92
+ rawStored: input.redaction?.rawStored ?? false,
93
+ rulesApplied: input.redaction?.rulesApplied ?? [],
94
+ truncated: input.redaction?.truncated ?? false
95
+ },
96
+ evidence
97
+ };
98
+ }
99
+ function makeTraceEventId(runId, seq) {
100
+ const safeRunId = runId.replace(/[^a-zA-Z0-9_-]/g, "_");
101
+ return `evt_${safeRunId}_${String(seq).padStart(6, "0")}`;
102
+ }
103
+ function validateTraceEvent(event) {
104
+ const errors = [];
105
+ if (!isRecord(event)) {
106
+ return { ok: false, errors: ["event must be an object"] };
107
+ }
108
+ requireString(event, "id", errors);
109
+ requireString(event, "ts", errors);
110
+ requireNumber(event, "seq", errors);
111
+ requireEnum(event, "host", traceHosts, errors);
112
+ requireString(event, "runId", errors);
113
+ requireString(event, "sessionId", errors);
114
+ requireEnum(event, "kind", traceEventKinds, errors);
115
+ requireEnum(event, "sensitivity", dataSensitivities, errors);
116
+ if (!isRecord(event.payload)) {
117
+ errors.push("payload must be an object");
118
+ }
119
+ if (!isRecord(event.redaction)) {
120
+ errors.push("redaction must be an object");
121
+ }
122
+ if (!isRecord(event.evidence)) {
123
+ errors.push("evidence must be an object");
124
+ }
125
+ if (typeof event.ts === "string" && Number.isNaN(Date.parse(event.ts))) {
126
+ errors.push("ts must be an ISO-compatible timestamp");
127
+ }
128
+ if (typeof event.seq === "number" && (!Number.isInteger(event.seq) || event.seq < 1)) {
129
+ errors.push("seq must be a positive integer");
130
+ }
131
+ return { ok: errors.length === 0, errors };
132
+ }
133
+ function assertTraceEvent(event) {
134
+ const result = validateTraceEvent(event);
135
+ if (!result.ok) {
136
+ throw new Error(`Invalid trace event: ${result.errors.join("; ")}`);
137
+ }
138
+ }
139
+ function inferEvidence(kind, override) {
140
+ return {
141
+ observed: override?.observed ?? observedKinds.has(kind),
142
+ claimedByModel: override?.claimedByModel ?? claimKinds.has(kind)
143
+ };
144
+ }
145
+ function isRecord(value) {
146
+ return typeof value === "object" && value !== null && !Array.isArray(value);
147
+ }
148
+ function requireString(value, key, errors) {
149
+ if (typeof value[key] !== "string" || value[key] === "") {
150
+ errors.push(`${key} must be a non-empty string`);
151
+ }
152
+ }
153
+ function requireNumber(value, key, errors) {
154
+ if (typeof value[key] !== "number") {
155
+ errors.push(`${key} must be a number`);
156
+ }
157
+ }
158
+ function requireEnum(value, key, allowed, errors) {
159
+ if (typeof value[key] !== "string" || !allowed.includes(value[key])) {
160
+ errors.push(`${key} must be one of ${allowed.join(", ")}`);
161
+ }
162
+ }
163
+
164
+ // packages/core/src/redaction.ts
165
+ var defaultRedactionRules = [
166
+ {
167
+ name: "github-token",
168
+ pattern: /\b(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9_]{20,}\b/g,
169
+ replacement: "[REDACTED_GITHUB_TOKEN]"
170
+ },
171
+ {
172
+ name: "openai-key",
173
+ pattern: /\bsk-[A-Za-z0-9_-]{20,}\b/g,
174
+ replacement: "[REDACTED_OPENAI_KEY]"
175
+ },
176
+ {
177
+ name: "anthropic-key",
178
+ pattern: /\bsk-ant-[A-Za-z0-9_-]{20,}\b/g,
179
+ replacement: "[REDACTED_ANTHROPIC_KEY]"
180
+ },
181
+ {
182
+ name: "private-key",
183
+ pattern: /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g,
184
+ replacement: "[REDACTED_PRIVATE_KEY]"
185
+ }
186
+ ];
187
+ function redactJsonObject(payload, options = {}) {
188
+ return redactJsonValue(payload, options);
189
+ }
190
+ function redactJsonValue(value, options = {}) {
191
+ const rules = [...defaultRedactionRules, ...options.extraRules ?? []];
192
+ const applied = /* @__PURE__ */ new Set();
193
+ let truncated = false;
194
+ const maxStringLength = options.maxStringLength ?? 4e3;
195
+ const visit = (current) => {
196
+ if (typeof current === "string") {
197
+ let next = current;
198
+ if (options.homeDir) {
199
+ next = replaceLiteral(next, options.homeDir, "~", "home-dir", applied);
200
+ }
201
+ if (options.projectDir) {
202
+ next = replaceLiteral(next, options.projectDir, "$PROJECT", "project-dir", applied);
203
+ }
204
+ for (const rule of rules) {
205
+ if (rule.pattern.test(next)) {
206
+ applied.add(rule.name);
207
+ next = next.replace(rule.pattern, rule.replacement);
208
+ }
209
+ rule.pattern.lastIndex = 0;
210
+ }
211
+ if (next.length > maxStringLength) {
212
+ truncated = true;
213
+ applied.add("truncate-string");
214
+ return `${next.slice(0, maxStringLength)}...[TRUNCATED ${next.length - maxStringLength} chars]`;
215
+ }
216
+ return next;
217
+ }
218
+ if (Array.isArray(current)) {
219
+ return current.map((item) => visit(item));
220
+ }
221
+ if (isJsonObject(current)) {
222
+ const next = {};
223
+ for (const [key, nested] of Object.entries(current)) {
224
+ next[key] = visit(nested);
225
+ }
226
+ return next;
227
+ }
228
+ return current;
229
+ };
230
+ return {
231
+ value: visit(value),
232
+ rulesApplied: [...applied].sort(),
233
+ truncated
234
+ };
235
+ }
236
+ function replaceLiteral(value, search, replacement, ruleName, applied) {
237
+ if (!search || !value.includes(search)) {
238
+ return value;
239
+ }
240
+ applied.add(ruleName);
241
+ return value.split(search).join(replacement);
242
+ }
243
+
244
+ // packages/opencode-adapter/dist/normalize.js
245
+ var eventKindMap = {
246
+ "session.created": "session_created",
247
+ "session.updated": "session_updated",
248
+ "session.status": "session_updated",
249
+ "session.idle": "session_idle",
250
+ "session.error": "session_error",
251
+ "message.updated": "message",
252
+ "message.removed": "message",
253
+ "message.part.updated": "message",
254
+ "message.part.removed": "message",
255
+ "file.edited": "file_edit",
256
+ // `command.executed` is a SLASH/template command (/init, /review, custom, MCP) —
257
+ // not the bash tool (that arrives via tool.execute.*). Give it its own node.
258
+ "command.executed": "command_run",
259
+ // Context compaction is published as `session.compacted` at the end of a compact.
260
+ "session.compacted": "context_compacted",
261
+ "session.next.compaction.ended": "context_compacted",
262
+ // experimental engine equivalent
263
+ "permission.asked": "permission_asked",
264
+ "permission.updated": "permission_asked",
265
+ // SDK union name for a permission request
266
+ "permission.replied": "permission_replied",
267
+ "todo.updated": "todo_updated",
268
+ // The active agent / model changed mid-run (multi-model harnesses route per task).
269
+ "session.next.agent.switched": "agent_switched",
270
+ "session.next.model.switched": "model_switched"
271
+ };
272
+ var ignoredEventTypes = /* @__PURE__ */ new Set([
273
+ "message.part.delta",
274
+ "catalog.updated",
275
+ "plugin.added",
276
+ "plugin.removed",
277
+ "integration.updated",
278
+ "reference.updated",
279
+ "models-dev.refreshed",
280
+ "global.disposed",
281
+ // High-volume or no-signal session chatter: `session.diff` is the cumulative
282
+ // running diff (actual edits are captured as file_edit), `session.deleted` is
283
+ // teardown, and the experimental compaction start/delta are superseded by the
284
+ // `*.ended` node.
285
+ "session.diff",
286
+ "session.deleted",
287
+ "session.next.compaction.started",
288
+ "session.next.compaction.delta"
289
+ ]);
290
+ var ignoredEventPrefixes = [
291
+ "tui.",
292
+ "lsp.",
293
+ "pty.",
294
+ "mcp.",
295
+ "vcs.",
296
+ "file.watcher.",
297
+ "workspace.",
298
+ "worktree.",
299
+ "project.",
300
+ "server.",
301
+ "installation."
302
+ ];
303
+ function shouldRecordOpenCodeEvent(rawEvent) {
304
+ const type = readString(asRecord(rawEvent), ["type"]);
305
+ if (!type)
306
+ return true;
307
+ if (ignoredEventTypes.has(type))
308
+ return false;
309
+ return !ignoredEventPrefixes.some((prefix) => type.startsWith(prefix));
310
+ }
311
+ function subagentSessionFromEvent(rawEvent) {
312
+ const raw = asRecord(rawEvent);
313
+ if (readString(raw, ["type"]) !== "session.created")
314
+ return null;
315
+ const properties = asRecord(raw.properties);
316
+ const info = asRecord(properties.info);
317
+ const parentId = readString(info, ["parentID", "parentId"]);
318
+ const sessionId = readString(info, ["id"]) ?? readString(properties, ["sessionID", "sessionId"]);
319
+ if (!parentId || !sessionId)
320
+ return null;
321
+ const agent = readString(info, ["agent"]) ?? agentFromSubagentTitle(readString(info, ["title"])) ?? "subagent";
322
+ return { agent, parentId, sessionId };
323
+ }
324
+ function agentFromSubagentTitle(title) {
325
+ if (!title)
326
+ return void 0;
327
+ const match = title.match(/@([A-Za-z0-9][\w-]*)/);
328
+ return match ? match[1] : void 0;
329
+ }
330
+ function normalizeOpenCodeEvent(rawEvent, context) {
331
+ const raw = asRecord(rawEvent);
332
+ const type = readString(raw, ["type"]) ?? "unknown";
333
+ const kind = eventKindMap[type] ?? "host_event";
334
+ const payload = normalizePayload(minimizeOpenCodeEventPayload(raw, type), context);
335
+ const sessionId = extractSessionId(raw, context.defaultSessionId);
336
+ const agentId = extractAgentId(raw);
337
+ const agentRole = extractAgentRole(raw);
338
+ return createTraceEvent(context.seq, {
339
+ host: "opencode",
340
+ runId: context.runId,
341
+ sessionId,
342
+ ...agentId ? { agentId } : {},
343
+ ...agentId ? { agentRole } : {},
344
+ kind,
345
+ summary: type,
346
+ payload: payload.value,
347
+ redaction: {
348
+ rawStored: context.rawStored ?? false,
349
+ rulesApplied: payload.rulesApplied,
350
+ truncated: payload.truncated
351
+ }
352
+ });
353
+ }
354
+ function normalizeSyntheticUserPrompt(prompt, sourceEvent, context) {
355
+ const payload = normalizePayload({
356
+ type: "opencode.run.prompt",
357
+ properties: {
358
+ sessionID: sourceEvent.sessionId,
359
+ role: "user",
360
+ text: stripWrappingQuotes(prompt)
361
+ }
362
+ }, context);
363
+ return createTraceEvent(context.seq, {
364
+ host: "opencode",
365
+ runId: context.runId,
366
+ sessionId: sourceEvent.sessionId,
367
+ kind: "message",
368
+ summary: "opencode.run.prompt",
369
+ payload: payload.value,
370
+ redaction: {
371
+ rawStored: context.rawStored ?? false,
372
+ rulesApplied: payload.rulesApplied,
373
+ truncated: payload.truncated
374
+ }
375
+ });
376
+ }
377
+ function minimizeOpenCodeEventPayload(raw, type) {
378
+ if (!type.startsWith("message.")) {
379
+ return toJsonObject(raw);
380
+ }
381
+ const properties = asRecord(raw.properties);
382
+ const info = asRecord(properties.info);
383
+ const part = asRecord(properties.part);
384
+ const state = asRecord(part.state);
385
+ const role = readString(info, ["role"]) ?? readString(properties, ["role"]);
386
+ const userText = role === "user" ? shortenOptional(readMessageText(properties, info, part), 2e3) : void 0;
387
+ return compactJsonObject({
388
+ id: readString(raw, ["id"]),
389
+ type,
390
+ properties: compactJsonObject({
391
+ sessionID: readString(properties, ["sessionID"]) ?? readString(info, ["sessionID"]) ?? readString(part, ["sessionID"]),
392
+ messageID: readString(properties, ["messageID"]) ?? readString(info, ["id"]) ?? readString(part, ["messageID"]),
393
+ role,
394
+ agent: readString(info, ["agent"]),
395
+ modelID: readString(info, ["modelID", "model.modelID"]),
396
+ text: userText,
397
+ field: readString(properties, ["field"]),
398
+ deltaLength: readString(properties, ["delta"])?.length,
399
+ part: compactJsonObject({
400
+ id: readString(part, ["id"]),
401
+ type: readString(part, ["type"]),
402
+ tool: readString(part, ["tool"]),
403
+ callID: readString(part, ["callID"]),
404
+ stateStatus: readString(state, ["status"])
405
+ })
406
+ })
407
+ });
408
+ }
409
+ function normalizeToolBefore(input, output, context) {
410
+ const payload = normalizePayload({
411
+ phase: "before",
412
+ tool: readString(input, ["tool", "name"]) ?? readString(output, ["tool", "name"]) ?? "unknown-tool",
413
+ input,
414
+ output
415
+ }, context);
416
+ const traceInput = {
417
+ host: "opencode",
418
+ runId: context.runId,
419
+ sessionId: extractSessionId(input, context.defaultSessionId),
420
+ kind: "tool_call",
421
+ summary: `tool.before:${payload.value.tool ?? "unknown-tool"}`,
422
+ payload: payload.value,
423
+ redaction: {
424
+ rawStored: context.rawStored ?? false,
425
+ rulesApplied: payload.rulesApplied,
426
+ truncated: payload.truncated
427
+ }
428
+ };
429
+ const agentId = extractAgentId(input);
430
+ if (agentId) {
431
+ traceInput.agentId = agentId;
432
+ }
433
+ return createTraceEvent(context.seq, traceInput);
434
+ }
435
+ function normalizeToolAfter(input, output, context) {
436
+ const tool = readString(input, ["tool", "name"]) ?? readString(output, ["tool", "name"]) ?? "unknown-tool";
437
+ const payload = normalizePayload({
438
+ phase: "after",
439
+ tool,
440
+ input,
441
+ output
442
+ }, context);
443
+ const observed = deriveObservedToolResult(tool, payload.value);
444
+ const traceInput = {
445
+ host: "opencode",
446
+ runId: context.runId,
447
+ sessionId: extractSessionId(input, context.defaultSessionId),
448
+ kind: observed?.kind ?? "tool_result",
449
+ summary: observed?.summary ?? `tool.after:${payload.value.tool ?? "unknown-tool"}`,
450
+ payload: observed?.payload ?? payload.value,
451
+ redaction: {
452
+ rawStored: context.rawStored ?? false,
453
+ rulesApplied: payload.rulesApplied,
454
+ truncated: payload.truncated
455
+ }
456
+ };
457
+ if (observed?.kind === "subagent_spawned") {
458
+ const subagentId = readString(observed.payload, ["agent"]);
459
+ traceInput.agentId = subagentId ?? extractAgentId(input) ?? "subagent";
460
+ traceInput.agentRole = "subagent";
461
+ } else {
462
+ const agentId = extractAgentId(input);
463
+ if (agentId) {
464
+ traceInput.agentId = agentId;
465
+ }
466
+ }
467
+ return createTraceEvent(context.seq, traceInput);
468
+ }
469
+ function deriveObservedToolResult(tool, payload) {
470
+ if (tool === "read") {
471
+ const path = readString(payload, ["input.args.filePath", "output.metadata.display.path"]) ?? readString(payload, ["output.title"]);
472
+ const size2 = measureContent(payload, ["output.output", "output.content", "output.metadata.preview"]);
473
+ const observedPayload = compactJsonObject({
474
+ tool,
475
+ source: "tool.after",
476
+ path,
477
+ callID: readString(payload, ["input.callID"]),
478
+ preview: readString(payload, ["output.metadata.preview"]),
479
+ truncated: readBoolean(payload, ["output.metadata.truncated"]),
480
+ chars: size2?.chars,
481
+ lines: size2?.lines
482
+ });
483
+ return {
484
+ kind: "file_read",
485
+ summary: path ? `Read ${path}` : "Read file",
486
+ payload: observedPayload
487
+ };
488
+ }
489
+ if (tool === "bash") {
490
+ const command = readString(payload, ["input.args.command", "output.metadata.command"]);
491
+ const exitCode = readNumber(payload, ["output.metadata.exit", "output.metadata.exitCode"]);
492
+ const outputPreview = shortenOptional(readString(payload, ["output.metadata.output", "output.output"]), 1200);
493
+ const size2 = measureContent(payload, ["output.metadata.output", "output.output"]);
494
+ const observedPayload = compactJsonObject({
495
+ tool,
496
+ source: "tool.after",
497
+ command,
498
+ exitCode,
499
+ description: readString(payload, ["input.args.description", "output.metadata.description"]),
500
+ outputPreview,
501
+ truncated: readBoolean(payload, ["output.metadata.truncated"]),
502
+ outputChars: size2?.chars,
503
+ outputLines: size2?.lines
504
+ });
505
+ return {
506
+ kind: "bash",
507
+ summary: command ? `Ran ${command}` : "Ran shell command",
508
+ payload: observedPayload
509
+ };
510
+ }
511
+ if (tool === "edit" || tool === "write" || tool === "patch") {
512
+ const path = readString(payload, ["input.args.filePath", "input.args.path", "output.metadata.path"]);
513
+ const size2 = measureContent(payload, [
514
+ "input.args.content",
515
+ "input.args.newString",
516
+ "input.args.replacement",
517
+ "input.args.patch"
518
+ ]);
519
+ const observedPayload = compactJsonObject({
520
+ tool,
521
+ source: "tool.after",
522
+ path,
523
+ callID: readString(payload, ["input.callID"]),
524
+ chars: size2?.chars,
525
+ lines: size2?.lines
526
+ });
527
+ return {
528
+ kind: tool === "write" ? "file_created" : "file_edit",
529
+ summary: path ? `Edited ${path}` : "Edited file",
530
+ payload: observedPayload
531
+ };
532
+ }
533
+ if (tool === "task") {
534
+ const subagentType = readString(payload, ["input.args.subagent_type", "output.args.subagent_type"]);
535
+ const description = readString(payload, ["input.args.description", "output.args.description"]);
536
+ const observedPayload = compactJsonObject({
537
+ tool,
538
+ source: "tool.after",
539
+ agent: subagentType,
540
+ description,
541
+ prompt: shortenOptional(readString(payload, ["input.args.prompt", "output.args.prompt"]), 600),
542
+ callID: readString(payload, ["input.callID"])
543
+ });
544
+ return {
545
+ kind: "subagent_spawned",
546
+ summary: subagentType ? `Delegated to ${subagentType} subagent` : "Delegated to a subagent",
547
+ payload: observedPayload
548
+ };
549
+ }
550
+ if (tool === "skill") {
551
+ const name = readString(payload, ["input.args.name", "output.args.name"]);
552
+ const size2 = measureContent(payload, ["output.output", "output.content"]);
553
+ return {
554
+ kind: "tool_result",
555
+ summary: name ? `Used the ${name} skill` : "Used a skill",
556
+ payload: compactJsonObject({
557
+ tool: "skill",
558
+ source: "tool.after",
559
+ skill: name,
560
+ title: readString(payload, ["output.title"]),
561
+ callID: readString(payload, ["input.callID"]),
562
+ outputChars: size2?.chars,
563
+ outputLines: size2?.lines
564
+ })
565
+ };
566
+ }
567
+ const size = measureContent(payload, ["output.output", "output.content", "output.metadata.output"]);
568
+ return {
569
+ kind: "tool_result",
570
+ summary: `Used ${tool}`,
571
+ payload: compactJsonObject({
572
+ tool,
573
+ source: "tool.after",
574
+ title: readString(payload, ["output.title"]),
575
+ description: readString(payload, ["input.args.description", "output.metadata.description"]),
576
+ callID: readString(payload, ["input.callID"]),
577
+ outputChars: size?.chars,
578
+ outputLines: size?.lines
579
+ })
580
+ };
581
+ }
582
+ function readMessageText(...records) {
583
+ for (const record of records) {
584
+ const direct = readString(record, ["text", "content", "prompt", "message"]);
585
+ if (direct) {
586
+ return direct;
587
+ }
588
+ const parts = readPath(record, "parts");
589
+ if (Array.isArray(parts)) {
590
+ const text = parts.map((part) => {
591
+ if (!isRecord2(part))
592
+ return void 0;
593
+ return readString(part, ["text", "content"]);
594
+ }).filter((part) => Boolean(part)).join("\n").trim();
595
+ if (text.length > 0) {
596
+ return text;
597
+ }
598
+ }
599
+ }
600
+ return void 0;
601
+ }
602
+ function normalizePayload(value, context) {
603
+ return redactJsonObject(toJsonObject(value), {
604
+ ...context.homeDir ? { homeDir: context.homeDir } : {},
605
+ ...context.projectDir ? { projectDir: context.projectDir } : {},
606
+ maxStringLength: 4e3
607
+ });
608
+ }
609
+ function toJsonObject(value) {
610
+ return sanitizeJson(value, /* @__PURE__ */ new WeakSet());
611
+ }
612
+ function sanitizeJson(value, seen) {
613
+ if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
614
+ return value;
615
+ }
616
+ if (Array.isArray(value)) {
617
+ if (seen.has(value)) {
618
+ return "[Circular]";
619
+ }
620
+ seen.add(value);
621
+ return value.map((item) => sanitizeJson(item, seen));
622
+ }
623
+ if (typeof value === "object") {
624
+ if (seen.has(value)) {
625
+ return "[Circular]";
626
+ }
627
+ seen.add(value);
628
+ const output = {};
629
+ for (const [key, nested] of Object.entries(value)) {
630
+ if (typeof nested === "undefined" || typeof nested === "function" || typeof nested === "symbol") {
631
+ continue;
632
+ }
633
+ output[key] = sanitizeJson(nested, seen);
634
+ }
635
+ return output;
636
+ }
637
+ return String(value);
638
+ }
639
+ function extractSessionId(raw, fallback) {
640
+ const properties = asRecord(raw.properties);
641
+ return readString(raw, ["sessionID", "sessionId", "session.id"]) ?? readString(properties, ["sessionID", "sessionId", "session.id"]) ?? readString(asRecord(properties.info), ["sessionID", "sessionId"]) ?? readString(asRecord(properties.part), ["sessionID", "sessionId"]) ?? readString(asRecord(raw.event), ["sessionID", "sessionId", "session.id"]) ?? fallback;
642
+ }
643
+ function extractAgentId(raw) {
644
+ const properties = asRecord(raw.properties);
645
+ const info = asRecord(properties.info);
646
+ return readString(raw, ["agentID", "agentId", "agent.id", "agent.name"]) ?? readString(properties, ["agentID", "agentId", "agent.id", "agent.name", "agent"]) ?? readString(info, ["agentID", "agentId", "agent.id", "agent.name", "agent"]) ?? readString(asRecord(raw.event), ["agentID", "agentId", "agent.id", "agent.name", "agent"]);
647
+ }
648
+ function extractAgentRole(raw) {
649
+ const role = readString(raw, ["agentRole", "agent.role"]) ?? readString(asRecord(raw.properties), ["agentRole", "agent.role"]) ?? readString(asRecord(asRecord(raw.properties).info), ["agentRole", "agent.role"]);
650
+ if (role === "subagent" || role === "system" || role === "unknown")
651
+ return role;
652
+ return "primary";
653
+ }
654
+ function stripWrappingQuotes(value) {
655
+ const trimmed = value.trim();
656
+ if (trimmed.length >= 2) {
657
+ const first = trimmed[0];
658
+ const last = trimmed[trimmed.length - 1];
659
+ if (first === `"` && last === `"` || first === "'" && last === "'") {
660
+ return trimmed.slice(1, -1).trim();
661
+ }
662
+ }
663
+ return trimmed;
664
+ }
665
+ function readString(record, keys) {
666
+ for (const key of keys) {
667
+ const value = readPath(record, key);
668
+ if (typeof value === "string" && value.length > 0) {
669
+ return value;
670
+ }
671
+ }
672
+ return void 0;
673
+ }
674
+ function readNumber(record, keys) {
675
+ for (const key of keys) {
676
+ const value = readPath(record, key);
677
+ if (typeof value === "number") {
678
+ return value;
679
+ }
680
+ }
681
+ return void 0;
682
+ }
683
+ function readBoolean(record, keys) {
684
+ for (const key of keys) {
685
+ const value = readPath(record, key);
686
+ if (typeof value === "boolean") {
687
+ return value;
688
+ }
689
+ }
690
+ return void 0;
691
+ }
692
+ function readPath(record, path) {
693
+ const parts = path.split(".");
694
+ let current = record;
695
+ for (const part of parts) {
696
+ if (!isRecord2(current)) {
697
+ return void 0;
698
+ }
699
+ current = current[part];
700
+ }
701
+ return current;
702
+ }
703
+ function compactJsonObject(input) {
704
+ const output = {};
705
+ for (const [key, value] of Object.entries(input)) {
706
+ if (typeof value === "undefined") {
707
+ continue;
708
+ }
709
+ output[key] = sanitizeJson(value, /* @__PURE__ */ new WeakSet());
710
+ }
711
+ return output;
712
+ }
713
+ function shortenOptional(value, maxLength) {
714
+ if (value === void 0 || value.length <= maxLength) {
715
+ return value;
716
+ }
717
+ return `${value.slice(0, maxLength)}...`;
718
+ }
719
+ function measureContent(record, paths) {
720
+ let longest;
721
+ for (const path of paths) {
722
+ const value = readPath(record, path);
723
+ if (typeof value === "string" && (longest === void 0 || value.length > longest.length)) {
724
+ longest = value;
725
+ }
726
+ }
727
+ if (longest === void 0) {
728
+ return void 0;
729
+ }
730
+ return { chars: longest.length, lines: longest.split("\n").length };
731
+ }
732
+ function asRecord(value) {
733
+ return isRecord2(value) ? value : {};
734
+ }
735
+ function isRecord2(value) {
736
+ return typeof value === "object" && value !== null && !Array.isArray(value);
737
+ }
738
+
739
+ // packages/opencode-adapter/dist/optimize.js
740
+ import { createHash } from "node:crypto";
741
+ var hashContent = (content) => createHash("sha1").update(content).digest("hex").slice(0, 12);
742
+ var READ_TOOLS = /* @__PURE__ */ new Set(["read", "view", "cat", "readfile", "read_file"]);
743
+ var isReadTool = (tool) => typeof tool === "string" && READ_TOOLS.has(tool.toLowerCase());
744
+ function readArgPath(args) {
745
+ if (!args || typeof args !== "object")
746
+ return void 0;
747
+ const a = args;
748
+ for (const key of ["filePath", "path", "file", "filename", "target"]) {
749
+ const v = a[key];
750
+ if (typeof v === "string" && v.length > 0)
751
+ return v;
752
+ }
753
+ return void 0;
754
+ }
755
+ var baseName = (path) => path.split(/[\\/]/).filter(Boolean).pop() ?? path;
756
+ function computeReadDelta(prior, current, path) {
757
+ const a = prior.split("\n");
758
+ const b = current.split("\n");
759
+ let p = 0;
760
+ while (p < a.length && p < b.length && a[p] === b[p])
761
+ p += 1;
762
+ let s = 0;
763
+ while (s < a.length - p && s < b.length - p && a[a.length - 1 - s] === b[b.length - 1 - s])
764
+ s += 1;
765
+ const changed = b.slice(p, b.length - s);
766
+ if (changed.length === 0)
767
+ return null;
768
+ const from = p + 1;
769
+ const to = b.length - s;
770
+ return `\u27E8Agent-Blackbox: ${baseName(path)} changed since your last read \u2014 showing only lines ${from}\u2013${to}; the ${p} leading and ${s} trailing lines are unchanged from your earlier copy.\u27E9
771
+ ` + changed.join("\n");
772
+ }
773
+ function decideReadServe(prior, current, gen, path) {
774
+ if (!prior || prior.gen !== gen)
775
+ return { mode: "full", saved: 0 };
776
+ if (prior.hash === current.hash) {
777
+ const lines = current.content.split("\n").length;
778
+ const note = `\u27E8Agent-Blackbox: identical to your earlier read of ${baseName(path)} (${lines} lines, unchanged) \u2014 reuse that copy instead of re-reading.\u27E9`;
779
+ return { mode: "noop", output: note, saved: Math.max(0, current.content.length - note.length) };
780
+ }
781
+ const diff = computeReadDelta(prior.content, current.content, path);
782
+ if (diff && diff.length < current.content.length * 0.8) {
783
+ return { mode: "diff", output: diff, saved: current.content.length - diff.length };
784
+ }
785
+ return { mode: "full", saved: 0 };
786
+ }
787
+ var WORKING_SET_START = "\u27E8agent-blackbox:working-set\u27E9";
788
+ var WORKING_SET_END = "\u27E8/agent-blackbox:working-set\u27E9";
789
+ function buildWorkingSetBlock(files, commands) {
790
+ const hot = [...files].sort((x, y) => y.reads + y.edits - (x.reads + x.edits)).slice(0, 8);
791
+ const cmds = [...new Set(commands)].slice(0, 4);
792
+ if (hot.length === 0 && cmds.length === 0)
793
+ return null;
794
+ const lines = [];
795
+ if (hot.length > 0) {
796
+ lines.push("Files already in play (read once and reuse \u2014 don't re-read whole files):");
797
+ for (const f of hot) {
798
+ const touches = [f.reads ? `read ${f.reads}\xD7` : "", f.edits ? `edited ${f.edits}\xD7` : ""].filter(Boolean).join(", ");
799
+ lines.push(`- ${f.path}${touches ? ` (${touches})` : ""}`);
800
+ }
801
+ }
802
+ if (cmds.length > 0) {
803
+ lines.push("Verified commands (reuse, don't rediscover):");
804
+ for (const c of cmds)
805
+ lines.push(`- ${c}`);
806
+ }
807
+ return [WORKING_SET_START, "Agent-Blackbox working set \u2014 what this run has already established:", ...lines, WORKING_SET_END].join("\n");
808
+ }
809
+ var NAV_VERBS = /* @__PURE__ */ new Set([
810
+ "ls",
811
+ "pwd",
812
+ "cat",
813
+ "find",
814
+ "grep",
815
+ "rg",
816
+ "fd",
817
+ "head",
818
+ "tail",
819
+ "echo",
820
+ "which",
821
+ "env",
822
+ "cd",
823
+ "tree",
824
+ "stat",
825
+ "wc",
826
+ "sort",
827
+ "uniq",
828
+ "clear",
829
+ "sleep",
830
+ "true",
831
+ "false"
832
+ ]);
833
+ var isReusableCommand = (command) => {
834
+ const verb = command.trim().split(/\s+/)[0] ?? "";
835
+ return verb.length > 0 && !NAV_VERBS.has(verb);
836
+ };
837
+
838
+ // packages/storage/src/ndjson.ts
839
+ import { appendFile, mkdir, readFile } from "node:fs/promises";
840
+ import { dirname } from "node:path";
841
+ function serializeTraceEvent(event) {
842
+ assertTraceEvent(event);
843
+ return `${JSON.stringify(event)}
844
+ `;
845
+ }
846
+ async function appendTraceEvent(filePath, event) {
847
+ await mkdir(dirname(filePath), { recursive: true });
848
+ await appendFile(filePath, serializeTraceEvent(event), "utf8");
849
+ }
850
+
851
+ // packages/opencode-adapter/dist/sink.js
852
+ import { join } from "node:path";
853
+ function createTraceSink(options) {
854
+ if (options.sink) {
855
+ return options.sink;
856
+ }
857
+ if (options.daemonUrl) {
858
+ return createHttpTraceSink(options.daemonUrl);
859
+ }
860
+ return createFileTraceSink(options.eventsFile ?? join(options.directory, ".agent-blackbox", "events.ndjson"));
861
+ }
862
+ function createFileTraceSink(eventsFile) {
863
+ return {
864
+ async write(event) {
865
+ await appendTraceEvent(eventsFile, event);
866
+ }
867
+ };
868
+ }
869
+ function createHttpTraceSink(daemonUrl, options = {}) {
870
+ const endpoint = new URL("/events", daemonUrl.endsWith("/") ? daemonUrl : `${daemonUrl}/`);
871
+ const retries = options.retries ?? 3;
872
+ const timeoutMs = options.timeoutMs ?? 4e3;
873
+ const retryDelayMs = options.retryDelayMs ?? 120;
874
+ const warn = options.onWarn ?? ((message) => console.warn(`[agent-blackbox] ${message}`));
875
+ return {
876
+ async write(event) {
877
+ let lastError = "";
878
+ for (let attempt = 0; attempt <= retries; attempt += 1) {
879
+ try {
880
+ const response = await fetch(endpoint, {
881
+ method: "POST",
882
+ headers: { "content-type": "application/json" },
883
+ body: JSON.stringify(event),
884
+ signal: AbortSignal.timeout(timeoutMs)
885
+ });
886
+ if (response.ok) {
887
+ return;
888
+ }
889
+ if (response.status < 500) {
890
+ warn(`daemon rejected event ${event.id}: HTTP ${response.status}`);
891
+ return;
892
+ }
893
+ lastError = `HTTP ${response.status}`;
894
+ } catch (error) {
895
+ lastError = error instanceof Error ? error.message : String(error);
896
+ }
897
+ if (attempt < retries) {
898
+ await delay(retryDelayMs * (attempt + 1));
899
+ }
900
+ }
901
+ warn(`could not deliver event ${event.id} after ${retries + 1} attempts (${lastError}); dropping it`);
902
+ }
903
+ };
904
+ }
905
+ function delay(ms) {
906
+ return new Promise((resolve) => {
907
+ setTimeout(resolve, ms);
908
+ });
909
+ }
910
+ function resolveRecorderOptions(options) {
911
+ const resolved = {};
912
+ const daemonUrl = options.daemonUrl ?? process.env.AGENT_BLACKBOX_DAEMON_URL;
913
+ const runId = options.runId ?? process.env.AGENT_BLACKBOX_RUN_ID;
914
+ if (daemonUrl)
915
+ resolved.daemonUrl = daemonUrl;
916
+ if (runId)
917
+ resolved.runId = runId;
918
+ if (options.cliPrompt)
919
+ resolved.cliPrompt = options.cliPrompt;
920
+ if (options.eventsFile)
921
+ resolved.eventsFile = options.eventsFile;
922
+ if (options.sink)
923
+ resolved.sink = options.sink;
924
+ if (options.homeDir)
925
+ resolved.homeDir = options.homeDir;
926
+ if (options.projectDir)
927
+ resolved.projectDir = options.projectDir;
928
+ if (typeof options.rawStored === "boolean")
929
+ resolved.rawStored = options.rawStored;
930
+ if (typeof options.optimize === "boolean")
931
+ resolved.optimize = options.optimize;
932
+ return resolved;
933
+ }
934
+
935
+ // packages/opencode-adapter/dist/index.js
936
+ async function createOpenCodeRecorder(context, options = {}) {
937
+ const resolved = resolveRecorderOptions(options);
938
+ const directory = context.directory ?? process.cwd();
939
+ const runId = resolved.runId ?? `opencode-${Date.now()}`;
940
+ const cliPrompt = resolved.cliPrompt ?? detectOpenCodeRunPrompt(process.argv);
941
+ const sink = createTraceSink({
942
+ directory,
943
+ ...resolved.daemonUrl ? { daemonUrl: resolved.daemonUrl } : {},
944
+ ...resolved.eventsFile ? { eventsFile: resolved.eventsFile } : {},
945
+ ...resolved.sink ? { sink: resolved.sink } : {}
946
+ });
947
+ const optimize = resolved.optimize ?? process.env.AGENT_BLACKBOX_OPTIMIZE === "1";
948
+ const factory = createOpenCodeEventFactory({
949
+ runId,
950
+ sink,
951
+ defaultSessionId: "unknown-session",
952
+ optimize,
953
+ ...resolved.homeDir ? { homeDir: resolved.homeDir } : {},
954
+ projectDir: resolved.projectDir ?? directory,
955
+ ...cliPrompt ? { cliPrompt } : {},
956
+ rawStored: resolved.rawStored ?? false
957
+ });
958
+ return {
959
+ event: async ({ event }) => {
960
+ await factory.writeEvent(event);
961
+ },
962
+ "tool.execute.before": async (input, output) => {
963
+ await factory.writeToolBefore(input, output);
964
+ },
965
+ "tool.execute.after": async (input, output) => {
966
+ await factory.writeToolAfter(input, output);
967
+ },
968
+ // In-run actuator (opt-in). A compaction means the agent may have lost content,
969
+ // so we reset the "still in context" generation; the system transform injects
970
+ // the working-set memory. Both no-op when optimize is off.
971
+ "experimental.session.compacting": async () => {
972
+ factory.onCompaction();
973
+ },
974
+ "experimental.chat.system.transform": async (_input, output) => {
975
+ factory.injectWorkingSet(output);
976
+ }
977
+ };
978
+ }
979
+ function createOpenCodePlugin(options = {}) {
980
+ return async (context) => createOpenCodeRecorder(context, options);
981
+ }
982
+ var AgentBlackboxOpenCode = createOpenCodePlugin();
983
+ function createOpenCodeEventFactory(options) {
984
+ let seq = 0;
985
+ let cliPromptEmitted = false;
986
+ const subagentSessions = /* @__PURE__ */ new Map();
987
+ const readCache = /* @__PURE__ */ new Map();
988
+ const wsFiles = /* @__PURE__ */ new Map();
989
+ const wsCommands = [];
990
+ let compactionGen = 0;
991
+ const bumpFile = (path, kind, hash) => {
992
+ const f = wsFiles.get(path) ?? { path, reads: 0, edits: 0 };
993
+ if (kind === "read")
994
+ f.reads += 1;
995
+ else
996
+ f.edits += 1;
997
+ if (hash)
998
+ f.hash = hash;
999
+ wsFiles.set(path, f);
1000
+ };
1001
+ const optimizeReadAfter = (input, output) => {
1002
+ const tool = input.tool;
1003
+ const path = readArgPath(input.args);
1004
+ if (isEditTool(tool) && path)
1005
+ bumpFile(path, "edit");
1006
+ if (isBashTool(tool)) {
1007
+ const command = readString2(input.args, "command");
1008
+ const exit = readExitCode(output);
1009
+ if (command && isReusableCommand(command) && (exit === 0 || exit === void 0)) {
1010
+ if (!wsCommands.includes(command))
1011
+ wsCommands.push(command);
1012
+ }
1013
+ }
1014
+ if (!isReadTool(tool) || !path)
1015
+ return;
1016
+ const current = typeof output.output === "string" ? output.output : "";
1017
+ if (!current)
1018
+ return;
1019
+ const key = `${readString2(input, "sessionID") ?? "s"}::${path}`;
1020
+ const hash = hashContent(current);
1021
+ const decision = decideReadServe(readCache.get(key), { hash, content: current }, compactionGen, path);
1022
+ readCache.set(key, { hash, content: current, gen: compactionGen });
1023
+ bumpFile(path, "read", hash);
1024
+ if (decision.mode !== "full" && typeof decision.output === "string") {
1025
+ output.output = decision.output;
1026
+ }
1027
+ };
1028
+ const attributeToSubagent = (event) => {
1029
+ const owner = subagentSessions.get(event.sessionId);
1030
+ if (!owner)
1031
+ return event;
1032
+ return {
1033
+ ...event,
1034
+ agentId: owner.agent,
1035
+ agentRole: "subagent",
1036
+ parentSessionId: event.parentSessionId ?? owner.parentId
1037
+ };
1038
+ };
1039
+ const nextContext = () => {
1040
+ seq += 1;
1041
+ return {
1042
+ runId: options.runId,
1043
+ seq,
1044
+ defaultSessionId: options.defaultSessionId,
1045
+ ...options.homeDir ? { homeDir: options.homeDir } : {},
1046
+ ...options.projectDir ? { projectDir: options.projectDir } : {},
1047
+ rawStored: options.rawStored
1048
+ };
1049
+ };
1050
+ return {
1051
+ async writeEvent(rawEvent) {
1052
+ const subagent = subagentSessionFromEvent(rawEvent);
1053
+ if (subagent) {
1054
+ subagentSessions.set(subagent.sessionId, { agent: subagent.agent, parentId: subagent.parentId });
1055
+ }
1056
+ if (!shouldRecordOpenCodeEvent(rawEvent)) {
1057
+ return;
1058
+ }
1059
+ const event = attributeToSubagent(normalizeOpenCodeEvent(rawEvent, nextContext()));
1060
+ await options.sink.write(event);
1061
+ if (options.cliPrompt && !cliPromptEmitted && !subagent && event.kind === "session_created") {
1062
+ cliPromptEmitted = true;
1063
+ await options.sink.write(normalizeSyntheticUserPrompt(options.cliPrompt, event, nextContext()));
1064
+ }
1065
+ },
1066
+ async writeToolBefore(input, output) {
1067
+ await options.sink.write(attributeToSubagent(normalizeToolBefore(input, output, nextContext())));
1068
+ },
1069
+ async writeToolAfter(input, output) {
1070
+ if (options.optimize)
1071
+ optimizeReadAfter(input, output);
1072
+ await options.sink.write(attributeToSubagent(normalizeToolAfter(input, output, nextContext())));
1073
+ },
1074
+ onCompaction() {
1075
+ if (options.optimize)
1076
+ compactionGen += 1;
1077
+ },
1078
+ injectWorkingSet(output) {
1079
+ if (!options.optimize)
1080
+ return;
1081
+ const block = buildWorkingSetBlock([...wsFiles.values()], wsCommands);
1082
+ if (!block)
1083
+ return;
1084
+ const system = output.system;
1085
+ if (Array.isArray(system))
1086
+ system.push(block);
1087
+ }
1088
+ };
1089
+ }
1090
+ function isEditTool(tool) {
1091
+ return typeof tool === "string" && ["edit", "write", "patch", "multiedit", "apply_patch"].includes(tool.toLowerCase());
1092
+ }
1093
+ function isBashTool(tool) {
1094
+ return typeof tool === "string" && ["bash", "shell", "sh", "run", "command"].includes(tool.toLowerCase());
1095
+ }
1096
+ function readString2(record, key) {
1097
+ if (!record || typeof record !== "object")
1098
+ return void 0;
1099
+ const value = record[key];
1100
+ return typeof value === "string" ? value : void 0;
1101
+ }
1102
+ function readExitCode(output) {
1103
+ const direct = output.exitCode ?? output.exit;
1104
+ if (typeof direct === "number")
1105
+ return direct;
1106
+ const meta = output.metadata;
1107
+ if (meta && typeof meta === "object") {
1108
+ const m = meta;
1109
+ const v = m.exitCode ?? m.exit;
1110
+ if (typeof v === "number")
1111
+ return v;
1112
+ }
1113
+ return void 0;
1114
+ }
1115
+ function detectOpenCodeRunPrompt(argv) {
1116
+ const runIndex = argv.indexOf("run");
1117
+ const promptFlagIndex = argv.indexOf("--prompt");
1118
+ if (promptFlagIndex >= 0 && argv[promptFlagIndex + 1]) {
1119
+ return cleanCliPrompt(argv[promptFlagIndex + 1]);
1120
+ }
1121
+ if (runIndex < 0)
1122
+ return void 0;
1123
+ const valueFlags = /* @__PURE__ */ new Set([
1124
+ "-m",
1125
+ "--model",
1126
+ "-s",
1127
+ "--session",
1128
+ "--dir",
1129
+ "--port",
1130
+ "--hostname",
1131
+ "--log-level",
1132
+ "--mdns-domain",
1133
+ "--agent",
1134
+ "--prompt"
1135
+ ]);
1136
+ const messageParts = [];
1137
+ for (let index = runIndex + 1; index < argv.length; index += 1) {
1138
+ const part = argv[index];
1139
+ if (valueFlags.has(part)) {
1140
+ index += 1;
1141
+ continue;
1142
+ }
1143
+ if (part.startsWith("--"))
1144
+ continue;
1145
+ messageParts.push(part);
1146
+ }
1147
+ return cleanCliPrompt(messageParts.join(" "));
1148
+ }
1149
+ function cleanCliPrompt(value) {
1150
+ const prompt = value?.trim();
1151
+ if (!prompt)
1152
+ return void 0;
1153
+ return prompt.length > 2e3 ? `${prompt.slice(0, 1997)}...` : prompt;
1154
+ }
1155
+
1156
+ // packages/opencode-adapter/dist/plugin-entry.js
1157
+ var AgentBlackbox = createOpenCodePlugin({
1158
+ daemonUrl: process.env.AGENT_BLACKBOX_DAEMON_URL ?? "__ABB_DAEMON_URL__"
1159
+ });
1160
+ export {
1161
+ AgentBlackbox
1162
+ };