sentinelayer-cli 0.39.1 → 0.40.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 +1 -1
- package/src/agents/ai-governance/tools/base.js +19 -166
- package/src/agents/backend/tools/base.js +33 -186
- package/src/agents/code-quality/tools/base.js +27 -154
- package/src/agents/data-layer/tools/base.js +35 -180
- package/src/agents/documentation/tools/base.js +18 -146
- package/src/agents/infrastructure/tools/base.js +17 -164
- package/src/agents/observability/tools/base.js +35 -180
- package/src/agents/performance/tools/base.js +35 -205
- package/src/agents/release/tools/base.js +35 -180
- package/src/agents/reliability/tools/base.js +35 -180
- package/src/agents/security/tools/base.js +33 -187
- package/src/agents/shared-tools/domain-base.js +246 -0
- package/src/agents/supply-chain/tools/base.js +15 -144
- package/src/agents/testing/tools/base.js +33 -184
- package/src/cli.js +5 -0
- package/src/commands/mcp.js +1 -1
- package/src/commands/ring-owner.js +58 -0
- package/src/commands/session.js +92 -31
- package/src/legacy-cli.js +2 -2
- package/src/mcp/smoke.js +9 -8
- package/src/pocket/ring-owner.js +97 -0
- package/src/review/local-review.js +9 -5
- package/src/review/omargate-orchestrator.js +111 -14
- package/src/review/spec-binding.js +16 -1
- package/src/scan/generator.js +1 -1
- package/src/session/checkpoints.js +194 -0
- package/src/session/pricing-ledger.js +2 -0
- package/src/session/recap.js +13 -1
|
@@ -3,6 +3,7 @@ import process from "node:process";
|
|
|
3
3
|
|
|
4
4
|
import { SentinelayerApiError, requestJson, requestJsonMutation } from "../auth/http.js";
|
|
5
5
|
import { resolveActiveAuthSession } from "../auth/service.js";
|
|
6
|
+
import { pollSessionEventsBefore } from "./sync.js";
|
|
6
7
|
|
|
7
8
|
const DEFAULT_API_BASE_URL = "https://api.sentinelayer.com";
|
|
8
9
|
const DEFAULT_CHECKPOINT_LIMIT = 100;
|
|
@@ -11,6 +12,10 @@ const DEFAULT_MIN_EVENTS = 20;
|
|
|
11
12
|
const DEFAULT_MAX_EVENTS = 80;
|
|
12
13
|
const DEFAULT_BATCH_MAX_CHECKPOINTS = 5;
|
|
13
14
|
const MAX_BATCH_MAX_CHECKPOINTS = 50;
|
|
15
|
+
const DEFAULT_RESTORE_CONTEXT_EVENTS = 3;
|
|
16
|
+
const DEFAULT_RESTORE_MAX_EVENTS = 120;
|
|
17
|
+
const MAX_RESTORE_CONTEXT_EVENTS = 50;
|
|
18
|
+
const MAX_RESTORE_EVENTS = 200;
|
|
14
19
|
const DEFAULT_CREATED_BY_AGENT_ID = "senti";
|
|
15
20
|
|
|
16
21
|
function normalizeString(value) {
|
|
@@ -33,6 +38,17 @@ function parsePositiveInteger(value, field, fallbackValue = null) {
|
|
|
33
38
|
return Math.floor(parsed);
|
|
34
39
|
}
|
|
35
40
|
|
|
41
|
+
function parseNonNegativeInteger(value, field, fallbackValue = 0) {
|
|
42
|
+
if (value === undefined || value === null || normalizeString(value) === "") {
|
|
43
|
+
return Math.max(0, Math.floor(Number(fallbackValue) || 0));
|
|
44
|
+
}
|
|
45
|
+
const parsed = Number(value);
|
|
46
|
+
if (!Number.isFinite(parsed) || parsed < 0) {
|
|
47
|
+
throw new Error(`${field} must be a non-negative integer.`);
|
|
48
|
+
}
|
|
49
|
+
return Math.floor(parsed);
|
|
50
|
+
}
|
|
51
|
+
|
|
36
52
|
function normalizeLimit(value) {
|
|
37
53
|
const parsed = parsePositiveInteger(value, "limit", DEFAULT_CHECKPOINT_LIMIT);
|
|
38
54
|
return Math.max(1, Math.min(MAX_CHECKPOINT_LIMIT, parsed));
|
|
@@ -47,6 +63,16 @@ function normalizeBatchMaxCheckpoints(value) {
|
|
|
47
63
|
return Math.max(1, Math.min(MAX_BATCH_MAX_CHECKPOINTS, parsed));
|
|
48
64
|
}
|
|
49
65
|
|
|
66
|
+
function normalizeRestoreMaxEvents(value) {
|
|
67
|
+
const parsed = parsePositiveInteger(value, "max-events", DEFAULT_RESTORE_MAX_EVENTS);
|
|
68
|
+
return Math.max(1, Math.min(MAX_RESTORE_EVENTS, parsed));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function normalizeRestoreContextEvents(value) {
|
|
72
|
+
const parsed = parseNonNegativeInteger(value, "context-events", DEFAULT_RESTORE_CONTEXT_EVENTS);
|
|
73
|
+
return Math.max(0, Math.min(MAX_RESTORE_CONTEXT_EVENTS, parsed));
|
|
74
|
+
}
|
|
75
|
+
|
|
50
76
|
function stableHash(value) {
|
|
51
77
|
return crypto
|
|
52
78
|
.createHash("sha256")
|
|
@@ -148,6 +174,20 @@ function normalizeTokenRange({ tokenStart, tokenEnd } = {}) {
|
|
|
148
174
|
return { start, end };
|
|
149
175
|
}
|
|
150
176
|
|
|
177
|
+
function checkpointIdOf(checkpoint = {}) {
|
|
178
|
+
return normalizeString(checkpoint.checkpointId || checkpoint.checkpoint_id);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function checkpointSequenceValue(checkpoint = {}, camelKey, snakeKey) {
|
|
182
|
+
const parsed = Number(checkpoint[camelKey] ?? checkpoint[snakeKey]);
|
|
183
|
+
return Number.isFinite(parsed) && parsed > 0 ? Math.floor(parsed) : 0;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function eventSequenceValue(event = {}) {
|
|
187
|
+
const parsed = Number(event.sequenceId ?? event.sequence_id ?? event.sequence);
|
|
188
|
+
return Number.isFinite(parsed) && parsed > 0 ? Math.floor(parsed) : 0;
|
|
189
|
+
}
|
|
190
|
+
|
|
151
191
|
export function buildManualCheckpointPayload(sessionId, {
|
|
152
192
|
checkpointId = "",
|
|
153
193
|
startSequence,
|
|
@@ -235,6 +275,99 @@ export function buildGenerateCheckpointPayload(sessionId, {
|
|
|
235
275
|
};
|
|
236
276
|
}
|
|
237
277
|
|
|
278
|
+
export function findCheckpointById(checkpoints = [], checkpointId = "") {
|
|
279
|
+
const normalizedCheckpointId = normalizeString(checkpointId);
|
|
280
|
+
if (!normalizedCheckpointId) return null;
|
|
281
|
+
return Array.isArray(checkpoints)
|
|
282
|
+
? checkpoints.find((checkpoint) => checkpointIdOf(checkpoint) === normalizedCheckpointId) || null
|
|
283
|
+
: null;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
export function buildCheckpointRestoreWindow(checkpoint = {}, {
|
|
287
|
+
contextEvents = DEFAULT_RESTORE_CONTEXT_EVENTS,
|
|
288
|
+
maxEvents = DEFAULT_RESTORE_MAX_EVENTS,
|
|
289
|
+
} = {}) {
|
|
290
|
+
const startSequence = checkpointSequenceValue(checkpoint, "startSequence", "start_sequence");
|
|
291
|
+
const endSequence = checkpointSequenceValue(checkpoint, "endSequence", "end_sequence");
|
|
292
|
+
if (startSequence <= 0) {
|
|
293
|
+
throw new Error("checkpoint startSequence is required for restore.");
|
|
294
|
+
}
|
|
295
|
+
if (endSequence <= 0) {
|
|
296
|
+
throw new Error("checkpoint endSequence is required for restore.");
|
|
297
|
+
}
|
|
298
|
+
if (startSequence > endSequence) {
|
|
299
|
+
throw new Error("checkpoint startSequence must be less than or equal to endSequence.");
|
|
300
|
+
}
|
|
301
|
+
const normalizedContext = normalizeRestoreContextEvents(contextEvents);
|
|
302
|
+
const normalizedMaxEvents = normalizeRestoreMaxEvents(maxEvents);
|
|
303
|
+
const sourceEventCountExpected = endSequence - startSequence + 1;
|
|
304
|
+
const requestedEventCount = sourceEventCountExpected + normalizedContext * 2;
|
|
305
|
+
const limit = Math.min(normalizedMaxEvents, requestedEventCount, MAX_RESTORE_EVENTS);
|
|
306
|
+
return {
|
|
307
|
+
checkpointId: checkpointIdOf(checkpoint) || null,
|
|
308
|
+
startSequence,
|
|
309
|
+
endSequence,
|
|
310
|
+
contextEvents: normalizedContext,
|
|
311
|
+
maxEvents: normalizedMaxEvents,
|
|
312
|
+
limit,
|
|
313
|
+
beforeSequence: endSequence + normalizedContext + 1,
|
|
314
|
+
lowerBoundSequence: Math.max(1, startSequence - normalizedContext),
|
|
315
|
+
sourceEventCountExpected,
|
|
316
|
+
truncatedByLimit: limit < requestedEventCount,
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
export function classifyCheckpointRestoreEvents(events = [], window = {}) {
|
|
321
|
+
const normalizedEvents = Array.isArray(events) ? events : [];
|
|
322
|
+
const startSequence = Number(window.startSequence || 0);
|
|
323
|
+
const endSequence = Number(window.endSequence || 0);
|
|
324
|
+
const sourceEventCountExpected = Number(window.sourceEventCountExpected || 0);
|
|
325
|
+
const beforeContextEvents = [];
|
|
326
|
+
const sourceEvents = [];
|
|
327
|
+
const afterContextEvents = [];
|
|
328
|
+
const sourceSequenceIds = new Set();
|
|
329
|
+
|
|
330
|
+
for (const event of normalizedEvents) {
|
|
331
|
+
const sequence = eventSequenceValue(event);
|
|
332
|
+
if (sequence <= 0) continue;
|
|
333
|
+
if (sequence < startSequence) {
|
|
334
|
+
beforeContextEvents.push(event);
|
|
335
|
+
} else if (sequence > endSequence) {
|
|
336
|
+
afterContextEvents.push(event);
|
|
337
|
+
} else {
|
|
338
|
+
sourceEvents.push(event);
|
|
339
|
+
sourceSequenceIds.add(sequence);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const missingStart = startSequence > 0 && !sourceSequenceIds.has(startSequence);
|
|
344
|
+
const missingEnd = endSequence > 0 && !sourceSequenceIds.has(endSequence);
|
|
345
|
+
const missingSourceEvents = Math.max(0, sourceEventCountExpected - sourceSequenceIds.size);
|
|
346
|
+
return {
|
|
347
|
+
events: normalizedEvents,
|
|
348
|
+
beforeContextEvents,
|
|
349
|
+
sourceEvents,
|
|
350
|
+
afterContextEvents,
|
|
351
|
+
eventCount: normalizedEvents.length,
|
|
352
|
+
beforeContextCount: beforeContextEvents.length,
|
|
353
|
+
sourceEventCount: sourceEvents.length,
|
|
354
|
+
afterContextCount: afterContextEvents.length,
|
|
355
|
+
missingStart,
|
|
356
|
+
missingEnd,
|
|
357
|
+
missingSourceEvents,
|
|
358
|
+
completeSourceRange:
|
|
359
|
+
sourceEventCountExpected > 0 &&
|
|
360
|
+
!missingStart &&
|
|
361
|
+
!missingEnd &&
|
|
362
|
+
missingSourceEvents === 0,
|
|
363
|
+
partial:
|
|
364
|
+
Boolean(window.truncatedByLimit) ||
|
|
365
|
+
missingStart ||
|
|
366
|
+
missingEnd ||
|
|
367
|
+
missingSourceEvents > 0,
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
|
|
238
371
|
export function normalizeCheckpointGenerationResult(payload = {}) {
|
|
239
372
|
const source = payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
|
|
240
373
|
const checkpoint =
|
|
@@ -308,6 +441,63 @@ export async function listSessionCheckpoints(sessionId, {
|
|
|
308
441
|
};
|
|
309
442
|
}
|
|
310
443
|
|
|
444
|
+
export async function showSessionCheckpoint(sessionId, checkpointId, {
|
|
445
|
+
targetPath = process.cwd(),
|
|
446
|
+
contextEvents = DEFAULT_RESTORE_CONTEXT_EVENTS,
|
|
447
|
+
maxEvents = DEFAULT_RESTORE_MAX_EVENTS,
|
|
448
|
+
resolveAuthSession = resolveActiveAuthSession,
|
|
449
|
+
request = requestJson,
|
|
450
|
+
fetchEventsBefore = pollSessionEventsBefore,
|
|
451
|
+
} = {}) {
|
|
452
|
+
const normalizedSessionId = normalizeString(sessionId);
|
|
453
|
+
if (!normalizedSessionId) {
|
|
454
|
+
throw new Error("session id is required.");
|
|
455
|
+
}
|
|
456
|
+
const normalizedCheckpointId = normalizeString(checkpointId);
|
|
457
|
+
if (!normalizedCheckpointId) {
|
|
458
|
+
throw new Error("checkpoint id is required.");
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
const listed = await listSessionCheckpoints(normalizedSessionId, {
|
|
462
|
+
targetPath,
|
|
463
|
+
limit: MAX_CHECKPOINT_LIMIT,
|
|
464
|
+
resolveAuthSession,
|
|
465
|
+
request,
|
|
466
|
+
});
|
|
467
|
+
const checkpoint = findCheckpointById(listed.checkpoints, normalizedCheckpointId);
|
|
468
|
+
if (!checkpoint) {
|
|
469
|
+
throw new Error(`Checkpoint '${normalizedCheckpointId}' was not found in session ${normalizedSessionId}.`);
|
|
470
|
+
}
|
|
471
|
+
const restoreWindow = buildCheckpointRestoreWindow(checkpoint, {
|
|
472
|
+
contextEvents,
|
|
473
|
+
maxEvents,
|
|
474
|
+
});
|
|
475
|
+
const remote = await fetchEventsBefore(normalizedSessionId, {
|
|
476
|
+
targetPath,
|
|
477
|
+
beforeSequence: restoreWindow.beforeSequence,
|
|
478
|
+
limit: restoreWindow.limit,
|
|
479
|
+
forceCircuitProbe: true,
|
|
480
|
+
resolveAuthSession,
|
|
481
|
+
});
|
|
482
|
+
const classified = classifyCheckpointRestoreEvents(remote?.events || [], restoreWindow);
|
|
483
|
+
return {
|
|
484
|
+
ok: Boolean(remote?.ok),
|
|
485
|
+
reason: normalizeString(remote?.reason),
|
|
486
|
+
sessionId: normalizedSessionId,
|
|
487
|
+
checkpointId: normalizedCheckpointId,
|
|
488
|
+
apiUrl: listed.apiUrl,
|
|
489
|
+
checkpoint,
|
|
490
|
+
window: {
|
|
491
|
+
...restoreWindow,
|
|
492
|
+
...classified,
|
|
493
|
+
remoteOk: Boolean(remote?.ok),
|
|
494
|
+
remoteReason: normalizeString(remote?.reason),
|
|
495
|
+
remoteBeforeSequence: remote?.beforeSequence || null,
|
|
496
|
+
remoteCursor: remote?.cursor || null,
|
|
497
|
+
},
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
|
|
311
501
|
export async function createSessionCheckpoint(sessionId, options = {}) {
|
|
312
502
|
const normalizedSessionId = normalizeString(sessionId);
|
|
313
503
|
const {
|
|
@@ -446,5 +636,9 @@ export {
|
|
|
446
636
|
DEFAULT_BATCH_MAX_CHECKPOINTS,
|
|
447
637
|
DEFAULT_MAX_EVENTS,
|
|
448
638
|
DEFAULT_MIN_EVENTS,
|
|
639
|
+
DEFAULT_RESTORE_CONTEXT_EVENTS,
|
|
640
|
+
DEFAULT_RESTORE_MAX_EVENTS,
|
|
449
641
|
MAX_BATCH_MAX_CHECKPOINTS,
|
|
642
|
+
MAX_RESTORE_CONTEXT_EVENTS,
|
|
643
|
+
MAX_RESTORE_EVENTS,
|
|
450
644
|
};
|
|
@@ -296,6 +296,7 @@ export function buildEstimatedMessageLedgerEntry(
|
|
|
296
296
|
billingTier = "estimated",
|
|
297
297
|
includeHumanMessages = false,
|
|
298
298
|
action = "estimated_agent_message",
|
|
299
|
+
estimatedMessageTokenBackend = null,
|
|
299
300
|
} = {},
|
|
300
301
|
) {
|
|
301
302
|
const kind = n(event?.event || event?.type);
|
|
@@ -322,6 +323,7 @@ export function buildEstimatedMessageLedgerEntry(
|
|
|
322
323
|
const outputTokens = estimateTokens(text, {
|
|
323
324
|
model,
|
|
324
325
|
provider: n(agent.provider || payload.provider),
|
|
326
|
+
backend: estimatedMessageTokenBackend,
|
|
325
327
|
});
|
|
326
328
|
if (outputTokens <= 0) return null;
|
|
327
329
|
|
package/src/session/recap.js
CHANGED
|
@@ -21,6 +21,7 @@ const DEFAULT_RECAP_ACTIVITY_THRESHOLD = 5;
|
|
|
21
21
|
const DEFAULT_TASK_SUMMARY_LIMIT = 3;
|
|
22
22
|
const DEFAULT_WORK_PLAN_SUMMARY_LIMIT = 5;
|
|
23
23
|
const DEFAULT_ACTIVITY_SNIPPET_MAX_CHARS = 120;
|
|
24
|
+
const RECAP_ESTIMATED_USAGE_CHARS_PER_TOKEN = 4;
|
|
24
25
|
const MAX_WORK_PLAN_BYTES = 128_000;
|
|
25
26
|
const WORK_PLAN_RELATIVE_PATH = "tasks/todo.md";
|
|
26
27
|
const ACTIVE_WORK_PLAN_FILE_PATTERN = /^reconciled-fix-plan-\d{4}-\d{2}-\d{2}\.md$/;
|
|
@@ -1027,8 +1028,19 @@ function roundCurrency(value) {
|
|
|
1027
1028
|
return Math.round(normalized * 1_000_000) / 1_000_000;
|
|
1028
1029
|
}
|
|
1029
1030
|
|
|
1031
|
+
function estimateRecapMessageTokens(text) {
|
|
1032
|
+
const normalized = normalizeString(text);
|
|
1033
|
+
if (!normalized) {
|
|
1034
|
+
return 0;
|
|
1035
|
+
}
|
|
1036
|
+
return Math.max(1, Math.ceil(normalized.length / RECAP_ESTIMATED_USAGE_CHARS_PER_TOKEN));
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1030
1039
|
function normalizeUsageSummary(events = []) {
|
|
1031
|
-
const aggregate = aggregateSessionUsage(events, {
|
|
1040
|
+
const aggregate = aggregateSessionUsage(events, {
|
|
1041
|
+
includeEstimatedMessages: true,
|
|
1042
|
+
estimatedMessageTokenBackend: estimateRecapMessageTokens,
|
|
1043
|
+
});
|
|
1032
1044
|
const totals = {
|
|
1033
1045
|
totalTokens: Number(aggregate.totals.totalTokens || 0),
|
|
1034
1046
|
inputTokens: Number(aggregate.totals.inputTokens || 0),
|