sentinelayer-cli 0.16.0 → 0.17.1
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 +16 -6
- package/package.json +3 -2
- package/src/commands/legacy-args.js +1 -0
- package/src/commands/omargate.js +1 -0
- package/src/commands/session.js +322 -25
- package/src/events/schema.js +21 -0
- package/src/legacy-cli.js +16 -0
- package/src/review/investor-dd-devtestbot.js +83 -8
- package/src/review/investor-dd-file-loop.js +83 -6
- package/src/review/investor-dd-orchestrator.js +42 -1
- package/src/review/investor-dd-progress.js +351 -0
- package/src/review/investor-dd-usage.js +227 -0
- package/src/session/daemon.js +341 -2
- package/src/session/recap.js +288 -69
- package/src/session/sync.js +1 -4
|
@@ -14,6 +14,12 @@ import crypto from "node:crypto";
|
|
|
14
14
|
import { recordProvisionedIdentity } from "../ai/identity-store.js";
|
|
15
15
|
import { runDevTestBotSession } from "../agents/devtestbot/tool.js";
|
|
16
16
|
import { checkBudget } from "./investor-dd-file-loop.js";
|
|
17
|
+
import {
|
|
18
|
+
INVESTOR_DD_USAGE_ACTIONS,
|
|
19
|
+
assertInvestorDdUsageContextReady,
|
|
20
|
+
isInvestorDdUsageLedgerError,
|
|
21
|
+
recordInvestorDdLlmUsage,
|
|
22
|
+
} from "./investor-dd-usage.js";
|
|
17
23
|
|
|
18
24
|
export const DEVTESTBOT_PHASE_MAX_CONCURRENT = 4;
|
|
19
25
|
export const DEVTESTBOT_PHASE_DEFAULT_SCOPE = "smoke";
|
|
@@ -80,23 +86,69 @@ function buildPlannerPrompt({ rootPath, files = [], findings = [], budget = {} }
|
|
|
80
86
|
].join("\n");
|
|
81
87
|
}
|
|
82
88
|
|
|
83
|
-
async function callPlannerClient({ plannerClient, rootPath, files, findings, budget }) {
|
|
84
|
-
if (!plannerClient) return {};
|
|
89
|
+
async function callPlannerClient({ plannerClient, rootPath, files, findings, budget, sessionUsage }) {
|
|
90
|
+
if (!plannerClient) return { planned: {}, usageLedger: null };
|
|
85
91
|
const prompt = buildPlannerPrompt({ rootPath, files, findings, budget });
|
|
86
92
|
if (typeof plannerClient.decideDevTestBotPhase === "function") {
|
|
87
|
-
return
|
|
93
|
+
return {
|
|
94
|
+
planned: await plannerClient.decideDevTestBotPhase({ rootPath, files, findings, budget, prompt }),
|
|
95
|
+
usageLedger: null,
|
|
96
|
+
};
|
|
88
97
|
}
|
|
98
|
+
if (sessionUsage) {
|
|
99
|
+
assertInvestorDdUsageContextReady({
|
|
100
|
+
usageContext: sessionUsage,
|
|
101
|
+
action: INVESTOR_DD_USAGE_ACTIONS.devTestBotPlanner,
|
|
102
|
+
agentId: "investor-dd-devtestbot-planner",
|
|
103
|
+
targetPath: rootPath,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
const startedAtIso = new Date().toISOString();
|
|
89
107
|
if (typeof plannerClient.invoke === "function") {
|
|
90
108
|
const response = await plannerClient.invoke({ prompt, stream: false });
|
|
91
|
-
|
|
109
|
+
const usageLedger = sessionUsage
|
|
110
|
+
? await recordInvestorDdLlmUsage({
|
|
111
|
+
usageContext: sessionUsage,
|
|
112
|
+
action: INVESTOR_DD_USAGE_ACTIONS.devTestBotPlanner,
|
|
113
|
+
agentId: "investor-dd-devtestbot-planner",
|
|
114
|
+
phase: "devtestbot_planner",
|
|
115
|
+
prompt,
|
|
116
|
+
response,
|
|
117
|
+
model: response?.model,
|
|
118
|
+
provider: response?.provider,
|
|
119
|
+
startedAtIso,
|
|
120
|
+
targetPath: rootPath,
|
|
121
|
+
metadata: {
|
|
122
|
+
plannerClient: "invoke",
|
|
123
|
+
},
|
|
124
|
+
})
|
|
125
|
+
: null;
|
|
126
|
+
return { planned: parsePlannerJson(response?.text || response), usageLedger };
|
|
92
127
|
}
|
|
93
128
|
if (typeof plannerClient.generatePlan === "function") {
|
|
94
129
|
const response = await plannerClient.generatePlan([{ role: "user", content: prompt }], {
|
|
95
130
|
phase: "devtestbot",
|
|
96
131
|
});
|
|
97
|
-
|
|
132
|
+
const usageLedger = sessionUsage
|
|
133
|
+
? await recordInvestorDdLlmUsage({
|
|
134
|
+
usageContext: sessionUsage,
|
|
135
|
+
action: INVESTOR_DD_USAGE_ACTIONS.devTestBotPlanner,
|
|
136
|
+
agentId: "investor-dd-devtestbot-planner",
|
|
137
|
+
phase: "devtestbot_planner",
|
|
138
|
+
messages: [{ role: "user", content: prompt }],
|
|
139
|
+
response,
|
|
140
|
+
model: response?.model,
|
|
141
|
+
provider: response?.provider,
|
|
142
|
+
startedAtIso,
|
|
143
|
+
targetPath: rootPath,
|
|
144
|
+
metadata: {
|
|
145
|
+
plannerClient: "generatePlan",
|
|
146
|
+
},
|
|
147
|
+
})
|
|
148
|
+
: null;
|
|
149
|
+
return { planned: parsePlannerJson(response?.text || response?.content || response), usageLedger };
|
|
98
150
|
}
|
|
99
|
-
return {};
|
|
151
|
+
return { planned: {}, usageLedger: null };
|
|
100
152
|
}
|
|
101
153
|
|
|
102
154
|
function chooseScope({ requestedScope, files = [], findings = [], plannedScope }) {
|
|
@@ -135,6 +187,7 @@ function normalizePhaseOptions(options = {}) {
|
|
|
135
187
|
runner: source.runner || null,
|
|
136
188
|
provisionIdentity: source.provisionIdentity || null,
|
|
137
189
|
maxConcurrentAgents: source.maxConcurrentAgents,
|
|
190
|
+
sessionUsage: source.sessionUsage || null,
|
|
138
191
|
};
|
|
139
192
|
}
|
|
140
193
|
|
|
@@ -178,15 +231,22 @@ export async function planDevTestBotPhase({
|
|
|
178
231
|
}
|
|
179
232
|
|
|
180
233
|
let planned = {};
|
|
234
|
+
let usageLedger = null;
|
|
181
235
|
try {
|
|
182
|
-
|
|
236
|
+
const plannerResult = await callPlannerClient({
|
|
183
237
|
plannerClient: normalized.plannerClient,
|
|
184
238
|
rootPath,
|
|
185
239
|
files,
|
|
186
240
|
findings,
|
|
187
241
|
budget,
|
|
242
|
+
sessionUsage: normalized.sessionUsage,
|
|
188
243
|
});
|
|
189
|
-
|
|
244
|
+
planned = plannerResult.planned || {};
|
|
245
|
+
usageLedger = plannerResult.usageLedger || null;
|
|
246
|
+
} catch (error) {
|
|
247
|
+
if (isInvestorDdUsageLedgerError(error)) {
|
|
248
|
+
throw error;
|
|
249
|
+
}
|
|
190
250
|
planned = {};
|
|
191
251
|
}
|
|
192
252
|
const swarmCount = clampInt(normalized.swarmCount ?? planned.swarmCount, {
|
|
@@ -236,6 +296,7 @@ export async function planDevTestBotPhase({
|
|
|
236
296
|
baseUrl: normalized.baseUrl,
|
|
237
297
|
recordVideo: normalized.recordVideo !== false,
|
|
238
298
|
maxConcurrentAgents,
|
|
299
|
+
usageLedger,
|
|
239
300
|
};
|
|
240
301
|
}
|
|
241
302
|
|
|
@@ -415,6 +476,20 @@ export async function runDevTestBotPhase({
|
|
|
415
476
|
maxConcurrentAgents: plan.maxConcurrentAgents,
|
|
416
477
|
},
|
|
417
478
|
});
|
|
479
|
+
if (plan.usageLedger?.ok) {
|
|
480
|
+
onEvent({
|
|
481
|
+
type: "devtestbot_planner_usage_recorded",
|
|
482
|
+
phase: "devtestbot",
|
|
483
|
+
action: INVESTOR_DD_USAGE_ACTIONS.devTestBotPlanner,
|
|
484
|
+
ledgerEntryId: plan.usageLedger.ledgerEntry?.ledgerEntryId || "",
|
|
485
|
+
});
|
|
486
|
+
} else if (plan.usageLedger?.ok === false) {
|
|
487
|
+
onEvent({
|
|
488
|
+
type: "devtestbot_planner_usage_unrecorded",
|
|
489
|
+
phase: "devtestbot",
|
|
490
|
+
reason: plan.usageLedger.reason || "unknown",
|
|
491
|
+
});
|
|
492
|
+
}
|
|
418
493
|
|
|
419
494
|
if (!plan.enabled) {
|
|
420
495
|
const skipped = {
|
|
@@ -18,6 +18,11 @@
|
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
20
|
import { runEnvelopeLoop } from "../agents/envelope/index.js";
|
|
21
|
+
import {
|
|
22
|
+
INVESTOR_DD_USAGE_ACTIONS,
|
|
23
|
+
assertInvestorDdUsageContextReady,
|
|
24
|
+
recordInvestorDdLlmUsage,
|
|
25
|
+
} from "./investor-dd-usage.js";
|
|
21
26
|
|
|
22
27
|
export const INVESTOR_DD_DEFAULT_MAX_TURNS_PER_FILE = 6;
|
|
23
28
|
export const INVESTOR_DD_DEFAULT_STUCK_THRESHOLD = 2;
|
|
@@ -122,21 +127,75 @@ function meterTools(tools, budget, onToolCall) {
|
|
|
122
127
|
}));
|
|
123
128
|
}
|
|
124
129
|
|
|
130
|
+
function rememberUsageLedgerEntry(budget, entry) {
|
|
131
|
+
if (!budget || typeof budget !== "object" || !entry?.ok) return;
|
|
132
|
+
if (!Array.isArray(budget.sessionUsageLedgerEntries)) {
|
|
133
|
+
budget.sessionUsageLedgerEntries = [];
|
|
134
|
+
}
|
|
135
|
+
budget.sessionUsageLedgerEntries.push(entry);
|
|
136
|
+
}
|
|
137
|
+
|
|
125
138
|
/**
|
|
126
139
|
* Wrap the caller's LLM client so every generatePlan call increments the
|
|
127
|
-
* llmCalls counter.
|
|
128
|
-
*
|
|
129
|
-
* `budget.spentUsd` directly.
|
|
140
|
+
* llmCalls counter. Billing-grade token accounting is recorded only from
|
|
141
|
+
* provider-returned usage after the planner returns.
|
|
130
142
|
*
|
|
131
143
|
* @param {object} client
|
|
132
144
|
* @param {InvestorDdBudgetState} budget
|
|
133
145
|
*/
|
|
134
|
-
function meterClient(client, budget) {
|
|
146
|
+
function meterClient(client, budget, { personaId, sessionUsage, onEvent }) {
|
|
135
147
|
return {
|
|
136
148
|
...client,
|
|
137
149
|
generatePlan: async (messages, options) => {
|
|
150
|
+
const startedAtIso = new Date().toISOString();
|
|
151
|
+
if (sessionUsage) {
|
|
152
|
+
try {
|
|
153
|
+
assertInvestorDdUsageContextReady({
|
|
154
|
+
usageContext: sessionUsage,
|
|
155
|
+
action: INVESTOR_DD_USAGE_ACTIONS.filePlanner,
|
|
156
|
+
agentId: `investor-dd-${personaId}`,
|
|
157
|
+
});
|
|
158
|
+
} catch (error) {
|
|
159
|
+
budget.usageLedgerError = error;
|
|
160
|
+
throw error;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
138
163
|
budget.llmCalls += 1;
|
|
139
|
-
|
|
164
|
+
const response = await client.generatePlan(messages, options);
|
|
165
|
+
if (!sessionUsage) {
|
|
166
|
+
return response;
|
|
167
|
+
}
|
|
168
|
+
const usageResult = await recordInvestorDdLlmUsage({
|
|
169
|
+
usageContext: sessionUsage,
|
|
170
|
+
action: INVESTOR_DD_USAGE_ACTIONS.filePlanner,
|
|
171
|
+
agentId: `investor-dd-${personaId}`,
|
|
172
|
+
phase: "persona_file_loop",
|
|
173
|
+
messages,
|
|
174
|
+
response,
|
|
175
|
+
startedAtIso,
|
|
176
|
+
metadata: {
|
|
177
|
+
personaId,
|
|
178
|
+
turn: options?.turn || 0,
|
|
179
|
+
},
|
|
180
|
+
}).catch((error) => {
|
|
181
|
+
budget.usageLedgerError = error;
|
|
182
|
+
throw error;
|
|
183
|
+
});
|
|
184
|
+
if (usageResult?.ok === false) {
|
|
185
|
+
onEvent({
|
|
186
|
+
type: "persona_llm_usage_unrecorded",
|
|
187
|
+
personaId,
|
|
188
|
+
reason: usageResult.reason || "unknown",
|
|
189
|
+
});
|
|
190
|
+
} else if (usageResult?.ok) {
|
|
191
|
+
rememberUsageLedgerEntry(budget, usageResult);
|
|
192
|
+
onEvent({
|
|
193
|
+
type: "persona_llm_usage_recorded",
|
|
194
|
+
personaId,
|
|
195
|
+
action: INVESTOR_DD_USAGE_ACTIONS.filePlanner,
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
return response;
|
|
140
199
|
},
|
|
141
200
|
};
|
|
142
201
|
}
|
|
@@ -157,6 +216,7 @@ function meterClient(client, budget) {
|
|
|
157
216
|
* @param {object} [params.options]
|
|
158
217
|
* @param {number} [params.options.maxTurnsPerFile]
|
|
159
218
|
* @param {number} [params.options.stuckThreshold]
|
|
219
|
+
* @param {object} [params.sessionUsage] - Optional billing-grade Senti usage context.
|
|
160
220
|
* @returns {Promise<InvestorDdFileLoopResult>}
|
|
161
221
|
*/
|
|
162
222
|
export async function runPerFileReviewLoop({
|
|
@@ -167,6 +227,7 @@ export async function runPerFileReviewLoop({
|
|
|
167
227
|
buildInitialMessages,
|
|
168
228
|
budget,
|
|
169
229
|
onEvent = () => {},
|
|
230
|
+
sessionUsage = null,
|
|
170
231
|
options = {},
|
|
171
232
|
} = {}) {
|
|
172
233
|
if (!personaId || typeof personaId !== "string") {
|
|
@@ -193,7 +254,9 @@ export async function runPerFileReviewLoop({
|
|
|
193
254
|
: INVESTOR_DD_DEFAULT_STUCK_THRESHOLD;
|
|
194
255
|
|
|
195
256
|
const safeBudget = budget || createBudgetState();
|
|
196
|
-
const
|
|
257
|
+
const initialUsageLedgerEntryCount = Array.isArray(safeBudget.sessionUsageLedgerEntries)
|
|
258
|
+
? safeBudget.sessionUsageLedgerEntries.length
|
|
259
|
+
: 0;
|
|
197
260
|
|
|
198
261
|
const perFile = [];
|
|
199
262
|
const allFindings = [];
|
|
@@ -225,6 +288,11 @@ export async function runPerFileReviewLoop({
|
|
|
225
288
|
emit({ type: "persona_file_tool_call", personaId, file, tool, input });
|
|
226
289
|
});
|
|
227
290
|
const initialMessages = buildInitialMessages(file);
|
|
291
|
+
const meteredClient = meterClient(client, safeBudget, {
|
|
292
|
+
personaId,
|
|
293
|
+
sessionUsage,
|
|
294
|
+
onEvent: emit,
|
|
295
|
+
});
|
|
228
296
|
|
|
229
297
|
let loopResult;
|
|
230
298
|
try {
|
|
@@ -256,7 +324,13 @@ export async function runPerFileReviewLoop({
|
|
|
256
324
|
},
|
|
257
325
|
},
|
|
258
326
|
});
|
|
327
|
+
if (safeBudget.usageLedgerError) {
|
|
328
|
+
throw safeBudget.usageLedgerError;
|
|
329
|
+
}
|
|
259
330
|
} catch (err) {
|
|
331
|
+
if (safeBudget.usageLedgerError && err === safeBudget.usageLedgerError) {
|
|
332
|
+
throw err;
|
|
333
|
+
}
|
|
260
334
|
terminationReason = "client-error";
|
|
261
335
|
emit({
|
|
262
336
|
type: "persona_file_error",
|
|
@@ -298,6 +372,9 @@ export async function runPerFileReviewLoop({
|
|
|
298
372
|
findings: allFindings,
|
|
299
373
|
visited,
|
|
300
374
|
skipped,
|
|
375
|
+
usageLedgerEntries: Array.isArray(safeBudget.sessionUsageLedgerEntries)
|
|
376
|
+
? safeBudget.sessionUsageLedgerEntries.slice(initialUsageLedgerEntryCount).filter((entry) => entry?.ok)
|
|
377
|
+
: [],
|
|
301
378
|
terminationReason,
|
|
302
379
|
};
|
|
303
380
|
}
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* persona-<id>.json — per-persona findings + coverage proof
|
|
11
11
|
* findings.json — flat list across all personas (dedup in PR-29)
|
|
12
12
|
* summary.json — run metadata (timings, cost, terminationReason)
|
|
13
|
+
* progress.json — truthful sellable-readiness capability ledger
|
|
13
14
|
* report.md — human-readable summary
|
|
14
15
|
* manifest.json — SHA-256 chain of every artifact
|
|
15
16
|
*
|
|
@@ -39,6 +40,7 @@ import { attachReproducibilityChain } from "./reproducibility-chain.js";
|
|
|
39
40
|
import { renderInvestorDdHtml } from "./investor-dd-html-report.js";
|
|
40
41
|
import { runDevTestBotPhase } from "./investor-dd-devtestbot.js";
|
|
41
42
|
import { redactDdEmailError } from "./dd-report-email-client.js";
|
|
43
|
+
import { buildInvestorDdProgress } from "./investor-dd-progress.js";
|
|
42
44
|
|
|
43
45
|
const INVESTOR_DD_PERSONAS = Object.freeze([
|
|
44
46
|
"security",
|
|
@@ -271,6 +273,7 @@ async function triggerReportEmail({ reportEmail, runResult, dryRun, emit }) {
|
|
|
271
273
|
* @param {object} [params.liveValidator.aidenid] - AIdenID client.
|
|
272
274
|
* @param {number} [params.liveValidator.maxInteractions]
|
|
273
275
|
* @param {object|false} [params.devTestBot] - Automated devTestBot phase config.
|
|
276
|
+
* @param {object|null} [params.sessionUsage] - Optional Senti session_usage context for DD LLM calls.
|
|
274
277
|
* @param {object|null} [params.reportEmail] - Optional API-side report email trigger.
|
|
275
278
|
* @param {string} [params.reportEmail.to]
|
|
276
279
|
* @param {object} [params.reportEmail.client] - { send({ runId, to, run }) }.
|
|
@@ -290,6 +293,7 @@ export async function runInvestorDd({
|
|
|
290
293
|
compliancePacks = COMPLIANCE_PACK_CATALOG,
|
|
291
294
|
liveValidator = null,
|
|
292
295
|
devTestBot = {},
|
|
296
|
+
sessionUsage = null,
|
|
293
297
|
reportEmail = null,
|
|
294
298
|
notification = null,
|
|
295
299
|
} = {}) {
|
|
@@ -382,7 +386,12 @@ export async function runInvestorDd({
|
|
|
382
386
|
files,
|
|
383
387
|
findings,
|
|
384
388
|
budget: budgetState,
|
|
385
|
-
options: devTestBot === false
|
|
389
|
+
options: devTestBot === false
|
|
390
|
+
? { enabled: false }
|
|
391
|
+
: {
|
|
392
|
+
...(devTestBot || {}),
|
|
393
|
+
sessionUsage,
|
|
394
|
+
},
|
|
386
395
|
onEvent: emit,
|
|
387
396
|
});
|
|
388
397
|
findings.push(...(devTestBotPhase.findings || []));
|
|
@@ -503,6 +512,38 @@ export async function runInvestorDd({
|
|
|
503
512
|
});
|
|
504
513
|
}
|
|
505
514
|
|
|
515
|
+
const artifactFilesBeforeManifest = await fsp.readdir(artifactBase);
|
|
516
|
+
const ddProgress = buildInvestorDdProgress({
|
|
517
|
+
runId,
|
|
518
|
+
personas,
|
|
519
|
+
dryRun,
|
|
520
|
+
routing,
|
|
521
|
+
byPersona,
|
|
522
|
+
findings,
|
|
523
|
+
compliance,
|
|
524
|
+
reconciliationAvailable,
|
|
525
|
+
liveValidator,
|
|
526
|
+
devTestBotPhase,
|
|
527
|
+
reportEmailConfigured: Boolean(reportEmail),
|
|
528
|
+
reportEmailResult: runResult.reportEmail || null,
|
|
529
|
+
notification,
|
|
530
|
+
artifactFiles: artifactFilesBeforeManifest,
|
|
531
|
+
budgetState,
|
|
532
|
+
});
|
|
533
|
+
summary.ddProgress = {
|
|
534
|
+
version: ddProgress.version,
|
|
535
|
+
overallStatus: ddProgress.overallStatus,
|
|
536
|
+
sellableReady: ddProgress.sellableReady,
|
|
537
|
+
complete: ddProgress.summary.complete,
|
|
538
|
+
requiredComplete: ddProgress.summary.requiredComplete,
|
|
539
|
+
requiredTotal: ddProgress.summary.requiredTotal,
|
|
540
|
+
blockingGapCount: ddProgress.summary.blockingGapCount,
|
|
541
|
+
artifact: "progress.json",
|
|
542
|
+
};
|
|
543
|
+
runResult.progress = ddProgress;
|
|
544
|
+
await writeJson(path.join(artifactBase, "progress.json"), ddProgress);
|
|
545
|
+
await writeJson(path.join(artifactBase, "summary.json"), summary);
|
|
546
|
+
|
|
506
547
|
await streamHandle.close();
|
|
507
548
|
|
|
508
549
|
const artifactFiles = await fsp.readdir(artifactBase);
|