sentinelayer-cli 0.34.6 → 0.35.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/commands/session.js +114 -17
- package/src/mcp/registry.js +56 -1
- package/src/mcp/session-stdio-server.js +205 -1
- package/src/scan/generator.js +19 -11
- package/src/session/coordination-guidance.js +1 -1
- package/src/session/first-message.js +1 -1
- package/src/session/listener-process.js +259 -0
- package/src/session/listener.js +3 -3
- package/src/session/listeners.js +8 -2
- package/src/session/pricing-ledger.js +208 -1
- package/src/session/recap.js +66 -2
- package/src/session/sync.js +28 -2
- package/src/session/transcript.js +27 -3
- package/src/session/usage-report.js +23 -4
- package/src/session/usage.js +2 -2
package/src/session/sync.js
CHANGED
|
@@ -1479,6 +1479,7 @@ export async function pollSessionEvents(
|
|
|
1479
1479
|
* @param {object} [options]
|
|
1480
1480
|
* @param {string|null} [options.since] - durable cursor to resume after
|
|
1481
1481
|
* @param {AbortSignal} [options.signal]
|
|
1482
|
+
* @param {number} [options.idleTimeoutMs] - abort a connected stream after this many silent milliseconds
|
|
1482
1483
|
* @param {(event: object) => Promise<void>|void} [options.onEvent]
|
|
1483
1484
|
* @param {(payload: object) => Promise<void>|void} [options.onError]
|
|
1484
1485
|
* @param {() => Promise<void>|void} [options.onHeartbeat]
|
|
@@ -1490,6 +1491,7 @@ export async function streamSessionEvents(
|
|
|
1490
1491
|
targetPath = process.cwd(),
|
|
1491
1492
|
since = null,
|
|
1492
1493
|
timeoutMs = DEFAULT_SYNC_TIMEOUT_MS,
|
|
1494
|
+
idleTimeoutMs = 0,
|
|
1493
1495
|
signal = undefined,
|
|
1494
1496
|
resolveAuthSession = resolveActiveAuthSession,
|
|
1495
1497
|
fetchImpl = fetch,
|
|
@@ -1545,10 +1547,26 @@ export async function streamSessionEvents(
|
|
|
1545
1547
|
const endpoint = `${apiBaseUrl}/api/v1/sessions/${encodeURIComponent(normalizedSessionId)}/stream${suffix}`;
|
|
1546
1548
|
const controller = new AbortController();
|
|
1547
1549
|
const normalizedTimeoutMs = normalizePositiveInteger(timeoutMs, DEFAULT_SYNC_TIMEOUT_MS);
|
|
1550
|
+
const normalizedIdleTimeoutMs = normalizePositiveInteger(idleTimeoutMs, 0);
|
|
1548
1551
|
const timeoutHandle = setTimeout(() => controller.abort(), normalizedTimeoutMs);
|
|
1549
1552
|
if (typeof timeoutHandle.unref === "function") {
|
|
1550
1553
|
timeoutHandle.unref();
|
|
1551
1554
|
}
|
|
1555
|
+
let idleTimeoutHandle = null;
|
|
1556
|
+
let idleTimedOut = false;
|
|
1557
|
+
const clearIdleTimeout = () => {
|
|
1558
|
+
if (!idleTimeoutHandle) return;
|
|
1559
|
+
clearTimeout(idleTimeoutHandle);
|
|
1560
|
+
idleTimeoutHandle = null;
|
|
1561
|
+
};
|
|
1562
|
+
const armIdleTimeout = () => {
|
|
1563
|
+
if (!normalizedIdleTimeoutMs) return;
|
|
1564
|
+
clearIdleTimeout();
|
|
1565
|
+
idleTimeoutHandle = setTimeout(() => {
|
|
1566
|
+
idleTimedOut = true;
|
|
1567
|
+
controller.abort();
|
|
1568
|
+
}, normalizedIdleTimeoutMs);
|
|
1569
|
+
};
|
|
1552
1570
|
const forwardAbort = () => controller.abort(signal?.reason);
|
|
1553
1571
|
if (signal) {
|
|
1554
1572
|
if (signal.aborted) {
|
|
@@ -1574,10 +1592,13 @@ export async function streamSessionEvents(
|
|
|
1574
1592
|
);
|
|
1575
1593
|
} catch (error) {
|
|
1576
1594
|
clearTimeout(timeoutHandle);
|
|
1595
|
+
clearIdleTimeout();
|
|
1577
1596
|
if (signal) signal.removeEventListener("abort", forwardAbort);
|
|
1578
1597
|
return {
|
|
1579
1598
|
ok: false,
|
|
1580
|
-
reason:
|
|
1599
|
+
reason: idleTimedOut
|
|
1600
|
+
? "stream_idle_timeout"
|
|
1601
|
+
: isAbortLike(error) || signal?.aborted ? "aborted" : normalizeString(error?.message) || "stream_failed",
|
|
1581
1602
|
cursor: normalizedSince,
|
|
1582
1603
|
eventCount: 0,
|
|
1583
1604
|
errorCount: 0,
|
|
@@ -1621,7 +1642,9 @@ export async function streamSessionEvents(
|
|
|
1621
1642
|
};
|
|
1622
1643
|
}
|
|
1623
1644
|
|
|
1645
|
+
armIdleTimeout();
|
|
1624
1646
|
for await (const chunk of readResponseTextChunks(response)) {
|
|
1647
|
+
armIdleTimeout();
|
|
1625
1648
|
buffer += decoder.decode(chunk, { stream: true });
|
|
1626
1649
|
buffer = buffer.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
1627
1650
|
const blocks = buffer.split("\n\n");
|
|
@@ -1637,13 +1660,16 @@ export async function streamSessionEvents(
|
|
|
1637
1660
|
} catch (error) {
|
|
1638
1661
|
return {
|
|
1639
1662
|
ok: false,
|
|
1640
|
-
reason:
|
|
1663
|
+
reason: idleTimedOut
|
|
1664
|
+
? "stream_idle_timeout"
|
|
1665
|
+
: isAbortLike(error) || signal?.aborted ? "aborted" : normalizeString(error?.message) || "stream_failed",
|
|
1641
1666
|
cursor,
|
|
1642
1667
|
eventCount,
|
|
1643
1668
|
errorCount,
|
|
1644
1669
|
aborted: Boolean(signal?.aborted || isAbortLike(error)),
|
|
1645
1670
|
};
|
|
1646
1671
|
} finally {
|
|
1672
|
+
clearIdleTimeout();
|
|
1647
1673
|
if (signal) signal.removeEventListener("abort", forwardAbort);
|
|
1648
1674
|
}
|
|
1649
1675
|
|
|
@@ -425,6 +425,7 @@ export function computeTranscriptStats({
|
|
|
425
425
|
usageEntries: resolvedUsageLedger.entries.length,
|
|
426
426
|
duplicatesSkipped: resolvedUsageLedger.duplicatesSkipped,
|
|
427
427
|
unpriced: resolvedUsageLedger.totals.unpriced,
|
|
428
|
+
estimatedEntries: resolvedUsageLedger.totals.estimatedEntries,
|
|
428
429
|
priceBookVersions: resolvedUsageLedger.priceBookVersions,
|
|
429
430
|
},
|
|
430
431
|
sentiActions,
|
|
@@ -504,6 +505,12 @@ function rollupsByCostAndTokens(map) {
|
|
|
504
505
|
));
|
|
505
506
|
}
|
|
506
507
|
|
|
508
|
+
function estimateNote(count) {
|
|
509
|
+
const normalized = Math.max(0, Math.floor(Number(count || 0)));
|
|
510
|
+
if (normalized <= 0) return "";
|
|
511
|
+
return `${normalized.toLocaleString("en-US")} estimated`;
|
|
512
|
+
}
|
|
513
|
+
|
|
507
514
|
function recentUsageEntries(entries, limit = 10) {
|
|
508
515
|
return entries
|
|
509
516
|
.map((entry, index) => ({ entry, index }))
|
|
@@ -561,7 +568,15 @@ function appendUsageLedgerSection(lines, usageLedger) {
|
|
|
561
568
|
);
|
|
562
569
|
lines.push(`Provider cost: ${usdCell(totals.providerCostUsd)} · ${customerCostText}`);
|
|
563
570
|
lines.push(`Price books: ${usageLedger.priceBookVersions.map(tableText).filter(Boolean).join(", ") || "—"}`);
|
|
564
|
-
|
|
571
|
+
const estimateText = totals.estimatedEntries > 0
|
|
572
|
+
? ` · Estimated entries: ${intCell(totals.estimatedEntries)}`
|
|
573
|
+
: "";
|
|
574
|
+
lines.push(
|
|
575
|
+
`Duplicates skipped: ${intCell(usageLedger.duplicatesSkipped)} · Unpriced entries: ${intCell(totals.unpriced)}${estimateText}`,
|
|
576
|
+
);
|
|
577
|
+
if (totals.estimatedEntries > 0) {
|
|
578
|
+
lines.push("_Estimated entries are output-text estimates from non-human session messages, not billing-grade provider usage._");
|
|
579
|
+
}
|
|
565
580
|
lines.push("");
|
|
566
581
|
|
|
567
582
|
appendUsageRollupTable(lines, {
|
|
@@ -580,8 +595,11 @@ function appendUsageLedgerSection(lines, usageLedger) {
|
|
|
580
595
|
lines.push("| Time | Agent | Action | Model | Tokens | Provider cost | Customer cost | Idempotency key |");
|
|
581
596
|
lines.push("|---|---|---|---|---:|---:|---:|---|");
|
|
582
597
|
for (const entry of recentUsageEntries(usageLedger.entries)) {
|
|
598
|
+
const action = entry.estimated
|
|
599
|
+
? `${entry.action} (${estimateNote(1)})`
|
|
600
|
+
: entry.action;
|
|
583
601
|
lines.push(
|
|
584
|
-
`| ${tableText(timestampOnly(entry.timestamp)) || "—"} | ${codeCell(entry.agentId)} | ${codeCell(
|
|
602
|
+
`| ${tableText(timestampOnly(entry.timestamp)) || "—"} | ${codeCell(entry.agentId)} | ${codeCell(action)} | ${codeCell(entry.model)} | ${intCell(entry.totalTokens)} | ${usdCell(entry.providerCostUsd)} | ${optionalUsdCell(entry.customerCostUsd, entry.customerCostUsd != null)} | ${codeCell(shortenIdempotencyKey(entry.idempotencyKey))} |`,
|
|
585
603
|
);
|
|
586
604
|
}
|
|
587
605
|
lines.push("");
|
|
@@ -608,8 +626,10 @@ export function buildTranscriptMarkdown({
|
|
|
608
626
|
speakerProfiles = new Map(),
|
|
609
627
|
options = {},
|
|
610
628
|
} = {}) {
|
|
629
|
+
const estimateMessageUsage = options.estimateMessageUsage !== false;
|
|
611
630
|
const usageLedger = buildSessionUsageLedger(events, {
|
|
612
631
|
sessionId: normalize(sessionMeta.sessionId),
|
|
632
|
+
includeEstimatedMessages: estimateMessageUsage,
|
|
613
633
|
});
|
|
614
634
|
const includeSystemEvents = options.includeSystemEvents !== false;
|
|
615
635
|
const stats = computeTranscriptStats({ sessionMeta, events, speakerProfiles, usageLedger });
|
|
@@ -628,8 +648,12 @@ export function buildTranscriptMarkdown({
|
|
|
628
648
|
stats.totals.customerCostTotalUsd == null
|
|
629
649
|
? ""
|
|
630
650
|
: ` · Billable: $${stats.totals.customerCostTotalUsd.toFixed(4)}`;
|
|
651
|
+
const estimatedText =
|
|
652
|
+
stats.totals.estimatedEntries > 0
|
|
653
|
+
? ` · Estimated entries: ${stats.totals.estimatedEntries.toLocaleString("en-US")}`
|
|
654
|
+
: "";
|
|
631
655
|
lines.push(
|
|
632
|
-
`Tokens: ${stats.totals.tokenTotal.toLocaleString("en-US")} · Cost: $${stats.totals.costTotalUsd.toFixed(4)}${billableText}`,
|
|
656
|
+
`Tokens: ${stats.totals.tokenTotal.toLocaleString("en-US")} · Cost: $${stats.totals.costTotalUsd.toFixed(4)}${billableText}${estimatedText}`,
|
|
633
657
|
);
|
|
634
658
|
}
|
|
635
659
|
lines.push("");
|
|
@@ -116,6 +116,7 @@ function rollupToJson(rollup) {
|
|
|
116
116
|
providerCostUsd: rollup.providerCostUsd,
|
|
117
117
|
customerCostUsd: rollup.hasCustomerCost ? rollup.customerCostUsd : null,
|
|
118
118
|
unpriced: rollup.unpriced,
|
|
119
|
+
estimatedEntries: rollup.estimatedEntries || 0,
|
|
119
120
|
};
|
|
120
121
|
}
|
|
121
122
|
|
|
@@ -133,6 +134,7 @@ function hostedRollupToJson(rollup, fallbackLabel = "") {
|
|
|
133
134
|
? money(bag.customerCostUsd ?? bag.customer_cost_usd ?? bag.billableCostUsd ?? bag.billable_cost_usd) ?? 0
|
|
134
135
|
: null,
|
|
135
136
|
unpriced: nonNegativeInt(bag.unpriced),
|
|
137
|
+
estimatedEntries: nonNegativeInt(bag.estimatedEntries ?? bag.estimated_entries),
|
|
136
138
|
};
|
|
137
139
|
}
|
|
138
140
|
|
|
@@ -155,6 +157,7 @@ function entryToJson(entry) {
|
|
|
155
157
|
providerCostUsd: entry.providerCostUsd,
|
|
156
158
|
customerCostUsd: entry.customerCostUsd,
|
|
157
159
|
unpriced: entry.unpriced,
|
|
160
|
+
estimated: Boolean(entry.estimated),
|
|
158
161
|
};
|
|
159
162
|
}
|
|
160
163
|
|
|
@@ -181,6 +184,7 @@ function hostedEntryToJson(entry) {
|
|
|
181
184
|
providerCostUsd: money(bag.providerCostUsd ?? bag.provider_cost_usd ?? bag.costUsd ?? bag.cost_usd ?? bag.cost) ?? 0,
|
|
182
185
|
customerCostUsd: money(bag.customerCostUsd ?? bag.customer_cost_usd ?? bag.billableCostUsd ?? bag.billable_cost_usd),
|
|
183
186
|
unpriced: Boolean(bag.unpriced),
|
|
187
|
+
estimated: Boolean(bag.estimated),
|
|
184
188
|
};
|
|
185
189
|
}
|
|
186
190
|
|
|
@@ -214,8 +218,12 @@ export function buildSessionUsageReport({
|
|
|
214
218
|
sessionId = "",
|
|
215
219
|
events = [],
|
|
216
220
|
recentLimit = DEFAULT_RECENT_LIMIT,
|
|
221
|
+
estimateMessageUsage = true,
|
|
217
222
|
} = {}) {
|
|
218
|
-
const ledger = buildSessionUsageLedger(events, {
|
|
223
|
+
const ledger = buildSessionUsageLedger(events, {
|
|
224
|
+
sessionId: normalize(sessionId),
|
|
225
|
+
includeEstimatedMessages: estimateMessageUsage,
|
|
226
|
+
});
|
|
219
227
|
const limit = clampRecentLimit(recentLimit);
|
|
220
228
|
return {
|
|
221
229
|
schema: SESSION_USAGE_REPORT_SCHEMA,
|
|
@@ -230,6 +238,7 @@ export function buildSessionUsageReport({
|
|
|
230
238
|
providerCostUsd: ledger.totals.providerCostUsd,
|
|
231
239
|
customerCostUsd: ledger.totals.hasCustomerCost ? ledger.totals.customerCostUsd : null,
|
|
232
240
|
unpriced: ledger.totals.unpriced,
|
|
241
|
+
estimatedEntries: ledger.totals.estimatedEntries || 0,
|
|
233
242
|
priceBookVersions: ledger.priceBookVersions,
|
|
234
243
|
},
|
|
235
244
|
perAgent: sortRollups(ledger.perAgent).map(rollupToJson),
|
|
@@ -281,6 +290,7 @@ export function buildSessionUsageReportFromLedgerPayload({
|
|
|
281
290
|
providerCostUsd: totals.providerCostUsd,
|
|
282
291
|
customerCostUsd: totals.customerCostUsd,
|
|
283
292
|
unpriced: totals.unpriced,
|
|
293
|
+
estimatedEntries: nonNegativeInt(source.estimatedEntries ?? source.estimated_entries ?? sourceTotals.estimatedEntries ?? sourceTotals.estimated_entries),
|
|
284
294
|
priceBookVersions: resolvedPriceBookVersions,
|
|
285
295
|
},
|
|
286
296
|
perAgent: sourceArray(
|
|
@@ -343,7 +353,12 @@ export function renderSessionUsageMarkdown(report = {}) {
|
|
|
343
353
|
`Provider cost: ${usdCell(totals.providerCostUsd)} - Customer cost: ${optionalUsdCell(totals.customerCostUsd, totals.customerCostUsd != null)}`,
|
|
344
354
|
);
|
|
345
355
|
lines.push(`Price books: ${(totals.priceBookVersions || []).map(tableText).filter(Boolean).join(", ") || "-"}`);
|
|
346
|
-
lines.push(
|
|
356
|
+
lines.push(
|
|
357
|
+
`Duplicates skipped: ${intCell(totals.duplicatesSkipped)} - Unpriced entries: ${intCell(totals.unpriced)} - Estimated entries: ${intCell(totals.estimatedEntries)}`,
|
|
358
|
+
);
|
|
359
|
+
if (totals.estimatedEntries > 0) {
|
|
360
|
+
lines.push("_Estimated entries are output-text estimates from non-human session messages, not billing-grade provider usage._");
|
|
361
|
+
}
|
|
347
362
|
lines.push("");
|
|
348
363
|
|
|
349
364
|
appendRollupTable(lines, {
|
|
@@ -366,8 +381,9 @@ export function renderSessionUsageMarkdown(report = {}) {
|
|
|
366
381
|
lines.push(`| - | - | - | - | 0 | ${usdCell(0)} | - | - | - |`);
|
|
367
382
|
} else {
|
|
368
383
|
for (const entry of entries) {
|
|
384
|
+
const action = entry.estimated ? `${entry.action} (estimated)` : entry.action;
|
|
369
385
|
lines.push(
|
|
370
|
-
`| ${tableText(timestampOnly(entry.timestamp)) || "-"} | ${codeCell(entry.agentId)} | ${codeCell(
|
|
386
|
+
`| ${tableText(timestampOnly(entry.timestamp)) || "-"} | ${codeCell(entry.agentId)} | ${codeCell(action)} | ${codeCell(entry.model)} | ${intCell(entry.totalTokens)} | ${usdCell(entry.providerCostUsd)} | ${optionalUsdCell(entry.customerCostUsd, entry.customerCostUsd != null)} | ${codeCell(entry.ledgerEntryId)} | ${codeCell(entry.idempotencyKeyHash)} |`,
|
|
371
387
|
);
|
|
372
388
|
}
|
|
373
389
|
}
|
|
@@ -387,6 +403,9 @@ export function renderSessionUsageSummary(report = {}) {
|
|
|
387
403
|
lines.push(
|
|
388
404
|
`priceBooks=${(totals.priceBookVersions || []).map(tableText).filter(Boolean).join(",") || "-"} duplicatesSkipped=${intCell(totals.duplicatesSkipped)} unpriced=${intCell(totals.unpriced)}`,
|
|
389
405
|
);
|
|
406
|
+
if (totals.estimatedEntries > 0) {
|
|
407
|
+
lines.push(`estimatedEntries=${intCell(totals.estimatedEntries)} (output-text estimates; not billing-grade)`);
|
|
408
|
+
}
|
|
390
409
|
const agents = Array.isArray(report.perAgent) ? report.perAgent.slice(0, 8) : [];
|
|
391
410
|
if (agents.length) {
|
|
392
411
|
lines.push("");
|
|
@@ -403,7 +422,7 @@ export function renderSessionUsageSummary(report = {}) {
|
|
|
403
422
|
lines.push("Recent:");
|
|
404
423
|
for (const entry of entries) {
|
|
405
424
|
lines.push(
|
|
406
|
-
`- ${timestampOnly(entry.timestamp) || "-"} ${entry.agentId} ${entry.action} ${intCell(entry.totalTokens)} tokens ${usdCell(entry.providerCostUsd)} ledger=${entry.ledgerEntryId}`,
|
|
425
|
+
`- ${timestampOnly(entry.timestamp) || "-"} ${entry.agentId} ${entry.action}${entry.estimated ? " (estimated)" : ""} ${intCell(entry.totalTokens)} tokens ${usdCell(entry.providerCostUsd)} ledger=${entry.ledgerEntryId}`,
|
|
407
426
|
);
|
|
408
427
|
}
|
|
409
428
|
}
|
package/src/session/usage.js
CHANGED
|
@@ -241,8 +241,8 @@ export async function emitLLMInteraction(
|
|
|
241
241
|
* totals: { totalTokens, inputTokens, outputTokens, costUsd, interactions },
|
|
242
242
|
* }}
|
|
243
243
|
*/
|
|
244
|
-
export function aggregateSessionUsage(events = []) {
|
|
245
|
-
const ledger = buildSessionUsageLedger(events);
|
|
244
|
+
export function aggregateSessionUsage(events = [], options = {}) {
|
|
245
|
+
const ledger = buildSessionUsageLedger(events, options);
|
|
246
246
|
const perAgent = new Map();
|
|
247
247
|
const totals = {
|
|
248
248
|
totalTokens: 0,
|