@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.
Files changed (183) hide show
  1. package/README.md +2 -2
  2. package/dist/config.d.ts +75 -2
  3. package/dist/config.d.ts.map +1 -1
  4. package/dist/config.js +201 -22
  5. package/dist/config.js.map +1 -1
  6. package/dist/dashboard/routes/api-handler.d.ts +1 -0
  7. package/dist/dashboard/routes/api-handler.d.ts.map +1 -1
  8. package/dist/dashboard/routes/api-handler.js +120 -94
  9. package/dist/dashboard/routes/api-handler.js.map +1 -1
  10. package/dist/data/dashboards/ai-coding-assistant-adoption-cost.json +291 -1
  11. package/dist/data/dashboards/ai-coding-assistant-team-view.json +6 -6
  12. package/dist/hooks/billed-turn-ledger.d.ts +46 -0
  13. package/dist/hooks/billed-turn-ledger.d.ts.map +1 -0
  14. package/dist/hooks/billed-turn-ledger.js +274 -0
  15. package/dist/hooks/billed-turn-ledger.js.map +1 -0
  16. package/dist/hooks/collector-script.d.ts.map +1 -1
  17. package/dist/hooks/collector-script.js +5 -1
  18. package/dist/hooks/collector-script.js.map +1 -1
  19. package/dist/hooks/event-processor.d.ts +3 -7
  20. package/dist/hooks/event-processor.d.ts.map +1 -1
  21. package/dist/hooks/event-processor.js +21 -7
  22. package/dist/hooks/event-processor.js.map +1 -1
  23. package/dist/hooks/parent-transcript-watcher.d.ts +6 -5
  24. package/dist/hooks/parent-transcript-watcher.d.ts.map +1 -1
  25. package/dist/hooks/parent-transcript-watcher.js +64 -62
  26. package/dist/hooks/parent-transcript-watcher.js.map +1 -1
  27. package/dist/hooks/throttled-warning.d.ts +16 -0
  28. package/dist/hooks/throttled-warning.d.ts.map +1 -0
  29. package/dist/hooks/throttled-warning.js +32 -0
  30. package/dist/hooks/throttled-warning.js.map +1 -0
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +90 -83
  33. package/dist/index.js.map +1 -1
  34. package/dist/install/cli.d.ts.map +1 -1
  35. package/dist/install/cli.js +69 -1
  36. package/dist/install/cli.js.map +1 -1
  37. package/dist/install/dashboard-health.js +1 -1
  38. package/dist/install/dashboard-health.js.map +1 -1
  39. package/dist/install/diagnostics.d.ts +2 -0
  40. package/dist/install/diagnostics.d.ts.map +1 -1
  41. package/dist/install/diagnostics.js +132 -28
  42. package/dist/install/diagnostics.js.map +1 -1
  43. package/dist/install/headless-install.d.ts +26 -0
  44. package/dist/install/headless-install.d.ts.map +1 -1
  45. package/dist/install/headless-install.js +44 -1
  46. package/dist/install/headless-install.js.map +1 -1
  47. package/dist/install/install-helper.d.ts +2 -0
  48. package/dist/install/install-helper.d.ts.map +1 -1
  49. package/dist/install/install-helper.js +5 -0
  50. package/dist/install/install-helper.js.map +1 -1
  51. package/dist/install/keychain.d.ts +18 -0
  52. package/dist/install/keychain.d.ts.map +1 -0
  53. package/dist/install/keychain.js +60 -0
  54. package/dist/install/keychain.js.map +1 -0
  55. package/dist/install/kiro-install-helper.d.ts +27 -0
  56. package/dist/install/kiro-install-helper.d.ts.map +1 -0
  57. package/dist/install/kiro-install-helper.js +53 -0
  58. package/dist/install/kiro-install-helper.js.map +1 -0
  59. package/dist/install/setup-wizard.d.ts +4 -0
  60. package/dist/install/setup-wizard.d.ts.map +1 -1
  61. package/dist/install/setup-wizard.js +94 -9
  62. package/dist/install/setup-wizard.js.map +1 -1
  63. package/dist/lib/child-env.d.ts +11 -0
  64. package/dist/lib/child-env.d.ts.map +1 -0
  65. package/dist/lib/child-env.js +35 -0
  66. package/dist/lib/child-env.js.map +1 -0
  67. package/dist/lib/git-remote.d.ts +7 -0
  68. package/dist/lib/git-remote.d.ts.map +1 -1
  69. package/dist/lib/git-remote.js +13 -0
  70. package/dist/lib/git-remote.js.map +1 -1
  71. package/dist/metrics/cost-tracker-seed.d.ts +16 -0
  72. package/dist/metrics/cost-tracker-seed.d.ts.map +1 -1
  73. package/dist/metrics/cost-tracker-seed.js +42 -7
  74. package/dist/metrics/cost-tracker-seed.js.map +1 -1
  75. package/dist/metrics/cost-tracker.d.ts +11 -3
  76. package/dist/metrics/cost-tracker.d.ts.map +1 -1
  77. package/dist/metrics/cost-tracker.js +4 -1
  78. package/dist/metrics/cost-tracker.js.map +1 -1
  79. package/dist/metrics/git-activity-recorder.d.ts +3 -3
  80. package/dist/metrics/git-activity-recorder.d.ts.map +1 -1
  81. package/dist/metrics/git-activity-recorder.js +3 -3
  82. package/dist/metrics/git-activity-recorder.js.map +1 -1
  83. package/dist/metrics/git-workspace-identity.d.ts +6 -1
  84. package/dist/metrics/git-workspace-identity.d.ts.map +1 -1
  85. package/dist/metrics/git-workspace-identity.js +56 -32
  86. package/dist/metrics/git-workspace-identity.js.map +1 -1
  87. package/dist/metrics/git-workspace-reporter.d.ts +4 -3
  88. package/dist/metrics/git-workspace-reporter.d.ts.map +1 -1
  89. package/dist/metrics/git-workspace-reporter.js +4 -3
  90. package/dist/metrics/git-workspace-reporter.js.map +1 -1
  91. package/dist/metrics/pricing-init.d.ts +13 -0
  92. package/dist/metrics/pricing-init.d.ts.map +1 -0
  93. package/dist/metrics/pricing-init.js +19 -0
  94. package/dist/metrics/pricing-init.js.map +1 -0
  95. package/dist/metrics/skill-activation-tracker.d.ts +27 -0
  96. package/dist/metrics/skill-activation-tracker.d.ts.map +1 -0
  97. package/dist/metrics/skill-activation-tracker.js +92 -0
  98. package/dist/metrics/skill-activation-tracker.js.map +1 -0
  99. package/dist/metrics/turn-cost-attributor.d.ts +34 -16
  100. package/dist/metrics/turn-cost-attributor.d.ts.map +1 -1
  101. package/dist/metrics/turn-cost-attributor.js +95 -60
  102. package/dist/metrics/turn-cost-attributor.js.map +1 -1
  103. package/dist/proxy/proxy-manager.d.ts +3 -0
  104. package/dist/proxy/proxy-manager.d.ts.map +1 -1
  105. package/dist/proxy/proxy-manager.js +43 -3
  106. package/dist/proxy/proxy-manager.js.map +1 -1
  107. package/dist/proxy/types.d.ts +7 -0
  108. package/dist/proxy/types.d.ts.map +1 -1
  109. package/dist/proxy/types.js.map +1 -1
  110. package/dist/proxy/upstream-http.d.ts.map +1 -1
  111. package/dist/proxy/upstream-http.js +44 -1
  112. package/dist/proxy/upstream-http.js.map +1 -1
  113. package/dist/proxy/upstream-stdio.d.ts +0 -1
  114. package/dist/proxy/upstream-stdio.d.ts.map +1 -1
  115. package/dist/proxy/upstream-stdio.js +2 -26
  116. package/dist/proxy/upstream-stdio.js.map +1 -1
  117. package/dist/redaction-patterns.d.ts.map +1 -1
  118. package/dist/redaction-patterns.js +1 -0
  119. package/dist/redaction-patterns.js.map +1 -1
  120. package/dist/secret-command.d.ts +59 -0
  121. package/dist/secret-command.d.ts.map +1 -0
  122. package/dist/secret-command.js +123 -0
  123. package/dist/secret-command.js.map +1 -0
  124. package/dist/security/audit-trail.d.ts +7 -1
  125. package/dist/security/audit-trail.d.ts.map +1 -1
  126. package/dist/security/audit-trail.js +24 -0
  127. package/dist/security/audit-trail.js.map +1 -1
  128. package/dist/server.d.ts.map +1 -1
  129. package/dist/server.js +9 -4
  130. package/dist/server.js.map +1 -1
  131. package/dist/shared/pricing-data.d.ts.map +1 -1
  132. package/dist/shared/pricing-data.js +223 -32
  133. package/dist/shared/pricing-data.js.map +1 -1
  134. package/dist/shared/pricing.d.ts +25 -3
  135. package/dist/shared/pricing.d.ts.map +1 -1
  136. package/dist/shared/pricing.js +46 -9
  137. package/dist/shared/pricing.js.map +1 -1
  138. package/dist/shared/tokens.d.ts +16 -0
  139. package/dist/shared/tokens.d.ts.map +1 -1
  140. package/dist/shared/tokens.js +18 -0
  141. package/dist/shared/tokens.js.map +1 -1
  142. package/dist/shared/transport/otlp-shared.d.ts.map +1 -1
  143. package/dist/shared/transport/otlp-shared.js +7 -14
  144. package/dist/shared/transport/otlp-shared.js.map +1 -1
  145. package/dist/storage/local-store.d.ts +6 -4
  146. package/dist/storage/local-store.d.ts.map +1 -1
  147. package/dist/storage/local-store.js +21 -7
  148. package/dist/storage/local-store.js.map +1 -1
  149. package/dist/storage/session-store.d.ts +24 -1
  150. package/dist/storage/session-store.d.ts.map +1 -1
  151. package/dist/storage/session-store.js +38 -7
  152. package/dist/storage/session-store.js.map +1 -1
  153. package/dist/storage/types.d.ts +13 -0
  154. package/dist/storage/types.d.ts.map +1 -1
  155. package/dist/storage/types.js.map +1 -1
  156. package/dist/tools/cross-session-tools.d.ts +7 -1
  157. package/dist/tools/cross-session-tools.d.ts.map +1 -1
  158. package/dist/tools/cross-session-tools.js +47 -14
  159. package/dist/tools/cross-session-tools.js.map +1 -1
  160. package/dist/tools/session-stats.d.ts +6 -0
  161. package/dist/tools/session-stats.d.ts.map +1 -1
  162. package/dist/tools/session-stats.js +3 -2
  163. package/dist/tools/session-stats.js.map +1 -1
  164. package/dist/transport/audit-record-sender.d.ts +20 -0
  165. package/dist/transport/audit-record-sender.d.ts.map +1 -0
  166. package/dist/transport/audit-record-sender.js +54 -0
  167. package/dist/transport/audit-record-sender.js.map +1 -0
  168. package/dist/transport/nr-ingest.d.ts +7 -0
  169. package/dist/transport/nr-ingest.d.ts.map +1 -1
  170. package/dist/transport/nr-ingest.js +64 -27
  171. package/dist/transport/nr-ingest.js.map +1 -1
  172. package/dist/transport/tier-types.d.ts +11 -1
  173. package/dist/transport/tier-types.d.ts.map +1 -1
  174. package/dist/transport/tier-types.js +28 -6
  175. package/dist/transport/tier-types.js.map +1 -1
  176. package/dist/transport/unforwarded-session-monitor.d.ts +12 -6
  177. package/dist/transport/unforwarded-session-monitor.d.ts.map +1 -1
  178. package/dist/transport/unforwarded-session-monitor.js +24 -5
  179. package/dist/transport/unforwarded-session-monitor.js.map +1 -1
  180. package/dist/web/assets/{index-lvdXUntr.js → index-U9DPZ-Tb.js} +3 -3
  181. package/dist/web/index.html +1 -1
  182. package/examples/local-alert-rules.json +28 -0
  183. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -9,7 +9,7 @@ import { AlertSnapshotCollector } from './alerts/alert-snapshot-collector.js';
