@newrelic/preflight 1.67.3 → 1.68.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 (96) hide show
  1. package/README.md +1 -1
  2. package/dist/config.d.ts +57 -2
  3. package/dist/config.d.ts.map +1 -1
  4. package/dist/config.js +127 -18
  5. package/dist/config.js.map +1 -1
  6. package/dist/dashboard/routes/api-handler.d.ts.map +1 -1
  7. package/dist/dashboard/routes/api-handler.js +13 -5
  8. package/dist/dashboard/routes/api-handler.js.map +1 -1
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +64 -75
  11. package/dist/index.js.map +1 -1
  12. package/dist/install/cli.d.ts.map +1 -1
  13. package/dist/install/cli.js +16 -1
  14. package/dist/install/cli.js.map +1 -1
  15. package/dist/install/dashboard-health.js +1 -1
  16. package/dist/install/dashboard-health.js.map +1 -1
  17. package/dist/install/diagnostics.d.ts +2 -0
  18. package/dist/install/diagnostics.d.ts.map +1 -1
  19. package/dist/install/diagnostics.js +117 -27
  20. package/dist/install/diagnostics.js.map +1 -1
  21. package/dist/install/headless-install.d.ts +26 -0
  22. package/dist/install/headless-install.d.ts.map +1 -1
  23. package/dist/install/headless-install.js +44 -1
  24. package/dist/install/headless-install.js.map +1 -1
  25. package/dist/install/install-helper.d.ts +2 -0
  26. package/dist/install/install-helper.d.ts.map +1 -1
  27. package/dist/install/install-helper.js +5 -0
  28. package/dist/install/install-helper.js.map +1 -1
  29. package/dist/lib/child-env.d.ts +11 -0
  30. package/dist/lib/child-env.d.ts.map +1 -0
  31. package/dist/lib/child-env.js +35 -0
  32. package/dist/lib/child-env.js.map +1 -0
  33. package/dist/lib/git-remote.d.ts +7 -0
  34. package/dist/lib/git-remote.d.ts.map +1 -1
  35. package/dist/lib/git-remote.js +13 -0
  36. package/dist/lib/git-remote.js.map +1 -1
  37. package/dist/metrics/pricing-init.d.ts +13 -0
  38. package/dist/metrics/pricing-init.d.ts.map +1 -0
  39. package/dist/metrics/pricing-init.js +19 -0
  40. package/dist/metrics/pricing-init.js.map +1 -0
  41. package/dist/proxy/proxy-manager.d.ts +3 -0
  42. package/dist/proxy/proxy-manager.d.ts.map +1 -1
  43. package/dist/proxy/proxy-manager.js +43 -3
  44. package/dist/proxy/proxy-manager.js.map +1 -1
  45. package/dist/proxy/types.d.ts +7 -0
  46. package/dist/proxy/types.d.ts.map +1 -1
  47. package/dist/proxy/types.js.map +1 -1
  48. package/dist/proxy/upstream-http.d.ts.map +1 -1
  49. package/dist/proxy/upstream-http.js +44 -1
  50. package/dist/proxy/upstream-http.js.map +1 -1
  51. package/dist/proxy/upstream-stdio.d.ts +0 -1
  52. package/dist/proxy/upstream-stdio.d.ts.map +1 -1
  53. package/dist/proxy/upstream-stdio.js +2 -26
  54. package/dist/proxy/upstream-stdio.js.map +1 -1
  55. package/dist/redaction-patterns.d.ts.map +1 -1
  56. package/dist/redaction-patterns.js +1 -0
  57. package/dist/redaction-patterns.js.map +1 -1
  58. package/dist/secret-command.d.ts +57 -0
  59. package/dist/secret-command.d.ts.map +1 -0
  60. package/dist/secret-command.js +117 -0
  61. package/dist/secret-command.js.map +1 -0
  62. package/dist/security/audit-trail.d.ts +7 -1
  63. package/dist/security/audit-trail.d.ts.map +1 -1
  64. package/dist/security/audit-trail.js +24 -0
  65. package/dist/security/audit-trail.js.map +1 -1
  66. package/dist/server.d.ts.map +1 -1
  67. package/dist/server.js +9 -4
  68. package/dist/server.js.map +1 -1
  69. package/dist/storage/local-store.d.ts +6 -4
  70. package/dist/storage/local-store.d.ts.map +1 -1
  71. package/dist/storage/local-store.js +21 -7
  72. package/dist/storage/local-store.js.map +1 -1
  73. package/dist/storage/session-store.d.ts.map +1 -1
  74. package/dist/storage/session-store.js +4 -3
  75. package/dist/storage/session-store.js.map +1 -1
  76. package/dist/tools/cross-session-tools.d.ts +7 -1
  77. package/dist/tools/cross-session-tools.d.ts.map +1 -1
  78. package/dist/tools/cross-session-tools.js +47 -14
  79. package/dist/tools/cross-session-tools.js.map +1 -1
  80. package/dist/tools/session-stats.d.ts +6 -0
  81. package/dist/tools/session-stats.d.ts.map +1 -1
  82. package/dist/tools/session-stats.js +2 -1
  83. package/dist/tools/session-stats.js.map +1 -1
  84. package/dist/transport/audit-record-sender.d.ts +20 -0
  85. package/dist/transport/audit-record-sender.d.ts.map +1 -0
  86. package/dist/transport/audit-record-sender.js +54 -0
  87. package/dist/transport/audit-record-sender.js.map +1 -0
  88. package/dist/transport/nr-ingest.d.ts +3 -0
  89. package/dist/transport/nr-ingest.d.ts.map +1 -1
  90. package/dist/transport/nr-ingest.js +21 -8
  91. package/dist/transport/nr-ingest.js.map +1 -1
  92. package/dist/transport/unforwarded-session-monitor.d.ts +12 -6
  93. package/dist/transport/unforwarded-session-monitor.d.ts.map +1 -1
  94. package/dist/transport/unforwarded-session-monitor.js +24 -5
  95. package/dist/transport/unforwarded-session-monitor.js.map +1 -1
  96. 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';
