sentinelayer-cli 0.8.11 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +10 -5
- package/src/agents/devtestbot/config/definition.js +100 -0
- package/src/agents/devtestbot/config/system-prompt.js +92 -0
- package/src/agents/devtestbot/index.js +9 -0
- package/src/agents/devtestbot/runner.js +769 -0
- package/src/agents/devtestbot/tool.js +707 -0
- 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 +32 -1
- package/src/commands/omargate.js +4 -0
- package/src/commands/session.js +417 -89
- package/src/commands/swarm.js +11 -2
- package/src/cost/history.js +41 -21
- package/src/events/schema.js +27 -1
- package/src/guide/generator.js +14 -0
- package/src/legacy-cli.js +110 -18
- package/src/prompt/generator.js +4 -16
- package/src/review/ai-review.js +95 -6
- package/src/review/dd-report-email-client.js +148 -0
- package/src/review/investor-dd-devtestbot.js +599 -0
- package/src/review/investor-dd-orchestrator.js +135 -3
- package/src/review/omargate-cache.js +285 -0
- package/src/review/omargate-orchestrator.js +605 -4
- package/src/review/persona-prompts.js +34 -1
- package/src/review/report.js +189 -4
- package/src/session/coordination-guidance.js +48 -0
- package/src/session/daemon.js +3 -2
- package/src/session/listener.js +236 -0
- package/src/session/senti-naming.js +36 -0
- package/src/session/setup-guides.js +3 -15
- package/src/session/store.js +54 -5
- package/src/session/sync.js +23 -0
- package/src/spec/generator.js +8 -10
- package/src/swarm/registry.js +20 -0
- package/src/swarm/runtime.js +139 -1
|
@@ -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,318 @@ 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 =
|
|
325
|
+
deterministic?.artifacts?.runDirectory || path.join(targetPath, ".sentinelayer", "reviews", runId);
|
|
326
|
+
const systemPrompt = buildPersonaReviewPrompt({
|
|
327
|
+
personaId,
|
|
328
|
+
targetPath,
|
|
329
|
+
deterministicSummary: deterministic?.summary || {},
|
|
330
|
+
});
|
|
331
|
+
const subagentResults = await runWithConcurrency(
|
|
332
|
+
partitions.map((files, index) => ({ files, subagentIndex: index + 1 })),
|
|
333
|
+
maxConcurrent,
|
|
334
|
+
async ({ files, subagentIndex }) => {
|
|
335
|
+
const subagentStart = Date.now();
|
|
336
|
+
const subagentIdentity = buildSubagentIdentity(identity, subagentIndex);
|
|
337
|
+
const scopedFiles = files.map((file) => file.path);
|
|
338
|
+
const subagentRunId = `${swarmRunId}-${subagentIndex}`;
|
|
339
|
+
|
|
340
|
+
if (onEvent) {
|
|
341
|
+
onEvent(createAgentEvent({
|
|
342
|
+
event: "agent_start",
|
|
343
|
+
agent: subagentIdentity,
|
|
344
|
+
payload: {
|
|
345
|
+
runId,
|
|
346
|
+
swarmRunId,
|
|
347
|
+
subagentRunId,
|
|
348
|
+
personaId,
|
|
349
|
+
identity,
|
|
350
|
+
subagentIndex,
|
|
351
|
+
partitionCount: partitions.length,
|
|
352
|
+
files: scopedFiles,
|
|
353
|
+
fileCount: scopedFiles.length,
|
|
354
|
+
maxCostUsd: budget.maxCostUsd,
|
|
355
|
+
},
|
|
356
|
+
runId,
|
|
357
|
+
}));
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
try {
|
|
361
|
+
const result = await runAiReviewLayer({
|
|
362
|
+
targetPath,
|
|
363
|
+
mode: "full",
|
|
364
|
+
runId: subagentRunId,
|
|
365
|
+
runDirectory: path.join(parentRunDirectory, "swarm", personaId, `subagent-${subagentIndex}`),
|
|
366
|
+
deterministic: {
|
|
367
|
+
...deterministic,
|
|
368
|
+
scope: {
|
|
369
|
+
...(deterministic?.scope || {}),
|
|
370
|
+
scannedFiles: scopedFiles.length,
|
|
371
|
+
scannedRelativeFiles: scopedFiles,
|
|
372
|
+
},
|
|
373
|
+
metadata: {
|
|
374
|
+
...(deterministic?.metadata || {}),
|
|
375
|
+
omarSwarm: {
|
|
376
|
+
personaId,
|
|
377
|
+
subagentIndex,
|
|
378
|
+
partitionCount: partitions.length,
|
|
379
|
+
parentRunId: runId,
|
|
380
|
+
swarmRunId,
|
|
381
|
+
},
|
|
382
|
+
},
|
|
383
|
+
},
|
|
384
|
+
outputDir,
|
|
385
|
+
provider: provider || undefined,
|
|
386
|
+
model: model || undefined,
|
|
387
|
+
sessionId: `${subagentRunId}-ai`,
|
|
388
|
+
maxCostUsd: budget.maxCostUsd,
|
|
389
|
+
systemPrompt,
|
|
390
|
+
dryRun,
|
|
391
|
+
env: process.env,
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
const personaConfidenceFloor = omargateConfidenceFloorForPersona(personaId);
|
|
395
|
+
const findings = (result?.findings || []).map((finding) => {
|
|
396
|
+
const normalized = {
|
|
397
|
+
...finding,
|
|
398
|
+
persona: personaId,
|
|
399
|
+
layer: personaId,
|
|
400
|
+
confidenceFloor: personaConfidenceFloor,
|
|
401
|
+
personaConfidenceFloor,
|
|
402
|
+
swarm: {
|
|
403
|
+
personaId,
|
|
404
|
+
subagentIndex,
|
|
405
|
+
partitionCount: partitions.length,
|
|
406
|
+
files: scopedFiles,
|
|
407
|
+
},
|
|
408
|
+
};
|
|
409
|
+
blackboard.push({
|
|
410
|
+
agentId: subagentIdentity.id,
|
|
411
|
+
source: personaId,
|
|
412
|
+
...normalized,
|
|
413
|
+
});
|
|
414
|
+
return normalized;
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
if (onEvent) {
|
|
418
|
+
for (const finding of findings) {
|
|
419
|
+
onEvent(createAgentEvent({
|
|
420
|
+
event: "persona_finding",
|
|
421
|
+
agent: personaAgentFromIdentity(identity),
|
|
422
|
+
payload: { personaId, identity, subagentIndex, ...finding },
|
|
423
|
+
runId,
|
|
424
|
+
}));
|
|
425
|
+
}
|
|
426
|
+
onEvent(createAgentEvent({
|
|
427
|
+
event: "agent_complete",
|
|
428
|
+
agent: subagentIdentity,
|
|
429
|
+
payload: {
|
|
430
|
+
runId,
|
|
431
|
+
swarmRunId,
|
|
432
|
+
subagentRunId,
|
|
433
|
+
personaId,
|
|
434
|
+
subagentIndex,
|
|
435
|
+
partitionCount: partitions.length,
|
|
436
|
+
fileCount: scopedFiles.length,
|
|
437
|
+
findings: findings.length,
|
|
438
|
+
summary: result?.summary || summarizeFindings(findings),
|
|
439
|
+
costUsd: result?.usage?.costUsd || 0,
|
|
440
|
+
durationMs: Date.now() - subagentStart,
|
|
441
|
+
},
|
|
442
|
+
usage: {
|
|
443
|
+
costUsd: result?.usage?.costUsd || 0,
|
|
444
|
+
durationMs: Date.now() - subagentStart,
|
|
445
|
+
toolCalls: result?.usage?.toolCalls || 0,
|
|
446
|
+
},
|
|
447
|
+
runId,
|
|
448
|
+
}));
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
return {
|
|
452
|
+
status: "ok",
|
|
453
|
+
subagentIndex,
|
|
454
|
+
agentId: subagentIdentity.id,
|
|
455
|
+
files: scopedFiles,
|
|
456
|
+
findings,
|
|
457
|
+
summary: result?.summary || summarizeFindings(findings),
|
|
458
|
+
costUsd: result?.usage?.costUsd || 0,
|
|
459
|
+
model: result?.model || model || null,
|
|
460
|
+
artifacts: result?.artifacts || null,
|
|
461
|
+
durationMs: Date.now() - subagentStart,
|
|
462
|
+
};
|
|
463
|
+
} catch (err) {
|
|
464
|
+
if (onEvent) {
|
|
465
|
+
onEvent(createAgentEvent({
|
|
466
|
+
event: "agent_error",
|
|
467
|
+
agent: subagentIdentity,
|
|
468
|
+
payload: {
|
|
469
|
+
runId,
|
|
470
|
+
swarmRunId,
|
|
471
|
+
subagentRunId,
|
|
472
|
+
personaId,
|
|
473
|
+
subagentIndex,
|
|
474
|
+
partitionCount: partitions.length,
|
|
475
|
+
error: err.message,
|
|
476
|
+
durationMs: Date.now() - subagentStart,
|
|
477
|
+
},
|
|
478
|
+
usage: {
|
|
479
|
+
costUsd: 0,
|
|
480
|
+
durationMs: Date.now() - subagentStart,
|
|
481
|
+
toolCalls: 0,
|
|
482
|
+
},
|
|
483
|
+
runId,
|
|
484
|
+
}));
|
|
485
|
+
}
|
|
486
|
+
return {
|
|
487
|
+
status: "error",
|
|
488
|
+
subagentIndex,
|
|
489
|
+
agentId: subagentIdentity.id,
|
|
490
|
+
files: scopedFiles,
|
|
491
|
+
findings: [],
|
|
492
|
+
summary: { P0: 0, P1: 0, P2: 0, P3: 0, blocking: false },
|
|
493
|
+
costUsd: 0,
|
|
494
|
+
error: err.message,
|
|
495
|
+
durationMs: Date.now() - subagentStart,
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
);
|
|
500
|
+
|
|
501
|
+
const settledSubagents = subagentResults.map((result) =>
|
|
502
|
+
result.status === "fulfilled"
|
|
503
|
+
? result.value
|
|
504
|
+
: {
|
|
505
|
+
status: "error",
|
|
506
|
+
subagentIndex: 0,
|
|
507
|
+
agentId: "unknown",
|
|
508
|
+
files: [],
|
|
509
|
+
findings: [],
|
|
510
|
+
summary: { P0: 0, P1: 0, P2: 0, P3: 0, blocking: false },
|
|
511
|
+
costUsd: 0,
|
|
512
|
+
error: result.reason?.message || "unknown",
|
|
513
|
+
durationMs: 0,
|
|
514
|
+
}
|
|
515
|
+
);
|
|
516
|
+
const findings = settledSubagents.flatMap((result) => result.findings || []);
|
|
517
|
+
const totalCostUsd = settledSubagents.reduce((sum, result) => sum + (result.costUsd || 0), 0);
|
|
518
|
+
const summary = summarizeFindings(findings);
|
|
519
|
+
const okCount = settledSubagents.filter((result) => result.status === "ok").length;
|
|
520
|
+
const errorCount = settledSubagents.filter((result) => result.status === "error").length;
|
|
521
|
+
|
|
522
|
+
if (onEvent) {
|
|
523
|
+
onEvent(createAgentEvent({
|
|
524
|
+
event: "swarm_complete",
|
|
525
|
+
agent: personaAgentFromIdentity(identity),
|
|
526
|
+
payload: {
|
|
527
|
+
runId,
|
|
528
|
+
swarmRunId,
|
|
529
|
+
personaId,
|
|
530
|
+
identity,
|
|
531
|
+
subagentCount: settledSubagents.length,
|
|
532
|
+
ok: okCount,
|
|
533
|
+
error: errorCount,
|
|
534
|
+
findings: findings.length,
|
|
535
|
+
summary,
|
|
536
|
+
totalCostUsd,
|
|
537
|
+
durationMs: Date.now() - startedAt,
|
|
538
|
+
blackboardEntries: blackboard.length,
|
|
539
|
+
},
|
|
540
|
+
usage: {
|
|
541
|
+
costUsd: totalCostUsd,
|
|
542
|
+
durationMs: Date.now() - startedAt,
|
|
543
|
+
toolCalls: settledSubagents.length,
|
|
544
|
+
},
|
|
545
|
+
runId,
|
|
546
|
+
}));
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
return {
|
|
550
|
+
personaId,
|
|
551
|
+
status: okCount > 0 ? "ok" : "error",
|
|
552
|
+
findings,
|
|
553
|
+
summary,
|
|
554
|
+
costUsd: totalCostUsd,
|
|
555
|
+
model: model || null,
|
|
556
|
+
durationMs: Date.now() - startedAt,
|
|
557
|
+
error: okCount > 0 ? null : settledSubagents.find((result) => result.error)?.error || null,
|
|
558
|
+
swarm: {
|
|
559
|
+
runId: swarmRunId,
|
|
560
|
+
decision,
|
|
561
|
+
subagentCount: settledSubagents.length,
|
|
562
|
+
ok: okCount,
|
|
563
|
+
error: errorCount,
|
|
564
|
+
partitionSizes: partitions.map((files) => files.length),
|
|
565
|
+
blackboardEntries: blackboard.length,
|
|
566
|
+
subagents: settledSubagents.map((result) => ({
|
|
567
|
+
id: result.agentId,
|
|
568
|
+
index: result.subagentIndex,
|
|
569
|
+
status: result.status,
|
|
570
|
+
files: result.files,
|
|
571
|
+
findings: (result.findings || []).length,
|
|
572
|
+
costUsd: result.costUsd || 0,
|
|
573
|
+
durationMs: result.durationMs || 0,
|
|
574
|
+
error: result.error || null,
|
|
575
|
+
artifacts: result.artifacts || null,
|
|
576
|
+
})),
|
|
577
|
+
},
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
|
|
70
581
|
/**
|
|
71
582
|
* Run the Omar Gate multi-persona orchestrator.
|
|
72
583
|
*
|
|
@@ -218,6 +729,62 @@ export async function runOmarGateOrchestrator({
|
|
|
218
729
|
}
|
|
219
730
|
|
|
220
731
|
try {
|
|
732
|
+
const swarmResult = await runOmarPersonaSwarm({
|
|
733
|
+
personaId,
|
|
734
|
+
identity,
|
|
735
|
+
targetPath,
|
|
736
|
+
mode,
|
|
737
|
+
runId,
|
|
738
|
+
deterministic,
|
|
739
|
+
outputDir,
|
|
740
|
+
provider,
|
|
741
|
+
model,
|
|
742
|
+
perPersonaCost,
|
|
743
|
+
dryRun,
|
|
744
|
+
onEvent,
|
|
745
|
+
});
|
|
746
|
+
|
|
747
|
+
if (swarmResult) {
|
|
748
|
+
const personaCost = swarmResult.costUsd || 0;
|
|
749
|
+
runningCostUsd += personaCost;
|
|
750
|
+
|
|
751
|
+
if (onEvent) {
|
|
752
|
+
if (swarmResult.status === "error") {
|
|
753
|
+
onEvent(createAgentEvent({
|
|
754
|
+
event: "persona_error",
|
|
755
|
+
agent: personaAgentFromIdentity(identity),
|
|
756
|
+
payload: {
|
|
757
|
+
personaId,
|
|
758
|
+
identity,
|
|
759
|
+
error: swarmResult.error || "all subagents failed",
|
|
760
|
+
swarm: swarmResult.swarm,
|
|
761
|
+
},
|
|
762
|
+
runId,
|
|
763
|
+
}));
|
|
764
|
+
} else {
|
|
765
|
+
onEvent(createAgentEvent({
|
|
766
|
+
event: "persona_complete",
|
|
767
|
+
agent: personaAgentFromIdentity(identity),
|
|
768
|
+
payload: {
|
|
769
|
+
personaId,
|
|
770
|
+
identity,
|
|
771
|
+
findings: swarmResult.findings.length,
|
|
772
|
+
summary: swarmResult.summary,
|
|
773
|
+
costUsd: personaCost,
|
|
774
|
+
durationMs: Date.now() - personaStart,
|
|
775
|
+
swarm: swarmResult.swarm,
|
|
776
|
+
},
|
|
777
|
+
runId,
|
|
778
|
+
}));
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
return {
|
|
783
|
+
...swarmResult,
|
|
784
|
+
durationMs: Date.now() - personaStart,
|
|
785
|
+
};
|
|
786
|
+
}
|
|
787
|
+
|
|
221
788
|
const systemPrompt = buildPersonaReviewPrompt({
|
|
222
789
|
personaId,
|
|
223
790
|
targetPath,
|
|
@@ -228,24 +795,34 @@ export async function runOmarGateOrchestrator({
|
|
|
228
795
|
targetPath,
|
|
229
796
|
mode: "full",
|
|
230
797
|
runId: `${runId}-${personaId}`,
|
|
231
|
-
runDirectory:
|
|
798
|
+
runDirectory: path.join(
|
|
799
|
+
deterministic?.artifacts?.runDirectory || path.join(targetPath, ".sentinelayer", "reviews", runId),
|
|
800
|
+
"personas",
|
|
801
|
+
personaId
|
|
802
|
+
),
|
|
232
803
|
deterministic: {
|
|
233
804
|
summary: detSummary,
|
|
234
805
|
findings: detFindings,
|
|
806
|
+
scope: deterministic?.scope || {},
|
|
807
|
+
layers: deterministic?.layers || {},
|
|
235
808
|
metadata: deterministic?.metadata || {},
|
|
236
809
|
},
|
|
237
810
|
outputDir,
|
|
238
811
|
provider: provider || undefined,
|
|
239
812
|
model: model || undefined,
|
|
240
813
|
maxCostUsd: perPersonaCost,
|
|
814
|
+
systemPrompt,
|
|
241
815
|
dryRun,
|
|
242
816
|
env: process.env,
|
|
243
817
|
});
|
|
244
818
|
|
|
819
|
+
const personaConfidenceFloor = omargateConfidenceFloorForPersona(personaId);
|
|
245
820
|
const findings = (result?.findings || []).map((f) => ({
|
|
246
821
|
...f,
|
|
247
822
|
persona: personaId,
|
|
248
823
|
layer: personaId,
|
|
824
|
+
confidenceFloor: personaConfidenceFloor,
|
|
825
|
+
personaConfidenceFloor,
|
|
249
826
|
}));
|
|
250
827
|
|
|
251
828
|
if (onEvent) {
|
|
@@ -296,6 +873,7 @@ export async function runOmarGateOrchestrator({
|
|
|
296
873
|
summary: result?.summary || { P0: 0, P1: 0, P2: 0, P3: 0 },
|
|
297
874
|
costUsd: personaCost,
|
|
298
875
|
model: result?.model || model || null,
|
|
876
|
+
artifacts: result?.artifacts || null,
|
|
299
877
|
durationMs: Date.now() - personaStart,
|
|
300
878
|
};
|
|
301
879
|
} catch (err) {
|
|
@@ -347,9 +925,17 @@ export async function runOmarGateOrchestrator({
|
|
|
347
925
|
const reconciled = reconcileReviewFindings({
|
|
348
926
|
deterministicFindings: detFindings,
|
|
349
927
|
aiFindings: allAiFindings,
|
|
928
|
+
defaultConfidenceFloor: OMARGATE_DEFAULT_CONFIDENCE_FLOOR,
|
|
929
|
+
confidenceFloors: OMARGATE_CONFIDENCE_FLOORS,
|
|
350
930
|
});
|
|
351
931
|
const reconciledFindings = reconciled.findings;
|
|
352
932
|
const reconciledSummary = reconciled.summary;
|
|
933
|
+
const droppedBelowConfidence = Number(reconciledSummary?.droppedBelowConfidence || 0);
|
|
934
|
+
const candidateFindingCount = detFindings.length + allAiFindings.length;
|
|
935
|
+
const dedupedCount = Math.max(
|
|
936
|
+
0,
|
|
937
|
+
candidateFindingCount - reconciledFindings.length - droppedBelowConfidence
|
|
938
|
+
);
|
|
353
939
|
|
|
354
940
|
const totalCost = settled.reduce((sum, r) => sum + (r.costUsd || 0), 0);
|
|
355
941
|
const totalDuration = Date.now() - startTime;
|
|
@@ -407,6 +993,8 @@ export async function runOmarGateOrchestrator({
|
|
|
407
993
|
durationMs: r.durationMs,
|
|
408
994
|
model: r.model || null,
|
|
409
995
|
error: r.error || null,
|
|
996
|
+
swarm: r.swarm || null,
|
|
997
|
+
artifacts: r.artifacts || null,
|
|
410
998
|
})),
|
|
411
999
|
personaHealth,
|
|
412
1000
|
findings: reconciledFindings,
|
|
@@ -414,6 +1002,7 @@ export async function runOmarGateOrchestrator({
|
|
|
414
1002
|
deterministic: detFindings.length,
|
|
415
1003
|
ai: allAiFindings.length,
|
|
416
1004
|
reconciled: reconciledFindings.length,
|
|
1005
|
+
droppedBelowConfidence,
|
|
417
1006
|
},
|
|
418
1007
|
summary: reconciledSummary,
|
|
419
1008
|
totalCostUsd: totalCost,
|
|
@@ -422,7 +1011,12 @@ export async function runOmarGateOrchestrator({
|
|
|
422
1011
|
deterministicFindings: detFindings.length,
|
|
423
1012
|
aiFindings: allAiFindings.length,
|
|
424
1013
|
reconciledFindings: reconciledFindings.length,
|
|
425
|
-
dedupedCount
|
|
1014
|
+
dedupedCount,
|
|
1015
|
+
droppedBelowConfidence,
|
|
1016
|
+
droppedLowConfidence: droppedBelowConfidence,
|
|
1017
|
+
droppedLowConfidenceSingleSource: Number(
|
|
1018
|
+
reconciledSummary?.droppedBelowConfidenceSingleSource || droppedBelowConfidence
|
|
1019
|
+
),
|
|
426
1020
|
multiSourceFindings: reconciledFindings.filter(
|
|
427
1021
|
(f) => Array.isArray(f.sources) && f.sources.length > 1
|
|
428
1022
|
).length,
|
|
@@ -485,6 +1079,13 @@ export async function runOmarGateOrchestrator({
|
|
|
485
1079
|
costUsd: r.costUsd || 0,
|
|
486
1080
|
durationMs: r.durationMs || 0,
|
|
487
1081
|
status: r.status,
|
|
1082
|
+
swarm: r.swarm
|
|
1083
|
+
? {
|
|
1084
|
+
subagentCount: r.swarm.subagentCount || 0,
|
|
1085
|
+
ok: r.swarm.ok || 0,
|
|
1086
|
+
error: r.swarm.error || 0,
|
|
1087
|
+
}
|
|
1088
|
+
: null,
|
|
488
1089
|
})),
|
|
489
1090
|
}).catch(() => {});
|
|
490
1091
|
|