9
9
  import { LocalAlertEngine } from './alerts/local-alert-engine.js';
10
10
  import { parseLocalAlertRules } from './alerts/local-alert-rule.js';
11
11
  import { OsNotifier } from './alerts/os-notifier.js';
12
- import { DEFAULT_STORAGE_PATH, loadMcpConfig, redactSensitive } from './config.js';
12
+ import { DEFAULT_STORAGE_PATH, digestWebhookUrlOverride, loadMcpConfig, redactSensitive, } from './config.js';
13
13
  import { checkBasicAuthToken } from './dashboard/basic-auth.js';
14
14
  import { DashboardServer } from './dashboard/dashboard-server.js';
15
15
  import { LiveEventBus } from './dashboard/index.js';
@@ -21,10 +21,13 @@ import { CopilotUsageWatcher } from './hooks/copilot-usage-watcher.js';
21
21
  import { HookEventProcessor } from './hooks/index.js';
22
22
  import { isPlatformDetectionFellBack } from './hooks/platform-detection-health.js';
23
23
  import { ParentTranscriptWatcher } from './hooks/parent-transcript-watcher.js';
24
+ import { BilledTurnLedger, pruneBilledTurns } from './hooks/billed-turn-ledger.js';
24
25
  import { isSyntheticSessionId, readJobState, readTranscriptTitle, resolveFromBreadcrumb, resolveFromCwd, resolveFromJobDir, resolveSessionId, resolveSessionName, watchCwdBreadcrumbForCorrection, watchPpidBreadcrumb, } from './hooks/session-resolver.js';