@@ -25,6 +25,8 @@ import { isSyntheticSessionId, readJobState, readTranscriptTitle, resolveFromBre
25
25
  import { SubagentWatcher } from './hooks/subagent-watcher.js';
26
26
  import { WorkflowWatcher } from './hooks/workflow-watcher.js';
27
27
  import { migrateStoragePath } from './install/migrate.js';
28
+ import { createHeadlessInstaller } from './install/headless-install.js';
29
+ import { areHooksInstalledAtAnyScope } from './install/install-helper.js';
28
30
  import { checkNodeVersion } from './install/node-version-check.js';
29
31
  import { localDateKey } from './lib/date.js';
30
32
  import { repoNameFromRemote } from './lib/git-remote.js';
@@ -39,8 +41,7 @@ import { isTokenEventForThisProcess, shouldApplyCostEstimate, } from './metrics/
39
41
  import { ContextCompositionTracker } from './metrics/context-composition-tracker.js';
40
42
  import { ContextTrackerRegistry } from './metrics/context-tracker.js';
41
43
  import { ContextWindowTracker } from './metrics/context-window-tracker.js';
42
- import { applyPricingOverlay } from './metrics/pricing-overlay.js';
43
- import { startPricingRefresh } from './metrics/pricing-refresh.js';
44
+ import { initModelPricing } from './metrics/pricing-init.js';
44
45
  import { buildCostForecastFromInputs } from './metrics/cost-forecast.js';
45
46
  import { CostPerOutcomeAnalyzer } from './metrics/cost-per-outcome.js';
46
47
  import { buildCostTrackerSeed } from './metrics/cost-tracker-seed.js';
@@ -70,7 +71,7 @@ import { TrendAnalyzer } from './metrics/trend-analyzer.js';
70
71
  import { TurnCostAttributor } from './metrics/turn-cost-attributor.js';
71
72
  import { TurnTracker } from './metrics/turn-tracker.js';
72
73
  import { WorkflowRunTracker } from './metrics/workflow-run-tracker.js';
73
- import { createDefaultRegistry, GenericMcpAdapter } from './platforms/index.js';
74
+ import { ClaudeCodeAdapter, createDefaultRegistry, GenericMcpAdapter } from './platforms/index.js';
74
75
  import { ProxyManager } from './proxy/index.js';
75
76
  import { AuditTrailManager } from './security/audit-trail.js';
76
77
  import { createServer } from './server.js';
@@ -86,8 +87,9 @@ import { initMcpTracer } from './tracing/mcp-tracer.js';
86
87
  import { SessionSpan } from './tracing/session-span.js';
87
88
  import { TaskSpanTracker } from './tracing/task-span-tracker.js';
88
89
  import { emitToolCallSpan } from './tracing/tool-call-span.js';
90
+ import { nrIngestOptionsFromConfig, sendAuditRecord } from './transport/audit-record-sender.js';
89
91
  import { NrIngestManager } from './transport/nr-ingest.js';
90
- import { UNFORWARDED_SESSIONS_HINT, UnforwardedSessionMonitor, detectCloudForwardingGap, } from './transport/unforwarded-session-monitor.js';
92
+ import { unforwardedSessionsHint, UnforwardedSessionMonitor, detectCloudForwardingGap, } from './transport/unforwarded-session-monitor.js';
91
93
  import { HomelabAccumulator, HomelabForwarder } from './homelab/index.js';
92
94
  import { VERSION } from './version.js';
93
95
  export { loadMcpConfig, redactSensitive } from './config.js';
@@ -566,13 +568,6 @@ export function parseArgs(argv) {
566
568
  validate,
567
569
  };
568
570
  }
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
571
  async function main() {
577
572
  const nodeVersionError = checkNodeVersion();
578
573
  if (nodeVersionError) {
@@ -799,6 +794,21 @@ async function main() {
799
794
  process.on('SIGTERM', handleSignal);
800
795
  if (options.stdio || options.local) {
801
796
  let sessionTraceId;
797
+ // Sends the install's audit record at install time: a pending session that
798
+ // never resolves never builds a live ingest manager to send it later.
799
+ const buildHeadlessInstaller = () => createHeadlessInstaller({
800
+ allowHeadlessInstall: config.allowHeadlessInstall,
801
+ getAuditTrail: () => mcpServer?.auditTrailManager,
802
+ onAuditRecord: (record) => {
803
+ sendAuditRecord(record, {
804
+ config: config,
805
+ liveIngest: nrIngest,
806
+ sessionTraceId,
807
+ }).catch((err) => {
808
+ logger.warn('Failed to send the hook install audit event', { error: String(err) });
809
+ });
810
+ },
811
+ });
802
812
  // True when the synchronous resolution chain below succeeded ONLY via
803
813
  // the cwd fallback — the collision-prone source (shared by every
804
814
  // session that ever ran in this directory), unlike the ppid breadcrumb
@@ -889,7 +899,14 @@ async function main() {
889
899
  process.env.NR_AI_MODE = 'local';
890
900
  config = loadConfigOrDie(options);
891
901
  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 });
902
+ const problem = gap.reason === 'secret-helper-failed'
903
+ ? `could not read ${gap.missingField} from its helper command`
904
+ : `has no ${gap.missingField}`;
905
+ logger.warn(`Cloud export is configured (mode='${gap.requestedMode}') but this --local process ${problem}; sessions it drains will not reach New Relic`, {
906
+ reason: gap.reason,
907
+ requestedMode: gap.requestedMode,
908
+ hint: unforwardedSessionsHint(gap.reason),
909
+ });
893
910
  }
894
911
  if (!config.enabled) {
895
912
  logger.info('Server disabled via config — exiting');
@@ -921,8 +938,12 @@ async function main() {
921
938
  // dashboard owner's GC pass can tell which buffer files still have a live
922
939
  // owner. Removed in the shutdown handler below. Skipped during the
923
940
  // provisional window — the real heartbeat is written after resolution.
924
- if (options.stdio && !isProvisional)
925
- localStore.writeHeartbeat();
941
+ if (options.stdio && !isProvisional) {
942
+ localStore.writeHeartbeat(process.pid, process.argv.slice(1), process.cwd(), {
943
+ value: config.companionMode,
944
+ source: config.companionModeSource,
945
+ });
946
+ }
926
947
  localStoreForShutdown = localStore;
927
948
  // Migrate any pre-existing events from the legacy shared `buffer.jsonl` into
928
949
  // per-session files. Idempotent and a no-op on fresh installs.
@@ -1703,30 +1724,11 @@ async function main() {
1703
1724
  // AiMcpToolCall/etc. event is silently dropped — see issue #320.
1704
1725
  let warnedMissingIngest = false;
1705
1726
  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
1727
  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,
1728
+ ...nrIngestOptionsFromConfig(config),
1725
1729
  sessionTracker,
1726
1730
  localStore,
1727
1731
  auditTrail,
1728
- eventHarvestIntervalMs: config.harvestIntervalMs.events,
1729
- metricHarvestIntervalMs: config.harvestIntervalMs.metrics,
1730
1732
  costTracker,
1731
1733
  efficiencyScorer,
1732
1734
  feedbackCollector,
@@ -2656,7 +2658,10 @@ async function main() {
2656
2658
  // Replace the current LocalStore with one scoped to the real id.
2657
2659
  const realLocalStore = new LocalStore(config.storagePath, realId);
2658
2660
  realLocalStore.initialize();
2659
- realLocalStore.writeHeartbeat();
2661
+ realLocalStore.writeHeartbeat(process.pid, process.argv.slice(1), process.cwd(), {
2662
+ value: config.companionMode,
2663
+ source: config.companionModeSource,
2664
+ });
2660
2665
  localStoreForShutdown = realLocalStore;
2661
2666
  try {
2662
2667
  realLocalStore.migrateLegacyBuffer();
@@ -2688,29 +2693,11 @@ async function main() {
2688
2693
  });
2689
2694
  }
2690
2695
  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
2696
  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,
2697
+ ...nrIngestOptionsFromConfig(config),
2709
2698
  sessionTracker: sessionTracker,
2710
2699
  localStore: realLocalStore,
2711
2700
  auditTrail,
2712
- eventHarvestIntervalMs: config.harvestIntervalMs.events,
2713
- metricHarvestIntervalMs: config.harvestIntervalMs.metrics,
2714
2701
  costTracker,
2715
2702
  efficiencyScorer,
2716
2703
  feedbackCollector,
@@ -2737,13 +2724,17 @@ async function main() {
2737
2724
  accountId: config.accountId ?? null,
2738
2725
  licenseKeyMasked: config.licenseKey ? maskCredential(config.licenseKey) : null,
2739
2726
  nrApiKeyMasked: config.nrApiKey ? maskCredential(config.nrApiKey) : null,
2727
+ secretSources: config.secretSources,
2740
2728
  region: config.collectorHost ?? 'us',
2741
2729
  storagePath: config.storagePath,
2742
2730
  dashboardUrl: `http://${config.dashboard.host}:${config.dashboard.port}`,
2743
2731
  configFilePath,
2744
2732
  companionMode: config.companionMode,
2733
+ allowHeadlessInstall: config.allowHeadlessInstall,
2734
+ companionModeSource: config.companionModeSource,
2745
2735
  };
2746
2736
  registerTools(mcpServer.server, {
2737
+ headlessInstaller: buildHeadlessInstaller(),
2747
2738
  sessionTracker: sessionTracker,
2748
2739
  costTracker,
2749
2740
  budgetTracker,
@@ -2788,6 +2779,7 @@ async function main() {
2788
2779
  nrApiKey: config.nrApiKey,
2789
2780
  collectorHost: config.collectorHost,
2790
2781
  configFilePath,
2782
+ digestWebhookUrlOverride: digestWebhookUrlOverride(config),
2791
2783
  configSummary,
2792
2784
  });
2793
2785
  // The client cached tools/list during the pending window, when only the
@@ -2899,12 +2891,22 @@ async function main() {
2899
2891
  accountId: config.accountId ?? null,
2900
2892
  licenseKeyMasked: config.licenseKey ? maskCredential(config.licenseKey) : null,
2901
2893
  nrApiKeyMasked: config.nrApiKey ? maskCredential(config.nrApiKey) : null,
2894
+ secretSources: config.secretSources,
2902
2895
  region: config.collectorHost ?? 'us',
2903
2896
  storagePath: config.storagePath,
2904
2897
  dashboardUrl: `http://${config.dashboard.host}:${config.dashboard.port}`,
2905
2898
  configFilePath: pendingConfigFilePath,
2906
2899
  companionMode: config.companionMode,
2900
+ allowHeadlessInstall: config.allowHeadlessInstall,
2901
+ companionModeSource: config.companionModeSource,
2907
2902
  },
2903
+ // A Claude Code session with no hooks never resolves, so this is
2904
+ // where the opt-in install has to work. Other stdio clients have no
2905
+ // Claude Code hooks to install or report missing.
2906
+ ...(createDefaultRegistry().getActive() instanceof ClaudeCodeAdapter && {
2907
+ hooksInstalledFn: () => areHooksInstalledAtAnyScope(),
2908
+ headlessInstaller: buildHeadlessInstaller(),
2909
+ }),
2908
2910
  });
2909
2911
  logger.info('Dashboard started early; awaiting session_id resolution (breadcrumb poll)');
2910
2912
  sessionResolutionAbort = new AbortController();
@@ -2963,13 +2965,17 @@ async function main() {
2963
2965
  accountId: config.accountId ?? null,
2964
2966
  licenseKeyMasked: config.licenseKey ? maskCredential(config.licenseKey) : null,
2965
2967
  nrApiKeyMasked: config.nrApiKey ? maskCredential(config.nrApiKey) : null,
2968
+ secretSources: config.secretSources,
2966
2969
  region: config.collectorHost ?? 'us',
2967
2970
  storagePath: config.storagePath,
2968
2971
  dashboardUrl: `http://${config.dashboard.host}:${config.dashboard.port}`,
2969
2972
  configFilePath,
2970
2973
  companionMode: config.companionMode,
2974
+ allowHeadlessInstall: config.allowHeadlessInstall,
2975
+ companionModeSource: config.companionModeSource,
2971
2976
  };
2972
2977
  registerTools(mcpServer.server, {
2978
+ headlessInstaller: buildHeadlessInstaller(),
2973
2979
  sessionTracker,
2974
2980
  costTracker,
2975
2981
  budgetTracker,
@@ -3014,6 +3020,7 @@ async function main() {
3014
3020
  nrApiKey: config.nrApiKey,
3015
3021
  collectorHost: config.collectorHost,
3016
3022
  configFilePath,
3023
+ digestWebhookUrlOverride: digestWebhookUrlOverride(config),
3017
3024
  configSummary,
3018
3025
  });
3019
3026
  // Harmless if the client hasn't listed yet, and necessary if it listed
@@ -3069,11 +3076,7 @@ async function main() {
3069
3076
  // load-time validation), so this mirrors that guard rather than
3070
3077
  // re-deriving it.
3071
3078
  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
- }
3079
+ const configOptions = nrIngestOptionsFromConfig(config);
3077
3080
  // Unscoped LocalStore (mirrors --local mode) — proxy mode multiplexes
3078
3081
  // many concurrent clients, so there's no single session id to scope a
3079
3082
  // buffer file to. Passing it into NrIngestManager gives AuditTrailManager
@@ -3089,23 +3092,9 @@ async function main() {
3089
3092
  // to handle a missing tracker.
3090
3093
  const apiFailureTracker = new ApiFailureTracker();
3091
3094
  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,
3095
+ ...configOptions,
3105
3096
  sessionTracker: new SessionTracker(sessionTraceId),
3106
3097
  localStore: proxyLocalStore,
3107
- eventHarvestIntervalMs: config.harvestIntervalMs.events,
3108
- metricHarvestIntervalMs: config.harvestIntervalMs.metrics,
3109
3098
  apiFailureTracker,
3110
3099
  sessionTraceId,
3111
3100
  // Proxy mode has no single coherent session — sessionTracker above is