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.
- package/README.md +3 -3
- package/README.zh_cn.md +3 -3
- package/THIRD_PARTY_NOTICES.md +17 -1
- package/backend/acp/packaged-codex-acp.js +13 -0
- package/backend/acp-runtime.js +413 -24
- package/backend/agent-manager.js +572 -557
- package/backend/agent-provider-session.js +15 -1
- package/backend/agent-runtime-binding.js +9 -55
- package/backend/chat-runtime.js +28 -0
- package/backend/codex-rollout-follower.js +83 -0
- package/backend/codex-session-archive.js +12 -1
- package/backend/codex-session-history.js +94 -0
- package/backend/codex-transcript.js +2 -1
- package/backend/config-manager.js +25 -17
- package/backend/control-api.js +3 -1
- package/backend/farming-app-cli.js +21 -0
- package/backend/farming-session-store.js +2 -1
- package/backend/native-pty-host-client.js +59 -2
- package/backend/npm-update-helper.js +65 -7
- package/backend/provider-adapters.js +123 -38
- package/backend/provider-session-service.js +138 -34
- package/backend/server.js +94 -105
- package/backend/storage-layout.js +5 -16
- package/backend/update-service.js +94 -5
- package/backend/usage-history-client.js +305 -0
- package/backend/usage-history-scanner.generated.js +2250 -0
- package/backend/usage-history-scanner.ts +2718 -0
- package/backend/usage-history-smoke.js +64 -0
- package/backend/usage-history-worker.js +34 -0
- package/backend/usage-monitor.js +188 -588
- package/backend/vendor/usage-parsers/CC_STATISTICS_LICENSE +21 -0
- package/backend/vendor/usage-parsers/CODEXBAR_LICENSE +21 -0
- package/backend/vendor/usage-parsers/VENDOR.json +45 -0
- package/dist/acp/LICENSE.codex-acp +190 -0
- package/dist/acp/codex-acp-1.1.4.js +30718 -0
- package/dist/assets/App-6yWdwzj8.js +210 -0
- package/dist/assets/{FileEditorMarkdownPreview-UlVd0O4D.js → FileEditorMarkdownPreview-R0jwwLME.js} +4 -4
- package/dist/assets/FileEditorPane-DNy3LAqS.js +2 -0
- package/dist/assets/{IconGlyphs-AU22gPDY.js → IconGlyphs-BsUfqpKN.js} +1 -1
- package/dist/assets/ProjectFilesSection-BhDEUVHA.js +12 -0
- package/dist/assets/{ReviewPage-CeDKDyZh.js → ReviewPage-ChtByBe3.js} +1 -1
- package/dist/assets/{abnfDiagram-VRR7QNED-BgawzDQT.js → abnfDiagram-VRR7QNED-nnglrXFi.js} +1 -1
- package/dist/assets/{architectureDiagram-ZJ3FMSHR-DUShwPg0.js → architectureDiagram-ZJ3FMSHR-BEXMaFfY.js} +1 -1
- package/dist/assets/{blockDiagram-677ZJIJ3-CeGZviXR.js → blockDiagram-677ZJIJ3-1mCezSBk.js} +1 -1
- package/dist/assets/{c4Diagram-LMCZKHZV-CLhYht6S.js → c4Diagram-LMCZKHZV-DhTiI6Uv.js} +1 -1
- package/dist/assets/channel-BVWNatUB.js +1 -0
- package/dist/assets/{chunk-32BRIVSS-LFYpT-Cu.js → chunk-32BRIVSS-CCdsKd73.js} +1 -1
- package/dist/assets/{chunk-52WLFC77-BH6jDw9J.js → chunk-52WLFC77-Gncjk4LF.js} +1 -1
- package/dist/assets/{chunk-C7G6YPKG-B_mIUlb7.js → chunk-C7G6YPKG-CiDVUP82.js} +1 -1
- package/dist/assets/{chunk-EX3LRPZG-C63hVuAJ.js → chunk-EX3LRPZG-z6gXP2_N.js} +1 -1
- package/dist/assets/{chunk-FWX5IMBZ-CboX-WF_.js → chunk-FWX5IMBZ-DMZBccQa.js} +2 -2
- package/dist/assets/{chunk-HOUHSVGY-BOgSd6Un.js → chunk-HOUHSVGY-D8tIeoPk.js} +1 -1
- package/dist/assets/{chunk-ICXQ74PX-C0K-pgZt.js → chunk-ICXQ74PX-b6oQ2Q17.js} +1 -1
- package/dist/assets/{chunk-MOJQB5TN-CVMfwN_7.js → chunk-MOJQB5TN-DAVH_p2a.js} +1 -1
- package/dist/assets/{chunk-OGEWGWER-DLT3pxyw.js → chunk-OGEWGWER-DxoiMCI-.js} +1 -1
- package/dist/assets/{chunk-PUDLZKDR-Cd079yIQ.js → chunk-PUDLZKDR-NsOWh7HM.js} +1 -1
- package/dist/assets/{chunk-Q4XR5HBZ-D0jHa6SO.js → chunk-Q4XR5HBZ-Di4H7L21.js} +1 -1
- package/dist/assets/{chunk-V7JOEXUC-CVDtR75H.js → chunk-V7JOEXUC-DttpZg9Y.js} +1 -1
- package/dist/assets/{chunk-VAUOI2AC-B-W2_9Ot.js → chunk-VAUOI2AC-UHNr_yhX.js} +1 -1
- package/dist/assets/{chunk-VR4S4FIN-FD3kWh32.js → chunk-VR4S4FIN-D-Fmtd4Z.js} +1 -1
- package/dist/assets/{chunk-WYO6CB5R-C2bGaS5r.js → chunk-WYO6CB5R-CUxMtfK2.js} +4 -4
- package/dist/assets/{chunk-ZGVPDNZ5-ymgts0zo.js → chunk-ZGVPDNZ5-CoV2QEl0.js} +1 -1
- package/dist/assets/classDiagram-OUVF2IWQ-Db0ib5iE.js +1 -0
- package/dist/assets/classDiagram-v2-EOCWNBFH-Bkf69XF9.js +1 -0
- package/dist/assets/code-dark-5esj0bRu.css +1 -0
- package/dist/assets/{cynefinDiagram-TSTJHNR4-CwpobXIl.js → cynefinDiagram-TSTJHNR4-0xPI92Lg.js} +1 -1
- package/dist/assets/{dagre-VKFMJZFB-Dkhlfjna.js → dagre-VKFMJZFB-DfuMG4gU.js} +1 -1
- package/dist/assets/{diagram-FQU43EPY-hA8dicp6.js → diagram-FQU43EPY-CT00bLZ6.js} +1 -1
- package/dist/assets/{diagram-G47NLZAW-BTY6JTzO.js → diagram-G47NLZAW-Xbh7zXUx.js} +1 -1
- package/dist/assets/{diagram-NH7WQ7WH-xkaHHX9M.js → diagram-NH7WQ7WH-SRDVPfn7.js} +1 -1
- package/dist/assets/{diagram-OA4YK3LP-gk_fg9UU.js → diagram-OA4YK3LP-CQRli4Np.js} +1 -1
- package/dist/assets/{diagram-WEI45ONY-BvC8xd6T.js → diagram-WEI45ONY-DMf17WQi.js} +1 -1
- package/dist/assets/{ebnfDiagram-CCIWWBDH-CR7RaNat.js → ebnfDiagram-CCIWWBDH-DhpFkgJ5.js} +1 -1
- package/dist/assets/{erDiagram-Q63AITRT-_M-JFPbj.js → erDiagram-Q63AITRT-1IwxWmJo.js} +1 -1
- package/dist/assets/file-icons-DpbrD5ZP.js +1 -0
- package/dist/assets/flowDiagram-23GEKE2U-DYFo2xmB.js +1 -0
- package/dist/assets/{ganttDiagram-NO4QXBWP-Bl8P1yf_.js → ganttDiagram-NO4QXBWP-BNCdfyr1.js} +1 -1
- package/dist/assets/{gitGraphDiagram-IHSO6WYX-DoHa5ysz.js → gitGraphDiagram-IHSO6WYX-pbyWZuiX.js} +1 -1
- package/dist/assets/{index-BA-iH2Kg.js → index-C8ZFKye1.js} +2 -2
- package/dist/assets/{infoDiagram-FWYZ7A6U-DSFcSuUs.js → infoDiagram-FWYZ7A6U-BcTOgDbe.js} +1 -1
- package/dist/assets/{ishikawaDiagram-FXEZZL3T-DdkqullI.js → ishikawaDiagram-FXEZZL3T-DCdvGKrn.js} +1 -1
- package/dist/assets/{journeyDiagram-5HDEW3XC-B4hRnQJr.js → journeyDiagram-5HDEW3XC-BZ-1q_IT.js} +1 -1
- package/dist/assets/{kanban-definition-HUTT4EX6-tm2FZB2E.js → kanban-definition-HUTT4EX6-BIgejCjj.js} +1 -1
- package/dist/assets/{line-CmsjK4kn.js → line-CfptFMmX.js} +1 -1
- package/dist/assets/main-DSO3i_Gs.css +1 -0
- package/dist/assets/{mermaid.core-C7UT_EEL.js → mermaid.core-gSpX30pV.js} +3 -3
- package/dist/assets/{mindmap-definition-LN4V7U3C-DplfjGms.js → mindmap-definition-LN4V7U3C-BKBaI4LW.js} +1 -1
- package/dist/assets/{pegDiagram-2B236MQR-lu3SdkQs.js → pegDiagram-2B236MQR-sBvDY3VX.js} +1 -1
- package/dist/assets/{pieDiagram-ENE6RG2P-3fHbmDf9.js → pieDiagram-ENE6RG2P-DWlWcu08.js} +1 -1
- package/dist/assets/{quadrantDiagram-ABIIQ3AL-a5qoWz-X.js → quadrantDiagram-ABIIQ3AL-DZutUd3J.js} +1 -1
- package/dist/assets/{railroadDiagram-RFXS5EU6-D2TOuXW9.js → railroadDiagram-RFXS5EU6-DQb3lXGM.js} +1 -1
- package/dist/assets/{requirementDiagram-TGXJPOKE-LQBWpUvd.js → requirementDiagram-TGXJPOKE-DqFGXu2e.js} +1 -1
- package/dist/assets/{sankeyDiagram-HTMAVEWB-B1ohYoyK.js → sankeyDiagram-HTMAVEWB-d7ECahnO.js} +1 -1
- package/dist/assets/{sequenceDiagram-DBY2YBRQ-C3vM-hMq.js → sequenceDiagram-DBY2YBRQ-DdsbKqct.js} +1 -1
- package/dist/assets/{stateDiagram-2N3HPSRC-BvFhOryI.js → stateDiagram-2N3HPSRC-P6iSrgwR.js} +1 -1
- package/dist/assets/stateDiagram-v2-6OUMAXLB-DIYSf_qh.js +1 -0
- package/dist/assets/{swimlanes-5IMT3BWC-DKF_b-2s.js → swimlanes-5IMT3BWC-BGnZJx1M.js} +1 -1
- package/dist/assets/swimlanesDiagram-G3AALYLV-BIkrlKrZ.js +8 -0
- package/dist/assets/{timeline-definition-FHXFAJF6-vAs11ux_.js → timeline-definition-FHXFAJF6-rA-r3aUR.js} +1 -1
- package/dist/assets/{vennDiagram-L72KCM5P-BybTX_9B.js → vennDiagram-L72KCM5P-DiVKUjA9.js} +1 -1
- package/dist/assets/{wardleyDiagram-EHGQE667-CicUmMBR.js → wardleyDiagram-EHGQE667-Cfxh7UOn.js} +1 -1
- package/dist/assets/workspace-editor-model-D0Im34S5.js +1 -0
- package/dist/assets/{workspace-editor-monaco-BICJESG0.js → workspace-editor-monaco-BIpqcWf1.js} +1 -1
- package/dist/assets/workspace-editor-monaco-DLlws5Ig.js +1 -0
- package/dist/assets/{workspace-view-state-DvYG_9PH.js → workspace-view-state-BpPINu84.js} +1 -1
- package/dist/assets/{xychartDiagram-FW5EYKEG-BhWDyCX-.js → xychartDiagram-FW5EYKEG--hmryQeL.js} +1 -1
- package/dist/crt-mermaid-renderer.js +313 -313
- package/dist/index.html +1 -1
- package/frontend/session-bridge.js +24 -2
- package/frontend/skins/crt/app.js +203 -85
- package/frontend/skins/crt/index.html +2 -11
- package/frontend/skins/crt/styles/effects.css +2 -34
- package/package.json +30 -12
- package/shared/backend-connection-status.d.ts +25 -0
- package/shared/backend-connection-status.js +40 -0
- package/shared/browser-protocol.js +3 -3
- package/backend/app-server-api.js +0 -917
- package/backend/codex-app-server-home.js +0 -39
- package/backend/codex-app-server-runtime.js +0 -901
- package/dist/assets/App-Bumi772v.js +0 -206
- package/dist/assets/FileEditorPane-Dxx4rKOg.js +0 -2
- package/dist/assets/ProjectFilesSection-DE7Nb9Jk.js +0 -12
- package/dist/assets/channel-Uw90AO0b.js +0 -1
- package/dist/assets/classDiagram-OUVF2IWQ-DKW_dtku.js +0 -1
- package/dist/assets/classDiagram-v2-EOCWNBFH-C8Fi7-yv.js +0 -1
- package/dist/assets/code-dark-DPiuyeSp.css +0 -1
- package/dist/assets/code-mobile-3C2Rr-CN.css +0 -1
- package/dist/assets/file-icons-Bw2qd5iT.js +0 -1
- package/dist/assets/flowDiagram-23GEKE2U-DAiS5Y7G.js +0 -1
- package/dist/assets/main-Bqtb5kuz.css +0 -1
- package/dist/assets/stateDiagram-v2-6OUMAXLB-WMrz4PNo.js +0 -1
- package/dist/assets/swimlanesDiagram-G3AALYLV-BGswC9oC.js +0 -8
- package/dist/assets/workspace-editor-model-BQol4qbA.js +0 -1
- package/dist/assets/workspace-editor-monaco-DRJ7IaXk.js +0 -1
- package/scripts/sync-ghostty-vendor.js +0 -66
- /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
|
|
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:
|
|
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
|
+
};
|