sentinelayer-cli 0.8.11 → 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.
- package/package.json +4 -4
- package/src/agents/jules/stream.js +2 -12
- package/src/audit/orchestrator.js +471 -114
- package/src/audit/persona-loop.js +1342 -0
- package/src/audit/registry.js +58 -2
- package/src/commands/audit.js +42 -1
- package/src/commands/legacy-args.js +28 -1
- package/src/commands/session.js +80 -20
- package/src/cost/history.js +41 -21
- package/src/events/schema.js +27 -1
- package/src/legacy-cli.js +76 -1
- package/src/review/omargate-cache.js +285 -0
- package/src/review/omargate-orchestrator.js +586 -3
- package/src/review/report.js +128 -2
- package/src/session/senti-naming.js +36 -0
- package/src/session/sync.js +23 -0
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { randomUUID } from "node:crypto";
|
|
9
|
+
import path from "node:path";
|
|
9
10
|
|
|
10
11
|
import { runAiReviewLayer } from "./ai-review.js";
|
|
11
12
|
import { buildPersonaReviewPrompt, PERSONA_IDS } from "./persona-prompts.js";
|
|
@@ -20,6 +21,19 @@ const OMAR_ORCHESTRATOR_AGENT = Object.freeze({
|
|
|
20
21
|
persona: "Omar Gate Orchestrator",
|
|
21
22
|
});
|
|
22
23
|
|
|
24
|
+
const OMAR_SWARM_THRESHOLDS = Object.freeze({
|
|
25
|
+
minFilesForSwarm: 15,
|
|
26
|
+
minRouteGroupsForSwarm: 3,
|
|
27
|
+
minLocForSwarm: 5000,
|
|
28
|
+
maxFilesPerScanner: 12,
|
|
29
|
+
maxConcurrentAgents: 4,
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const OMARGATE_DEFAULT_CONFIDENCE_FLOOR = 0.7;
|
|
33
|
+
const OMARGATE_CONFIDENCE_FLOORS = Object.freeze(
|
|
34
|
+
Object.fromEntries(PERSONA_IDS.map((personaId) => [personaId, OMARGATE_DEFAULT_CONFIDENCE_FLOOR]))
|
|
35
|
+
);
|
|
36
|
+
|
|
23
37
|
/**
|
|
24
38
|
* Run bounded-concurrency parallel execution.
|
|
25
39
|
* @param {Array} items
|
|
@@ -32,9 +46,8 @@ async function runWithConcurrency(items, maxConcurrent, fn) {
|
|
|
32
46
|
const executing = new Set();
|
|
33
47
|
|
|
34
48
|
for (const item of items) {
|
|
35
|
-
const p = fn(item).
|
|
49
|
+
const p = Promise.resolve().then(() => fn(item)).finally(() => {
|
|
36
50
|
executing.delete(p);
|
|
37
|
-
return result;
|
|
38
51
|
});
|
|
39
52
|
executing.add(p);
|
|
40
53
|
results.push(p);
|
|
@@ -47,6 +60,192 @@ async function runWithConcurrency(items, maxConcurrent, fn) {
|
|
|
47
60
|
return Promise.allSettled(results);
|
|
48
61
|
}
|
|
49
62
|
|
|
63
|
+
function normalizeString(value) {
|
|
64
|
+
return String(value || "").trim();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function normalizeNumber(value, fallback = 0) {
|
|
68
|
+
const parsed = Number(value);
|
|
69
|
+
if (!Number.isFinite(parsed)) {
|
|
70
|
+
return fallback;
|
|
71
|
+
}
|
|
72
|
+
return parsed;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function toPosixPath(value) {
|
|
76
|
+
return normalizeString(value).replace(/\\/g, "/");
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function uniqueScopeFiles(files = []) {
|
|
80
|
+
const seen = new Set();
|
|
81
|
+
const normalized = [];
|
|
82
|
+
for (const item of Array.isArray(files) ? files : []) {
|
|
83
|
+
const rawPath =
|
|
84
|
+
typeof item === "string"
|
|
85
|
+
? item
|
|
86
|
+
: item?.path || item?.file || item?.relativePath || "";
|
|
87
|
+
const filePath = toPosixPath(rawPath);
|
|
88
|
+
if (!filePath || seen.has(filePath)) {
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
seen.add(filePath);
|
|
92
|
+
const loc =
|
|
93
|
+
typeof item === "object" && item
|
|
94
|
+
? Math.max(0, Math.floor(normalizeNumber(item.loc ?? item.lines ?? item.lineCount, 0)))
|
|
95
|
+
: 0;
|
|
96
|
+
normalized.push({ path: filePath, loc });
|
|
97
|
+
}
|
|
98
|
+
return normalized;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function filesFromScope(scope = {}) {
|
|
102
|
+
if (Array.isArray(scope.files)) {
|
|
103
|
+
return uniqueScopeFiles(scope.files);
|
|
104
|
+
}
|
|
105
|
+
if (Array.isArray(scope.primary)) {
|
|
106
|
+
return uniqueScopeFiles(scope.primary);
|
|
107
|
+
}
|
|
108
|
+
if (Array.isArray(scope.scannedRelativeFiles)) {
|
|
109
|
+
return uniqueScopeFiles(scope.scannedRelativeFiles);
|
|
110
|
+
}
|
|
111
|
+
return [];
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function estimateScopeLoc(scope = {}, files = []) {
|
|
115
|
+
const explicit =
|
|
116
|
+
normalizeNumber(scope.totalLoc, 0) ||
|
|
117
|
+
normalizeNumber(scope.estimatedLoc, 0) ||
|
|
118
|
+
normalizeNumber(scope.summary?.totalLoc, 0);
|
|
119
|
+
if (explicit > 0) {
|
|
120
|
+
return Math.floor(explicit);
|
|
121
|
+
}
|
|
122
|
+
return files.reduce((sum, file) => sum + (file.loc > 0 ? file.loc : 80), 0);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function detectRouteGroups(files = []) {
|
|
126
|
+
const routeGroups = new Set();
|
|
127
|
+
for (const file of files) {
|
|
128
|
+
const filePath = toPosixPath(file.path || file);
|
|
129
|
+
const match = filePath.match(/(?:^|\/)(?:app|pages|routes)\/([^/]+)/);
|
|
130
|
+
if (match?.[1]) {
|
|
131
|
+
routeGroups.add(match[1]);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return [...routeGroups].sort();
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Build the OmarGate persona file scope from deterministic review output.
|
|
139
|
+
*/
|
|
140
|
+
export function buildPersonaFileScope({ deterministic = {} } = {}) {
|
|
141
|
+
const rawScope = deterministic?.scope || {};
|
|
142
|
+
const ingestSummary = deterministic?.layers?.ingest?.summary || {};
|
|
143
|
+
const files = filesFromScope(rawScope);
|
|
144
|
+
const totalLoc =
|
|
145
|
+
normalizeNumber(rawScope.totalLoc, 0) ||
|
|
146
|
+
normalizeNumber(rawScope.estimatedLoc, 0) ||
|
|
147
|
+
normalizeNumber(ingestSummary.totalLoc, 0) ||
|
|
148
|
+
estimateScopeLoc(rawScope, files);
|
|
149
|
+
|
|
150
|
+
return {
|
|
151
|
+
files,
|
|
152
|
+
scannedFiles: files.length,
|
|
153
|
+
scannedRelativeFiles: files.map((file) => file.path),
|
|
154
|
+
totalLoc: Math.floor(totalLoc),
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Decide whether an OmarGate persona should fan out into scoped subagents.
|
|
160
|
+
*/
|
|
161
|
+
export function decideSwarm({ scope = {} } = {}) {
|
|
162
|
+
const files = filesFromScope(scope);
|
|
163
|
+
const routeGroups = detectRouteGroups(files);
|
|
164
|
+
const estimatedLoc = estimateScopeLoc(scope, files);
|
|
165
|
+
const spawn =
|
|
166
|
+
files.length > OMAR_SWARM_THRESHOLDS.minFilesForSwarm ||
|
|
167
|
+
routeGroups.length >= OMAR_SWARM_THRESHOLDS.minRouteGroupsForSwarm ||
|
|
168
|
+
estimatedLoc > OMAR_SWARM_THRESHOLDS.minLocForSwarm;
|
|
169
|
+
|
|
170
|
+
let reason = "below all thresholds";
|
|
171
|
+
if (files.length > OMAR_SWARM_THRESHOLDS.minFilesForSwarm) {
|
|
172
|
+
reason = `${files.length} files exceeds threshold (${OMAR_SWARM_THRESHOLDS.minFilesForSwarm})`;
|
|
173
|
+
} else if (routeGroups.length >= OMAR_SWARM_THRESHOLDS.minRouteGroupsForSwarm) {
|
|
174
|
+
reason = `${routeGroups.length} route groups exceeds threshold (${OMAR_SWARM_THRESHOLDS.minRouteGroupsForSwarm})`;
|
|
175
|
+
} else if (estimatedLoc > OMAR_SWARM_THRESHOLDS.minLocForSwarm) {
|
|
176
|
+
reason = `${estimatedLoc} LOC exceeds threshold (${OMAR_SWARM_THRESHOLDS.minLocForSwarm})`;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return {
|
|
180
|
+
spawn,
|
|
181
|
+
fileCount: files.length,
|
|
182
|
+
routeGroups: routeGroups.length,
|
|
183
|
+
routeGroupNames: routeGroups,
|
|
184
|
+
estimatedLoc,
|
|
185
|
+
reason,
|
|
186
|
+
thresholds: { ...OMAR_SWARM_THRESHOLDS },
|
|
187
|
+
maxConcurrent: OMAR_SWARM_THRESHOLDS.maxConcurrentAgents,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export function partitionFiles(files = [], maxPerPartition = OMAR_SWARM_THRESHOLDS.maxFilesPerScanner) {
|
|
192
|
+
const normalizedFiles = uniqueScopeFiles(files);
|
|
193
|
+
const partitionSize = Math.max(1, Math.floor(normalizeNumber(maxPerPartition, OMAR_SWARM_THRESHOLDS.maxFilesPerScanner)));
|
|
194
|
+
const partitions = [];
|
|
195
|
+
for (let index = 0; index < normalizedFiles.length; index += partitionSize) {
|
|
196
|
+
partitions.push(normalizedFiles.slice(index, index + partitionSize));
|
|
197
|
+
}
|
|
198
|
+
return partitions;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export function divideSwarmBudget(perPersonaCost, subagentCount) {
|
|
202
|
+
const count = Math.max(1, Math.floor(normalizeNumber(subagentCount, 1)));
|
|
203
|
+
const maxCostUsd = Math.max(0, normalizeNumber(perPersonaCost, 0)) / count;
|
|
204
|
+
return {
|
|
205
|
+
maxCostUsd,
|
|
206
|
+
subagentCount: count,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function summarizeFindings(findings = []) {
|
|
211
|
+
const summary = { P0: 0, P1: 0, P2: 0, P3: 0 };
|
|
212
|
+
for (const finding of findings) {
|
|
213
|
+
const severity = normalizeString(finding.severity).toUpperCase();
|
|
214
|
+
if (summary[severity] !== undefined) {
|
|
215
|
+
summary[severity] += 1;
|
|
216
|
+
} else {
|
|
217
|
+
summary.P3 += 1;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return {
|
|
221
|
+
...summary,
|
|
222
|
+
blocking: summary.P0 > 0 || summary.P1 > 0,
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function personaAgentFromIdentity(identity = {}) {
|
|
227
|
+
return {
|
|
228
|
+
id: identity.id,
|
|
229
|
+
persona: identity.fullName,
|
|
230
|
+
shortName: identity.shortName,
|
|
231
|
+
color: identity.color,
|
|
232
|
+
avatar: identity.avatar,
|
|
233
|
+
domain: identity.domain,
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function buildSubagentIdentity(identity, subagentIndex) {
|
|
238
|
+
return {
|
|
239
|
+
id: `${identity.id}-subagent-${subagentIndex}`,
|
|
240
|
+
persona: `${identity.fullName} Subagent ${subagentIndex}`,
|
|
241
|
+
shortName: `${identity.shortName || identity.id} #${subagentIndex}`,
|
|
242
|
+
color: identity.color,
|
|
243
|
+
avatar: identity.avatar,
|
|
244
|
+
domain: identity.domain,
|
|
245
|
+
parentId: identity.id,
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
50
249
|
/**
|
|
51
250
|
* Annotate persona result with visual identity so stream consumers
|
|
52
251
|
* and downstream reports never see faceless persona IDs.
|
|
@@ -67,6 +266,309 @@ function decoratePersonaResult(personaId, baseResult) {
|
|
|
67
266
|
};
|
|
68
267
|
}
|
|
69
268
|
|
|
269
|
+
function omargateConfidenceFloorForPersona(personaId) {
|
|
270
|
+
return OMARGATE_CONFIDENCE_FLOORS[personaId] || OMARGATE_DEFAULT_CONFIDENCE_FLOOR;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
async function runOmarPersonaSwarm({
|
|
274
|
+
personaId,
|
|
275
|
+
identity,
|
|
276
|
+
targetPath,
|
|
277
|
+
mode,
|
|
278
|
+
runId,
|
|
279
|
+
deterministic,
|
|
280
|
+
outputDir,
|
|
281
|
+
provider,
|
|
282
|
+
model,
|
|
283
|
+
perPersonaCost,
|
|
284
|
+
dryRun,
|
|
285
|
+
onEvent,
|
|
286
|
+
} = {}) {
|
|
287
|
+
const scope = buildPersonaFileScope({ deterministic });
|
|
288
|
+
const decision = decideSwarm({ scope });
|
|
289
|
+
if (!decision.spawn || scope.files.length === 0) {
|
|
290
|
+
return null;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const partitions = partitionFiles(scope.files, OMAR_SWARM_THRESHOLDS.maxFilesPerScanner);
|
|
294
|
+
const budget = divideSwarmBudget(perPersonaCost, partitions.length);
|
|
295
|
+
const maxConcurrent = Math.min(OMAR_SWARM_THRESHOLDS.maxConcurrentAgents, partitions.length);
|
|
296
|
+
const swarmRunId = `${runId}-${personaId}-swarm`;
|
|
297
|
+
const startedAt = Date.now();
|
|
298
|
+
const blackboard = [];
|
|
299
|
+
|
|
300
|
+
if (onEvent) {
|
|
301
|
+
onEvent(createAgentEvent({
|
|
302
|
+
event: "swarm_start",
|
|
303
|
+
agent: personaAgentFromIdentity(identity),
|
|
304
|
+
payload: {
|
|
305
|
+
runId,
|
|
306
|
+
swarmRunId,
|
|
307
|
+
personaId,
|
|
308
|
+
identity,
|
|
309
|
+
mode,
|
|
310
|
+
reason: decision.reason,
|
|
311
|
+
fileCount: decision.fileCount,
|
|
312
|
+
estimatedLoc: decision.estimatedLoc,
|
|
313
|
+
routeGroups: decision.routeGroups,
|
|
314
|
+
partitionCount: partitions.length,
|
|
315
|
+
maxFilesPerSubagent: OMAR_SWARM_THRESHOLDS.maxFilesPerScanner,
|
|
316
|
+
maxConcurrent,
|
|
317
|
+
perPersonaCost,
|
|
318
|
+
subagentMaxCostUsd: budget.maxCostUsd,
|
|
319
|
+
},
|
|
320
|
+
runId,
|
|
321
|
+
}));
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const parentRunDirectory = deterministic?.artifacts?.runDirectory || targetPath;
|
|
325
|
+
const subagentResults = await runWithConcurrency(
|
|
326
|
+
partitions.map((files, index) => ({ files, subagentIndex: index + 1 })),
|
|
327
|
+
maxConcurrent,
|
|
328
|
+
async ({ files, subagentIndex }) => {
|
|
329
|
+
const subagentStart = Date.now();
|
|
330
|
+
const subagentIdentity = buildSubagentIdentity(identity, subagentIndex);
|
|
331
|
+
const scopedFiles = files.map((file) => file.path);
|
|
332
|
+
const subagentRunId = `${swarmRunId}-${subagentIndex}`;
|
|
333
|
+
|
|
334
|
+
if (onEvent) {
|
|
335
|
+
onEvent(createAgentEvent({
|
|
336
|
+
event: "agent_start",
|
|
337
|
+
agent: subagentIdentity,
|
|
338
|
+
payload: {
|
|
339
|
+
runId,
|
|
340
|
+
swarmRunId,
|
|
341
|
+
subagentRunId,
|
|
342
|
+
personaId,
|
|
343
|
+
identity,
|
|
344
|
+
subagentIndex,
|
|
345
|
+
partitionCount: partitions.length,
|
|
346
|
+
files: scopedFiles,
|
|
347
|
+
fileCount: scopedFiles.length,
|
|
348
|
+
maxCostUsd: budget.maxCostUsd,
|
|
349
|
+
},
|
|
350
|
+
runId,
|
|
351
|
+
}));
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
try {
|
|
355
|
+
const result = await runAiReviewLayer({
|
|
356
|
+
targetPath,
|
|
357
|
+
mode: "full",
|
|
358
|
+
runId: subagentRunId,
|
|
359
|
+
runDirectory: path.join(parentRunDirectory, "swarm", personaId, `subagent-${subagentIndex}`),
|
|
360
|
+
deterministic: {
|
|
361
|
+
...deterministic,
|
|
362
|
+
scope: {
|
|
363
|
+
...(deterministic?.scope || {}),
|
|
364
|
+
scannedFiles: scopedFiles.length,
|
|
365
|
+
scannedRelativeFiles: scopedFiles,
|
|
366
|
+
},
|
|
367
|
+
metadata: {
|
|
368
|
+
...(deterministic?.metadata || {}),
|
|
369
|
+
omarSwarm: {
|
|
370
|
+
personaId,
|
|
371
|
+
subagentIndex,
|
|
372
|
+
partitionCount: partitions.length,
|
|
373
|
+
parentRunId: runId,
|
|
374
|
+
swarmRunId,
|
|
375
|
+
},
|
|
376
|
+
},
|
|
377
|
+
},
|
|
378
|
+
outputDir,
|
|
379
|
+
provider: provider || undefined,
|
|
380
|
+
model: model || undefined,
|
|
381
|
+
sessionId: `${subagentRunId}-ai`,
|
|
382
|
+
maxCostUsd: budget.maxCostUsd,
|
|
383
|
+
dryRun,
|
|
384
|
+
env: process.env,
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
const personaConfidenceFloor = omargateConfidenceFloorForPersona(personaId);
|
|
388
|
+
const findings = (result?.findings || []).map((finding) => {
|
|
389
|
+
const normalized = {
|
|
390
|
+
...finding,
|
|
391
|
+
persona: personaId,
|
|
392
|
+
layer: personaId,
|
|
393
|
+
confidenceFloor: personaConfidenceFloor,
|
|
394
|
+
personaConfidenceFloor,
|
|
395
|
+
swarm: {
|
|
396
|
+
personaId,
|
|
397
|
+
subagentIndex,
|
|
398
|
+
partitionCount: partitions.length,
|
|
399
|
+
files: scopedFiles,
|
|
400
|
+
},
|
|
401
|
+
};
|
|
402
|
+
blackboard.push({
|
|
403
|
+
agentId: subagentIdentity.id,
|
|
404
|
+
source: personaId,
|
|
405
|
+
...normalized,
|
|
406
|
+
});
|
|
407
|
+
return normalized;
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
if (onEvent) {
|
|
411
|
+
for (const finding of findings) {
|
|
412
|
+
onEvent(createAgentEvent({
|
|
413
|
+
event: "persona_finding",
|
|
414
|
+
agent: personaAgentFromIdentity(identity),
|
|
415
|
+
payload: { personaId, identity, subagentIndex, ...finding },
|
|
416
|
+
runId,
|
|
417
|
+
}));
|
|
418
|
+
}
|
|
419
|
+
onEvent(createAgentEvent({
|
|
420
|
+
event: "agent_complete",
|
|
421
|
+
agent: subagentIdentity,
|
|
422
|
+
payload: {
|
|
423
|
+
runId,
|
|
424
|
+
swarmRunId,
|
|
425
|
+
subagentRunId,
|
|
426
|
+
personaId,
|
|
427
|
+
subagentIndex,
|
|
428
|
+
partitionCount: partitions.length,
|
|
429
|
+
fileCount: scopedFiles.length,
|
|
430
|
+
findings: findings.length,
|
|
431
|
+
summary: result?.summary || summarizeFindings(findings),
|
|
432
|
+
costUsd: result?.usage?.costUsd || 0,
|
|
433
|
+
durationMs: Date.now() - subagentStart,
|
|
434
|
+
},
|
|
435
|
+
usage: {
|
|
436
|
+
costUsd: result?.usage?.costUsd || 0,
|
|
437
|
+
durationMs: Date.now() - subagentStart,
|
|
438
|
+
toolCalls: result?.usage?.toolCalls || 0,
|
|
439
|
+
},
|
|
440
|
+
runId,
|
|
441
|
+
}));
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
return {
|
|
445
|
+
status: "ok",
|
|
446
|
+
subagentIndex,
|
|
447
|
+
agentId: subagentIdentity.id,
|
|
448
|
+
files: scopedFiles,
|
|
449
|
+
findings,
|
|
450
|
+
summary: result?.summary || summarizeFindings(findings),
|
|
451
|
+
costUsd: result?.usage?.costUsd || 0,
|
|
452
|
+
model: result?.model || model || null,
|
|
453
|
+
durationMs: Date.now() - subagentStart,
|
|
454
|
+
};
|
|
455
|
+
} catch (err) {
|
|
456
|
+
if (onEvent) {
|
|
457
|
+
onEvent(createAgentEvent({
|
|
458
|
+
event: "agent_error",
|
|
459
|
+
agent: subagentIdentity,
|
|
460
|
+
payload: {
|
|
461
|
+
runId,
|
|
462
|
+
swarmRunId,
|
|
463
|
+
subagentRunId,
|
|
464
|
+
personaId,
|
|
465
|
+
subagentIndex,
|
|
466
|
+
partitionCount: partitions.length,
|
|
467
|
+
error: err.message,
|
|
468
|
+
durationMs: Date.now() - subagentStart,
|
|
469
|
+
},
|
|
470
|
+
usage: {
|
|
471
|
+
costUsd: 0,
|
|
472
|
+
durationMs: Date.now() - subagentStart,
|
|
473
|
+
toolCalls: 0,
|
|
474
|
+
},
|
|
475
|
+
runId,
|
|
476
|
+
}));
|
|
477
|
+
}
|
|
478
|
+
return {
|
|
479
|
+
status: "error",
|
|
480
|
+
subagentIndex,
|
|
481
|
+
agentId: subagentIdentity.id,
|
|
482
|
+
files: scopedFiles,
|
|
483
|
+
findings: [],
|
|
484
|
+
summary: { P0: 0, P1: 0, P2: 0, P3: 0, blocking: false },
|
|
485
|
+
costUsd: 0,
|
|
486
|
+
error: err.message,
|
|
487
|
+
durationMs: Date.now() - subagentStart,
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
);
|
|
492
|
+
|
|
493
|
+
const settledSubagents = subagentResults.map((result) =>
|
|
494
|
+
result.status === "fulfilled"
|
|
495
|
+
? result.value
|
|
496
|
+
: {
|
|
497
|
+
status: "error",
|
|
498
|
+
subagentIndex: 0,
|
|
499
|
+
agentId: "unknown",
|
|
500
|
+
files: [],
|
|
501
|
+
findings: [],
|
|
502
|
+
summary: { P0: 0, P1: 0, P2: 0, P3: 0, blocking: false },
|
|
503
|
+
costUsd: 0,
|
|
504
|
+
error: result.reason?.message || "unknown",
|
|
505
|
+
durationMs: 0,
|
|
506
|
+
}
|
|
507
|
+
);
|
|
508
|
+
const findings = settledSubagents.flatMap((result) => result.findings || []);
|
|
509
|
+
const totalCostUsd = settledSubagents.reduce((sum, result) => sum + (result.costUsd || 0), 0);
|
|
510
|
+
const summary = summarizeFindings(findings);
|
|
511
|
+
const okCount = settledSubagents.filter((result) => result.status === "ok").length;
|
|
512
|
+
const errorCount = settledSubagents.filter((result) => result.status === "error").length;
|
|
513
|
+
|
|
514
|
+
if (onEvent) {
|
|
515
|
+
onEvent(createAgentEvent({
|
|
516
|
+
event: "swarm_complete",
|
|
517
|
+
agent: personaAgentFromIdentity(identity),
|
|
518
|
+
payload: {
|
|
519
|
+
runId,
|
|
520
|
+
swarmRunId,
|
|
521
|
+
personaId,
|
|
522
|
+
identity,
|
|
523
|
+
subagentCount: settledSubagents.length,
|
|
524
|
+
ok: okCount,
|
|
525
|
+
error: errorCount,
|
|
526
|
+
findings: findings.length,
|
|
527
|
+
summary,
|
|
528
|
+
totalCostUsd,
|
|
529
|
+
durationMs: Date.now() - startedAt,
|
|
530
|
+
blackboardEntries: blackboard.length,
|
|
531
|
+
},
|
|
532
|
+
usage: {
|
|
533
|
+
costUsd: totalCostUsd,
|
|
534
|
+
durationMs: Date.now() - startedAt,
|
|
535
|
+
toolCalls: settledSubagents.length,
|
|
536
|
+
},
|
|
537
|
+
runId,
|
|
538
|
+
}));
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
return {
|
|
542
|
+
personaId,
|
|
543
|
+
status: okCount > 0 ? "ok" : "error",
|
|
544
|
+
findings,
|
|
545
|
+
summary,
|
|
546
|
+
costUsd: totalCostUsd,
|
|
547
|
+
model: model || null,
|
|
548
|
+
durationMs: Date.now() - startedAt,
|
|
549
|
+
error: okCount > 0 ? null : settledSubagents.find((result) => result.error)?.error || null,
|
|
550
|
+
swarm: {
|
|
551
|
+
runId: swarmRunId,
|
|
552
|
+
decision,
|
|
553
|
+
subagentCount: settledSubagents.length,
|
|
554
|
+
ok: okCount,
|
|
555
|
+
error: errorCount,
|
|
556
|
+
partitionSizes: partitions.map((files) => files.length),
|
|
557
|
+
blackboardEntries: blackboard.length,
|
|
558
|
+
subagents: settledSubagents.map((result) => ({
|
|
559
|
+
id: result.agentId,
|
|
560
|
+
index: result.subagentIndex,
|
|
561
|
+
status: result.status,
|
|
562
|
+
files: result.files,
|
|
563
|
+
findings: (result.findings || []).length,
|
|
564
|
+
costUsd: result.costUsd || 0,
|
|
565
|
+
durationMs: result.durationMs || 0,
|
|
566
|
+
error: result.error || null,
|
|
567
|
+
})),
|
|
568
|
+
},
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
|
|
70
572
|
/**
|
|
71
573
|
* Run the Omar Gate multi-persona orchestrator.
|
|
72
574
|
*
|
|
@@ -218,6 +720,62 @@ export async function runOmarGateOrchestrator({
|
|
|
218
720
|
}
|
|
219
721
|
|
|
220
722
|
try {
|
|
723
|
+
const swarmResult = await runOmarPersonaSwarm({
|
|
724
|
+
personaId,
|
|
725
|
+
identity,
|
|
726
|
+
targetPath,
|
|
727
|
+
mode,
|
|
728
|
+
runId,
|
|
729
|
+
deterministic,
|
|
730
|
+
outputDir,
|
|
731
|
+
provider,
|
|
732
|
+
model,
|
|
733
|
+
perPersonaCost,
|
|
734
|
+
dryRun,
|
|
735
|
+
onEvent,
|
|
736
|
+
});
|
|
737
|
+
|
|
738
|
+
if (swarmResult) {
|
|
739
|
+
const personaCost = swarmResult.costUsd || 0;
|
|
740
|
+
runningCostUsd += personaCost;
|
|
741
|
+
|
|
742
|
+
if (onEvent) {
|
|
743
|
+
if (swarmResult.status === "error") {
|
|
744
|
+
onEvent(createAgentEvent({
|
|
745
|
+
event: "persona_error",
|
|
746
|
+
agent: personaAgentFromIdentity(identity),
|
|
747
|
+
payload: {
|
|
748
|
+
personaId,
|
|
749
|
+
identity,
|
|
750
|
+
error: swarmResult.error || "all subagents failed",
|
|
751
|
+
swarm: swarmResult.swarm,
|
|
752
|
+
},
|
|
753
|
+
runId,
|
|
754
|
+
}));
|
|
755
|
+
} else {
|
|
756
|
+
onEvent(createAgentEvent({
|
|
757
|
+
event: "persona_complete",
|
|
758
|
+
agent: personaAgentFromIdentity(identity),
|
|
759
|
+
payload: {
|
|
760
|
+
personaId,
|
|
761
|
+
identity,
|
|
762
|
+
findings: swarmResult.findings.length,
|
|
763
|
+
summary: swarmResult.summary,
|
|
764
|
+
costUsd: personaCost,
|
|
765
|
+
durationMs: Date.now() - personaStart,
|
|
766
|
+
swarm: swarmResult.swarm,
|
|
767
|
+
},
|
|
768
|
+
runId,
|
|
769
|
+
}));
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
return {
|
|
774
|
+
...swarmResult,
|
|
775
|
+
durationMs: Date.now() - personaStart,
|
|
776
|
+
};
|
|
777
|
+
}
|
|
778
|
+
|
|
221
779
|
const systemPrompt = buildPersonaReviewPrompt({
|
|
222
780
|
personaId,
|
|
223
781
|
targetPath,
|
|
@@ -242,10 +800,13 @@ export async function runOmarGateOrchestrator({
|
|
|
242
800
|
env: process.env,
|
|
243
801
|
});
|
|
244
802
|
|
|
803
|
+
const personaConfidenceFloor = omargateConfidenceFloorForPersona(personaId);
|
|
245
804
|
const findings = (result?.findings || []).map((f) => ({
|
|
246
805
|
...f,
|
|
247
806
|
persona: personaId,
|
|
248
807
|
layer: personaId,
|
|
808
|
+
confidenceFloor: personaConfidenceFloor,
|
|
809
|
+
personaConfidenceFloor,
|
|
249
810
|
}));
|
|
250
811
|
|
|
251
812
|
if (onEvent) {
|
|
@@ -347,9 +908,17 @@ export async function runOmarGateOrchestrator({
|
|
|
347
908
|
const reconciled = reconcileReviewFindings({
|
|
348
909
|
deterministicFindings: detFindings,
|
|
349
910
|
aiFindings: allAiFindings,
|
|
911
|
+
defaultConfidenceFloor: OMARGATE_DEFAULT_CONFIDENCE_FLOOR,
|
|
912
|
+
confidenceFloors: OMARGATE_CONFIDENCE_FLOORS,
|
|
350
913
|
});
|
|
351
914
|
const reconciledFindings = reconciled.findings;
|
|
352
915
|
const reconciledSummary = reconciled.summary;
|
|
916
|
+
const droppedBelowConfidence = Number(reconciledSummary?.droppedBelowConfidence || 0);
|
|
917
|
+
const candidateFindingCount = detFindings.length + allAiFindings.length;
|
|
918
|
+
const dedupedCount = Math.max(
|
|
919
|
+
0,
|
|
920
|
+
candidateFindingCount - reconciledFindings.length - droppedBelowConfidence
|
|
921
|
+
);
|
|
353
922
|
|
|
354
923
|
const totalCost = settled.reduce((sum, r) => sum + (r.costUsd || 0), 0);
|
|
355
924
|
const totalDuration = Date.now() - startTime;
|
|
@@ -407,6 +976,7 @@ export async function runOmarGateOrchestrator({
|
|
|
407
976
|
durationMs: r.durationMs,
|
|
408
977
|
model: r.model || null,
|
|
409
978
|
error: r.error || null,
|
|
979
|
+
swarm: r.swarm || null,
|
|
410
980
|
})),
|
|
411
981
|
personaHealth,
|
|
412
982
|
findings: reconciledFindings,
|
|
@@ -414,6 +984,7 @@ export async function runOmarGateOrchestrator({
|
|
|
414
984
|
deterministic: detFindings.length,
|
|
415
985
|
ai: allAiFindings.length,
|
|
416
986
|
reconciled: reconciledFindings.length,
|
|
987
|
+
droppedBelowConfidence,
|
|
417
988
|
},
|
|
418
989
|
summary: reconciledSummary,
|
|
419
990
|
totalCostUsd: totalCost,
|
|
@@ -422,7 +993,12 @@ export async function runOmarGateOrchestrator({
|
|
|
422
993
|
deterministicFindings: detFindings.length,
|
|
423
994
|
aiFindings: allAiFindings.length,
|
|
424
995
|
reconciledFindings: reconciledFindings.length,
|
|
425
|
-
dedupedCount
|
|
996
|
+
dedupedCount,
|
|
997
|
+
droppedBelowConfidence,
|
|
998
|
+
droppedLowConfidence: droppedBelowConfidence,
|
|
999
|
+
droppedLowConfidenceSingleSource: Number(
|
|
1000
|
+
reconciledSummary?.droppedBelowConfidenceSingleSource || droppedBelowConfidence
|
|
1001
|
+
),
|
|
426
1002
|
multiSourceFindings: reconciledFindings.filter(
|
|
427
1003
|
(f) => Array.isArray(f.sources) && f.sources.length > 1
|
|
428
1004
|
).length,
|
|
@@ -485,6 +1061,13 @@ export async function runOmarGateOrchestrator({
|
|
|
485
1061
|
costUsd: r.costUsd || 0,
|
|
486
1062
|
durationMs: r.durationMs || 0,
|
|
487
1063
|
status: r.status,
|
|
1064
|
+
swarm: r.swarm
|
|
1065
|
+
? {
|
|
1066
|
+
subagentCount: r.swarm.subagentCount || 0,
|
|
1067
|
+
ok: r.swarm.ok || 0,
|
|
1068
|
+
error: r.swarm.error || 0,
|
|
1069
|
+
}
|
|
1070
|
+
: null,
|
|
488
1071
|
})),
|
|
489
1072
|
}).catch(() => {});
|
|
490
1073
|
|