farming-code 2.2.14 → 2.2.17

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 (136) hide show
  1. package/README.md +3 -3
  2. package/README.zh_cn.md +3 -3
  3. package/THIRD_PARTY_NOTICES.md +17 -1
  4. package/backend/acp/packaged-codex-acp.js +13 -0
  5. package/backend/acp-runtime.js +413 -24
  6. package/backend/agent-manager.js +572 -557
  7. package/backend/agent-provider-session.js +15 -1
  8. package/backend/agent-runtime-binding.js +9 -55
  9. package/backend/chat-runtime.js +28 -0
  10. package/backend/codex-rollout-follower.js +83 -0
  11. package/backend/codex-session-archive.js +12 -1
  12. package/backend/codex-session-history.js +94 -0
  13. package/backend/codex-transcript.js +2 -1
  14. package/backend/config-manager.js +25 -17
  15. package/backend/control-api.js +3 -1
  16. package/backend/farming-app-cli.js +21 -0
  17. package/backend/farming-session-store.js +2 -1
  18. package/backend/native-pty-host-client.js +59 -2
  19. package/backend/npm-update-helper.js +65 -7
  20. package/backend/provider-adapters.js +123 -38
  21. package/backend/provider-session-service.js +138 -34
  22. package/backend/server.js +94 -105
  23. package/backend/storage-layout.js +5 -16
  24. package/backend/update-service.js +94 -5
  25. package/backend/usage-history-client.js +305 -0
  26. package/backend/usage-history-scanner.generated.js +2250 -0
  27. package/backend/usage-history-scanner.ts +2718 -0
  28. package/backend/usage-history-smoke.js +64 -0
  29. package/backend/usage-history-worker.js +34 -0
  30. package/backend/usage-monitor.js +188 -588
  31. package/backend/vendor/usage-parsers/CC_STATISTICS_LICENSE +21 -0
  32. package/backend/vendor/usage-parsers/CODEXBAR_LICENSE +21 -0
  33. package/backend/vendor/usage-parsers/VENDOR.json +45 -0
  34. package/dist/acp/LICENSE.codex-acp +190 -0
  35. package/dist/acp/codex-acp-1.1.4.js +30718 -0
  36. package/dist/assets/App-6yWdwzj8.js +210 -0
  37. package/dist/assets/{FileEditorMarkdownPreview-UlVd0O4D.js → FileEditorMarkdownPreview-R0jwwLME.js} +4 -4
  38. package/dist/assets/FileEditorPane-DNy3LAqS.js +2 -0
  39. package/dist/assets/{IconGlyphs-AU22gPDY.js → IconGlyphs-BsUfqpKN.js} +1 -1
  40. package/dist/assets/ProjectFilesSection-BhDEUVHA.js +12 -0
  41. package/dist/assets/{ReviewPage-CeDKDyZh.js → ReviewPage-ChtByBe3.js} +1 -1
  42. package/dist/assets/{abnfDiagram-VRR7QNED-BgawzDQT.js → abnfDiagram-VRR7QNED-nnglrXFi.js} +1 -1
  43. package/dist/assets/{architectureDiagram-ZJ3FMSHR-DUShwPg0.js → architectureDiagram-ZJ3FMSHR-BEXMaFfY.js} +1 -1
  44. package/dist/assets/{blockDiagram-677ZJIJ3-CeGZviXR.js → blockDiagram-677ZJIJ3-1mCezSBk.js} +1 -1
  45. package/dist/assets/{c4Diagram-LMCZKHZV-CLhYht6S.js → c4Diagram-LMCZKHZV-DhTiI6Uv.js} +1 -1
  46. package/dist/assets/channel-BVWNatUB.js +1 -0
  47. package/dist/assets/{chunk-32BRIVSS-LFYpT-Cu.js → chunk-32BRIVSS-CCdsKd73.js} +1 -1
  48. package/dist/assets/{chunk-52WLFC77-BH6jDw9J.js → chunk-52WLFC77-Gncjk4LF.js} +1 -1
  49. package/dist/assets/{chunk-C7G6YPKG-B_mIUlb7.js → chunk-C7G6YPKG-CiDVUP82.js} +1 -1
  50. package/dist/assets/{chunk-EX3LRPZG-C63hVuAJ.js → chunk-EX3LRPZG-z6gXP2_N.js} +1 -1
  51. package/dist/assets/{chunk-FWX5IMBZ-CboX-WF_.js → chunk-FWX5IMBZ-DMZBccQa.js} +2 -2
  52. package/dist/assets/{chunk-HOUHSVGY-BOgSd6Un.js → chunk-HOUHSVGY-D8tIeoPk.js} +1 -1
  53. package/dist/assets/{chunk-ICXQ74PX-C0K-pgZt.js → chunk-ICXQ74PX-b6oQ2Q17.js} +1 -1
  54. package/dist/assets/{chunk-MOJQB5TN-CVMfwN_7.js → chunk-MOJQB5TN-DAVH_p2a.js} +1 -1
  55. package/dist/assets/{chunk-OGEWGWER-DLT3pxyw.js → chunk-OGEWGWER-DxoiMCI-.js} +1 -1
  56. package/dist/assets/{chunk-PUDLZKDR-Cd079yIQ.js → chunk-PUDLZKDR-NsOWh7HM.js} +1 -1
  57. package/dist/assets/{chunk-Q4XR5HBZ-D0jHa6SO.js → chunk-Q4XR5HBZ-Di4H7L21.js} +1 -1
  58. package/dist/assets/{chunk-V7JOEXUC-CVDtR75H.js → chunk-V7JOEXUC-DttpZg9Y.js} +1 -1
  59. package/dist/assets/{chunk-VAUOI2AC-B-W2_9Ot.js → chunk-VAUOI2AC-UHNr_yhX.js} +1 -1
  60. package/dist/assets/{chunk-VR4S4FIN-FD3kWh32.js → chunk-VR4S4FIN-D-Fmtd4Z.js} +1 -1
  61. package/dist/assets/{chunk-WYO6CB5R-C2bGaS5r.js → chunk-WYO6CB5R-CUxMtfK2.js} +4 -4
  62. package/dist/assets/{chunk-ZGVPDNZ5-ymgts0zo.js → chunk-ZGVPDNZ5-CoV2QEl0.js} +1 -1
  63. package/dist/assets/classDiagram-OUVF2IWQ-Db0ib5iE.js +1 -0
  64. package/dist/assets/classDiagram-v2-EOCWNBFH-Bkf69XF9.js +1 -0
  65. package/dist/assets/code-dark-5esj0bRu.css +1 -0
  66. package/dist/assets/{cynefinDiagram-TSTJHNR4-CwpobXIl.js → cynefinDiagram-TSTJHNR4-0xPI92Lg.js} +1 -1
  67. package/dist/assets/{dagre-VKFMJZFB-Dkhlfjna.js → dagre-VKFMJZFB-DfuMG4gU.js} +1 -1
  68. package/dist/assets/{diagram-FQU43EPY-hA8dicp6.js → diagram-FQU43EPY-CT00bLZ6.js} +1 -1
  69. package/dist/assets/{diagram-G47NLZAW-BTY6JTzO.js → diagram-G47NLZAW-Xbh7zXUx.js} +1 -1
  70. package/dist/assets/{diagram-NH7WQ7WH-xkaHHX9M.js → diagram-NH7WQ7WH-SRDVPfn7.js} +1 -1
  71. package/dist/assets/{diagram-OA4YK3LP-gk_fg9UU.js → diagram-OA4YK3LP-CQRli4Np.js} +1 -1
  72. package/dist/assets/{diagram-WEI45ONY-BvC8xd6T.js → diagram-WEI45ONY-DMf17WQi.js} +1 -1
  73. package/dist/assets/{ebnfDiagram-CCIWWBDH-CR7RaNat.js → ebnfDiagram-CCIWWBDH-DhpFkgJ5.js} +1 -1
  74. package/dist/assets/{erDiagram-Q63AITRT-_M-JFPbj.js → erDiagram-Q63AITRT-1IwxWmJo.js} +1 -1
  75. package/dist/assets/file-icons-DpbrD5ZP.js +1 -0
  76. package/dist/assets/flowDiagram-23GEKE2U-DYFo2xmB.js +1 -0
  77. package/dist/assets/{ganttDiagram-NO4QXBWP-Bl8P1yf_.js → ganttDiagram-NO4QXBWP-BNCdfyr1.js} +1 -1
  78. package/dist/assets/{gitGraphDiagram-IHSO6WYX-DoHa5ysz.js → gitGraphDiagram-IHSO6WYX-pbyWZuiX.js} +1 -1
  79. package/dist/assets/{index-BA-iH2Kg.js → index-C8ZFKye1.js} +2 -2
  80. package/dist/assets/{infoDiagram-FWYZ7A6U-DSFcSuUs.js → infoDiagram-FWYZ7A6U-BcTOgDbe.js} +1 -1
  81. package/dist/assets/{ishikawaDiagram-FXEZZL3T-DdkqullI.js → ishikawaDiagram-FXEZZL3T-DCdvGKrn.js} +1 -1
  82. package/dist/assets/{journeyDiagram-5HDEW3XC-B4hRnQJr.js → journeyDiagram-5HDEW3XC-BZ-1q_IT.js} +1 -1
  83. package/dist/assets/{kanban-definition-HUTT4EX6-tm2FZB2E.js → kanban-definition-HUTT4EX6-BIgejCjj.js} +1 -1
  84. package/dist/assets/{line-CmsjK4kn.js → line-CfptFMmX.js} +1 -1
  85. package/dist/assets/main-DSO3i_Gs.css +1 -0
  86. package/dist/assets/{mermaid.core-C7UT_EEL.js → mermaid.core-gSpX30pV.js} +3 -3
  87. package/dist/assets/{mindmap-definition-LN4V7U3C-DplfjGms.js → mindmap-definition-LN4V7U3C-BKBaI4LW.js} +1 -1
  88. package/dist/assets/{pegDiagram-2B236MQR-lu3SdkQs.js → pegDiagram-2B236MQR-sBvDY3VX.js} +1 -1
  89. package/dist/assets/{pieDiagram-ENE6RG2P-3fHbmDf9.js → pieDiagram-ENE6RG2P-DWlWcu08.js} +1 -1
  90. package/dist/assets/{quadrantDiagram-ABIIQ3AL-a5qoWz-X.js → quadrantDiagram-ABIIQ3AL-DZutUd3J.js} +1 -1
  91. package/dist/assets/{railroadDiagram-RFXS5EU6-D2TOuXW9.js → railroadDiagram-RFXS5EU6-DQb3lXGM.js} +1 -1
  92. package/dist/assets/{requirementDiagram-TGXJPOKE-LQBWpUvd.js → requirementDiagram-TGXJPOKE-DqFGXu2e.js} +1 -1
  93. package/dist/assets/{sankeyDiagram-HTMAVEWB-B1ohYoyK.js → sankeyDiagram-HTMAVEWB-d7ECahnO.js} +1 -1
  94. package/dist/assets/{sequenceDiagram-DBY2YBRQ-C3vM-hMq.js → sequenceDiagram-DBY2YBRQ-DdsbKqct.js} +1 -1
  95. package/dist/assets/{stateDiagram-2N3HPSRC-BvFhOryI.js → stateDiagram-2N3HPSRC-P6iSrgwR.js} +1 -1
  96. package/dist/assets/stateDiagram-v2-6OUMAXLB-DIYSf_qh.js +1 -0
  97. package/dist/assets/{swimlanes-5IMT3BWC-DKF_b-2s.js → swimlanes-5IMT3BWC-BGnZJx1M.js} +1 -1
  98. package/dist/assets/swimlanesDiagram-G3AALYLV-BIkrlKrZ.js +8 -0
  99. package/dist/assets/{timeline-definition-FHXFAJF6-vAs11ux_.js → timeline-definition-FHXFAJF6-rA-r3aUR.js} +1 -1
  100. package/dist/assets/{vennDiagram-L72KCM5P-BybTX_9B.js → vennDiagram-L72KCM5P-DiVKUjA9.js} +1 -1
  101. package/dist/assets/{wardleyDiagram-EHGQE667-CicUmMBR.js → wardleyDiagram-EHGQE667-Cfxh7UOn.js} +1 -1
  102. package/dist/assets/workspace-editor-model-D0Im34S5.js +1 -0
  103. package/dist/assets/{workspace-editor-monaco-BICJESG0.js → workspace-editor-monaco-BIpqcWf1.js} +1 -1
  104. package/dist/assets/workspace-editor-monaco-DLlws5Ig.js +1 -0
  105. package/dist/assets/{workspace-view-state-DvYG_9PH.js → workspace-view-state-BpPINu84.js} +1 -1
  106. package/dist/assets/{xychartDiagram-FW5EYKEG-BhWDyCX-.js → xychartDiagram-FW5EYKEG--hmryQeL.js} +1 -1
  107. package/dist/crt-mermaid-renderer.js +313 -313
  108. package/dist/index.html +1 -1
  109. package/frontend/session-bridge.js +24 -2
  110. package/frontend/skins/crt/app.js +203 -85
  111. package/frontend/skins/crt/index.html +2 -11
  112. package/frontend/skins/crt/styles/effects.css +2 -34
  113. package/package.json +30 -12
  114. package/shared/backend-connection-status.d.ts +25 -0
  115. package/shared/backend-connection-status.js +40 -0
  116. package/shared/browser-protocol.js +3 -3
  117. package/backend/app-server-api.js +0 -917
  118. package/backend/codex-app-server-home.js +0 -39
  119. package/backend/codex-app-server-runtime.js +0 -901
  120. package/dist/assets/App-Bumi772v.js +0 -206
  121. package/dist/assets/FileEditorPane-Dxx4rKOg.js +0 -2
  122. package/dist/assets/ProjectFilesSection-DE7Nb9Jk.js +0 -12
  123. package/dist/assets/channel-Uw90AO0b.js +0 -1
  124. package/dist/assets/classDiagram-OUVF2IWQ-DKW_dtku.js +0 -1
  125. package/dist/assets/classDiagram-v2-EOCWNBFH-C8Fi7-yv.js +0 -1
  126. package/dist/assets/code-dark-DPiuyeSp.css +0 -1
  127. package/dist/assets/code-mobile-3C2Rr-CN.css +0 -1
  128. package/dist/assets/file-icons-Bw2qd5iT.js +0 -1
  129. package/dist/assets/flowDiagram-23GEKE2U-DAiS5Y7G.js +0 -1
  130. package/dist/assets/main-Bqtb5kuz.css +0 -1
  131. package/dist/assets/stateDiagram-v2-6OUMAXLB-WMrz4PNo.js +0 -1
  132. package/dist/assets/swimlanesDiagram-G3AALYLV-BGswC9oC.js +0 -8
  133. package/dist/assets/workspace-editor-model-BQol4qbA.js +0 -1
  134. package/dist/assets/workspace-editor-monaco-DRJ7IaXk.js +0 -1
  135. package/scripts/sync-ghostty-vendor.js +0 -66
  136. /package/dist/assets/{core-D0LFJkDt.js → core-B9MwRjDE.js} +0 -0
