sentinelayer-cli 0.8.10 → 0.8.12

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.
@@ -1,6 +1,28 @@
1
1
  import fsp from "node:fs/promises";
2
2
  import path from "node:path";
3
3
 
4
+ export const DEFAULT_AUDIT_AGENT_TOOLS = Object.freeze([
5
+ "FileRead",
6
+ "Grep",
7
+ "Glob",
8
+ "Shell",
9
+ "FileEdit",
10
+ ]);
11
+
12
+ const TOOL_NAME_ALIASES = Object.freeze({
13
+ read: "FileRead",
14
+ file_read: "FileRead",
15
+ "file-read": "FileRead",
16
+ fileread: "FileRead",
17
+ grep: "Grep",
18
+ glob: "Glob",
19
+ shell: "Shell",
20
+ file_edit: "FileEdit",
21
+ "file-edit": "FileEdit",
22
+ fileedit: "FileEdit",
23
+ dispatch: "Dispatch",
24
+ });
25
+
4
26
  const BUILTIN_AUDIT_AGENTS = Object.freeze([
5
27
  {
6
28
  id: "security",
@@ -173,16 +195,40 @@ function normalizeString(value) {
173
195
  return String(value || "").trim();
174
196
  }
175
197
 
198
+ function normalizeToolName(value) {
199
+ const raw = normalizeString(value);
200
+ if (!raw) {
201
+ return "";
202
+ }
203
+ const alias = TOOL_NAME_ALIASES[raw.toLowerCase()];
204
+ if (alias) {
205
+ return alias;
206
+ }
207
+ return raw;
208
+ }
209
+
210
+ export function normalizeAuditAgentTools(tools = [], { useDefaultWhenEmpty = false } = {}) {
211
+ const normalized = Array.isArray(tools)
212
+ ? tools.map((item) => normalizeToolName(item)).filter(Boolean)
213
+ : [];
214
+ const unique = [...new Set(normalized)];
215
+ if (unique.length > 0) {
216
+ return unique;
217
+ }
218
+ return useDefaultWhenEmpty ? [...DEFAULT_AUDIT_AGENT_TOOLS] : [];
219
+ }
220
+
176
221
  function normalizeAgentId(value) {
177
222
  return normalizeString(value).toLowerCase();
178
223
  }
179
224
 
180
225
  function normalizeAgentRecord(record = {}) {
226
+ const hasToolOverride = Object.prototype.hasOwnProperty.call(record, "tools");
181
227
  return {
182
228
  id: normalizeAgentId(record.id),
183
229
  persona: normalizeString(record.persona),
184
230
  domain: normalizeString(record.domain),
185
- tools: Array.isArray(record.tools) ? record.tools.map((item) => normalizeString(item)).filter(Boolean) : [],
231
+ tools: normalizeAuditAgentTools(record.tools, { useDefaultWhenEmpty: !hasToolOverride }),
186
232
  permissionMode: normalizeString(record.permissionMode || "plan") || "plan",
187
233
  maxTurns: Math.max(1, Math.floor(Number(record.maxTurns || 1))),
188
234
  confidenceFloor: Math.max(0, Math.min(1, Number(record.confidenceFloor || 0))),
@@ -206,6 +252,11 @@ function mergeRegistry(builtinAgents = [], overrideAgents = []) {
206
252
  continue;
207
253
  }
208
254
  const existing = byId.get(normalized.id) || {};
255
+ if (!Object.prototype.hasOwnProperty.call(override, "tools")) {
256
+ normalized.tools = Array.isArray(existing.tools) && existing.tools.length > 0
257
+ ? [...existing.tools]
258
+ : [...DEFAULT_AUDIT_AGENT_TOOLS];
259
+ }
209
260
  byId.set(normalized.id, {
210
261
  ...existing,
211
262
  ...normalized,
@@ -223,7 +274,12 @@ function parseAgentFilter(rawValue) {
223
274
  }
224
275
 
225
276
  export function listBuiltinAuditAgents() {
226
- return BUILTIN_AUDIT_AGENTS.map((agent) => ({ ...agent }));
277
+ return BUILTIN_AUDIT_AGENTS.map((agent) =>
278
+ normalizeAgentRecord({
279
+ ...agent,
280
+ tools: DEFAULT_AUDIT_AGENT_TOOLS,
281
+ })
282
+ );
227
283
  }
228
284
 
229
285
  export async function loadAuditRegistry({ registryFile = "" } = {}) {
@@ -26,6 +26,14 @@ function parseMaxParallel(rawValue) {
26
26
  return Math.floor(normalized);
27
27
  }
28
28
 
29
+ function parseIsolationMode(rawValue) {
30
+ const normalized = String(rawValue || "strict").trim().toLowerCase();
31
+ if (normalized === "strict" || normalized === "relaxed") {
32
+ return normalized;
33
+ }
34
+ throw new Error("isolation must be one of: strict, relaxed.");
35
+ }
36
+
29
37
  function printAuditSummary(result) {
30
38
  console.log(pc.bold("Audit orchestrator complete"));
31
39
  console.log(pc.gray(`Run: ${result.runId}`));
@@ -34,6 +42,11 @@ function printAuditSummary(result) {
34
42
  if (result.sharedMemory?.artifactPath) {
35
43
  console.log(pc.gray(`Shared memory: ${result.sharedMemory.artifactPath}`));
36
44
  }
45
+ if (result.omargateReuse?.used) {
46
+ console.log(pc.gray(`Reused OmarGate run: ${result.omargateReuse.runId}`));
47
+ } else if (result.omargateReuse?.requested) {
48
+ console.log(pc.gray(`OmarGate reuse unavailable: ${result.omargateReuse.reason || "not_found"}`));
49
+ }
37
50
  if (result.ddPackage?.executiveSummaryPath) {
38
51
  console.log(pc.gray(`DD package: ${result.ddPackage.executiveSummaryPath}`));
39
52
  }
@@ -51,6 +64,15 @@ function printAuditSummary(result) {
51
64
  console.log(`Agents: ${result.agentResults.length}, max_parallel=${result.maxParallel}`);
52
65
  }
53
66
 
67
+ function buildAuditOrchestratorEventHandler(emitStream) {
68
+ if (!emitStream) {
69
+ return null;
70
+ }
71
+ return (evt) => {
72
+ console.log(JSON.stringify(evt));
73
+ };
74
+ }
75
+
54
76
  export function registerAuditCommand(program, invokeLegacy) {
55
77
  const audit = program
56
78
  .command("audit")
@@ -63,9 +85,14 @@ export function registerAuditCommand(program, invokeLegacy) {
63
85
  .option("--output-dir <path>", "Optional artifact output root override")
64
86
  .option("--refresh", "Refresh CODEBASE_INGEST before running audit")
65
87
  .option("--dry-run", "Skip deterministic baseline and run orchestration planning only")
88
+ .option("--isolation <mode>", "Persona isolation mode: strict | relaxed", "strict")
89
+ .option("--no-seed-from-deterministic", "Run personas without deterministic baseline or specialist seed findings")
90
+ .option("--reuse-omargate <runId>", "Reuse deterministic findings from an OmarGate run id or latest")
91
+ .option("--stream", "Emit NDJSON agent events to stdout")
66
92
  .option("--json", "Emit machine-readable output")
67
93
  .action(async (targetPathArg, options, command) => {
68
94
  const emitJson = shouldEmitJson(options, command);
95
+ const emitStream = Boolean(options.stream);
69
96
  const targetPath = path.resolve(process.cwd(), String(options.path || targetPathArg || "."));
70
97
  const registry = await loadAuditRegistry({
71
98
  registryFile: options.registryFile,
@@ -85,6 +112,10 @@ export function registerAuditCommand(program, invokeLegacy) {
85
112
  outputDir: options.outputDir,
86
113
  dryRun: Boolean(options.dryRun),
87
114
  refreshIngest: Boolean(options.refresh),
115
+ isolation: parseIsolationMode(options.isolation),
116
+ seedFromDeterministic: options.seedFromDeterministic !== false,
117
+ reuseOmarGate: options.reuseOmargate,
118
+ onEvent: buildAuditOrchestratorEventHandler(emitStream),
88
119
  });
89
120
 
90
121
  const payload = {
@@ -99,6 +130,11 @@ export function registerAuditCommand(program, invokeLegacy) {
99
130
  registryFile: registry.registryFile,
100
131
  selectedAgents: result.selectedAgents,
101
132
  maxParallel: result.maxParallel,
133
+ isolation: result.isolation,
134
+ seedFromDeterministic: result.seedFromDeterministic !== false,
135
+ omargateReuse: result.omargateReuse || null,
136
+ reusedOmarGateRunId: result.omargateReuse?.used ? result.omargateReuse.runId : "",
137
+ reusedOmarGateDeterministicPath: result.omargateReuse?.used ? result.omargateReuse.artifactPath : "",
102
138
  summary: result.summary,
103
139
  agentCount: result.agentResults.length,
104
140
  sharedMemoryPath: result.sharedMemory?.artifactPath || "",
@@ -112,7 +148,7 @@ export function registerAuditCommand(program, invokeLegacy) {
112
148
 
113
149
  if (emitJson) {
114
150
  console.log(JSON.stringify(payload, null, 2));
115
- } else {
151
+ } else if (!emitStream) {
116
152
  printAuditSummary(result);
117
153
  }
118
154
 
@@ -211,6 +247,8 @@ export function registerAuditCommand(program, invokeLegacy) {
211
247
  outputDir: options.outputDir,
212
248
  dryRun: Boolean(baseReport.dryRun),
213
249
  refreshIngest: Boolean(options.refresh),
250
+ isolation: baseReport.isolation || "strict",
251
+ seedFromDeterministic: baseReport.seedFromDeterministic !== false,
214
252
  });
215
253
 
216
254
  const comparison = await writeAuditComparisonArtifact({
@@ -231,6 +269,8 @@ export function registerAuditCommand(program, invokeLegacy) {
231
269
  deterministicEquivalent: comparison.comparison.deterministicEquivalent,
232
270
  addedCount: comparison.comparison.addedCount,
233
271
  removedCount: comparison.comparison.removedCount,
272
+ isolation: replayResult.isolation,
273
+ seedFromDeterministic: replayResult.seedFromDeterministic !== false,
234
274
  ingestRefresh: replayResult.ingest?.refresh || null,
235
275
  };
236
276
 
@@ -717,6 +757,7 @@ export function registerAuditCommand(program, invokeLegacy) {
717
757
  .description("Compatibility mode: run legacy local readiness + policy audit")
718
758
  .option("--path <path>", "Target repository path")
719
759
  .option("--output-dir <path>", "Artifact root for report output")
760
+ .option("--reuse-omargate <runId>", "Reuse deterministic findings from an OmarGate run id or latest")
720
761
  .option("--json", "Emit machine-readable output")
721
762
  .action(async (options, command) => {
722
763
  const legacyArgs = buildLegacyArgs(["/audit"], {
@@ -20,12 +20,24 @@ function appendOutputDirFlag(args, maybeOutputDir) {
20
20
  }
21
21
 
22
22
  function appendPassthroughFlag(args, flagName, maybeValue) {
23
- const value = String(maybeValue || "").trim();
23
+ const value = maybeValue === undefined || maybeValue === null ? "" : String(maybeValue).trim();
24
24
  if (value) {
25
25
  args.push(flagName, value);
26
26
  }
27
27
  }
28
28
 
29
+ function appendBooleanFlag(args, flagName, maybeValue) {
30
+ if (Boolean(maybeValue)) {
31
+ args.push(flagName);
32
+ }
33
+ }
34
+
35
+ function appendNegatedBooleanFlag(args, flagName, maybeValue) {
36
+ if (maybeValue === false) {
37
+ args.push(flagName);
38
+ }
39
+ }
40
+
29
41
  export function buildLegacyArgs(baseArgs, { commandOptions = {}, command } = {}) {
30
42
  const args = [...baseArgs];
31
43
  appendPathFlag(args, commandOptions.path);
@@ -33,6 +45,21 @@ export function buildLegacyArgs(baseArgs, { commandOptions = {}, command } = {})
33
45
  if (wantsJsonOutput(commandOptions, command)) {
34
46
  args.push("--json");
35
47
  }
48
+ appendNegatedBooleanFlag(args, "--no-ai", commandOptions.ai);
49
+ appendBooleanFlag(args, "--ai-dry-run", commandOptions.aiDryRun);
50
+ appendBooleanFlag(args, "--stream", commandOptions.stream);
51
+ appendBooleanFlag(args, "--dry-run", commandOptions.dryRun);
52
+ appendNegatedBooleanFlag(args, "--no-email", commandOptions.email);
53
+ appendNegatedBooleanFlag(args, "--no-dashboard", commandOptions.dashboard);
54
+ appendPassthroughFlag(args, "--scan-mode", commandOptions.scanMode);
55
+ appendPassthroughFlag(args, "--max-parallel", commandOptions.maxParallel);
56
+ appendPassthroughFlag(args, "--max-cost", commandOptions.maxCost);
57
+ appendPassthroughFlag(args, "--max-runtime-minutes", commandOptions.maxRuntimeMinutes);
58
+ appendPassthroughFlag(args, "--model", commandOptions.model);
59
+ appendPassthroughFlag(args, "--provider", commandOptions.provider);
60
+ appendPassthroughFlag(args, "--reuse-omargate", commandOptions.reuseOmargate);
61
+ appendPassthroughFlag(args, "--notify-email", commandOptions.notifyEmail);
62
+ appendPassthroughFlag(args, "--notify-session", commandOptions.notifySession);
36
63
  // Omar Gate per-persona filter flags (A-CLI-1).
37
64
  appendPassthroughFlag(args, "--persona", commandOptions.persona);
38
65
  appendPassthroughFlag(args, "--skip-persona", commandOptions.skipPersona);
@@ -57,6 +57,7 @@ import {
57
57
  } from "../session/sync.js";
58
58
  import { hydrateSessionFromRemote } from "../session/remote-hydrate.js";
59
59
  import { mergeLiveSources } from "../session/live-source.js";
60
+ import { deriveSessionTitle } from "../session/senti-naming.js";
60
61
  import {
61
62
  buildDashboardUrl,
62
63
  buildTemplateLaunchPlan,
@@ -302,30 +303,80 @@ export function registerSessionCommand(program) {
302
303
  3600,
303
304
  );
304
305
 
305
- // Auto-resume: if there's an active local session for this same
306
- // workspace path created in the last `reuseWindowSeconds`, reuse
307
- // it instead of minting a new id. Kills the orphan-creation
308
- // pattern where every CLI invocation produced a fresh empty
309
- // session. `--force-new` opts back into the old behavior.
306
+ // Auto-resume: prefer an existing active session for this codebase
307
+ // over minting a new one. We check both local filesystem state and the
308
+ // remote registry local-only resume meant a fresh checkout / second
309
+ // machine would orphan the room each time, exactly the mess Carter
310
+ // surfaced ("all of them look like one chat re-created").
311
+ //
312
+ // Order:
313
+ // 1. Local session for the same targetPath inside the reuse window.
314
+ // 2. Remote active session whose codebasePath matches the absolute
315
+ // targetPath, sorted by last activity. We fold these into the
316
+ // candidate pool so a session minted on another machine can be
317
+ // rejoined rather than duplicated.
318
+ // `--force-new` opts back into the old "always mint" behavior.
310
319
  let resumed = null;
311
320
  if (!options.forceNew) {
321
+ const cutoffMs = Date.now() - reuseWindowSeconds * 1000;
322
+ const candidates = [];
312
323
  try {
313
324
  const active = await listActiveSessions({ targetPath });
314
- const cutoffMs = Date.now() - reuseWindowSeconds * 1000;
315
- const candidates = active.filter((entry) => {
325
+ for (const entry of active) {
316
326
  const createdMs = Date.parse(entry.createdAt || "");
317
- return Number.isFinite(createdMs) && createdMs >= cutoffMs;
327
+ if (Number.isFinite(createdMs) && createdMs >= cutoffMs) {
328
+ candidates.push({ ...entry, _source: "local" });
329
+ }
330
+ }
331
+ } catch {
332
+ /* local lookup failure is non-fatal */
333
+ }
334
+ try {
335
+ const remote = await listSessionsFromApi({
336
+ targetPath,
337
+ includeArchived: false,
338
+ limit: 50,
318
339
  });
319
- candidates.sort((a, b) =>
340
+ if (remote && remote.ok) {
341
+ const normalizedTarget = String(targetPath).toLowerCase();
342
+ for (const entry of remote.sessions || []) {
343
+ const codebase = String(entry.codebasePath || "").toLowerCase();
344
+ if (!codebase || codebase !== normalizedTarget) continue;
345
+ if (entry.archiveStatus && entry.archiveStatus !== "active") continue;
346
+ const lastMs = Date.parse(entry.lastActivityAt || entry.createdAt || "");
347
+ if (Number.isFinite(lastMs) && lastMs >= cutoffMs) {
348
+ candidates.push({
349
+ sessionId: entry.sessionId,
350
+ createdAt: entry.createdAt,
351
+ lastActivityAt: entry.lastActivityAt,
352
+ expiresAt: entry.expiresAt,
353
+ status: entry.status || "active",
354
+ template: entry.templateName || null,
355
+ title: entry.title || null,
356
+ _source: "remote",
357
+ });
358
+ }
359
+ }
360
+ }
361
+ } catch {
362
+ /* remote lookup failure is non-fatal */
363
+ }
364
+ if (candidates.length > 0) {
365
+ // Prefer the most recent activity. Local + remote may name the
366
+ // same session; dedupe on sessionId before picking.
367
+ const seen = new Set();
368
+ const deduped = [];
369
+ for (const entry of candidates) {
370
+ if (seen.has(entry.sessionId)) continue;
371
+ seen.add(entry.sessionId);
372
+ deduped.push(entry);
373
+ }
374
+ deduped.sort((a, b) =>
320
375
  String(b.lastActivityAt || b.createdAt || "").localeCompare(
321
376
  String(a.lastActivityAt || a.createdAt || ""),
322
377
  ),
323
378
  );
324
- if (candidates.length > 0) {
325
- resumed = candidates[0];
326
- }
327
- } catch (error) {
328
- // listActiveSessions failure is non-fatal; fall through to fresh create.
379
+ resumed = deduped[0];
329
380
  }
330
381
  }
331
382
 
@@ -358,11 +409,19 @@ export function registerSessionCommand(program) {
358
409
  const durationMs = Date.now() - startedAt;
359
410
  const launchPlan = template ? buildTemplateLaunchPlan(created.sessionId, template) : [];
360
411
  const dashboardUrl = buildDashboardUrl(created.sessionId);
412
+ // Default the title to a stable codebase+date slug so the web sidebar
413
+ // never fills with anonymous "<null>" rows. The caller can still
414
+ // override with --title. We skip the auto-title for resumed sessions
415
+ // because the room already has a name we don't want to clobber.
361
416
  const titleArg = normalizeString(options.title);
362
-
363
- // If the caller passed --title, push it to the API so the web
364
- // sidebar shows the label (best-effort, non-blocking).
365
- if (titleArg) {
417
+ const autoTitle = !resumed && !titleArg ? deriveSessionTitle(targetPath) : "";
418
+ const effectiveTitle = titleArg || autoTitle;
419
+
420
+ // If a title needs to land on the dashboard, push it. We always push
421
+ // when the caller passed --title, AND we push the auto-derived title
422
+ // for fresh (non-resumed) sessions so the room is never anonymous on
423
+ // the web. Best-effort, non-blocking.
424
+ if (effectiveTitle) {
366
425
  void (async () => {
367
426
  try {
368
427
  const session = await resolveActiveAuthSession({
@@ -378,7 +437,7 @@ export function registerSessionCommand(program) {
378
437
  method: "POST",
379
438
  operationName: "session.set_title",
380
439
  headers: { Authorization: `Bearer ${session.token}` },
381
- body: { title: titleArg },
440
+ body: { title: effectiveTitle },
382
441
  },
383
442
  );
384
443
  } catch (_error) {
@@ -405,7 +464,8 @@ export function registerSessionCommand(program) {
405
464
  launchPlan,
406
465
  dashboardUrl,
407
466
  resumed: Boolean(resumed),
408
- title: titleArg || null,
467
+ title: effectiveTitle || null,
468
+ titleAuto: Boolean(autoTitle && !titleArg),
409
469
  };
410
470
 
411
471
  // Best-effort admin visibility sync. Session creation remains local-first.
@@ -7,6 +7,7 @@ import { rollupUsage } from "./tracker.js";
7
7
 
8
8
  const HISTORY_VERSION = 1;
9
9
  const HISTORY_FILE_NAME = "cost-history.json";
10
+ const costHistoryWriteQueues = new Map();
10
11
 
11
12
  function normalizeNumber(value, field) {
12
13
  const normalized = Number(value || 0);
@@ -76,6 +77,14 @@ export async function resolveCostHistoryPath({
76
77
 
77
78
  export async function loadCostHistory(options = {}) {
78
79
  const filePath = await resolveCostHistoryPath(options);
80
+ const history = await readCostHistoryFile(filePath);
81
+ return {
82
+ filePath,
83
+ history,
84
+ };
85
+ }
86
+
87
+ async function readCostHistoryFile(filePath) {
79
88
  try {
80
89
  const raw = await fsp.readFile(filePath, "utf-8");
81
90
  const parsed = JSON.parse(raw);
@@ -83,20 +92,14 @@ export async function loadCostHistory(options = {}) {
83
92
  throw new Error("Invalid cost history payload.");
84
93
  }
85
94
  return {
86
- filePath,
87
- history: {
88
- version: Number(parsed.version || HISTORY_VERSION),
89
- entries: parsed.entries,
90
- },
95
+ version: Number(parsed.version || HISTORY_VERSION),
96
+ entries: parsed.entries,
91
97
  };
92
98
  } catch (error) {
93
99
  if (error && typeof error === "object" && error.code === "ENOENT") {
94
100
  return {
95
- filePath,
96
- history: {
97
- version: HISTORY_VERSION,
98
- entries: [],
99
- },
101
+ version: HISTORY_VERSION,
102
+ entries: [],
100
103
  };
101
104
  }
102
105
  throw error;
@@ -114,17 +117,34 @@ export async function saveCostHistory({ filePath, history }) {
114
117
 
115
118
  export async function appendCostEntry(options = {}, entry = {}) {
116
119
  const normalizedEntry = normalizeEntry(entry);
117
- const { filePath, history } = await loadCostHistory(options);
118
- const nextHistory = {
119
- version: HISTORY_VERSION,
120
- entries: [...history.entries, normalizedEntry],
121
- };
122
- await saveCostHistory({ filePath, history: nextHistory });
123
- return {
124
- filePath,
125
- entry: normalizedEntry,
126
- history: nextHistory,
127
- };
120
+ const filePath = await resolveCostHistoryPath(options);
121
+ return withCostHistoryWriteQueue(filePath, async () => {
122
+ const history = await readCostHistoryFile(filePath);
123
+ const nextHistory = {
124
+ version: HISTORY_VERSION,
125
+ entries: [...history.entries, normalizedEntry],
126
+ };
127
+ await saveCostHistory({ filePath, history: nextHistory });
128
+ return {
129
+ filePath,
130
+ entry: normalizedEntry,
131
+ history: nextHistory,
132
+ };
133
+ });
134
+ }
135
+
136
+ async function withCostHistoryWriteQueue(filePath, fn) {
137
+ const previous = costHistoryWriteQueues.get(filePath) || Promise.resolve();
138
+ const next = previous.catch(() => {}).then(fn);
139
+ const queued = next.catch(() => {});
140
+ costHistoryWriteQueues.set(filePath, queued);
141
+ try {
142
+ return await next;
143
+ } finally {
144
+ if (costHistoryWriteQueues.get(filePath) === queued) {
145
+ costHistoryWriteQueues.delete(filePath);
146
+ }
147
+ }
128
148
  }
129
149
 
130
150
  function summarizeSessionEntries(entries) {
@@ -1,5 +1,31 @@
1
1
  const AGENT_EVENT_STREAM = "sl_event";
2
2
  const LEGACY_AGENT_ID = "legacy-emitter";
3
+ const AGENT_EVENT_TYPES = Object.freeze([
4
+ "agent_start",
5
+ "agent_complete",
6
+ "agent_abort",
7
+ "agent_error",
8
+ "progress",
9
+ "heartbeat",
10
+ "tool_call",
11
+ "tool_result",
12
+ "finding",
13
+ "reasoning",
14
+ "budget_warning",
15
+ "budget_stop",
16
+ "swarm_start",
17
+ "swarm_complete",
18
+ "phase_start",
19
+ "phase_complete",
20
+ "orchestrator_start",
21
+ "dispatch",
22
+ "reconcile_start",
23
+ "reconcile_complete",
24
+ "orchestrator_complete",
25
+ "convergence_expansion",
26
+ "coverage_gap",
27
+ "llm_error",
28
+ ]);
3
29
 
4
30
  function isPlainObject(value) {
5
31
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
@@ -187,4 +213,4 @@ export function validateAgentEvent(evt, options = {}) {
187
213
  return Boolean(normalizeAgentEvent(evt, options));
188
214
  }
189
215
 
190
- export { AGENT_EVENT_STREAM };
216
+ export { AGENT_EVENT_STREAM, AGENT_EVENT_TYPES };