@newrelic/preflight 1.15.6 → 1.16.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/dist/config.d.ts +3 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +3 -0
- package/dist/config.js.map +1 -1
- package/dist/dashboard/dashboard-server.d.ts.map +1 -1
- package/dist/dashboard/dashboard-server.js +15 -0
- package/dist/dashboard/dashboard-server.js.map +1 -1
- package/dist/dashboard/routes/api-handler.d.ts +26 -18
- package/dist/dashboard/routes/api-handler.d.ts.map +1 -1
- package/dist/dashboard/routes/api-handler.js +23 -15
- package/dist/dashboard/routes/api-handler.js.map +1 -1
- package/dist/data/copilot-pricing/README.md +42 -0
- package/dist/data/copilot-pricing/pricing.json +54 -0
- package/dist/data/copilot-sdk-extension/extension.mjs +123 -0
- package/dist/deploy/data-paths.d.ts +6 -4
- package/dist/deploy/data-paths.d.ts.map +1 -1
- package/dist/deploy/data-paths.js +5 -3
- package/dist/deploy/data-paths.js.map +1 -1
- package/dist/hooks/collector-script.d.ts +12 -3
- package/dist/hooks/collector-script.d.ts.map +1 -1
- package/dist/hooks/collector-script.js +70 -18
- package/dist/hooks/collector-script.js.map +1 -1
- package/dist/hooks/copilot-sdk-usage-mapper.d.ts +45 -0
- package/dist/hooks/copilot-sdk-usage-mapper.d.ts.map +1 -0
- package/dist/hooks/copilot-sdk-usage-mapper.js +73 -0
- package/dist/hooks/copilot-sdk-usage-mapper.js.map +1 -0
- package/dist/hooks/copilot-usage-watcher.d.ts +109 -0
- package/dist/hooks/copilot-usage-watcher.d.ts.map +1 -0
- package/dist/hooks/copilot-usage-watcher.js +436 -0
- package/dist/hooks/copilot-usage-watcher.js.map +1 -0
- package/dist/index.d.ts +11 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +162 -70
- package/dist/index.js.map +1 -1
- package/dist/metrics/copilot-pricing-overlay.d.ts +42 -0
- package/dist/metrics/copilot-pricing-overlay.d.ts.map +1 -0
- package/dist/metrics/copilot-pricing-overlay.js +97 -0
- package/dist/metrics/copilot-pricing-overlay.js.map +1 -0
- package/dist/metrics/git-efficiency-tracker.d.ts +12 -1
- package/dist/metrics/git-efficiency-tracker.d.ts.map +1 -1
- package/dist/metrics/git-efficiency-tracker.js +18 -3
- package/dist/metrics/git-efficiency-tracker.js.map +1 -1
- package/dist/metrics/local-session-aggregator.d.ts +164 -0
- package/dist/metrics/local-session-aggregator.d.ts.map +1 -0
- package/dist/metrics/local-session-aggregator.js +444 -0
- package/dist/metrics/local-session-aggregator.js.map +1 -0
- package/dist/platforms/copilot-adapter.d.ts +1 -1
- package/dist/platforms/copilot-adapter.d.ts.map +1 -1
- package/dist/platforms/copilot-adapter.js +64 -10
- package/dist/platforms/copilot-adapter.js.map +1 -1
- package/dist/platforms/copilot-sdk-adapter.d.ts +24 -0
- package/dist/platforms/copilot-sdk-adapter.d.ts.map +1 -0
- package/dist/platforms/copilot-sdk-adapter.js +173 -0
- package/dist/platforms/copilot-sdk-adapter.js.map +1 -0
- package/dist/platforms/index.d.ts +1 -0
- package/dist/platforms/index.d.ts.map +1 -1
- package/dist/platforms/index.js +1 -0
- package/dist/platforms/index.js.map +1 -1
- package/dist/platforms/platform-registry.d.ts.map +1 -1
- package/dist/platforms/platform-registry.js +13 -11
- package/dist/platforms/platform-registry.js.map +1 -1
- package/dist/storage/session-store.d.ts +9 -0
- package/dist/storage/session-store.d.ts.map +1 -1
- package/dist/storage/session-store.js +213 -20
- package/dist/storage/session-store.js.map +1 -1
- package/dist/web/assets/{index-BasTEtUs.js → index-Bwc4qaqj.js} +1 -1
- package/dist/web/assets/index-DwdSz9gz.css +2 -0
- package/dist/web/index.html +2 -2
- package/package.json +2 -2
- package/dist/web/assets/index-BI-2ELf2.css +0 -2
package/dist/index.js
CHANGED
|
@@ -1,83 +1,86 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import 'dotenv/config';
|
|
3
|
+
import { Command } from 'commander';
|
|
3
4
|
import { readFileSync, realpathSync } from 'node:fs';
|
|
4
5
|
import { resolve } from 'node:path';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
6
|
+
import { AlertLog } from './alerts/alert-log.js';
|
|
7
|
+
import { AlertSnapshotCollector } from './alerts/alert-snapshot-collector.js';
|
|
8
|
+
import { LocalAlertEngine } from './alerts/local-alert-engine.js';
|
|
9
|
+
import { parseLocalAlertRules } from './alerts/local-alert-rule.js';
|
|
10
|
+
import { OsNotifier } from './alerts/os-notifier.js';
|
|
11
|
+
import { DEFAULT_STORAGE_PATH, loadMcpConfig } from './config.js';
|
|
12
|
+
import { DashboardServer } from './dashboard/dashboard-server.js';
|
|
13
|
+
import { LiveEventBus } from './dashboard/index.js';
|
|
14
|
+
import { SubagentTimelineStore } from './dashboard/subagent-timeline-store.js';
|
|
15
|
+
import { WorkflowStore } from './dashboard/workflow-store.js';
|
|
16
|
+
import { CopilotUsageWatcher } from './hooks/copilot-usage-watcher.js';
|
|
16
17
|
import { HookEventProcessor } from './hooks/index.js';
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
18
|
+
import { ParentTranscriptWatcher } from './hooks/parent-transcript-watcher.js';
|
|
19
|
+
import { isSyntheticSessionId, resolveFromBreadcrumb, resolveFromCwd, resolveFromJobDir, resolveSessionId, watchPpidBreadcrumb, } from './hooks/session-resolver.js';
|
|
20
|
+
import { SubagentWatcher } from './hooks/subagent-watcher.js';
|
|
21
|
+
import { WorkflowWatcher } from './hooks/workflow-watcher.js';
|
|
22
|
+
import { migrateStoragePath } from './install/migrate.js';
|
|
23
|
+
import { checkNodeVersion } from './install/node-version-check.js';
|
|
24
|
+
import { localDateKey, todayPortionOfSessionCost } from './lib/date.js';
|
|
24
25
|
import { AntiPatternDetector } from './metrics/anti-patterns.js';
|
|
25
|
-
import {
|
|
26
|
-
import {
|
|
27
|
-
import { CollaborationProfiler } from './metrics/collaboration-profile.js';
|
|
26
|
+
import { ApiFailureTracker } from './metrics/api-failure-tracker.js';
|
|
27
|
+
import { BudgetTracker } from './metrics/budget-tracker.js';
|
|
28
28
|
import { ClaudeMdTracker } from './metrics/claudemd-tracker.js';
|
|
29
|
-
import {
|
|
29
|
+
import { CollaborationProfiler } from './metrics/collaboration-profile.js';
|
|
30
|
+
import { ContextCompositionTracker } from './metrics/context-composition-tracker.js';
|
|
31
|
+
import { ContextTrackerRegistry } from './metrics/context-tracker.js';
|
|
32
|
+
import { ContextWindowTracker } from './metrics/context-window-tracker.js';
|
|
33
|
+
import { applyCopilotPricingOverlay } from './metrics/copilot-pricing-overlay.js';
|
|
34
|
+
import { buildCostForecastFromInputs } from './metrics/cost-forecast.js';
|
|
30
35
|
import { CostPerOutcomeAnalyzer } from './metrics/cost-per-outcome.js';
|
|
36
|
+
import { buildCostTrackerSeed } from './metrics/cost-tracker-seed.js';
|
|
37
|
+
import { CostTracker } from './metrics/cost-tracker.js';
|
|
38
|
+
import { DecisionTracker } from './metrics/decision-tracker.js';
|
|
39
|
+
import { EfficiencyScorer } from './metrics/efficiency-score.js';
|
|
40
|
+
import { GitEfficiencyTracker, parseDefaultBranchFromSymbolicRef, } from './metrics/git-efficiency-tracker.js';
|
|
41
|
+
import { InstructionDriftTracker } from './metrics/instruction-drift-tracker.js';
|
|
42
|
+
import { LatencyTracker } from './metrics/latency-tracker.js';
|
|
43
|
+
import { LiveSessionRegistry } from './metrics/live-session-registry.js';
|
|
44
|
+
import { collectCommitsAcrossRepos, LocalSessionAggregator, RepoNameResolver, resolveAuthorEmail, } from './metrics/local-session-aggregator.js';
|
|
45
|
+
import { ModelUsageTracker } from './metrics/model-usage-tracker.js';
|
|
31
46
|
import { PersonalCoach } from './metrics/personal-coach.js';
|
|
32
47
|
import { PromptFeedbackEngine } from './metrics/prompt-feedback.js';
|
|
48
|
+
import { QualityProxyTracker } from './metrics/quality-proxy-tracker.js';
|
|
33
49
|
import { RecommendationEngine } from './metrics/recommendation-engine.js';
|
|
34
|
-
import { ContextWindowTracker } from './metrics/context-window-tracker.js';
|
|
35
|
-
import { LatencyTracker } from './metrics/latency-tracker.js';
|
|
36
|
-
import { TaskCompletionTracker } from './metrics/task-completion-tracker.js';
|
|
37
|
-
import { ModelUsageTracker } from './metrics/model-usage-tracker.js';
|
|
38
50
|
import { RetryDetector } from './metrics/retry-detector.js';
|
|
39
|
-
import {
|
|
40
|
-
import {
|
|
41
|
-
import {
|
|
42
|
-
import {
|
|
51
|
+
import { SessionTracker } from './metrics/session-tracker.js';
|
|
52
|
+
import { TaskCompletionTracker } from './metrics/task-completion-tracker.js';
|
|
53
|
+
import { TaskDetector } from './metrics/task-detector.js';
|
|
54
|
+
import { buildTaskDetectorSeed } from './metrics/task-detector-seed.js';
|
|
43
55
|
import { ToolSelectionScorer } from './metrics/tool-selection-scorer.js';
|
|
44
|
-
import {
|
|
45
|
-
import {
|
|
46
|
-
import { LiveSessionRegistry } from './metrics/live-session-registry.js';
|
|
56
|
+
import { TranscriptMessageTracker } from './metrics/transcript-message-tracker.js';
|
|
57
|
+
import { TrendAnalyzer } from './metrics/trend-analyzer.js';
|
|
47
58
|
import { TurnCostAttributor } from './metrics/turn-cost-attributor.js';
|
|
48
59
|
import { TurnTracker } from './metrics/turn-tracker.js';
|
|
49
|
-
import { GitEfficiencyTracker, parseDefaultBranchFromSymbolicRef, } from './metrics/git-efficiency-tracker.js';
|
|
50
|
-
import { TranscriptMessageTracker } from './metrics/transcript-message-tracker.js';
|
|
51
60
|
import { WorkflowRunTracker } from './metrics/workflow-run-tracker.js';
|
|
52
|
-
import {
|
|
53
|
-
import {
|
|
54
|
-
import { ParentTranscriptWatcher } from './hooks/parent-transcript-watcher.js';
|
|
55
|
-
import { WorkflowStore } from './dashboard/workflow-store.js';
|
|
56
|
-
import { SubagentTimelineStore } from './dashboard/subagent-timeline-store.js';
|
|
57
|
-
import { NrIngestManager } from './transport/nr-ingest.js';
|
|
61
|
+
import { createDefaultRegistry, GenericMcpAdapter } from './platforms/index.js';
|
|
62
|
+
import { ProxyManager } from './proxy/index.js';
|
|
58
63
|
import { AuditTrailManager } from './security/audit-trail.js';
|
|
59
|
-
import {
|
|
60
|
-
import {
|
|
61
|
-
import {
|
|
62
|
-
import {
|
|
63
|
-
import {
|
|
64
|
-
import {
|
|
65
|
-
import {
|
|
66
|
-
import { localDateKey, todayPortionOfSessionCost } from './lib/date.js';
|
|
64
|
+
import { createServer } from './server.js';
|
|
65
|
+
import { createLogger } from './shared/index.js';
|
|
66
|
+
import { LocalStore } from './storage/index.js';
|
|
67
|
+
import { purgeOldSessions, purgeOldWeeklySummaries } from './storage/retention.js';
|
|
68
|
+
import { buildSessionSummary, SessionStore, sessionSummaryToDriftRecord, } from './storage/session-store.js';
|
|
69
|
+
import { WeeklySummaryGenerator } from './storage/weekly-summary.js';
|
|
70
|
+
import { registerPendingTools, registerTools } from './tools/session-stats.js';
|
|
67
71
|
import { FeedbackCollector } from './tools/workflow-tools.js';
|
|
68
|
-
import { registerTools, registerPendingTools } from './tools/session-stats.js';
|
|
69
|
-
import { resolveSessionId, resolveFromJobDir, resolveFromBreadcrumb, resolveFromCwd, isSyntheticSessionId, watchPpidBreadcrumb, } from './hooks/session-resolver.js';
|
|
70
72
|
import { initMcpTracer } from './tracing/mcp-tracer.js';
|
|
71
73
|
import { SessionSpan } from './tracing/session-span.js';
|
|
72
74
|
import { TaskSpanTracker } from './tracing/task-span-tracker.js';
|
|
73
75
|
import { emitToolCallSpan } from './tracing/tool-call-span.js';
|
|
74
|
-
import {
|
|
75
|
-
|
|
76
|
-
export { NrMcpServer, createServer } from './server.js';
|
|
76
|
+
import { NrIngestManager } from './transport/nr-ingest.js';
|
|
77
|
+
import { VERSION } from './version.js';
|
|
77
78
|
export { loadMcpConfig, redactSensitive } from './config.js';
|
|
78
|
-
export {
|
|
79
|
+
export { AmazonQAdapter, ClaudeCodeAdapter, ContinueAdapter, CopilotAdapter, createDefaultRegistry, CursorAdapter, GenericMcpAdapter, parseCopilotUsageResponse, PlatformRegistry, REPORT_SESSION_END_TOOL, REPORT_SESSION_START_TOOL, REPORT_TOOL_CALL_TOOL, validateReportToolCallInput, WindsurfAdapter, ZedAdapter, } from './platforms/index.js';
|
|
79
80
|
export { ProxyManager } from './proxy/index.js';
|
|
80
|
-
export {
|
|
81
|
+
export { createServer, NrMcpServer } from './server.js';
|
|
82
|
+
export { LocalStore } from './storage/index.js';
|
|
83
|
+
export { VERSION };
|
|
81
84
|
const logger = createLogger('mcp-cli');
|
|
82
85
|
// Show first-4 and last-4 chars of a credential. Guards against short values
|
|
83
86
|
// (e.g. test stubs) that would otherwise expose the full secret.
|
|
@@ -327,6 +330,7 @@ const SUBCOMMAND_NAMES = [
|
|
|
327
330
|
'update',
|
|
328
331
|
'schedule',
|
|
329
332
|
'doctor',
|
|
333
|
+
'local',
|
|
330
334
|
];
|
|
331
335
|
// Install-CLI subcommands — a subset of SUBCOMMAND_NAMES routed to runInstallCli.
|
|
332
336
|
// Derived from SUBCOMMAND_NAMES to ensure a single source of truth: deploy-*
|
|
@@ -542,6 +546,7 @@ async function main() {
|
|
|
542
546
|
let activeSubagentWatcher = null;
|
|
543
547
|
let activeWorkflowWatcher = null;
|
|
544
548
|
let activeParentTranscriptWatcher = null;
|
|
549
|
+
let activeCopilotUsageWatcher = null;
|
|
545
550
|
// Aborts the async resolveSessionId polling loop when shutdown fires so
|
|
546
551
|
// the breadcrumb poll does not outlive the process.
|
|
547
552
|
let sessionResolutionAbort;
|
|
@@ -634,6 +639,7 @@ async function main() {
|
|
|
634
639
|
activeSubagentWatcher?.stop();
|
|
635
640
|
activeWorkflowWatcher?.stop();
|
|
636
641
|
activeParentTranscriptWatcher?.stop();
|
|
642
|
+
activeCopilotUsageWatcher?.stop();
|
|
637
643
|
liveSessionRegistry?.stopSampling();
|
|
638
644
|
// Use allSettled so a failure in one stop() doesn't prevent the others.
|
|
639
645
|
const stopResults = await Promise.allSettled([
|
|
@@ -696,6 +702,7 @@ async function main() {
|
|
|
696
702
|
await mcpServer.close();
|
|
697
703
|
process.exit(0);
|
|
698
704
|
}
|
|
705
|
+
applyCopilotPricingOverlay(config.customPricingFile);
|
|
699
706
|
const fromJobDir = resolveFromJobDir(process.env.CLAUDE_JOB_DIR ?? null);
|
|
700
707
|
const fromPpid = fromJobDir ? null : resolveFromBreadcrumb(config.storagePath, process.ppid);
|
|
701
708
|
const fromCwd = fromJobDir || fromPpid ? null : resolveFromCwd(config.storagePath, process.cwd());
|
|
@@ -736,6 +743,7 @@ async function main() {
|
|
|
736
743
|
logger.info('Server disabled via config — exiting');
|
|
737
744
|
process.exit(0);
|
|
738
745
|
}
|
|
746
|
+
applyCopilotPricingOverlay(config.customPricingFile);
|
|
739
747
|
// --local has no owning Claude Code session — derive a deterministic
|
|
740
748
|
// identifier so the rest of the codebase can rely on a non-empty
|
|
741
749
|
// sessionTraceId without fabricating a UUID.
|
|
@@ -862,6 +870,13 @@ async function main() {
|
|
|
862
870
|
sessionStore = new SessionStore({ storagePath: config.storagePath });
|
|
863
871
|
const currentSessionId = sessionTracker.getMetrics().sessionId;
|
|
864
872
|
let currentRepoName = null;
|
|
873
|
+
// An unscoped process (--local, or a provisional --stdio window) drains
|
|
874
|
+
// every unowned buffer and folds it into trackers keyed by its own
|
|
875
|
+
// synthetic id, which persistSession() skips — so without this rollup the
|
|
876
|
+
// sessions it observes are never written to disk at all. See
|
|
877
|
+
// local-session-aggregator.ts for why that hits Copilot but not Claude Code.
|
|
878
|
+
const localSessionAggregator = new LocalSessionAggregator();
|
|
879
|
+
const repoNameResolver = new RepoNameResolver();
|
|
865
880
|
const budgetTracker = new BudgetTracker({
|
|
866
881
|
sessionBudgetUsd: config.sessionBudgetUsd,
|
|
867
882
|
dailyBudgetUsd: config.dailyBudgetUsd,
|
|
@@ -992,19 +1007,45 @@ async function main() {
|
|
|
992
1007
|
// repo history to get an accurate commit count for today.
|
|
993
1008
|
// spawnSync with ENOENT doesn't throw — it returns { status: null, error: Error }.
|
|
994
1009
|
// The status === 0 guard handles unavailable-git without a try/catch.
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1010
|
+
//
|
|
1011
|
+
// Repos are collected from an explicit NR_AI_GIT_REPOS list plus every repo
|
|
1012
|
+
// root seen in observed session cwds, rather than relying on this process's
|
|
1013
|
+
// own cwd. A dashboard started in one repo would otherwise report that
|
|
1014
|
+
// repo's history no matter which repos are actually being worked in — and,
|
|
1015
|
+
// with no --author filter, count other contributors' commits as the user's.
|
|
1016
|
+
const gitAuthorEmail = resolveAuthorEmail(process.cwd());
|
|
1017
|
+
const configuredRepoRoots = (process.env.NR_AI_GIT_REPOS ?? '')
|
|
1018
|
+
.split(/[,:]/)
|
|
1019
|
+
.map((entry) => entry.trim())
|
|
1020
|
+
.filter(Boolean);
|
|
1021
|
+
const collectRepoRoots = () => {
|
|
1022
|
+
const roots = new Set();
|
|
1023
|
+
const selfRoot = repoNameResolver.root(process.cwd());
|
|
1024
|
+
if (selfRoot)
|
|
1025
|
+
roots.add(selfRoot);
|
|
1026
|
+
for (const dir of configuredRepoRoots) {
|
|
1027
|
+
const root = repoNameResolver.root(dir);
|
|
1028
|
+
if (root)
|
|
1029
|
+
roots.add(root);
|
|
1030
|
+
}
|
|
1031
|
+
for (const cwd of localSessionAggregator.cwds()) {
|
|
1032
|
+
const root = repoNameResolver.root(cwd);
|
|
1033
|
+
if (root)
|
|
1034
|
+
roots.add(root);
|
|
1035
|
+
}
|
|
1036
|
+
return [...roots];
|
|
1037
|
+
};
|
|
1038
|
+
const hydrateGitCommits = () => {
|
|
1039
|
+
// Recomputed per call so a long-lived dashboard rolls over at midnight
|
|
1040
|
+
// instead of reporting "today" relative to the day it was started.
|
|
1041
|
+
const since = new Date().toISOString().slice(0, 10);
|
|
1042
|
+
const commits = collectCommitsAcrossRepos(collectRepoRoots(), since, gitAuthorEmail);
|
|
1043
|
+
if (commits.length > 0)
|
|
1044
|
+
gitEfficiencyTracker.hydrateGitLog(commits);
|
|
1045
|
+
};
|
|
1046
|
+
hydrateGitCommits();
|
|
1047
|
+
const gitHydrationInterval = setInterval(hydrateGitCommits, 5 * 60_000);
|
|
1048
|
+
gitHydrationInterval.unref();
|
|
1008
1049
|
// Branch divergence from the real default branch — how far ahead/behind
|
|
1009
1050
|
// are we? The dashboard polls this every 5s, implying a live number, but
|
|
1010
1051
|
// without this re-hydration it would otherwise be computed once here at
|
|
@@ -1314,6 +1355,7 @@ async function main() {
|
|
|
1314
1355
|
watcherDisabledByLock: stats?.watcherDisabledByLock ?? false,
|
|
1315
1356
|
costSelfCheckDeltaPct: null,
|
|
1316
1357
|
watcherDisabledReason,
|
|
1358
|
+
copilotDebugLoggingDisabled: activeCopilotUsageWatcher?.getHealth().debugLoggingLikelyDisabled ?? false,
|
|
1317
1359
|
};
|
|
1318
1360
|
},
|
|
1319
1361
|
},
|
|
@@ -1462,6 +1504,7 @@ async function main() {
|
|
|
1462
1504
|
const taskIdBeforeRecord = config.otlp.transport !== 'nr-events-api' ? taskDetector.getActiveTaskId() : null;
|
|
1463
1505
|
sessionTracker.recordToolCall(rawRecord);
|
|
1464
1506
|
taskDetector.recordToolCall(rawRecord);
|
|
1507
|
+
localSessionAggregator.recordToolCall(rawRecord);
|
|
1465
1508
|
if (rawRecord.sessionId) {
|
|
1466
1509
|
liveSessionRegistry.touch(rawRecord.sessionId, rawRecord.cwd);
|
|
1467
1510
|
}
|
|
@@ -1660,6 +1703,15 @@ async function main() {
|
|
|
1660
1703
|
timestampMs: tokenEvent.timestamp,
|
|
1661
1704
|
});
|
|
1662
1705
|
modelUsageTracker.recordUsage(tokenEvent.model, tokenEvent.inputTokens, tokenEvent.outputTokens, breakdown.totalUsd);
|
|
1706
|
+
localSessionAggregator.recordTokenUsage(tokenEvent.sessionId, {
|
|
1707
|
+
timestamp: tokenEvent.timestamp,
|
|
1708
|
+
costUsd: breakdown.totalUsd,
|
|
1709
|
+
model: tokenEvent.model,
|
|
1710
|
+
inputTokens: tokenEvent.inputTokens,
|
|
1711
|
+
outputTokens: tokenEvent.outputTokens,
|
|
1712
|
+
cacheReadTokens: tokenEvent.cacheReadTokens,
|
|
1713
|
+
cacheCreationTokens: tokenEvent.cacheCreationTokens,
|
|
1714
|
+
});
|
|
1663
1715
|
contextCompositionTracker.recordTokenEvent(tokenEvent);
|
|
1664
1716
|
const ctxSnapshot = contextTracker.recordTurn(tokenEvent);
|
|
1665
1717
|
if (ctxSnapshot && tokenEvent.sessionId) {
|
|
@@ -1754,6 +1806,10 @@ async function main() {
|
|
|
1754
1806
|
workflowRunId: turn.workflowRunId,
|
|
1755
1807
|
agentId: turn.agentId,
|
|
1756
1808
|
});
|
|
1809
|
+
// Subagent turns are real model requests and cost real money, so they
|
|
1810
|
+
// belong in the model breakdown too — recording them only in the cost
|
|
1811
|
+
// tracker left Model Usage blind to every subagent-only session.
|
|
1812
|
+
modelUsageTracker.recordUsage(turn.model, turn.inputTokens, turn.outputTokens, breakdown.totalUsd);
|
|
1757
1813
|
// Pricing miss → usd:null on the wire; we recompute here so
|
|
1758
1814
|
// the breakdown view distinguishes "0 because pricing absent" from
|
|
1759
1815
|
// "0 because the turn truly had zero cost".
|
|
@@ -1832,9 +1888,24 @@ async function main() {
|
|
|
1832
1888
|
// real session id is still being resolved).
|
|
1833
1889
|
const isSyntheticId = isSyntheticSessionId(summary.sessionId);
|
|
1834
1890
|
if (isSyntheticId) {
|
|
1891
|
+
// This process owns no real session of its own, but it has been
|
|
1892
|
+
// draining other sessions' buffers destructively — so if it returns
|
|
1893
|
+
// here without writing them, those events are simply lost. Persist
|
|
1894
|
+
// each real session it observed instead.
|
|
1895
|
+
const rollups = localSessionAggregator.toSummaries({
|
|
1896
|
+
developer: config.developer ?? 'unknown',
|
|
1897
|
+
platform: eventProcessor?.activePlatform,
|
|
1898
|
+
outcome: opts?.periodic ? 'in progress' : 'completed',
|
|
1899
|
+
toolSelectionScorer,
|
|
1900
|
+
repoResolver: repoNameResolver,
|
|
1901
|
+
});
|
|
1902
|
+
for (const rollup of rollups) {
|
|
1903
|
+
sessionStore.saveSession(rollup);
|
|
1904
|
+
}
|
|
1835
1905
|
if (!opts?.periodic) {
|
|
1836
|
-
logger.info('
|
|
1906
|
+
logger.info('Persisted observed sessions on behalf of synthetic owner', {
|
|
1837
1907
|
sessionId: summary.sessionId,
|
|
1908
|
+
persistedSessions: rollups.length,
|
|
1838
1909
|
});
|
|
1839
1910
|
}
|
|
1840
1911
|
return;
|
|
@@ -1905,6 +1976,12 @@ async function main() {
|
|
|
1905
1976
|
// why. Do not "fix" this to match the other two; that would reintroduce
|
|
1906
1977
|
// the exact regression this divergence avoids.
|
|
1907
1978
|
const parentTranscriptWatcherEnabled = process.env['NR_AI_ENABLE_PARENT_TRANSCRIPT_WATCHER'] !== '0';
|
|
1979
|
+
// CopilotUsageWatcher is the Copilot analog of ParentTranscriptWatcher
|
|
1980
|
+
// (token-exact cost from VS Code's Copilot debug logs) and follows the
|
|
1981
|
+
// same always-run rationale: it feeds the primary cost signal, so it is
|
|
1982
|
+
// gated only by its own opt-out. It no-ops cheaply when no VS Code
|
|
1983
|
+
// workspaceStorage dirs exist.
|
|
1984
|
+
const copilotUsageWatcherEnabled = process.env['NR_AI_ENABLE_COPILOT_USAGE_WATCHER'] !== '0';
|
|
1908
1985
|
// Construct + start the watchers for a given session id. In `--stdio` mode
|
|
1909
1986
|
// the watchers filter discovered transcript dirs by `parentSessionId`; in
|
|
1910
1987
|
// `--local` mode they run unfiltered (parentSessionId: undefined). Shared by
|
|
@@ -1936,6 +2013,17 @@ async function main() {
|
|
|
1936
2013
|
parentSessionId: isStdioWatcher ? watcherSessionId : null,
|
|
1937
2014
|
});
|
|
1938
2015
|
}
|
|
2016
|
+
if (copilotUsageWatcherEnabled) {
|
|
2017
|
+
activeCopilotUsageWatcher = new CopilotUsageWatcher({
|
|
2018
|
+
storagePath: config.storagePath,
|
|
2019
|
+
parentSessionId: isStdioWatcher ? watcherSessionId : undefined,
|
|
2020
|
+
localStore,
|
|
2021
|
+
});
|
|
2022
|
+
activeCopilotUsageWatcher.start();
|
|
2023
|
+
logger.info('CopilotUsageWatcher started', {
|
|
2024
|
+
parentSessionId: isStdioWatcher ? watcherSessionId : null,
|
|
2025
|
+
});
|
|
2026
|
+
}
|
|
1939
2027
|
if (watcherShouldRun && subagentWatcherEnabled) {
|
|
1940
2028
|
activeSubagentWatcher = new SubagentWatcher({
|
|
1941
2029
|
storagePath: config.storagePath,
|
|
@@ -2006,6 +2094,10 @@ async function main() {
|
|
|
2006
2094
|
activeParentTranscriptWatcher.stop();
|
|
2007
2095
|
activeParentTranscriptWatcher = null;
|
|
2008
2096
|
}
|
|
2097
|
+
if (activeCopilotUsageWatcher) {
|
|
2098
|
+
activeCopilotUsageWatcher.stop();
|
|
2099
|
+
activeCopilotUsageWatcher = null;
|
|
2100
|
+
}
|
|
2009
2101
|
if (activeSubagentWatcher) {
|
|
2010
2102
|
activeSubagentWatcher.stop();
|
|
2011
2103
|
activeSubagentWatcher = null;
|