25
26
  import { SubagentWatcher } from './hooks/subagent-watcher.js';
26
27
  import { WorkflowWatcher } from './hooks/workflow-watcher.js';
27
28
  import { migrateStoragePath } from './install/migrate.js';
29
+ import { createHeadlessInstaller } from './install/headless-install.js';
30
+ import { areHooksInstalledAtAnyScope } from './install/install-helper.js';
28
31
  import { checkNodeVersion } from './install/node-version-check.js';
29
32
  import { localDateKey } from './lib/date.js';
30
33
  import { repoNameFromRemote } from './lib/git-remote.js';
@@ -39,8 +42,7 @@ import { isTokenEventForThisProcess, shouldApplyCostEstimate, } from './metrics/
39
42
  import { ContextCompositionTracker } from './metrics/context-composition-tracker.js';
40
43
  import { ContextTrackerRegistry } from './metrics/context-tracker.js';
41
44
  import { ContextWindowTracker } from './metrics/context-window-tracker.js';
42
- import { applyPricingOverlay } from './metrics/pricing-overlay.js';
43
- import { startPricingRefresh } from './metrics/pricing-refresh.js';
45
+ import { initModelPricing } from './metrics/pricing-init.js';
44
46
  import { buildCostForecastFromInputs } from './metrics/cost-forecast.js';