@@ -681,6 +681,45 @@ function npmPackageMetadataUrl(registryUrl, packageName) {
681
681
  return `${registry}/${encodeURIComponent(packageName).replace(/^%40/, '@')}`;
682
682
  }
683
683
 
684
+ function normalizePathForCompare(filePath) {
685
+ try {
686
+ return fs.realpathSync(filePath);
687
+ } catch {
688
+ return path.resolve(filePath);
689
+ }
690
+ }
691
+
692
+ function npmPackageRoot(npmRoot, packageName) {
693
+ return path.join(npmRoot, ...String(packageName || '').split('/').filter(Boolean));
694
+ }
695
+
696
+ function npmPrefixForPackageRoot(packageRoot, packageName) {
697
+ const segments = String(packageName || '').split('/').filter(Boolean);
698
+ if (!path.isAbsolute(packageRoot) || segments.length === 0) return '';
699
+ let npmRoot = packageRoot;
700
+ for (let index = 0; index < segments.length; index += 1) npmRoot = path.dirname(npmRoot);
701
+ return path.dirname(path.dirname(npmRoot));
702
+ }
703
+
704
+ function readNpmGlobalRoot(npmCommand, npmPrefix, execFile = childProcess.execFile) {
705
+ const args = ['root', '--global'];
706
+ if (npmPrefix) args.push('--prefix', npmPrefix);
707
+ return new Promise((resolve, reject) => {
708
+ execFile(npmCommand, args, (error, stdout) => {
709
+ if (error) {
710
+ reject(error);
711
+ return;
712
+ }
713
+ const root = String(stdout || '').split(/\r?\n/).map(value => value.trim()).find(Boolean);
714
+ if (!root) {
715
+ reject(new Error('npm root --global returned no path'));
716
+ return;
717
+ }
718
+ resolve(root);
719
+ });
720
+ });
721
+ }
722
+
684
723
  function npmVersionsFromMetadata(metadata, currentVersion) {
685
724
  const versions = metadata && metadata.versions && typeof metadata.versions === 'object'
686
725
  ? Object.keys(metadata.versions)
@@ -711,6 +750,8 @@ class FarmingUpdateService {
711
750
  });
