@newrelic/preflight 1.67.3 → 1.69.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/README.md +2 -2
- package/dist/config.d.ts +75 -2
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +201 -22
- package/dist/config.js.map +1 -1
- package/dist/dashboard/routes/api-handler.d.ts +1 -0
- package/dist/dashboard/routes/api-handler.d.ts.map +1 -1
- package/dist/dashboard/routes/api-handler.js +120 -94
- package/dist/dashboard/routes/api-handler.js.map +1 -1
- package/dist/data/dashboards/ai-coding-assistant-adoption-cost.json +291 -1
- package/dist/data/dashboards/ai-coding-assistant-team-view.json +6 -6
- package/dist/hooks/billed-turn-ledger.d.ts +46 -0
- package/dist/hooks/billed-turn-ledger.d.ts.map +1 -0
- package/dist/hooks/billed-turn-ledger.js +274 -0
- package/dist/hooks/billed-turn-ledger.js.map +1 -0
- package/dist/hooks/collector-script.d.ts.map +1 -1
- package/dist/hooks/collector-script.js +5 -1
- package/dist/hooks/collector-script.js.map +1 -1
- package/dist/hooks/event-processor.d.ts +3 -7
- package/dist/hooks/event-processor.d.ts.map +1 -1
- package/dist/hooks/event-processor.js +21 -7
- package/dist/hooks/event-processor.js.map +1 -1
- package/dist/hooks/parent-transcript-watcher.d.ts +6 -5
- package/dist/hooks/parent-transcript-watcher.d.ts.map +1 -1
- package/dist/hooks/parent-transcript-watcher.js +64 -62
- package/dist/hooks/parent-transcript-watcher.js.map +1 -1
- package/dist/hooks/throttled-warning.d.ts +16 -0
- package/dist/hooks/throttled-warning.d.ts.map +1 -0
- package/dist/hooks/throttled-warning.js +32 -0
- package/dist/hooks/throttled-warning.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +90 -83
- package/dist/index.js.map +1 -1
- package/dist/install/cli.d.ts.map +1 -1
- package/dist/install/cli.js +69 -1
- package/dist/install/cli.js.map +1 -1
- package/dist/install/dashboard-health.js +1 -1
- package/dist/install/dashboard-health.js.map +1 -1
- package/dist/install/diagnostics.d.ts +2 -0
- package/dist/install/diagnostics.d.ts.map +1 -1
- package/dist/install/diagnostics.js +132 -28
- package/dist/install/diagnostics.js.map +1 -1
- package/dist/install/headless-install.d.ts +26 -0
- package/dist/install/headless-install.d.ts.map +1 -1
- package/dist/install/headless-install.js +44 -1
- package/dist/install/headless-install.js.map +1 -1
- package/dist/install/install-helper.d.ts +2 -0
- package/dist/install/install-helper.d.ts.map +1 -1
- package/dist/install/install-helper.js +5 -0
- package/dist/install/install-helper.js.map +1 -1
- package/dist/install/keychain.d.ts +18 -0
- package/dist/install/keychain.d.ts.map +1 -0
- package/dist/install/keychain.js +60 -0
- package/dist/install/keychain.js.map +1 -0
- package/dist/install/kiro-install-helper.d.ts +27 -0
- package/dist/install/kiro-install-helper.d.ts.map +1 -0
- package/dist/install/kiro-install-helper.js +53 -0
- package/dist/install/kiro-install-helper.js.map +1 -0
- package/dist/install/setup-wizard.d.ts +4 -0
- package/dist/install/setup-wizard.d.ts.map +1 -1
- package/dist/install/setup-wizard.js +94 -9
- package/dist/install/setup-wizard.js.map +1 -1
- package/dist/lib/child-env.d.ts +11 -0
- package/dist/lib/child-env.d.ts.map +1 -0
- package/dist/lib/child-env.js +35 -0
- package/dist/lib/child-env.js.map +1 -0
- package/dist/lib/git-remote.d.ts +7 -0
- package/dist/lib/git-remote.d.ts.map +1 -1
- package/dist/lib/git-remote.js +13 -0
- package/dist/lib/git-remote.js.map +1 -1
- package/dist/metrics/cost-tracker-seed.d.ts +16 -0
- package/dist/metrics/cost-tracker-seed.d.ts.map +1 -1
- package/dist/metrics/cost-tracker-seed.js +42 -7
- package/dist/metrics/cost-tracker-seed.js.map +1 -1
- package/dist/metrics/cost-tracker.d.ts +11 -3
- package/dist/metrics/cost-tracker.d.ts.map +1 -1
- package/dist/metrics/cost-tracker.js +4 -1
- package/dist/metrics/cost-tracker.js.map +1 -1
- package/dist/metrics/git-activity-recorder.d.ts +3 -3
- package/dist/metrics/git-activity-recorder.d.ts.map +1 -1
- package/dist/metrics/git-activity-recorder.js +3 -3
- package/dist/metrics/git-activity-recorder.js.map +1 -1
- package/dist/metrics/git-workspace-identity.d.ts +6 -1
- package/dist/metrics/git-workspace-identity.d.ts.map +1 -1
- package/dist/metrics/git-workspace-identity.js +56 -32
- package/dist/metrics/git-workspace-identity.js.map +1 -1
- package/dist/metrics/git-workspace-reporter.d.ts +4 -3
- package/dist/metrics/git-workspace-reporter.d.ts.map +1 -1
- package/dist/metrics/git-workspace-reporter.js +4 -3
- package/dist/metrics/git-workspace-reporter.js.map +1 -1
- package/dist/metrics/pricing-init.d.ts +13 -0
- package/dist/metrics/pricing-init.d.ts.map +1 -0
- package/dist/metrics/pricing-init.js +19 -0
- package/dist/metrics/pricing-init.js.map +1 -0
- package/dist/metrics/skill-activation-tracker.d.ts +27 -0
- package/dist/metrics/skill-activation-tracker.d.ts.map +1 -0
- package/dist/metrics/skill-activation-tracker.js +92 -0
- package/dist/metrics/skill-activation-tracker.js.map +1 -0
- package/dist/metrics/turn-cost-attributor.d.ts +34 -16
- package/dist/metrics/turn-cost-attributor.d.ts.map +1 -1
- package/dist/metrics/turn-cost-attributor.js +95 -60
- package/dist/metrics/turn-cost-attributor.js.map +1 -1
- package/dist/proxy/proxy-manager.d.ts +3 -0
- package/dist/proxy/proxy-manager.d.ts.map +1 -1
- package/dist/proxy/proxy-manager.js +43 -3
- package/dist/proxy/proxy-manager.js.map +1 -1
- package/dist/proxy/types.d.ts +7 -0
- package/dist/proxy/types.d.ts.map +1 -1
- package/dist/proxy/types.js.map +1 -1
- package/dist/proxy/upstream-http.d.ts.map +1 -1
- package/dist/proxy/upstream-http.js +44 -1
- package/dist/proxy/upstream-http.js.map +1 -1
- package/dist/proxy/upstream-stdio.d.ts +0 -1
- package/dist/proxy/upstream-stdio.d.ts.map +1 -1
- package/dist/proxy/upstream-stdio.js +2 -26
- package/dist/proxy/upstream-stdio.js.map +1 -1
- package/dist/redaction-patterns.d.ts.map +1 -1
- package/dist/redaction-patterns.js +1 -0
- package/dist/redaction-patterns.js.map +1 -1
- package/dist/secret-command.d.ts +59 -0
- package/dist/secret-command.d.ts.map +1 -0
- package/dist/secret-command.js +123 -0
- package/dist/secret-command.js.map +1 -0
- package/dist/security/audit-trail.d.ts +7 -1
- package/dist/security/audit-trail.d.ts.map +1 -1
- package/dist/security/audit-trail.js +24 -0
- package/dist/security/audit-trail.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +9 -4
- package/dist/server.js.map +1 -1
- package/dist/shared/pricing-data.d.ts.map +1 -1
- package/dist/shared/pricing-data.js +223 -32
- package/dist/shared/pricing-data.js.map +1 -1
- package/dist/shared/pricing.d.ts +25 -3
- package/dist/shared/pricing.d.ts.map +1 -1
- package/dist/shared/pricing.js +46 -9
- package/dist/shared/pricing.js.map +1 -1
- package/dist/shared/tokens.d.ts +16 -0
- package/dist/shared/tokens.d.ts.map +1 -1
- package/dist/shared/tokens.js +18 -0
- package/dist/shared/tokens.js.map +1 -1
- package/dist/shared/transport/otlp-shared.d.ts.map +1 -1
- package/dist/shared/transport/otlp-shared.js +7 -14
- package/dist/shared/transport/otlp-shared.js.map +1 -1
- package/dist/storage/local-store.d.ts +6 -4
- package/dist/storage/local-store.d.ts.map +1 -1
- package/dist/storage/local-store.js +21 -7
- package/dist/storage/local-store.js.map +1 -1
- package/dist/storage/session-store.d.ts +24 -1
- package/dist/storage/session-store.d.ts.map +1 -1
- package/dist/storage/session-store.js +38 -7
- package/dist/storage/session-store.js.map +1 -1
- package/dist/storage/types.d.ts +13 -0
- package/dist/storage/types.d.ts.map +1 -1
- package/dist/storage/types.js.map +1 -1
- package/dist/tools/cross-session-tools.d.ts +7 -1
- package/dist/tools/cross-session-tools.d.ts.map +1 -1
- package/dist/tools/cross-session-tools.js +47 -14
- package/dist/tools/cross-session-tools.js.map +1 -1
- package/dist/tools/session-stats.d.ts +6 -0
- package/dist/tools/session-stats.d.ts.map +1 -1
- package/dist/tools/session-stats.js +3 -2
- package/dist/tools/session-stats.js.map +1 -1
- package/dist/transport/audit-record-sender.d.ts +20 -0
- package/dist/transport/audit-record-sender.d.ts.map +1 -0
- package/dist/transport/audit-record-sender.js +54 -0
- package/dist/transport/audit-record-sender.js.map +1 -0
- package/dist/transport/nr-ingest.d.ts +7 -0
- package/dist/transport/nr-ingest.d.ts.map +1 -1
- package/dist/transport/nr-ingest.js +64 -27
- package/dist/transport/nr-ingest.js.map +1 -1
- package/dist/transport/tier-types.d.ts +11 -1
- package/dist/transport/tier-types.d.ts.map +1 -1
- package/dist/transport/tier-types.js +28 -6
- package/dist/transport/tier-types.js.map +1 -1
- package/dist/transport/unforwarded-session-monitor.d.ts +12 -6
- package/dist/transport/unforwarded-session-monitor.d.ts.map +1 -1
- package/dist/transport/unforwarded-session-monitor.js +24 -5
- package/dist/transport/unforwarded-session-monitor.js.map +1 -1
- package/dist/web/assets/{index-lvdXUntr.js → index-U9DPZ-Tb.js} +3 -3
- package/dist/web/index.html +1 -1
- package/examples/local-alert-rules.json +28 -0
- package/package.json +1 -1
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
-
import { normalizeDeveloperName, redactSensitive } from '../../config.js';
|
|
2
|
+
import { digestWebhookUrlOverrideSource, digestWebhookUrlOverride, normalizeDeveloperName, redactSensitive, } from '../../config.js';
|
|
3
3
|
import { isSyntheticSessionId, isUnscopedAggregatorSessionId, } from '../../hooks/session-resolver.js';
|
|
4
|
-
import { localDateKey, localStartOfDay,
|
|
4
|
+
import { localDateKey, localStartOfDay, todayPortionRatio } from '../../lib/date.js';
|
|
5
|
+
import { todayCostOfSummary } from '../../metrics/cost-tracker-seed.js';
|
|
5
6
|
import { AntiPatternDetector } from '../../metrics/anti-patterns.js';
|
|
6
7
|
import { computeContextMetricsFromEvents } from '../../metrics/context-tracker.js';
|
|
7
8
|
import { sumCallUsageInto } from '../../metrics/call-usage.js';
|
|
@@ -17,9 +18,9 @@ import { combineQualityProxyRawCounts, QualityProxyTracker, ZERO_QUALITY_PROXY_C
|
|
|
17
18
|
import { deriveSessionStatus, SESSION_STATUSES, } from '../../metrics/session-status.js';
|
|
18
19
|
import { toToolSelectionSummary } from '../../metrics/tool-selection-scorer.js';
|
|
19
20
|
import { computeUsageInsights } from '../../metrics/usage-insights.js';
|
|
20
|
-
import {
|
|
21
|
+
import { toPersistedAntiPatterns } from '../../storage/session-store.js';
|
|
21
22
|
import { getIsoWeekId } from '../../storage/weekly-summary.js';
|
|
22
|
-
import { handleSendDigest } from '../../tools/cross-session-tools.js';
|
|
23
|
+
import { digestWebhookUrlShadowedBy, handleSendDigest, isDigestWebhookCommandConfigured, } from '../../tools/cross-session-tools.js';
|
|
23
24
|
import { computeCacheHealth } from './cache-health-aggregate.js';
|
|
24
25
|
import { computeLatencyPercentiles } from './latency-percentiles.js';
|
|
25
26
|
import { analyzeReplayTimeline } from './replay-analyzer.js';
|
|
@@ -207,14 +208,15 @@ function mergeToolTypeCostEntry(existing, bucket) {
|
|
|
207
208
|
*/
|
|
208
209
|
function mergeSkillCostEntry(existing, bucket) {
|
|
209
210
|
const totalCost = (existing?.totalCost ?? 0) + bucket.costUsd;
|
|
211
|
+
// A persisted skill bucket's `count` is runs, not tool calls, and it keeps
|
|
212
|
+
// no attributed-call count, so it adds runs only; avgCost is per run on
|
|
213
|
+
// every path, matching SkillCostEntry.
|
|
210
214
|
const callCount = (existing?.callCount ?? 0) + bucket.count;
|
|
211
|
-
// A persisted bucket only ever holds attributed cost, so every call it counts is attributed.
|
|
212
|
-
const attributedCallCount = (existing?.attributedCallCount ?? 0) + bucket.count;
|
|
213
215
|
return {
|
|
214
216
|
callCount,
|
|
215
|
-
attributedCallCount,
|
|
217
|
+
attributedCallCount: existing?.attributedCallCount ?? 0,
|
|
216
218
|
totalCost,
|
|
217
|
-
avgCost:
|
|
219
|
+
avgCost: callCount > 0 ? totalCost / callCount : 0,
|
|
218
220
|
inputTokens: (existing?.inputTokens ?? 0) + (bucket.breakdown?.inputTokens ?? 0),
|
|
219
221
|
outputTokens: (existing?.outputTokens ?? 0) + (bucket.breakdown?.outputTokens ?? 0),
|
|
220
222
|
cacheReadTokens: (existing?.cacheReadTokens ?? 0) + (bucket.breakdown?.cacheReadTokens ?? 0),
|
|
@@ -789,7 +791,7 @@ function computeSessionStatusAggregate(input) {
|
|
|
789
791
|
trackLatestTool(session.sessionId, entry.toolName, entry.timestamp);
|
|
790
792
|
}
|
|
791
793
|
}
|
|
792
|
-
const identityResolver =
|
|
794
|
+
const { identityResolver } = input;
|
|
793
795
|
const activityStore = new ActivityStore();
|
|
794
796
|
const activityRecorder = new GitActivityRecorder(activityStore, identityResolver);
|
|
795
797
|
const bufferToolCalls = pairToolCallsFromBufferEvents(peeked).filter((record) => record.timestamp >= startMs);
|
|
@@ -846,6 +848,9 @@ export function createApiHandler(deps) {
|
|
|
846
848
|
// its identity resolution makes — is only ever paid once across every
|
|
847
849
|
// future /api/git-efficiency request, not on every poll.
|
|
848
850
|
const gitReplayCache = new ReplaySessionCache();
|
|
851
|
+
// Shared so each repo's identity (several `git` spawns) is resolved once per
|
|
852
|
+
// 30s TTL instead of once per request.
|
|
853
|
+
const gitIdentityResolver = new WorktreeIdentityResolver();
|
|
849
854
|
routes.set('GET /api/session/current', (_req, res) => {
|
|
850
855
|
if (!deps.sessionTracker)
|
|
851
856
|
return unavailable(res, 'sessionTracker');
|
|
@@ -1227,14 +1232,14 @@ export function createApiHandler(deps) {
|
|
|
1227
1232
|
// (costByDayUsd) let us sum a session's REAL today-spend instead of
|
|
1228
1233
|
// pro-rating its lifetime estimatedCostUsd by a timeline.
|
|
1229
1234
|
const todayKey = localDateKey(now);
|
|
1235
|
+
let liveCheckpointTodayUsd = 0;
|
|
1236
|
+
let liveCheckpointSubagentUsd = 0;
|
|
1237
|
+
let liveCheckpointCacheSavingsUsd = 0;
|
|
1238
|
+
let liveCheckpointCacheReadTokens = 0;
|
|
1239
|
+
let liveCheckpointCacheCreationTokens = 0;
|
|
1240
|
+
let liveCheckpointInputTokens = 0;
|
|
1230
1241
|
for (const raw of overlappingTodaySessions) {
|
|
1231
1242
|
const s = raw;
|
|
1232
|
-
// Skip the live session here — its today-portion is added below from
|
|
1233
|
-
// costTracker.getCostForDay()/getSubagentCostForDay(), which is more
|
|
1234
|
-
// accurate (per-token-event) than pro-rating from a periodically-
|
|
1235
|
-
// persisted snapshot.
|
|
1236
|
-
if (s.sessionId === liveSid)
|
|
1237
|
-
continue;
|
|
1238
1243
|
// Cross-midnight sessions that started before today aren't covered by
|
|
1239
1244
|
// the todaySessions timeline walk above (loadTodaySessions() filters
|
|
1240
1245
|
// by file-date = start date, so a session that started yesterday never
|
|
@@ -1250,43 +1255,35 @@ export function createApiHandler(deps) {
|
|
|
1250
1255
|
}
|
|
1251
1256
|
}
|
|
1252
1257
|
}
|
|
1253
|
-
// Cost attributed to TODAY
|
|
1254
|
-
//
|
|
1255
|
-
//
|
|
1256
|
-
//
|
|
1257
|
-
// session
|
|
1258
|
-
//
|
|
1259
|
-
//
|
|
1260
|
-
//
|
|
1261
|
-
//
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1258
|
+
// Cost attributed to TODAY: the persisted per-day bucket, else a guarded
|
|
1259
|
+
// pro-rate (see todayCostOfSummary). The same helper seeds CostTracker, so
|
|
1260
|
+
// a restart cannot change what a checkpoint contributes.
|
|
1261
|
+
//
|
|
1262
|
+
// The live session is held back: step (3) adds it as the larger of this
|
|
1263
|
+
// checkpoint figure and the live CostTracker's, so a tracker that was never
|
|
1264
|
+
// seeded (cwd-only adoption defers seeding, rehydrate can throw) cannot
|
|
1265
|
+
// drop the checkpoint's today cost, and a seeded one is not counted twice.
|
|
1266
|
+
// This is per-key max, the same rule mergeSummaries uses.
|
|
1267
|
+
const todayCost = todayCostOfSummary(s, todayKey, now);
|
|
1268
|
+
if (s.sessionId === liveSid) {
|
|
1269
|
+
liveCheckpointTodayUsd = todayCost.costUsd;
|
|
1270
|
+
liveCheckpointSubagentUsd = todayCost.subagentCostUsd;
|
|
1271
|
+
const liveRatio = todayPortionRatio(s, now);
|
|
1272
|
+
liveCheckpointCacheSavingsUsd = (s.cacheSavingsUsd ?? 0) * liveRatio;
|
|
1273
|
+
liveCheckpointCacheReadTokens = (s.tokensCacheRead ?? 0) * liveRatio;
|
|
1274
|
+
liveCheckpointCacheCreationTokens = (s.tokensCacheCreation ?? 0) * liveRatio;
|
|
1275
|
+
liveCheckpointInputTokens = (s.tokensInput ?? 0) * liveRatio;
|
|
1276
|
+
continue;
|
|
1277
|
+
}
|
|
1265
1278
|
sumCallUsageInto(unpricedByModel, s.unpricedByModel);
|
|
1266
1279
|
sumCallUsageInto(estimatedByModel, s.estimatedByModel);
|
|
1267
|
-
|
|
1280
|
+
totalCostUsd += todayCost.costUsd;
|
|
1281
|
+
// Subagent-only portion of the same session. This is what makes the KPI
|
|
1282
|
+
// cross-session: each `--stdio` process persists its own subagent cost, so
|
|
1283
|
+
// summing it here (rather than reading only THIS process's live
|
|
1284
|
+
// CostTracker) picks up subagent activity from any concurrent session.
|
|
1285
|
+
subagentUsd += todayCost.subagentCostUsd;
|
|
1268
1286
|
const ratio = todayPortionRatio(s, now);
|
|
1269
|
-
totalCostUsd +=
|
|
1270
|
-
s.costByDayUsd !== undefined
|
|
1271
|
-
? (s.costByDayUsd[todayKey] ?? 0)
|
|
1272
|
-
: hasNoAttributableActivity
|
|
1273
|
-
? 0
|
|
1274
|
-
: todayPortionOfSessionCost(s, now);
|
|
1275
|
-
// (2b) subagent-only portion of the same session. Day-bucket-first, else
|
|
1276
|
-
// pro-rate — applying the same phantom guard explicitly rather than
|
|
1277
|
-
// relying on hasNoAttributableActivity's subagentCostUsd===0 term to
|
|
1278
|
-
// zero this out implicitly (that held today, but shouldn't be a silent
|
|
1279
|
-
// invariant across two separate expressions). This is what makes the
|
|
1280
|
-
// KPI cross-session: each `--stdio` process persists its own subagent
|
|
1281
|
-
// cost, so summing it here (rather than reading only THIS process's
|
|
1282
|
-
// live CostTracker) picks up subagent activity from any concurrent
|
|
1283
|
-
// session.
|
|
1284
|
-
subagentUsd +=
|
|
1285
|
-
s.subagentCostByDayUsd !== undefined
|
|
1286
|
-
? (s.subagentCostByDayUsd[todayKey] ?? 0)
|
|
1287
|
-
: hasNoAttributableActivity
|
|
1288
|
-
? 0
|
|
1289
|
-
: (s.subagentCostUsd ?? 0) * ratio;
|
|
1290
1287
|
// Cache token/dollar sums below carry the same unguarded overlap
|
|
1291
1288
|
// characteristic subagentUsd/totalCostUsd already have on this line:
|
|
1292
1289
|
// a session that's concurrently live in ANOTHER process (so
|
|
@@ -1314,6 +1311,7 @@ export function createApiHandler(deps) {
|
|
|
1314
1311
|
}
|
|
1315
1312
|
const sessionStatus = computeSessionStatusAggregate({
|
|
1316
1313
|
replayCache: gitReplayCache,
|
|
1314
|
+
identityResolver: gitIdentityResolver,
|
|
1317
1315
|
sessionIds: sessionsSeen,
|
|
1318
1316
|
liveSet,
|
|
1319
1317
|
startMs,
|
|
@@ -1323,8 +1321,16 @@ export function createApiHandler(deps) {
|
|
|
1323
1321
|
});
|
|
1324
1322
|
// (3) include this MCP's live session today-portion. Per-day attribution
|
|
1325
1323
|
// comes from CostTracker, which buckets each token event by local-day at
|
|
1326
|
-
// record time (see CostTracker.accumulateTokens).
|
|
1327
|
-
//
|
|
1324
|
+
// record time (see CostTracker.accumulateTokens).
|
|
1325
|
+
//
|
|
1326
|
+
// The persisted loop above holds the live session back, so this top-up is
|
|
1327
|
+
// the only place its today cost is counted. It takes the larger of the
|
|
1328
|
+
// live tracker's figure and the session's own checkpoint figure, per key.
|
|
1329
|
+
// A seeded tracker already contains the checkpoint (equal, counted once),
|
|
1330
|
+
// and an unseeded one falls back to the checkpoint instead of losing it.
|
|
1331
|
+
// liveAlreadyPersisted separately gates the anti-pattern and efficiency
|
|
1332
|
+
// figures below, which the todaySessions walk counts for a checkpointed
|
|
1333
|
+
// session.
|
|
1328
1334
|
const liveAlreadyPersisted = todaySessions.some((s) => s.sessionId === liveSid);
|
|
1329
1335
|
// An unscoped aggregator (--local/proxy session id) runs its
|
|
1330
1336
|
// SubagentWatcher unscoped — its own live CostTracker may already hold
|
|
@@ -1335,7 +1341,7 @@ export function createApiHandler(deps) {
|
|
|
1335
1341
|
// `--stdio` session (including one still on a pending-* provisional id),
|
|
1336
1342
|
// whose live cost really is exclusively its own.
|
|
1337
1343
|
const liveIsUnscopedAggregator = isUnscopedAggregatorSessionId(liveSid);
|
|
1338
|
-
if (!
|
|
1344
|
+
if (!liveIsUnscopedAggregator) {
|
|
1339
1345
|
const liveMetrics = deps.costTracker?.getMetrics();
|
|
1340
1346
|
sumCallUsageInto(unpricedByModel, liveMetrics?.unpricedByModel);
|
|
1341
1347
|
sumCallUsageInto(estimatedByModel, liveMetrics?.estimatedByModel);
|
|
@@ -1343,8 +1349,9 @@ export function createApiHandler(deps) {
|
|
|
1343
1349
|
// persisted-session loop) so the persisted-loop and this live top-up can
|
|
1344
1350
|
// never split onto two independently-derived day keys.
|
|
1345
1351
|
const liveTodayUsd = deps.costTracker?.getCostForDay?.(todayKey) ?? null;
|
|
1352
|
+
let liveTodayCostUsd = 0;
|
|
1346
1353
|
if (typeof liveTodayUsd === 'number') {
|
|
1347
|
-
|
|
1354
|
+
liveTodayCostUsd = liveTodayUsd;
|
|
1348
1355
|
}
|
|
1349
1356
|
else {
|
|
1350
1357
|
// Fallback for older deployments without the per-day API. NEVER add the
|
|
@@ -1357,9 +1364,10 @@ export function createApiHandler(deps) {
|
|
|
1357
1364
|
const startedTs = deps.sessionTracker?.getMetrics().sessionStartTime ?? null;
|
|
1358
1365
|
const startedToday = typeof startedTs === 'number' && localDateKey(startedTs) === todayKey;
|
|
1359
1366
|
if (typeof sessionCost === 'number' && startedToday) {
|
|
1360
|
-
|
|
1367
|
+
liveTodayCostUsd = sessionCost;
|
|
1361
1368
|
}
|
|
1362
1369
|
}
|
|
1370
|
+
totalCostUsd += Math.max(liveTodayCostUsd, liveCheckpointTodayUsd);
|
|
1363
1371
|
// This process's own first-activity-today, covering the gap between
|
|
1364
1372
|
// "drained from the buffer" and "persisted to a session file" — the
|
|
1365
1373
|
// same gap liveTodayUsd above already covers for cost.
|
|
@@ -1369,44 +1377,45 @@ export function createApiHandler(deps) {
|
|
|
1369
1377
|
dailyFirstActivityMs = liveFirstActivityMs;
|
|
1370
1378
|
}
|
|
1371
1379
|
}
|
|
1372
|
-
// This MCP's own live subagent-for-day figure
|
|
1373
|
-
//
|
|
1374
|
-
// this
|
|
1375
|
-
//
|
|
1376
|
-
//
|
|
1377
|
-
subagentUsd += deps.costTracker?.getSubagentCostForDay?.(todayKey) ?? 0;
|
|
1378
|
-
// Cache top-up (dollars AND token counts) for this process's own live
|
|
1379
|
-
// not
|
|
1380
|
-
// no per-day equivalent (unlike
|
|
1381
|
-
// the startedToday guard the cost
|
|
1382
|
-
// same reason
|
|
1383
|
-
// today; a cross-midnight live
|
|
1384
|
-
// otherwise leak yesterday's activity
|
|
1385
|
-
//
|
|
1386
|
-
//
|
|
1387
|
-
//
|
|
1388
|
-
//
|
|
1389
|
-
//
|
|
1380
|
+
// This MCP's own live subagent-for-day figure. The (2b) loop above
|
|
1381
|
+
// summed every OTHER today session's persisted subagent cost and held
|
|
1382
|
+
// this session's back, so it joins here as the larger of the live
|
|
1383
|
+
// per-day CostTracker value and its own checkpoint (same reasoning as
|
|
1384
|
+
// the cost above).
|
|
1385
|
+
subagentUsd += Math.max(deps.costTracker?.getSubagentCostForDay?.(todayKey) ?? 0, liveCheckpointSubagentUsd);
|
|
1386
|
+
// Cache top-up (dollars AND token counts) for this process's own live
|
|
1387
|
+
// session, whether or not it has a checkpoint. These are
|
|
1388
|
+
// session-cumulative totals with no per-day equivalent (unlike
|
|
1389
|
+
// getCostForDay above), so, mirroring the startedToday guard the cost
|
|
1390
|
+
// fallback above needs for the exact same reason, only add them when
|
|
1391
|
+
// this live session actually started today; a cross-midnight live
|
|
1392
|
+
// session's cumulative totals would otherwise leak yesterday's activity
|
|
1393
|
+
// in too. They do not overlap step (1)'s buffer sum: the tracker holds
|
|
1394
|
+
// only events already drained from the buffer (plus the seeded
|
|
1395
|
+
// checkpoint), while peekAllBuffers() sees only the undrained tail. The
|
|
1396
|
+
// persisted loop skips this session's cache sums, so each token is
|
|
1397
|
+
// counted once. Token counts are topped up alongside the dollar figure
|
|
1398
|
+
// so totalSavingsUsd, totalCacheReadTokens, cacheHitRatePct and status
|
|
1399
|
+
// all describe the same activity.
|
|
1390
1400
|
const liveStartedTs = deps.sessionTracker?.getMetrics().sessionStartTime ?? null;
|
|
1391
1401
|
if (typeof liveStartedTs === 'number' && localDateKey(liveStartedTs) === todayKey) {
|
|
1392
1402
|
const liveCostMetrics = deps.costTracker?.getMetrics();
|
|
1393
|
-
cacheSavingsUsdSum += liveCostMetrics?.totalCacheSavingsUsd ?? 0;
|
|
1394
|
-
cacheReadTokensSum += liveCostMetrics?.totalCacheReadTokens ?? 0;
|
|
1395
|
-
cacheCreationTokensSum += liveCostMetrics?.totalCacheCreationTokens ?? 0;
|
|
1396
|
-
cacheInputTokensSum += liveCostMetrics?.totalInputTokens ?? 0;
|
|
1403
|
+
cacheSavingsUsdSum += Math.max(liveCostMetrics?.totalCacheSavingsUsd ?? 0, liveCheckpointCacheSavingsUsd);
|
|
1404
|
+
cacheReadTokensSum += Math.max(liveCostMetrics?.totalCacheReadTokens ?? 0, liveCheckpointCacheReadTokens);
|
|
1405
|
+
cacheCreationTokensSum += Math.max(liveCostMetrics?.totalCacheCreationTokens ?? 0, liveCheckpointCacheCreationTokens);
|
|
1406
|
+
cacheInputTokensSum += Math.max(liveCostMetrics?.totalInputTokens ?? 0, liveCheckpointInputTokens);
|
|
1397
1407
|
}
|
|
1398
1408
|
}
|
|
1399
|
-
// Live session anti-patterns (in-memory, not yet persisted).
|
|
1400
|
-
//
|
|
1401
|
-
//
|
|
1402
|
-
// the loop above — don't double-count.
|
|
1409
|
+
// Live session anti-patterns (in-memory, not yet persisted). If this MCP's
|
|
1410
|
+
// session is already in todaySessions, its checkpointed anti-patterns were
|
|
1411
|
+
// counted in the todaySessions walk above, so don't double-count.
|
|
1403
1412
|
if (deps.antiPatternDetector && !liveAlreadyPersisted) {
|
|
1404
1413
|
const live = deps.antiPatternDetector.getCurrentPatterns();
|
|
1405
1414
|
antiPatternCount += live.length;
|
|
1406
1415
|
}
|
|
1407
1416
|
// Efficiency KPI: average `efficiencyScore` across today's persisted sessions, plus
|
|
1408
1417
|
// this process's own live score when its own session isn't in that persisted set yet
|
|
1409
|
-
// (same liveAlreadyPersisted guard
|
|
1418
|
+
// (same liveAlreadyPersisted guard as the anti-patterns above).
|
|
1410
1419
|
let efficiencyScoreSum = 0;
|
|
1411
1420
|
let efficiencyScoreCount = 0;
|
|
1412
1421
|
for (const s of todaySessions) {
|
|
@@ -2057,12 +2066,11 @@ export function createApiHandler(deps) {
|
|
|
2057
2066
|
let historical = [];
|
|
2058
2067
|
const historicalIdentities = new Map();
|
|
2059
2068
|
if (deps.sessionStore?.loadAllSessions) {
|
|
2060
|
-
const identityResolver = new WorktreeIdentityResolver();
|
|
2061
2069
|
const sessions = deps.sessionStore.loadAllSessions({
|
|
2062
2070
|
since: new Date(since),
|
|
2063
2071
|
});
|
|
2064
2072
|
for (const session of sessions) {
|
|
2065
|
-
const replayed = gitReplayCache.replay(session,
|
|
2073
|
+
const replayed = gitReplayCache.replay(session, gitIdentityResolver);
|
|
2066
2074
|
historical = historical.concat(replayed.records);
|
|
2067
2075
|
for (const [key, identity] of replayed.identities) {
|
|
2068
2076
|
historicalIdentities.set(key, identity);
|
|
@@ -2134,16 +2142,18 @@ export function createApiHandler(deps) {
|
|
|
2134
2142
|
// stays comparable to `peak` below without re-filtering here.
|
|
2135
2143
|
const allSessions = deps.sessionStore?.loadAllSessions?.() ?? [];
|
|
2136
2144
|
const allTimePeak = computeTodayPeakConcurrency(allSessions);
|
|
2137
|
-
// Prefer
|
|
2138
|
-
//
|
|
2139
|
-
//
|
|
2140
|
-
//
|
|
2141
|
-
// clamps each session's
|
|
2142
|
-
//
|
|
2145
|
+
// Prefer loadSessionsWithActivityToday() so a session that started
|
|
2146
|
+
// earlier and is still running, or was resumed from an older start date
|
|
2147
|
+
// (keeping its original filename), isn't invisible to the chart —
|
|
2148
|
+
// loadTodaySessions() filters by filename date (= start date) and would
|
|
2149
|
+
// silently drop it. Downstream window-building clamps each session's
|
|
2150
|
+
// activity to [startTimestamp, dayEnd], so widening the input set here
|
|
2151
|
+
// can't leak earlier days' activity into today's buckets.
|
|
2143
2152
|
// loadAllSessions()/loadTodaySessions() already exclude synthetic session
|
|
2144
2153
|
// ids, so the chart buckets below agree with the session list shown
|
|
2145
2154
|
// alongside the chart without re-filtering here.
|
|
2146
|
-
const todaySessions = deps.sessionStore?.
|
|
2155
|
+
const todaySessions = deps.sessionStore?.loadSessionsWithActivityToday?.() ??
|
|
2156
|
+
deps.sessionStore?.loadSessionsOverlappingToday?.() ??
|
|
2147
2157
|
deps.sessionStore?.loadTodaySessions() ??
|
|
2148
2158
|
[];
|
|
2149
2159
|
// 96 × 15-minute fixed-grid buckets covering today (local midnight →
|
|
@@ -2344,6 +2354,8 @@ export function createApiHandler(deps) {
|
|
|
2344
2354
|
// storagePath, so the same path the MCP server writes to is always checked.
|
|
2345
2355
|
storagePath: deps.config?.storagePath,
|
|
2346
2356
|
platform: deps.getActivePlatform?.(),
|
|
2357
|
+
// The Settings page polls this route; never run secret helpers here.
|
|
2358
|
+
resolveSecrets: false,
|
|
2347
2359
|
});
|
|
2348
2360
|
jsonOk(res, checks);
|
|
2349
2361
|
});
|
|
@@ -2364,6 +2376,11 @@ export function createApiHandler(deps) {
|
|
|
2364
2376
|
/* config file may not exist yet — fall through to startup defaults */
|
|
2365
2377
|
}
|
|
2366
2378
|
}
|
|
2379
|
+
// Sends read the file at call time and skip its URL when a command is
|
|
2380
|
+
// configured there, so follow the disk rather than the startup snapshot.
|
|
2381
|
+
const overrideSource = digestWebhookUrlOverrideSource(c) ??
|
|
2382
|
+
(isDigestWebhookCommandConfigured(disk) ? 'command' : undefined);
|
|
2383
|
+
const fileWebhookUrl = typeof disk.digestWebhookUrl === 'string' ? disk.digestWebhookUrl : null;
|
|
2367
2384
|
const diskAlerts = (disk.alerts ?? {});
|
|
2368
2385
|
const diskPersonal = (diskAlerts['personal'] ?? {});
|
|
2369
2386
|
jsonOk(res, {
|
|
@@ -2376,7 +2393,10 @@ export function createApiHandler(deps) {
|
|
|
2376
2393
|
retainSessionsDays: 'retainSessionsDays' in disk
|
|
2377
2394
|
? disk.retainSessionsDays
|
|
2378
2395
|
: c.retainSessionsDays,
|
|
2379
|
-
digestWebhookUrl:
|
|
2396
|
+
digestWebhookUrl: fileWebhookUrl,
|
|
2397
|
+
// Where sends actually read the URL from. The value above is only the
|
|
2398
|
+
// file's; env or a command outranks it, and its value is never returned.
|
|
2399
|
+
digestWebhookUrlSource: overrideSource ?? (fileWebhookUrl ? 'file' : 'unset'),
|
|
2380
2400
|
digestSchedule: typeof disk.digestSchedule === 'string' ? disk.digestSchedule : c.digestSchedule,
|
|
2381
2401
|
alerts: {
|
|
2382
2402
|
personal: {
|
|
@@ -2494,10 +2514,16 @@ export function createApiHandler(deps) {
|
|
|
2494
2514
|
errors.push('digestWebhookUrl must be a Slack incoming webhook URL (https://hooks.slack.com/...) or null');
|
|
2495
2515
|
}
|
|
2496
2516
|
else {
|
|
2497
|
-
|
|
2498
|
-
if (
|
|
2517
|
+
const shadowedBy = body.digestWebhookUrl === null ? null : digestWebhookUrlShadowedBy(existing);
|
|
2518
|
+
if (shadowedBy) {
|
|
2519
|
+
errors.push(shadowedBy);
|
|
2520
|
+
}
|
|
2521
|
+
else if (body.digestWebhookUrl === null) {
|
|
2499
2522
|
delete existing.digestWebhookUrl;
|
|
2500
2523
|
}
|
|
2524
|
+
else {
|
|
2525
|
+
existing.digestWebhookUrl = body.digestWebhookUrl;
|
|
2526
|
+
}
|
|
2501
2527
|
}
|
|
2502
2528
|
}
|
|
2503
2529
|
if ('digestSchedule' in body) {
|
|
@@ -2575,7 +2601,7 @@ export function createApiHandler(deps) {
|
|
|
2575
2601
|
routes.set('POST /api/digest/send', async (_req, res) => {
|
|
2576
2602
|
if (!deps.weeklySummaryGenerator || !deps.configFilePath)
|
|
2577
2603
|
return unavailable(res, 'digest');
|
|
2578
|
-
const result = await handleSendDigest(deps.weeklySummaryGenerator, deps.configFilePath);
|
|
2604
|
+
const result = await handleSendDigest(deps.weeklySummaryGenerator, deps.configFilePath, deps.config && digestWebhookUrlOverride(deps.config));
|
|
2579
2605
|
jsonOk(res, result);
|
|
2580
2606
|
});
|
|
2581
2607
|
return async (req, res) => {
|