sentinelayer-cli 0.8.0 → 0.8.2
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/README.md +23 -2
- package/package.json +4 -4
- package/src/agents/ai-governance/index.js +12 -0
- package/src/agents/ai-governance/tools/base.js +171 -0
- package/src/agents/ai-governance/tools/eval-regression.js +47 -0
- package/src/agents/ai-governance/tools/hitl-audit.js +81 -0
- package/src/agents/ai-governance/tools/index.js +52 -0
- package/src/agents/ai-governance/tools/prompt-drift.js +42 -0
- package/src/agents/ai-governance/tools/provenance-check.js +69 -0
- package/src/agents/backend/index.js +12 -0
- package/src/agents/backend/tools/base.js +189 -0
- package/src/agents/backend/tools/circuit-breaker-check.js +123 -0
- package/src/agents/backend/tools/idempotency-audit.js +105 -0
- package/src/agents/backend/tools/index.js +87 -0
- package/src/agents/backend/tools/retry-audit.js +132 -0
- package/src/agents/backend/tools/timeout-audit.js +144 -0
- package/src/agents/code-quality/index.js +12 -0
- package/src/agents/code-quality/tools/base.js +159 -0
- package/src/agents/code-quality/tools/complexity-measure.js +197 -0
- package/src/agents/code-quality/tools/coupling-analysis.js +81 -0
- package/src/agents/code-quality/tools/cycle-detect.js +49 -0
- package/src/agents/code-quality/tools/dep-graph.js +196 -0
- package/src/agents/code-quality/tools/index.js +89 -0
- package/src/agents/data-layer/index.js +12 -0
- package/src/agents/data-layer/tools/base.js +181 -0
- package/src/agents/data-layer/tools/index-audit.js +165 -0
- package/src/agents/data-layer/tools/index.js +83 -0
- package/src/agents/data-layer/tools/migration-scan.js +135 -0
- package/src/agents/data-layer/tools/query-explain.js +120 -0
- package/src/agents/data-layer/tools/tenancy-scan.js +166 -0
- package/src/agents/documentation/index.js +12 -0
- package/src/agents/documentation/tools/api-diff.js +91 -0
- package/src/agents/documentation/tools/base.js +151 -0
- package/src/agents/documentation/tools/dead-link-check.js +58 -0
- package/src/agents/documentation/tools/docstring-coverage.js +78 -0
- package/src/agents/documentation/tools/index.js +52 -0
- package/src/agents/documentation/tools/readme-freshness.js +61 -0
- package/src/agents/envelope/fix-cycle.js +45 -0
- package/src/agents/envelope/index.js +31 -0
- package/src/agents/envelope/loop.js +150 -0
- package/src/agents/envelope/pulse.js +18 -0
- package/src/agents/envelope/stream.js +40 -0
- package/src/agents/infrastructure/index.js +12 -0
- package/src/agents/infrastructure/tools/base.js +171 -0
- package/src/agents/infrastructure/tools/checkov-run.js +32 -0
- package/src/agents/infrastructure/tools/drift-detect.js +59 -0
- package/src/agents/infrastructure/tools/iam-least-priv-check.js +78 -0
- package/src/agents/infrastructure/tools/index.js +52 -0
- package/src/agents/infrastructure/tools/tflint-run.js +31 -0
- package/src/agents/jules/loop.js +7 -4
- package/src/agents/jules/swarm/sub-agent.js +5 -1
- package/src/agents/jules/tools/auth-audit.js +10 -1
- package/src/agents/mode.js +113 -0
- package/src/agents/observability/index.js +12 -0
- package/src/agents/observability/tools/alert-audit.js +39 -0
- package/src/agents/observability/tools/base.js +181 -0
- package/src/agents/observability/tools/dashboard-gap.js +42 -0
- package/src/agents/observability/tools/index.js +54 -0
- package/src/agents/observability/tools/log-schema-check.js +74 -0
- package/src/agents/observability/tools/span-coverage.js +74 -0
- package/src/agents/persona-visuals.js +38 -0
- package/src/agents/release/index.js +12 -0
- package/src/agents/release/tools/base.js +181 -0
- package/src/agents/release/tools/changelog-diff.js +86 -0
- package/src/agents/release/tools/feature-flag-audit.js +126 -0
- package/src/agents/release/tools/index.js +61 -0
- package/src/agents/release/tools/rollback-verify.js +129 -0
- package/src/agents/release/tools/semver-check.js +109 -0
- package/src/agents/reliability/index.js +12 -0
- package/src/agents/reliability/tools/backpressure-check.js +129 -0
- package/src/agents/reliability/tools/base.js +181 -0
- package/src/agents/reliability/tools/chaos-probe.js +109 -0
- package/src/agents/reliability/tools/graceful-degradation-check.js +114 -0
- package/src/agents/reliability/tools/health-check-audit.js +111 -0
- package/src/agents/reliability/tools/index.js +87 -0
- package/src/agents/run-persona.js +109 -0
- package/src/agents/security/index.js +12 -0
- package/src/agents/security/tools/authz-audit.js +134 -0
- package/src/agents/security/tools/base.js +190 -0
- package/src/agents/security/tools/crypto-review.js +175 -0
- package/src/agents/security/tools/index.js +97 -0
- package/src/agents/security/tools/sast-scan.js +175 -0
- package/src/agents/security/tools/secrets-scan.js +216 -0
- package/src/agents/supply-chain/index.js +12 -0
- package/src/agents/supply-chain/tools/attestation-check.js +42 -0
- package/src/agents/supply-chain/tools/base.js +151 -0
- package/src/agents/supply-chain/tools/index.js +52 -0
- package/src/agents/supply-chain/tools/lockfile-integrity.js +73 -0
- package/src/agents/supply-chain/tools/package-verify.js +56 -0
- package/src/agents/supply-chain/tools/sbom-diff.js +34 -0
- package/src/agents/testing/index.js +12 -0
- package/src/agents/testing/tools/base.js +202 -0
- package/src/agents/testing/tools/coverage-gap.js +144 -0
- package/src/agents/testing/tools/flake-detect.js +125 -0
- package/src/agents/testing/tools/index.js +85 -0
- package/src/agents/testing/tools/mutation-test.js +143 -0
- package/src/agents/testing/tools/snapshot-diff.js +103 -0
- package/src/auth/gate.js +65 -37
- package/src/cli.js +1 -1
- package/src/commands/chat.js +3 -10
- package/src/commands/legacy-args.js +10 -0
- package/src/commands/omargate.js +36 -2
- package/src/commands/persona.js +46 -1
- package/src/commands/scan.js +3 -10
- package/src/commands/session.js +654 -6
- package/src/commands/spec.js +3 -10
- package/src/coord/events-log.js +141 -0
- package/src/coord/handshake.js +719 -0
- package/src/coord/index.js +35 -0
- package/src/coord/paths.js +84 -0
- package/src/coord/priority.js +62 -0
- package/src/coord/tarjan.js +157 -0
- package/src/cost/tokenizer.js +160 -0
- package/src/cost/tracker.js +61 -0
- package/src/daemon/artifact-lineage.js +362 -0
- package/src/daemon/assignment-ledger.js +117 -0
- package/src/daemon/ast-drift.js +496 -0
- package/src/daemon/ingest-refresh.js +69 -2
- package/src/ingest/engine.js +15 -0
- package/src/ingest/ownership.js +380 -0
- package/src/legacy-cli.js +68 -1
- package/src/orchestrator/kai-chen.js +126 -0
- package/src/review/ai-review.js +3 -10
- package/src/review/compliance-pack.js +389 -0
- package/src/review/investor-dd-config.js +54 -0
- package/src/review/investor-dd-file-loop.js +303 -0
- package/src/review/investor-dd-file-router.js +406 -0
- package/src/review/investor-dd-html-report.js +233 -0
- package/src/review/investor-dd-notification.js +120 -0
- package/src/review/investor-dd-orchestrator.js +405 -0
- package/src/review/investor-dd-persona-runner.js +275 -0
- package/src/review/live-validator.js +253 -0
- package/src/review/omargate-orchestrator.js +90 -2
- package/src/review/persona-prompts.js +244 -56
- package/src/review/reconciliation-rules.js +329 -0
- package/src/review/reproducibility-chain.js +136 -0
- package/src/review/scan-modes.js +102 -3
- package/src/session/agent-registry.js +7 -0
- package/src/session/analytics.js +479 -0
- package/src/session/daemon.js +609 -14
- package/src/session/file-locks.js +666 -0
- package/src/session/paths.js +4 -0
- package/src/session/recap.js +567 -0
- package/src/session/redact.js +82 -0
- package/src/session/runtime-bridge.js +24 -1
- package/src/session/scoring.js +406 -0
- package/src/session/setup-guides.js +304 -0
- package/src/session/store.js +318 -2
- package/src/session/stream.js +9 -1
- package/src/session/sync.js +753 -0
- package/src/session/tasks.js +1054 -0
- package/src/session/templates.js +188 -0
- package/src/swarm/runtime.js +1 -8
|
@@ -0,0 +1,479 @@
|
|
|
1
|
+
import fsp from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import process from "node:process";
|
|
4
|
+
|
|
5
|
+
import { loadCostHistory } from "../cost/history.js";
|
|
6
|
+
import { resolveOutputRoot } from "../config/service.js";
|
|
7
|
+
import { loadRunEvents } from "../telemetry/ledger.js";
|
|
8
|
+
import { resolveSessionPaths } from "./paths.js";
|
|
9
|
+
import { readStream } from "./stream.js";
|
|
10
|
+
|
|
11
|
+
function normalizeString(value) {
|
|
12
|
+
return String(value || "").trim();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function normalizeIsoTimestamp(value, fallbackIso = new Date().toISOString()) {
|
|
16
|
+
const normalized = normalizeString(value);
|
|
17
|
+
if (!normalized) {
|
|
18
|
+
return fallbackIso;
|
|
19
|
+
}
|
|
20
|
+
const epoch = Date.parse(normalized);
|
|
21
|
+
if (!Number.isFinite(epoch)) {
|
|
22
|
+
return fallbackIso;
|
|
23
|
+
}
|
|
24
|
+
return new Date(epoch).toISOString();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function normalizeNonNegativeNumber(value, fallbackValue = 0) {
|
|
28
|
+
const normalized = Number(value);
|
|
29
|
+
if (!Number.isFinite(normalized) || normalized < 0) {
|
|
30
|
+
return fallbackValue;
|
|
31
|
+
}
|
|
32
|
+
return normalized;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function normalizeRate(value) {
|
|
36
|
+
const normalized = Number(value);
|
|
37
|
+
if (!Number.isFinite(normalized) || normalized < 0) {
|
|
38
|
+
return 0;
|
|
39
|
+
}
|
|
40
|
+
return Math.max(0, Math.min(1, Number(normalized.toFixed(4))));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function normalizeSessionMetadata(raw = {}, nowIso = new Date().toISOString()) {
|
|
44
|
+
const source = raw && typeof raw === "object" && !Array.isArray(raw) ? raw : {};
|
|
45
|
+
return {
|
|
46
|
+
sessionId: normalizeString(source.sessionId),
|
|
47
|
+
createdAt: normalizeIsoTimestamp(source.createdAt, nowIso),
|
|
48
|
+
lastInteractionAt: normalizeIsoTimestamp(source.lastInteractionAt, nowIso),
|
|
49
|
+
renewalCount: Math.max(0, Number(source.renewalCount || 0)),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function readJsonFileOptional(filePath) {
|
|
54
|
+
try {
|
|
55
|
+
const raw = await fsp.readFile(filePath, "utf-8");
|
|
56
|
+
return JSON.parse(raw);
|
|
57
|
+
} catch (error) {
|
|
58
|
+
if (error && typeof error === "object" && error.code === "ENOENT") {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function toEpoch(value, fallbackIso = new Date().toISOString()) {
|
|
66
|
+
return Date.parse(normalizeIsoTimestamp(value, fallbackIso)) || 0;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function buildInitialFindingsSummary() {
|
|
70
|
+
return {
|
|
71
|
+
P0: 0,
|
|
72
|
+
P1: 0,
|
|
73
|
+
P2: 0,
|
|
74
|
+
P3: 0,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function incrementFinding(summary, severity) {
|
|
79
|
+
const normalized = normalizeString(severity).toUpperCase();
|
|
80
|
+
if (!Object.prototype.hasOwnProperty.call(summary, normalized)) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
summary[normalized] += 1;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function extractFindingSeverity(message = "") {
|
|
87
|
+
const normalized = normalizeString(message);
|
|
88
|
+
if (!normalized) {
|
|
89
|
+
return "";
|
|
90
|
+
}
|
|
91
|
+
const findingMatch = /finding\s*:\s*\[(P[0-3])\]/i.exec(normalized);
|
|
92
|
+
if (findingMatch) {
|
|
93
|
+
return normalizeString(findingMatch[1]).toUpperCase();
|
|
94
|
+
}
|
|
95
|
+
const bracketSeverityMatch = /\[(P[0-3])\]/i.exec(normalized);
|
|
96
|
+
if (bracketSeverityMatch) {
|
|
97
|
+
return normalizeString(bracketSeverityMatch[1]).toUpperCase();
|
|
98
|
+
}
|
|
99
|
+
return "";
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function summarizeResponseLatencyMs(events = []) {
|
|
103
|
+
const messageEvents = events
|
|
104
|
+
.filter((event) => normalizeString(event.event) === "session_message")
|
|
105
|
+
.sort((left, right) => toEpoch(left.ts) - toEpoch(right.ts));
|
|
106
|
+
if (messageEvents.length < 2) {
|
|
107
|
+
return 0;
|
|
108
|
+
}
|
|
109
|
+
const deltas = [];
|
|
110
|
+
for (let index = 1; index < messageEvents.length; index += 1) {
|
|
111
|
+
const previous = messageEvents[index - 1];
|
|
112
|
+
const current = messageEvents[index];
|
|
113
|
+
const previousAgent = normalizeString(previous.agent?.id || previous.agentId);
|
|
114
|
+
const currentAgent = normalizeString(current.agent?.id || current.agentId);
|
|
115
|
+
if (!previousAgent || !currentAgent || previousAgent === currentAgent) {
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
const deltaMs = toEpoch(current.ts) - toEpoch(previous.ts);
|
|
119
|
+
if (Number.isFinite(deltaMs) && deltaMs >= 0) {
|
|
120
|
+
deltas.push(deltaMs);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
if (deltas.length === 0) {
|
|
124
|
+
return 0;
|
|
125
|
+
}
|
|
126
|
+
const sum = deltas.reduce((accumulator, value) => accumulator + value, 0);
|
|
127
|
+
return Math.round(sum / deltas.length);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function summarizeTaskMetrics(events = [], registryTasks = []) {
|
|
131
|
+
const taskAssignedEvents = events.filter((event) => normalizeString(event.event) === "task_assign");
|
|
132
|
+
const taskCompletedEvents = events.filter((event) => normalizeString(event.event) === "task_completed");
|
|
133
|
+
const fallbackAssignedCount = Array.isArray(registryTasks) ? registryTasks.length : 0;
|
|
134
|
+
const fallbackCompletedCount = Array.isArray(registryTasks)
|
|
135
|
+
? registryTasks.filter((task) => normalizeString(task.status).toUpperCase() === "COMPLETED").length
|
|
136
|
+
: 0;
|
|
137
|
+
|
|
138
|
+
const tasksAssigned = Math.max(taskAssignedEvents.length, fallbackAssignedCount);
|
|
139
|
+
const tasksCompleted = Math.max(taskCompletedEvents.length, fallbackCompletedCount);
|
|
140
|
+
|
|
141
|
+
let handoffsSuccessful = 0;
|
|
142
|
+
for (const event of taskCompletedEvents) {
|
|
143
|
+
const payload = event && typeof event.payload === "object" ? event.payload : {};
|
|
144
|
+
const fromAgent = normalizeString(payload.from);
|
|
145
|
+
const toAgent = normalizeString(payload.to);
|
|
146
|
+
if (fromAgent && toAgent && fromAgent !== toAgent) {
|
|
147
|
+
handoffsSuccessful += 1;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
if (handoffsSuccessful === 0 && Array.isArray(registryTasks)) {
|
|
151
|
+
handoffsSuccessful = registryTasks.filter((task) => {
|
|
152
|
+
const fromAgent = normalizeString(task.fromAgentId);
|
|
153
|
+
const toAgent = normalizeString(task.toAgentId);
|
|
154
|
+
const status = normalizeString(task.status).toUpperCase();
|
|
155
|
+
return status === "COMPLETED" && fromAgent && toAgent && fromAgent !== toAgent;
|
|
156
|
+
}).length;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return {
|
|
160
|
+
tasksAssigned,
|
|
161
|
+
tasksCompleted,
|
|
162
|
+
handoffsSuccessful,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function summarizeHitlMetrics(events = []) {
|
|
167
|
+
const hitlEvents = events.filter((event) => {
|
|
168
|
+
const eventName = normalizeString(event.event).toLowerCase();
|
|
169
|
+
const payload = event && typeof event.payload === "object" ? event.payload : {};
|
|
170
|
+
const channel = normalizeString(payload.channel).toLowerCase();
|
|
171
|
+
return eventName.includes("hitl") || channel === "hitl";
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
let humanOverrides = 0;
|
|
175
|
+
let disagreements = 0;
|
|
176
|
+
for (const event of hitlEvents) {
|
|
177
|
+
const payload = event && typeof event.payload === "object" ? event.payload : {};
|
|
178
|
+
const verdict = normalizeString(payload.verdict || payload.action).toLowerCase();
|
|
179
|
+
const humanVerdict = normalizeString(payload.humanVerdict).toLowerCase();
|
|
180
|
+
const modelVerdict = normalizeString(payload.modelVerdict).toLowerCase();
|
|
181
|
+
if (
|
|
182
|
+
payload.override === true ||
|
|
183
|
+
verdict === "override" ||
|
|
184
|
+
verdict === "reject" ||
|
|
185
|
+
verdict === "escalate"
|
|
186
|
+
) {
|
|
187
|
+
humanOverrides += 1;
|
|
188
|
+
}
|
|
189
|
+
if (
|
|
190
|
+
payload.disagreement === true ||
|
|
191
|
+
(humanVerdict && modelVerdict && humanVerdict !== modelVerdict)
|
|
192
|
+
) {
|
|
193
|
+
disagreements += 1;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const denominator = hitlEvents.length;
|
|
198
|
+
return {
|
|
199
|
+
humanOverrideRate: denominator > 0 ? humanOverrides / denominator : 0,
|
|
200
|
+
hitlDisagreementRate: denominator > 0 ? disagreements / denominator : 0,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function summarizeEvalRegressionRate(events = []) {
|
|
205
|
+
const evalEvents = events.filter((event) => normalizeString(event.event).toLowerCase().includes("eval"));
|
|
206
|
+
if (evalEvents.length === 0) {
|
|
207
|
+
return 0;
|
|
208
|
+
}
|
|
209
|
+
const regressions = evalEvents.filter((event) => {
|
|
210
|
+
const payload = event && typeof event.payload === "object" ? event.payload : {};
|
|
211
|
+
if (payload.regression === true) {
|
|
212
|
+
return true;
|
|
213
|
+
}
|
|
214
|
+
const status = normalizeString(payload.status || payload.result).toLowerCase();
|
|
215
|
+
return status.includes("regress");
|
|
216
|
+
}).length;
|
|
217
|
+
return regressions / evalEvents.length;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
async function walkDirectory(filePath, visitor) {
|
|
221
|
+
let entries = [];
|
|
222
|
+
try {
|
|
223
|
+
entries = await fsp.readdir(filePath, { withFileTypes: true });
|
|
224
|
+
} catch (error) {
|
|
225
|
+
if (error && typeof error === "object" && error.code === "ENOENT") {
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
throw error;
|
|
229
|
+
}
|
|
230
|
+
for (const entry of entries) {
|
|
231
|
+
const entryPath = path.join(filePath, entry.name);
|
|
232
|
+
if (entry.isDirectory()) {
|
|
233
|
+
await walkDirectory(entryPath, visitor);
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
if (entry.isFile()) {
|
|
237
|
+
await visitor(entryPath);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
async function loadCloseoutArtifactsForSession({
|
|
243
|
+
targetPath,
|
|
244
|
+
outputDir = "",
|
|
245
|
+
env,
|
|
246
|
+
homeDir,
|
|
247
|
+
sessionId,
|
|
248
|
+
} = {}) {
|
|
249
|
+
const outputRoot = await resolveOutputRoot({
|
|
250
|
+
cwd: path.resolve(String(targetPath || ".")),
|
|
251
|
+
outputDirOverride: outputDir,
|
|
252
|
+
env,
|
|
253
|
+
homeDir,
|
|
254
|
+
});
|
|
255
|
+
const observabilityRoot = path.join(outputRoot, "observability");
|
|
256
|
+
const closeouts = [];
|
|
257
|
+
|
|
258
|
+
await walkDirectory(observabilityRoot, async (candidatePath) => {
|
|
259
|
+
if (path.basename(candidatePath).toLowerCase() !== "closeout.json") {
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
const payload = await readJsonFileOptional(candidatePath);
|
|
263
|
+
if (!payload || typeof payload !== "object") {
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
if (normalizeString(payload.sessionId) !== normalizeString(sessionId)) {
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
closeouts.push(payload);
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
return closeouts;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function summarizeProvenanceCoverage(closeouts = []) {
|
|
276
|
+
if (!Array.isArray(closeouts) || closeouts.length === 0) {
|
|
277
|
+
return {
|
|
278
|
+
reproducibilitySuccessRate: 1,
|
|
279
|
+
provenanceAttestationCoverage: 0,
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
const reproducibleCount = closeouts.filter((closeout) => closeout.chainVerified !== false).length;
|
|
283
|
+
const attestedCount = closeouts.filter((closeout) => {
|
|
284
|
+
const hasCosignRef = Boolean(normalizeString(closeout.cosignAttestationRef));
|
|
285
|
+
const hasSbomRef = Boolean(normalizeString(closeout.sbomRef));
|
|
286
|
+
return hasCosignRef || hasSbomRef;
|
|
287
|
+
}).length;
|
|
288
|
+
return {
|
|
289
|
+
reproducibilitySuccessRate: reproducibleCount / closeouts.length,
|
|
290
|
+
provenanceAttestationCoverage: attestedCount / closeouts.length,
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function summarizeCostFromStream(events = []) {
|
|
295
|
+
return events.reduce((sum, event) => {
|
|
296
|
+
if (normalizeString(event.event) !== "model_span") {
|
|
297
|
+
return sum;
|
|
298
|
+
}
|
|
299
|
+
const payload = event && typeof event.payload === "object" ? event.payload : {};
|
|
300
|
+
return sum + normalizeNonNegativeNumber(payload.costUsd, 0);
|
|
301
|
+
}, 0);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
export async function computeSessionAnalytics(
|
|
305
|
+
sessionId,
|
|
306
|
+
{
|
|
307
|
+
targetPath = process.cwd(),
|
|
308
|
+
outputDir = "",
|
|
309
|
+
env,
|
|
310
|
+
homeDir,
|
|
311
|
+
nowIso = new Date().toISOString(),
|
|
312
|
+
} = {}
|
|
313
|
+
) {
|
|
314
|
+
const normalizedSessionId = normalizeString(sessionId);
|
|
315
|
+
if (!normalizedSessionId) {
|
|
316
|
+
throw new Error("sessionId is required.");
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const normalizedTargetPath = path.resolve(String(targetPath || "."));
|
|
320
|
+
const paths = resolveSessionPaths(normalizedSessionId, {
|
|
321
|
+
targetPath: normalizedTargetPath,
|
|
322
|
+
});
|
|
323
|
+
const rawMetadata = await readJsonFileOptional(paths.metadataPath);
|
|
324
|
+
if (!rawMetadata || typeof rawMetadata !== "object") {
|
|
325
|
+
throw new Error(`Session '${normalizedSessionId}' was not found.`);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const metadata = normalizeSessionMetadata(rawMetadata, nowIso);
|
|
329
|
+
const [events, rawTaskRegistry, costHistoryResult, runEventsResult, closeouts] = await Promise.all([
|
|
330
|
+
readStream(normalizedSessionId, {
|
|
331
|
+
targetPath: normalizedTargetPath,
|
|
332
|
+
tail: 0,
|
|
333
|
+
}),
|
|
334
|
+
readJsonFileOptional(paths.tasksPath),
|
|
335
|
+
loadCostHistory({
|
|
336
|
+
targetPath: normalizedTargetPath,
|
|
337
|
+
outputDirOverride: outputDir,
|
|
338
|
+
env,
|
|
339
|
+
homeDir,
|
|
340
|
+
}),
|
|
341
|
+
loadRunEvents({
|
|
342
|
+
targetPath: normalizedTargetPath,
|
|
343
|
+
outputDirOverride: outputDir,
|
|
344
|
+
env,
|
|
345
|
+
homeDir,
|
|
346
|
+
}),
|
|
347
|
+
loadCloseoutArtifactsForSession({
|
|
348
|
+
targetPath: normalizedTargetPath,
|
|
349
|
+
outputDir,
|
|
350
|
+
env,
|
|
351
|
+
homeDir,
|
|
352
|
+
sessionId: normalizedSessionId,
|
|
353
|
+
}),
|
|
354
|
+
]);
|
|
355
|
+
|
|
356
|
+
const normalizedEvents = Array.isArray(events) ? events : [];
|
|
357
|
+
const findingSummary = buildInitialFindingsSummary();
|
|
358
|
+
const uniqueAgents = new Set();
|
|
359
|
+
let totalMessages = 0;
|
|
360
|
+
let fileLockDeniedCount = 0;
|
|
361
|
+
let fileLockEvents = 0;
|
|
362
|
+
let stuckRecoveries = 0;
|
|
363
|
+
let productiveEvents = 0;
|
|
364
|
+
let idleEvents = 0;
|
|
365
|
+
|
|
366
|
+
for (const event of normalizedEvents) {
|
|
367
|
+
const eventName = normalizeString(event.event);
|
|
368
|
+
const payload = event && typeof event.payload === "object" ? event.payload : {};
|
|
369
|
+
const agentId = normalizeString(event.agent?.id || event.agentId);
|
|
370
|
+
if (agentId) {
|
|
371
|
+
uniqueAgents.add(agentId);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
if (eventName === "session_message") {
|
|
375
|
+
totalMessages += 1;
|
|
376
|
+
productiveEvents += 1;
|
|
377
|
+
incrementFinding(findingSummary, extractFindingSeverity(payload.message));
|
|
378
|
+
continue;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
if (eventName === "task_assign" || eventName === "task_accepted" || eventName === "task_completed") {
|
|
382
|
+
productiveEvents += 1;
|
|
383
|
+
continue;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
if (eventName === "file_lock" || eventName === "file_unlock") {
|
|
387
|
+
fileLockEvents += eventName === "file_lock" ? 1 : 0;
|
|
388
|
+
productiveEvents += 1;
|
|
389
|
+
continue;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
if (eventName === "help_response" || eventName === "runtime_run_heartbeat") {
|
|
393
|
+
productiveEvents += 1;
|
|
394
|
+
continue;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
if (eventName === "daemon_alert") {
|
|
398
|
+
const alert = normalizeString(payload.alert).toLowerCase();
|
|
399
|
+
if (alert === "file_lock_denied") {
|
|
400
|
+
fileLockDeniedCount += 1;
|
|
401
|
+
productiveEvents += 1;
|
|
402
|
+
continue;
|
|
403
|
+
}
|
|
404
|
+
if (alert === "stuck_recovered") {
|
|
405
|
+
stuckRecoveries += 1;
|
|
406
|
+
productiveEvents += 1;
|
|
407
|
+
continue;
|
|
408
|
+
}
|
|
409
|
+
if (alert === "stuck_detected") {
|
|
410
|
+
idleEvents += 1;
|
|
411
|
+
continue;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
const registryTasks =
|
|
417
|
+
rawTaskRegistry &&
|
|
418
|
+
typeof rawTaskRegistry === "object" &&
|
|
419
|
+
Array.isArray(rawTaskRegistry.tasks)
|
|
420
|
+
? rawTaskRegistry.tasks
|
|
421
|
+
: [];
|
|
422
|
+
const taskMetrics = summarizeTaskMetrics(normalizedEvents, registryTasks);
|
|
423
|
+
const hitlMetrics = summarizeHitlMetrics(normalizedEvents);
|
|
424
|
+
const provenanceMetrics = summarizeProvenanceCoverage(closeouts);
|
|
425
|
+
|
|
426
|
+
const streamCostUsd = summarizeCostFromStream(normalizedEvents);
|
|
427
|
+
const historyEntries = Array.isArray(costHistoryResult?.history?.entries)
|
|
428
|
+
? costHistoryResult.history.entries
|
|
429
|
+
: [];
|
|
430
|
+
const costHistoryUsd = historyEntries
|
|
431
|
+
.filter((entry) => normalizeString(entry.sessionId) === normalizedSessionId)
|
|
432
|
+
.reduce((sum, entry) => sum + normalizeNonNegativeNumber(entry.costUsd, 0), 0);
|
|
433
|
+
const runEvents = Array.isArray(runEventsResult?.events) ? runEventsResult.events : [];
|
|
434
|
+
const telemetryCostUsd = runEvents
|
|
435
|
+
.filter((event) => normalizeString(event.sessionId) === normalizedSessionId)
|
|
436
|
+
.filter((event) => normalizeString(event.eventType) === "usage")
|
|
437
|
+
.reduce((sum, event) => {
|
|
438
|
+
const usage = event && typeof event.usage === "object" ? event.usage : {};
|
|
439
|
+
return sum + normalizeNonNegativeNumber(usage.costUsd, 0);
|
|
440
|
+
}, 0);
|
|
441
|
+
const totalCostUsd = Number(
|
|
442
|
+
Math.max(streamCostUsd, costHistoryUsd, telemetryCostUsd).toFixed(6)
|
|
443
|
+
);
|
|
444
|
+
|
|
445
|
+
const denominator = productiveEvents + idleEvents;
|
|
446
|
+
const coordinationEfficiency = denominator > 0 ? productiveEvents / denominator : 0;
|
|
447
|
+
const createdEpoch = toEpoch(metadata.createdAt, nowIso);
|
|
448
|
+
const terminalIso = normalizeIsoTimestamp(
|
|
449
|
+
metadata.lastInteractionAt || nowIso,
|
|
450
|
+
nowIso
|
|
451
|
+
);
|
|
452
|
+
const terminalEpoch = Math.max(toEpoch(terminalIso, nowIso), createdEpoch);
|
|
453
|
+
const elapsedHours = Number(((terminalEpoch - createdEpoch) / (60 * 60 * 1000)).toFixed(4));
|
|
454
|
+
|
|
455
|
+
const fixPlanUsefulnessScore =
|
|
456
|
+
taskMetrics.tasksAssigned > 0 ? taskMetrics.tasksCompleted / taskMetrics.tasksAssigned : 0;
|
|
457
|
+
|
|
458
|
+
return {
|
|
459
|
+
totalMessages,
|
|
460
|
+
uniqueAgents: uniqueAgents.size,
|
|
461
|
+
totalFindings: findingSummary,
|
|
462
|
+
conflictsPrevented: fileLockDeniedCount + fileLockEvents,
|
|
463
|
+
tasksAssigned: taskMetrics.tasksAssigned,
|
|
464
|
+
tasksCompleted: taskMetrics.tasksCompleted,
|
|
465
|
+
handoffsSuccessful: taskMetrics.handoffsSuccessful,
|
|
466
|
+
avgResponseTimeMs: summarizeResponseLatencyMs(normalizedEvents),
|
|
467
|
+
stuckRecoveries,
|
|
468
|
+
totalCostUsd,
|
|
469
|
+
coordinationEfficiency: normalizeRate(coordinationEfficiency),
|
|
470
|
+
elapsedHours: Math.max(0, elapsedHours),
|
|
471
|
+
renewalCount: metadata.renewalCount,
|
|
472
|
+
humanOverrideRate: normalizeRate(hitlMetrics.humanOverrideRate),
|
|
473
|
+
hitlDisagreementRate: normalizeRate(hitlMetrics.hitlDisagreementRate),
|
|
474
|
+
reproducibilitySuccessRate: normalizeRate(provenanceMetrics.reproducibilitySuccessRate),
|
|
475
|
+
fixPlanUsefulnessScore: normalizeRate(fixPlanUsefulnessScore),
|
|
476
|
+
evalRegressionRate: normalizeRate(summarizeEvalRegressionRate(normalizedEvents)),
|
|
477
|
+
provenanceAttestationCoverage: normalizeRate(provenanceMetrics.provenanceAttestationCoverage),
|
|
478
|
+
};
|
|
479
|
+
}
|