45
47
  import { CostPerOutcomeAnalyzer } from './metrics/cost-per-outcome.js';
46
48
  import { buildCostTrackerSeed } from './metrics/cost-tracker-seed.js';
@@ -61,6 +63,7 @@ import { QualityProxyTracker } from './metrics/quality-proxy-tracker.js';
61
63
  import { RecommendationEngine } from './metrics/recommendation-engine.js';
62
64
  import { RetryDetector } from './metrics/retry-detector.js';
63
65
  import { SessionTracker } from './metrics/session-tracker.js';
66
+ import { SkillActivationTracker } from './metrics/skill-activation-tracker.js';
64
67
  import { TaskCompletionTracker } from './metrics/task-completion-tracker.js';
65
68
  import { buildTaskDetectorSeed } from './metrics/task-detector-seed.js';
66
69
  import { TaskDetector } from './metrics/task-detector.js';
@@ -70,7 +73,7 @@ import { TrendAnalyzer } from './metrics/trend-analyzer.js';
70
73
  import { TurnCostAttributor } from './metrics/turn-cost-attributor.js';
71
74
  import { TurnTracker } from './metrics/turn-tracker.js';
72
75
  import { WorkflowRunTracker } from './metrics/workflow-run-tracker.js';
73
- import { createDefaultRegistry, GenericMcpAdapter } from './platforms/index.js';
76
+ import { ClaudeCodeAdapter, createDefaultRegistry, GenericMcpAdapter } from './platforms/index.js';
74
77
  import { ProxyManager } from './proxy/index.js';
75
78
  import { AuditTrailManager } from './security/audit-trail.js';
76
79
  import { createServer } from './server.js';
@@ -86,8 +89,9 @@ import { initMcpTracer } from './tracing/mcp-tracer.js';
86
89
  import { SessionSpan } from './tracing/session-span.js';
87
90
  import { TaskSpanTracker } from './tracing/task-span-tracker.js';
88
91
  import { emitToolCallSpan } from './tracing/tool-call-span.js';
92
+ import { nrIngestOptionsFromConfig, sendAuditRecord } from './transport/audit-record-sender.js';
89
93
  import { NrIngestManager } from './transport/nr-ingest.js';
90
- import { UNFORWARDED_SESSIONS_HINT, UnforwardedSessionMonitor, detectCloudForwardingGap, } from './transport/unforwarded-session-monitor.js';
94
+ import { unforwardedSessionsHint, UnforwardedSessionMonitor, detectCloudForwardingGap, } from './transport/unforwarded-session-monitor.js';
91
95
  import { HomelabAccumulator, HomelabForwarder } from './homelab/index.js';
92
96
  import { VERSION } from './version.js';
93
97
  export { loadMcpConfig, redactSensitive } from './config.js';