712
751
  this.npmPackageName = options.npmPackageName || NPM_PACKAGE_NAME;
713
752
  this.npmRegistryUrl = options.npmRegistryUrl || process.env.FARMING_NPM_REGISTRY || DEFAULT_NPM_REGISTRY;
753
+ this.npmPackageRoot = options.npmPackageRoot || process.env.FARMING_MANAGED_PACKAGE_ROOT || '';
754
+ this.npmPrefix = options.npmPrefix || process.env.FARMING_NPM_PREFIX || '';
714
755
  this.runtime = options.platform || options.arch
715
756
  ? {
716
757
  platform: normalizePlatform(options.platform || process.platform),
@@ -724,6 +765,8 @@ class FarmingUpdateService {
724
765
  this.downloadFile = options.downloadFile || downloadFile;
725
766
  this.listArchiveEntries = options.listArchiveEntries || listTarArchiveEntries;
726
767
  this.execFile = options.execFile || childProcess.execFile;
768
+ this.getNpmGlobalRoot = options.getNpmGlobalRoot
769
+ || ((npmCommand, npmPrefix) => readNpmGlobalRoot(npmCommand, npmPrefix, this.execFile));
727
770
  this.spawn = options.spawn || childProcess.spawn;
728
771
  this.latestCache = null;
729
772
  this.npmCache = null;
@@ -782,6 +825,7 @@ class FarmingUpdateService {
782
825
  const current = this.currentVersion();
783
826
  const currentVersion = normalizeVersion(current.releaseVersion || current.packageVersion);
784
827
  const metadata = await this.npmMetadata(options);
828
+ const target = await this.npmUpdateTarget();
785
829
  const versions = npmVersionsFromMetadata(metadata, currentVersion);
786
830
  const latestVersion = normalizeVersion(metadata && metadata['dist-tags'] && metadata['dist-tags'].latest)
787
831
  || versions[0]?.version
@@ -791,7 +835,8 @@ class FarmingUpdateService {
791
835
  || versions.find(version => version.version === latestVersion)
792
836
  || versions[0]
793
837
  || null;
794
- const available = Boolean(selected && selected.available);
838
+ const blockedReason = target.proven ? '' : target.error;
839
+ const available = Boolean(selected && selected.available && target.proven);
795
840
  return {
796
841
  method: 'npm',
797
842
  current,
@@ -802,24 +847,63 @@ class FarmingUpdateService {
802
847
  publishedAt: '',
803
848
  assetName: latestVersion,
804
849
  assetSize: 0,
805
- blockedReason: '',
850
+ blockedReason,
806
851
  source: npmPackageMetadataUrl(this.npmRegistryUrl, this.npmPackageName),
807
852
  },
808
853
  selected: {
809
854
  version: selected?.version || '',
810
855
  assetName: selected?.assetName || '',
811
856
  assetSize: selected?.assetSize || 0,
812
- blockedReason: selected?.blockedReason || '',
857
+ blockedReason: selected?.blockedReason || blockedReason,
813
858
  },
814
859
  versions,
815
860
  runtime: this.runtime,
861
+ target,
816
862
  available,
817
- installable: Boolean(selected),
863
+ installable: Boolean(selected && target.proven),
818
864
  checkedAt: new Date(this.now()).toISOString(),
819
865
  state: this.currentInstallState(),
820
866
  };
821
867
  }
822
868
 
869
+ async npmUpdateTarget() {
870
+ const runningPackageRoot = String(this.npmPackageRoot || '').trim();
871
+ if (!path.isAbsolute(runningPackageRoot)) {
872
+ return {
873
+ proven: false,
874
+ error: 'npm update target could not be proven: the running package has no managed package-root provenance',
875
+ };
876
+ }
877
+ const npmCommand = process.env.FARMING_NPM_COMMAND || 'npm';
878
+ const npmPrefix = this.npmPrefix || npmPrefixForPackageRoot(runningPackageRoot, this.npmPackageName);
879
+ if (!npmPrefix) {
880
+ return {
881
+ proven: false,
882
+ error: 'npm update target could not be proven: the running package has no npm prefix',
883
+ };
884
+ }
885
+ try {
886
+ const root = await this.getNpmGlobalRoot(npmCommand, npmPrefix);
887
+ const targetPackageRoot = npmPackageRoot(root, this.npmPackageName);
888
+ if (normalizePathForCompare(runningPackageRoot) !== normalizePathForCompare(targetPackageRoot)) {
889
+ return {
890
+ proven: false,
891
+ error: `npm update would target a different installation: running ${runningPackageRoot}; npm ${targetPackageRoot}`,
892
+ };
893
+ }
894
+ return {
895
+ proven: true,
896
+ npmPrefix,
897
+ packageRoot: targetPackageRoot,
898
+ };
899
+ } catch (error) {
900
+ return {
901
+ proven: false,
902
+ error: `npm update target could not be inspected: ${error.message || String(error)}`,
903
+ };
904
+ }
905
+ }
906
+
823
907
  unsupportedStatus() {
824
908
  const current = this.currentVersion();
825
909
  const reason = installMethodBlockedReason(this.installMethod);
@@ -1081,9 +1165,11 @@ class FarmingUpdateService {
1081
1165
  stateFile: this.updateStateFile,
1082
1166
  logPath: this.updateLogFile,
1083
1167
  cliPath: path.join(this.rootDir, 'bin', 'farming'),
1168
+ packageRoot: status.target.packageRoot,
1084
1169
  nodePath,
1085
1170
  npmCommand: process.env.FARMING_NPM_COMMAND || 'npm',
1086
- npmPrefix: process.env.FARMING_NPM_PREFIX || '',
1171
+ npmPrefix: status.target.npmPrefix,
1172
+ npmFallbackRegistryUrl: this.npmRegistryUrl,
1087
1173
  serverPid: process.pid,
1088
1174
  configDir: this.configDir,
1089
1175
  port: process.env.FARMING_PORT || process.env.PORT || '6694',
@@ -1181,7 +1267,10 @@ module.exports = {
1181
1267
  installMethodAllowsBundleUpdate,
1182
1268
  normalizeVersion,
1183
1269
  npmPackageMetadataUrl,
1270
+ npmPrefixForPackageRoot,
1271
+ npmPackageRoot,
1184
1272
  npmVersionsFromMetadata,
1273
+ readNpmGlobalRoot,
1185
1274
  releaseInstallDir,
1186
1275
  manifestAssetSafety,
1187
1276
  normalizeSha256,
@@ -0,0 +1,305 @@
1
+ const path = require('path');
2
+ const fs = require('fs');
3
+ const { Worker } = require('worker_threads');
4
+ const { usageHistoryCacheFile } = require('./storage-layout');
5
+
6
+ const DEFAULT_TIMEOUT_MS = 120_000;
7
+ const DEFAULT_RETENTION_DAYS = 52 * 7;
8
+ const DEFAULT_RECENT_RAW_MS = 24 * 60 * 60 * 1000;
9
+ const DEFAULT_SCAN_BUDGET_MS = 5_000;
10
+ const BACKGROUND_SCAN_DELAY_MS = 100;
11
+ const RESULT_REUSE_MS = 2_000;
12
+ const SOURCE_WORKER_FILE = 'usage-history-worker.js';
13
+ const PACKAGED_WORKER_FILE = 'usage-history-worker.pkg.js';
14
+ let sharedWorkerSession = null;
15
+ let nextWorkerRequestId = 1;
16
+
17
+ function resolveWorkerFile() {
18
+ if (!process.pkg && process.env.FARMING_PACKAGED_RUNTIME !== '1') {
19
+ return SOURCE_WORKER_FILE;
20
+ }
21
+ const packaged = path.join(__dirname, PACKAGED_WORKER_FILE);
22
+ return fs.existsSync(packaged) ? PACKAGED_WORKER_FILE : SOURCE_WORKER_FILE;
23
+ }
24
+
25
+ function runUsageWorker(request, options = {}) {
26
+ if (options.WorkerClass) {
27
+ return runOneShotUsageWorker(request, options);
28
+ }
29
+ const workerFile = path.join(__dirname, resolveWorkerFile());
30
+ if (!sharedWorkerSession || sharedWorkerSession.workerFile !== workerFile) {
31
+ sharedWorkerSession?.terminate();
32
+ sharedWorkerSession = createSharedWorkerSession(workerFile, options.WorkerClass || Worker);
33
+ }
34
+ return sharedWorkerSession.run(request, options.timeoutMs || DEFAULT_TIMEOUT_MS);
35
+ }
36
+
37
+ function runOneShotUsageWorker(request, options = {}) {
38
+ return new Promise((resolve, reject) => {
39
+ const worker = new (options.WorkerClass || Worker)(
40
+ path.join(__dirname, resolveWorkerFile()),
41
+ { workerData: { request } },
42
+ );
43
+ let settled = false;
44
+ const finish = (error, value) => {
45
+ if (settled) return;
46
+ settled = true;
47
+ clearTimeout(timeout);
48
+ worker.terminate().catch(() => {});
49
+ if (error) reject(error);
50
+ else resolve(value);
51
+ };
52
+ const timeout = setTimeout(() => {
53
+ const error = new Error(
54
+ `TypeScript usage scanner exceeded ${options.timeoutMs || DEFAULT_TIMEOUT_MS}ms`,
55
+ );
56
+ error.code = 'ETIMEDOUT';
57
+ finish(error);
58
+ }, options.timeoutMs || DEFAULT_TIMEOUT_MS);
59
+ timeout.unref?.();
60
+ worker.once('error', finish);
61
+ worker.once('exit', (code) => {
62
+ if (!settled) {
63
+ const error = new Error(
64
+ code === 0
65
+ ? 'TypeScript usage scanner exited without returning a result'
66
+ : `TypeScript usage scanner exited with code ${code}`,
67
+ );
68
+ error.code = 'EUSAGEWORKER';
69
+ finish(error);
70
+ }
71
+ });
72
+ worker.once('message', (message) => {
73
+ if (message?.error) {
74
+ const error = new Error(message.error.message || 'Usage scanner failed');
75
+ error.code = message.error.code || 'EUSAGE';
76
+ error.stack = message.error.stack || error.stack;
77
+ finish(error);
78
+ return;
79
+ }
80
+ finish(null, message?.result);
81
+ });
82
+ });
83
+ }
84
+
85
+ function createSharedWorkerSession(workerFile, WorkerClass) {
86
+ const worker = new WorkerClass(workerFile);
87
+ const pending = new Map();
88
+ const failAll = (error) => {
89
+ for (const request of pending.values()) {
90
+ clearTimeout(request.timeout);
91
+ request.reject(error);
92
+ }
93
+ pending.clear();
94
+ if (sharedWorkerSession?.worker === worker) sharedWorkerSession = null;
95
+ };
96
+ worker.on('message', (message) => {
97
+ const request = pending.get(message?.requestId);
98
+ if (!request) return;
99
+ pending.delete(message.requestId);
100
+ clearTimeout(request.timeout);
101
+ if (message?.error) {
102
+ const error = new Error(message.error.message || 'Usage scanner failed');
103
+ error.code = message.error.code || 'EUSAGE';
104
+ error.stack = message.error.stack || error.stack;
105
+ request.reject(error);
106
+ } else {
107
+ request.resolve(message?.result);
108
+ }
109
+ if (pending.size === 0) worker.unref?.();
110
+ });
111
+ worker.once('error', failAll);
112
+ worker.once('exit', (code) => {
113
+ if (pending.size > 0) {
114
+ const error = new Error(
115
+ code === 0
116
+ ? 'TypeScript usage scanner exited before returning all results'
117
+ : `TypeScript usage scanner exited with code ${code}`,
118
+ );
119
+ error.code = 'EUSAGEWORKER';
120
+ failAll(error);
121
+ } else if (sharedWorkerSession?.worker === worker) {
122
+ sharedWorkerSession = null;
123
+ }
124
+ });
125
+ worker.unref?.();
126
+ return {
127
+ worker,
128
+ workerFile,
129
+ run(request, timeoutMs) {
130
+ return new Promise((resolve, reject) => {
131
+ const requestId = nextWorkerRequestId++;
132
+ const timeout = setTimeout(() => {
133
+ if (!pending.delete(requestId)) return;
134
+ const error = new Error(`TypeScript usage scanner exceeded ${timeoutMs}ms`);
135
+ error.code = 'ETIMEDOUT';
136
+ reject(error);
137
+ worker.terminate().catch(() => {});
138
+ if (sharedWorkerSession?.worker === worker) sharedWorkerSession = null;
139
+ }, timeoutMs);
140
+ timeout.unref?.();
141
+ pending.set(requestId, { resolve, reject, timeout });
142
+ worker.ref?.();
143
+ worker.postMessage({ requestId, request });
144
+ });
145
+ },
146
+ terminate() {
147
+ const error = new Error('Usage scanner worker was replaced');
148
+ error.code = 'EUSAGEWORKER';
149
+ failAll(error);
150
+ worker.terminate().catch(() => {});
151
+ },
152
+ };
153
+ }
154
+
155
+ class UsageHistoryClient {
156
+ constructor(options = {}) {
157
+ this.configDir = options.configDir;
158
+ this.timeoutMs = options.timeoutMs || DEFAULT_TIMEOUT_MS;
159
+ this.runner = options.runner || runUsageWorker;
160
+ this.pending = null;
161
+ this.pendingKey = '';
162
+ this.cached = null;
163
+ this.cachedAt = 0;
164
+ this.cacheKey = '';
165
+ this.backgroundTimer = null;
166
+ this.backgroundGeneration = 0;
167
+ this.backgroundDelayMs = options.backgroundDelayMs || BACKGROUND_SCAN_DELAY_MS;
168
+ this.backgroundErrorDelayMs = options.backgroundErrorDelayMs || 5_000;
169
+ this.backgroundErrorRetries = 0;
170
+ this.backgroundStalls = 0;
171
+ this.backgroundProgressSignature = '';
172
+ }
173
+
174
+ invoke(request) {
175
+ return this.runner(request, { timeoutMs: this.timeoutMs });
176
+ }
177
+
178
+ storeResult(result, cacheKey) {
179
+ if (cacheKey !== this.cacheKey) return;
180
+ this.cached = result;
181
+ this.cachedAt = Number.isFinite(result?.sampledAt)
182
+ ? result.sampledAt
183
+ : Date.now();
184
+ }
185
+
186
+ scheduleBackgroundScan(request, cacheKey, result) {
187
+ if (result?.cache?.scan_complete !== false || cacheKey !== this.cacheKey) {
188
+ this.backgroundErrorRetries = 0;
189
+ this.backgroundStalls = 0;
190
+ this.backgroundProgressSignature = '';
191
+ return;
192
+ }
193
+ if (this.backgroundTimer) return;
194
+ const hasErrors = Number(result?.cache?.errors) > 0;
195
+ this.backgroundErrorRetries = hasErrors ? this.backgroundErrorRetries + 1 : 0;
196
+ const progressSignature = [
197
+ result?.cache?.pending_directories,
198
+ result?.cache?.pending_files,
199
+ result?.cache?.discovered_files,
200
+ result?.cache?.committed_bytes,
201
+ ].join(':');
202
+ if (Number(result?.cache?.enumerated_entries) > 0) {
203
+ this.backgroundProgressSignature = progressSignature;
204
+ this.backgroundStalls = 0;
205
+ } else if (progressSignature === this.backgroundProgressSignature) {
206
+ this.backgroundStalls += 1;
207
+ } else {
208
+ this.backgroundProgressSignature = progressSignature;
209
+ this.backgroundStalls = 0;
210
+ }
211
+ if (this.backgroundErrorRetries >= 3 || this.backgroundStalls >= 3) return;
212
+ const generation = this.backgroundGeneration;
213
+ const retryDelayMs = hasErrors
214
+ ? this.backgroundErrorDelayMs
215
+ : this.backgroundDelayMs;
216
+ this.backgroundTimer = setTimeout(() => {
217
+ this.backgroundTimer = null;
218
+ if (generation !== this.backgroundGeneration || cacheKey !== this.cacheKey) return;
219
+ const backgroundRequest = { ...request, nowMs: Date.now() };
220
+ const pending = this.invoke(backgroundRequest).then((nextResult) => {
221
+ this.storeResult(nextResult, cacheKey);
222
+ this.scheduleBackgroundScan(backgroundRequest, cacheKey, nextResult);
223
+ return nextResult;
224
+ }).catch(() => {
225
+ // Keep the last usable snapshot. A later normal request retries.
226
+ }).finally(() => {
227
+ if (this.pending === pending) {
228
+ this.pending = null;
229
+ this.pendingKey = '';
230
+ }
231
+ });
232
+ this.pending = pending;
233
+ this.pendingKey = cacheKey;
234
+ }, retryDelayMs);
235
+ this.backgroundTimer.unref?.();
236
+ }
237
+
238
+ collect(options = {}) {
239
+ const now = options.now ?? Date.now();
240
+ const retentionDays = options.retentionDays ?? DEFAULT_RETENTION_DAYS;
241
+ const roots = {
242
+ codex: Array.from(new Set(options.codexRoots || [])).sort(),
243
+ claude: Array.from(new Set(options.claudeRoots || [])).sort(),
244
+ };
245
+ const recentRawMs = options.recentRawMs ?? DEFAULT_RECENT_RAW_MS;
246
+ const cacheKey = JSON.stringify({ roots, retentionDays, recentRawMs });
247
+ if (this.cacheKey && this.cacheKey !== cacheKey) {
248
+ this.backgroundGeneration += 1;
249
+ if (this.backgroundTimer) clearTimeout(this.backgroundTimer);
250
+ this.backgroundTimer = null;
251
+ this.backgroundErrorRetries = 0;
252
+ this.backgroundStalls = 0;
253
+ this.backgroundProgressSignature = '';
254
+ }
255
+ if (
256
+ options.fresh !== true
257
+ && this.cached
258
+ && this.cacheKey === cacheKey
259
+ && (
260
+ now - this.cachedAt <= RESULT_REUSE_MS
261
+ || this.backgroundTimer
262
+ || (this.pending && this.pendingKey === cacheKey)
263
+ )
264
+ ) {
265
+ return Promise.resolve(this.cached);
266
+ }
267
+ if (this.pending && this.pendingKey === cacheKey) return this.pending;
268
+ if (this.pending) return this.pending.then(() => this.collect(options));
269
+ this.cacheKey = cacheKey;
270
+ this.backgroundErrorRetries = 0;
271
+ this.backgroundStalls = 0;
272
+ this.backgroundProgressSignature = '';
273
+ const request = {
274
+ cacheFile: usageHistoryCacheFile(this.configDir),
275
+ legacyCacheFile: options.legacyCacheFile,
276
+ nowMs: now,
277
+ retentionDays,
278
+ recentRawMs,
279
+ scanBudgetMs: options.scanBudgetMs ?? DEFAULT_SCAN_BUDGET_MS,
280
+ roots,
281
+ };
282
+ const pending = this.invoke(request).then((result) => {
283
+ this.storeResult(result, cacheKey);
284
+ this.scheduleBackgroundScan(request, cacheKey, result);
285
+ return result;
286
+ }).finally(() => {
287
+ if (this.pending === pending) {
288
+ this.pending = null;
289
+ this.pendingKey = '';
290
+ }
291
+ });
292
+ this.pending = pending;
293
+ this.pendingKey = cacheKey;
294
+ return pending;
295
+ }
296
+ }
297
+
298
+ module.exports = {
299
+ UsageHistoryClient,
300
+ DEFAULT_RECENT_RAW_MS,
301
+ DEFAULT_RETENTION_DAYS,
302
+ DEFAULT_SCAN_BUDGET_MS,
303
+ resolveWorkerFile,
304
+ runUsageWorker,
305
+ };