@@ -109,9 +113,9 @@ export function maskCredential(key) {
109
113
  * Helps users troubleshoot configuration issues without requiring
110
114
  * manual diagnosis steps.
111
115
  */
112
- function loadConfigOrDie(options) {
116
+ function loadConfigOrDie(options, loadOptions) {
113
117
  try {
114
- return loadMcpConfig(options);
118
+ return loadMcpConfig(options, loadOptions);
115
119
  }
116
120
  catch (err) {
117
121
  const msg = err instanceof Error ? err.message : String(err);
@@ -221,8 +225,13 @@ export function runRetentionSweep(deps) {
221
225
  return;
222
226
  const deletedSessionFiles = purgeOldSessions(deps.storagePath, deps.retainSessionsDays);
223
227
  const deletedWeeklySummaryFiles = purgeOldWeeklySummaries(deps.storagePath, deps.retainSessionsDays);
224
- if (deletedSessionFiles > 0 || deletedWeeklySummaryFiles > 0) {
225
- logger.info('Retention purge complete', { deletedSessionFiles, deletedWeeklySummaryFiles });
228
+ const deletedBilledTurnDays = pruneBilledTurns(deps.storagePath, deps.retainSessionsDays, Date.now());
229
+ if (deletedSessionFiles > 0 || deletedWeeklySummaryFiles > 0 || deletedBilledTurnDays > 0) {
230
+ logger.info('Retention purge complete', {
231
+ deletedSessionFiles,
232
+ deletedWeeklySummaryFiles,
233
+ deletedBilledTurnDays,
234
+ });
226
235
  }
227
236
  }
228
237
  /**
@@ -566,13 +575,6 @@ export function parseArgs(argv) {
566
575
  validate,
567
576
  };
568
577
  }
569
- function initModelPricing(config) {
570
- applyPricingOverlay(config.customPricingFile);
571
- void startPricingRefresh({
572
- enabled: config.pricingRefresh,
573
- cachePath: resolve(config.storagePath, 'pricing-cache.json'),
574
- });
575
- }
576
578
  async function main() {
577
579
  const nodeVersionError = checkNodeVersion();
578
580
  if (nodeVersionError) {
@@ -799,6 +801,21 @@ async function main() {
799
801
  process.on('SIGTERM', handleSignal);
800
802
  if (options.stdio || options.local) {
801
803
  let sessionTraceId;
804
+ // Sends the install's audit record at install time: a pending session that
805
+ // never resolves never builds a live ingest manager to send it later.
806
+ const buildHeadlessInstaller = () => createHeadlessInstaller({
807
+ allowHeadlessInstall: config.allowHeadlessInstall,
808
+ getAuditTrail: () => mcpServer?.auditTrailManager,
809
+ onAuditRecord: (record) => {
810
+ sendAuditRecord(record, {
811
+ config: config,
812
+ liveIngest: nrIngest,
813
+ sessionTraceId,
814
+ }).catch((err) => {
815
+ logger.warn('Failed to send the hook install audit event', { error: String(err) });
816
+ });
817
+ },
818
+ });
802
819
  // True when the synchronous resolution chain below succeeded ONLY via
803
820
  // the cwd fallback — the collision-prone source (shared by every
804
821
  // session that ever ran in this directory), unlike the ppid breadcrumb
@@ -887,9 +904,16 @@ async function main() {
887
904
  if (!gap)
888
905
  throw err;
889
906
  process.env.NR_AI_MODE = 'local';
890
- config = loadConfigOrDie(options);
907
+ config = loadConfigOrDie(options, { ignoreTiers: true });
891
908
  unforwardedSessionMonitor = new UnforwardedSessionMonitor({ gap });
892
- logger.warn(`Cloud export is configured (mode='${gap.requestedMode}') but this --local process has no ${gap.missingField}; sessions it drains will not reach New Relic`, { reason: gap.reason, requestedMode: gap.requestedMode, hint: UNFORWARDED_SESSIONS_HINT });
909
+ const problem = gap.reason === 'secret-helper-failed'
910
+ ? `could not read ${gap.missingField} from its helper command`
911
+ : `has no ${gap.missingField}`;
912
+ logger.warn(`Cloud export is configured (mode='${gap.requestedMode}') but this --local process ${problem}; sessions it drains will not reach New Relic`, {
913
+ reason: gap.reason,
914
+ requestedMode: gap.requestedMode,
915
+ hint: unforwardedSessionsHint(gap.reason),
916
+ });
893
917
  }
894
918
  if (!config.enabled) {
895
919
  logger.info('Server disabled via config — exiting');
@@ -921,8 +945,12 @@ async function main() {
921
945
  // dashboard owner's GC pass can tell which buffer files still have a live
922
946
  // owner. Removed in the shutdown handler below. Skipped during the
923
947
  // provisional window — the real heartbeat is written after resolution.
924
- if (options.stdio && !isProvisional)
925
- localStore.writeHeartbeat();
948
+ if (options.stdio && !isProvisional) {
949
+ localStore.writeHeartbeat(process.pid, process.argv.slice(1), process.cwd(), {
950
+ value: config.companionMode,
951
+ source: config.companionModeSource,
952
+ });
953
+ }
926
954
  localStoreForShutdown = localStore;
927
955
  // Migrate any pre-existing events from the legacy shared `buffer.jsonl` into
928
956
  // per-session files. Idempotent and a no-op on fresh installs.
@@ -1060,6 +1088,7 @@ async function main() {
1060
1088
  if (options.stdio)
1061
1089
  applyAuthoritativeSessionName(sessionTraceId);
1062
1090
  const turnCostAttributor = new TurnCostAttributor({ rateMultiplier });
1091
+ const skillActivationTracker = new SkillActivationTracker();
1063
1092
  const turnTracker = new TurnTracker();
1064
1093
  const gitEfficiencyTracker = new GitEfficiencyTracker();
1065
1094
  const gitWorkspaceReporter = new GitWorkspaceReporter();
@@ -1703,30 +1732,11 @@ async function main() {
1703
1732
  // AiMcpToolCall/etc. event is silently dropped — see issue #320.
1704
1733
  let warnedMissingIngest = false;
1705
1734
  if (config.mode !== 'local' && !isProvisional) {
1706
- if (!config.licenseKey || !config.accountId) {
1707
- throw new Error('licenseKey and accountId must be defined. ' +
1708
- 'This should have been caught by config validation. ' +
1709
- 'Check that mode is not "local" or that cloud credentials are configured.');
1710
- }
1711
1735
  nrIngest = new NrIngestManager({
1712
- licenseKey: config.licenseKey,
1713
- transportOptions: {
1714
- accountId: config.accountId,
1715
- collectorHost: config.collectorHost,
1716
- },
1717
- developer: config.developer,
1718
- appName: config.appName,
1719
- teamId: config.teamId,
1720
- projectId: config.projectId,
1721
- orgId: config.orgId,
1722
- repoUrl: config.repoUrl,
1723
- companionMode: config.companionMode,
1724
- tiers: config.tiers,
1736
+ ...nrIngestOptionsFromConfig(config),
1725
1737
  sessionTracker,
1726
1738
  localStore,
1727
1739
  auditTrail,
1728
- eventHarvestIntervalMs: config.harvestIntervalMs.events,
1729
- metricHarvestIntervalMs: config.harvestIntervalMs.metrics,
1730
1740
  costTracker,
1731
1741
  efficiencyScorer,
1732
1742
  feedbackCollector,
@@ -1806,7 +1816,9 @@ async function main() {
1806
1816
  // record, so audit/security events carry agentId and agentType.
1807
1817
  // The watcher's on-demand read is synchronous, which is what keeps it
1808
1818
  // ahead of auditTrail.
1809
- const rawRecord = subagentAttribution.attributeAtIntake(incomingRecord, activeSubagentWatcher);
1819
+ const attributed = subagentAttribution.attributeAtIntake(incomingRecord, activeSubagentWatcher);
1820
+ const activeSkill = skillActivationTracker.recordToolCall(attributed);
1821
+ const rawRecord = activeSkill ? { ...attributed, activeSkill } : attributed;
1810
1822
  if (!config || !sessionTracker || !taskDetector) {
1811
1823
  logger.warn('onRecord called before full initialization; skipping');
1812
1824
  return;
@@ -2050,7 +2062,7 @@ async function main() {
2050
2062
  ownSessionId: sessionTraceId,
2051
2063
  });
2052
2064
  const closedTurn = turnCostAttributor.recordTokenEvent(tokenEvent);
2053
- if (closedTurn && isOwnEvent)
2065
+ if (isOwnEvent)
2054
2066
  capturedNrIngest?.ingestTurnCost(closedTurn);
2055
2067
  const usage = {
2056
2068
  inputTokens: tokenEvent.inputTokens,
@@ -2300,7 +2312,9 @@ async function main() {
2300
2312
  });
2301
2313
  },
2302
2314
  onUserPromptSubmit: (frame) => {
2303
- turnCostAttributor.recordSlashCommand(frame.sessionId, frame.slashCommand ?? null);
2315
+ const slashRun = skillActivationTracker.recordPrompt(frame.sessionId, frame.slashCommand ?? null);
2316
+ if (slashRun)
2317
+ turnCostAttributor.recordSkillRun(frame.sessionId, slashRun);
2304
2318
  taskDetector.startTaskIfNone(frame.timestamp);
2305
2319
  },
2306
2320
  onStop: (frame) => {
@@ -2455,6 +2469,11 @@ async function main() {
2455
2469
  // signal for that platform, so it is gated only by its own opt-out, and
2456
2470
  // it no-ops cheaply (a single existsSync) when data.db is absent.
2457
2471
  const copilotAppUsageWatcherEnabled = process.env['NR_AI_ENABLE_COPILOT_APP_USAGE_WATCHER'] !== '0';
2472
+ // One claimant per process, shared by every watcher that bills turns.
2473
+ const billedTurnLedger = new BilledTurnLedger({
2474
+ storagePath: config.storagePath,
2475
+ retainDays: config.retainSessionsDays,
2476
+ });
2458
2477
  // Construct + start the watchers for a given session id. In `--stdio` mode
2459
2478
  // the watchers filter discovered transcript dirs by `parentSessionId`; in
2460
2479
  // `--local` mode they run unfiltered (parentSessionId: undefined). Shared by
@@ -2479,6 +2498,7 @@ async function main() {
2479
2498
  storagePath: config.storagePath,
2480
2499
  parentSessionId: isStdioWatcher ? watcherSessionId : undefined,
2481
2500
  localStore,
2501
+ ledger: billedTurnLedger,
2482
2502
  });
2483
2503
  activeParentTranscriptWatcher.start();
2484
2504
  logger.info('ParentTranscriptWatcher started', {
@@ -2656,7 +2676,10 @@ async function main() {
2656
2676
  // Replace the current LocalStore with one scoped to the real id.
2657
2677
  const realLocalStore = new LocalStore(config.storagePath, realId);
2658
2678
  realLocalStore.initialize();
2659
- realLocalStore.writeHeartbeat();
2679
+ realLocalStore.writeHeartbeat(process.pid, process.argv.slice(1), process.cwd(), {
2680
+ value: config.companionMode,
2681
+ source: config.companionModeSource,
2682
+ });
2660
2683
  localStoreForShutdown = realLocalStore;
2661
2684
  try {
2662
2685
  realLocalStore.migrateLegacyBuffer();
@@ -2688,29 +2711,11 @@ async function main() {
2688
2711
  });
2689
2712
  }
2690
2713
  if (config.mode !== 'local') {
2691
- if (!config.licenseKey || !config.accountId) {
2692
- throw new Error('licenseKey and accountId must be defined for non-local mode. ' +
2693
- 'This should have been caught by config validation.');
2694
- }
2695
2714
  nrIngest = new NrIngestManager({
2696
- licenseKey: config.licenseKey,
2697
- transportOptions: {
2698
- accountId: config.accountId,
2699
- collectorHost: config.collectorHost,
2700
- },
2701
- developer: config.developer,
2702
- appName: config.appName,
2703
- teamId: config.teamId,
2704
- projectId: config.projectId,
2705
- orgId: config.orgId,
2706
- repoUrl: config.repoUrl,
2707
- companionMode: config.companionMode,
2708
- tiers: config.tiers,
2715
+ ...nrIngestOptionsFromConfig(config),
2709
2716
  sessionTracker: sessionTracker,
2710
2717
  localStore: realLocalStore,
2711
2718
  auditTrail,
2712
- eventHarvestIntervalMs: config.harvestIntervalMs.events,
2713
- metricHarvestIntervalMs: config.harvestIntervalMs.metrics,
2714
2719
  costTracker,
2715
2720
  efficiencyScorer,
2716
2721
  feedbackCollector,
@@ -2737,13 +2742,17 @@ async function main() {
2737
2742
  accountId: config.accountId ?? null,
2738
2743
  licenseKeyMasked: config.licenseKey ? maskCredential(config.licenseKey) : null,
2739
2744
  nrApiKeyMasked: config.nrApiKey ? maskCredential(config.nrApiKey) : null,
2745
+ secretSources: config.secretSources,
2740
2746
  region: config.collectorHost ?? 'us',
2741
2747
  storagePath: config.storagePath,
2742
2748
  dashboardUrl: `http://${config.dashboard.host}:${config.dashboard.port}`,
2743
2749
  configFilePath,
2744
2750
  companionMode: config.companionMode,
2751
+ allowHeadlessInstall: config.allowHeadlessInstall,
2752
+ companionModeSource: config.companionModeSource,
2745
2753
  };
2746
2754
  registerTools(mcpServer.server, {
2755
+ headlessInstaller: buildHeadlessInstaller(),
2747
2756
  sessionTracker: sessionTracker,
2748
2757
  costTracker,
2749
2758
  budgetTracker,
@@ -2788,6 +2797,7 @@ async function main() {
2788
2797
  nrApiKey: config.nrApiKey,
2789
2798
  collectorHost: config.collectorHost,
2790
2799
  configFilePath,
2800
+ digestWebhookUrlOverride: digestWebhookUrlOverride(config),
2791
2801
  configSummary,
2792
2802
  });
2793
2803
  // The client cached tools/list during the pending window, when only the
@@ -2899,12 +2909,22 @@ async function main() {
2899
2909
  accountId: config.accountId ?? null,
2900
2910
  licenseKeyMasked: config.licenseKey ? maskCredential(config.licenseKey) : null,
2901
2911
  nrApiKeyMasked: config.nrApiKey ? maskCredential(config.nrApiKey) : null,
2912
+ secretSources: config.secretSources,
2902
2913
  region: config.collectorHost ?? 'us',
2903
2914
  storagePath: config.storagePath,
2904
2915
  dashboardUrl: `http://${config.dashboard.host}:${config.dashboard.port}`,
2905
2916
  configFilePath: pendingConfigFilePath,
2906
2917
  companionMode: config.companionMode,
2918
+ allowHeadlessInstall: config.allowHeadlessInstall,
2919
+ companionModeSource: config.companionModeSource,
2907
2920
  },
2921
+ // A Claude Code session with no hooks never resolves, so this is
2922
+ // where the opt-in install has to work. Other stdio clients have no
2923
+ // Claude Code hooks to install or report missing.
2924
+ ...(createDefaultRegistry().getActive() instanceof ClaudeCodeAdapter && {
2925
+ hooksInstalledFn: () => areHooksInstalledAtAnyScope(),
2926
+ headlessInstaller: buildHeadlessInstaller(),
2927
+ }),
2908
2928
  });
2909
2929
  logger.info('Dashboard started early; awaiting session_id resolution (breadcrumb poll)');
2910
2930
  sessionResolutionAbort = new AbortController();
@@ -2963,13 +2983,17 @@ async function main() {
2963
2983
  accountId: config.accountId ?? null,
2964
2984
  licenseKeyMasked: config.licenseKey ? maskCredential(config.licenseKey) : null,
2965
2985
  nrApiKeyMasked: config.nrApiKey ? maskCredential(config.nrApiKey) : null,
2986
+ secretSources: config.secretSources,
2966
2987
  region: config.collectorHost ?? 'us',
2967
2988
  storagePath: config.storagePath,
2968
2989
  dashboardUrl: `http://${config.dashboard.host}:${config.dashboard.port}`,
2969
2990
  configFilePath,
2970
2991
  companionMode: config.companionMode,
2992
+ allowHeadlessInstall: config.allowHeadlessInstall,
2993
+ companionModeSource: config.companionModeSource,
2971
2994
  };
2972
2995
  registerTools(mcpServer.server, {
2996
+ headlessInstaller: buildHeadlessInstaller(),
2973
2997
  sessionTracker,
2974
2998
  costTracker,
2975
2999
  budgetTracker,
@@ -3014,6 +3038,7 @@ async function main() {
3014
3038
  nrApiKey: config.nrApiKey,
3015
3039
  collectorHost: config.collectorHost,
3016
3040
  configFilePath,
3041
+ digestWebhookUrlOverride: digestWebhookUrlOverride(config),
3017
3042
  configSummary,
3018
3043
  });
3019
3044
  // Harmless if the client hasn't listed yet, and necessary if it listed
@@ -3069,11 +3094,7 @@ async function main() {
3069
3094
  // load-time validation), so this mirrors that guard rather than
3070
3095
  // re-deriving it.
3071
3096
  if (config.mode !== 'local') {
3072
- if (!config.licenseKey || !config.accountId) {
3073
- throw new Error('licenseKey and accountId must be defined. ' +
3074
- 'This should have been caught by config validation. ' +
3075
- 'Check that mode is not "local" or that cloud credentials are configured.');
3076
- }
3097
+ const configOptions = nrIngestOptionsFromConfig(config);
3077
3098
  // Unscoped LocalStore (mirrors --local mode) — proxy mode multiplexes
3078
3099
  // many concurrent clients, so there's no single session id to scope a
3079
3100
  // buffer file to. Passing it into NrIngestManager gives AuditTrailManager
@@ -3089,23 +3110,9 @@ async function main() {
3089
3110
  // to handle a missing tracker.
3090
3111
  const apiFailureTracker = new ApiFailureTracker();
3091
3112
  nrIngest = new NrIngestManager({
3092
- licenseKey: config.licenseKey,
3093
- transportOptions: {
3094
- accountId: config.accountId,
3095
- collectorHost: config.collectorHost,
3096
- },
3097
- developer: config.developer,
3098
- appName: config.appName,
3099
- teamId: config.teamId,
3100
- projectId: config.projectId,
3101
- orgId: config.orgId,
3102
- repoUrl: config.repoUrl,
3103
- companionMode: config.companionMode,
3104
- tiers: config.tiers,
3113
+ ...configOptions,
3105
3114
  sessionTracker: new SessionTracker(sessionTraceId),
3106
3115
  localStore: proxyLocalStore,
3107
- eventHarvestIntervalMs: config.harvestIntervalMs.events,
3108
- metricHarvestIntervalMs: config.harvestIntervalMs.metrics,
3109
3116
  apiFailureTracker,
3110
3117
  sessionTraceId,
3111
3118
  // Proxy mode has no single coherent session — sessionTracker above is