nexus-agents 10.2.0 → 11.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -1
- package/dist/{ast-rule-runner-RI7ZJ4ZI.js → ast-rule-runner-3QRCP53C.js} +5 -5
- package/dist/{child-mcp-config-73BX4OTR.js → child-mcp-config-NWPUDUVE.js} +5 -5
- package/dist/{chunk-GAX75XCV.js → chunk-2BZXKNLN.js} +2 -2
- package/dist/{chunk-QG3VDSWD.js → chunk-2HYJTIDY.js} +3 -3
- package/dist/{chunk-OMSKBBL7.js → chunk-377L2ZEK.js} +18 -19
- package/dist/chunk-377L2ZEK.js.map +1 -0
- package/dist/{chunk-VLOPXV47.js → chunk-53XY7YJD.js} +7 -7
- package/dist/chunk-5CUI42NO.js +1046 -0
- package/dist/chunk-5CUI42NO.js.map +1 -0
- package/dist/{chunk-23QA6GQD.js → chunk-66BE4KF4.js} +7 -7
- package/dist/{chunk-TDUXUVUU.js → chunk-6LWUJOAS.js} +2 -2
- package/dist/{chunk-XDUTAUK3.js → chunk-7H3SOL4O.js} +65 -57
- package/dist/chunk-7H3SOL4O.js.map +1 -0
- package/dist/{chunk-JGY5HMWI.js → chunk-7HHXY7LM.js} +2 -2
- package/dist/{chunk-J44RKRGQ.js → chunk-AFJE7K6Q.js} +4 -4
- package/dist/{chunk-WD4DENPC.js → chunk-B3RAA47W.js} +3 -3
- package/dist/{chunk-TW6PJYTP.js → chunk-C73VZSC5.js} +1 -1
- package/dist/chunk-C73VZSC5.js.map +1 -0
- package/dist/{chunk-LWOX65CP.js → chunk-CH6IBWXH.js} +31 -31
- package/dist/chunk-CH6IBWXH.js.map +1 -0
- package/dist/{chunk-RIYFVDYN.js → chunk-CSDGNGAG.js} +2 -2
- package/dist/{chunk-KASBBKAQ.js → chunk-D33IH5VS.js} +54 -54
- package/dist/chunk-D33IH5VS.js.map +1 -0
- package/dist/{chunk-U37AITDA.js → chunk-DCPCO2XG.js} +2 -2
- package/dist/{chunk-SS53PZO6.js → chunk-EFZRQ67X.js} +5 -5
- package/dist/{chunk-WKFPKPEM.js → chunk-GPRYEKAA.js} +434 -124
- package/dist/chunk-GPRYEKAA.js.map +1 -0
- package/dist/{chunk-7EV3QAKO.js → chunk-HUYVIUF3.js} +2 -2
- package/dist/{chunk-V26YQQK4.js → chunk-I7VX7JWV.js} +2 -2
- package/dist/{chunk-LXYT2P2Y.js → chunk-IGYZQEKM.js} +3 -3
- package/dist/{chunk-XSAUVC6D.js → chunk-NLFCAEEK.js} +2 -2
- package/dist/{chunk-2TA6KTRF.js → chunk-OJ3PXTN3.js} +16 -18
- package/dist/{chunk-2TA6KTRF.js.map → chunk-OJ3PXTN3.js.map} +1 -1
- package/dist/{chunk-QRC3QLUQ.js → chunk-PY6HJQYC.js} +1086 -14
- package/dist/chunk-PY6HJQYC.js.map +1 -0
- package/dist/{chunk-QM5W3T3O.js → chunk-PZMODRBC.js} +2 -2
- package/dist/{chunk-GX5ZHZR2.js → chunk-QARJHIQM.js} +7 -7
- package/dist/{chunk-EJ5EBTAV.js → chunk-QTRPH2ZF.js} +2 -2
- package/dist/{chunk-FNHVTDHZ.js → chunk-R75NZRON.js} +3 -3
- package/dist/{chunk-UE7A2XHM.js → chunk-RUHKJC3X.js} +2 -2
- package/dist/{chunk-4JJI34RV.js → chunk-S6XQFO4A.js} +3 -3
- package/dist/{chunk-LP5ISXKM.js → chunk-SFWQ72ZS.js} +5 -5
- package/dist/{chunk-KLSNDPU6.js → chunk-SKM75Y7E.js} +17 -32
- package/dist/chunk-SKM75Y7E.js.map +1 -0
- package/dist/{chunk-D6E3LJGP.js → chunk-T4WTZTZY.js} +4 -4
- package/dist/{chunk-JPFRRNRR.js → chunk-U7HL7FGS.js} +2 -2
- package/dist/{chunk-SX2XWCGA.js → chunk-V6AJCRAU.js} +22 -7
- package/dist/chunk-V6AJCRAU.js.map +1 -0
- package/dist/{chunk-2DYCHHWW.js → chunk-VVMSE5LF.js} +4 -4
- package/dist/{chunk-QS5HGPCN.js → chunk-VZ7YZ2EF.js} +2 -2
- package/dist/{chunk-PXYSAG5B.js → chunk-WFMMJWMO.js} +2 -2
- package/dist/{chunk-FWQSOPBA.js → chunk-YOT3XEZM.js} +6 -6
- package/dist/{chunk-JUUGLXSN.js → chunk-YRZV6KXZ.js} +5 -5
- package/dist/{cli-auth-probe-A7ANEHJ7.js → cli-auth-probe-GUK3NXW3.js} +2 -2
- package/dist/{cli-circuit-breaker-62YOQQT3.js → cli-circuit-breaker-VWARWNGU.js} +5 -5
- package/dist/{cli-readiness-O6EOQV4L.js → cli-readiness-S23DWZCS.js} +9 -9
- package/dist/cli-server-gateway-DJXSHHXA.js +34 -0
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +80 -1215
- package/dist/cli.js.map +1 -1
- package/dist/{composite-router-YPZ3D5Y6.js → composite-router-3CBLBUGZ.js} +4 -4
- package/dist/{consensus-vote-RNQQUBXO.js → consensus-vote-JMWTJO4M.js} +21 -22
- package/dist/{consensus-vote-types-DexdhOdB.d.ts → consensus-vote-types-Ca0BY3kZ.d.ts} +5 -5
- package/dist/{context-retriever-HVPLM6DH.js → context-retriever-P6ZE6UXS.js} +11 -11
- package/dist/{doctor-deep-UZQTD65D.js → doctor-deep-HBIDPGWI.js} +5 -5
- package/dist/doctor-gateway-report-FWV7UN2Q.js +20 -0
- package/dist/{doctor-live-QFLT5SDB.js → doctor-live-676TWMP6.js} +18 -16
- package/dist/doctor-live-676TWMP6.js.map +1 -0
- package/dist/{expert-bridge-O3UZXLGB.js → expert-bridge-3VWOHLC7.js} +6 -6
- package/dist/factory-A44THDRG.js +31 -0
- package/dist/factory-YPJXDMN4.js +17 -0
- package/dist/{improvement-review-7IABFAU3.js → improvement-review-FIKPTCPE.js} +8 -8
- package/dist/index.d.ts +69 -77
- package/dist/index.js +65 -56
- package/dist/index.js.map +1 -1
- package/dist/{init-opencode-RYPBCM4P.js → init-opencode-YRHLGHIE.js} +7 -7
- package/dist/issue-triage-BLZHWC6U.js +20 -0
- package/dist/{learning-persistence-FB4U5AX2.js → learning-persistence-X24OOBYY.js} +3 -3
- package/dist/{model-config-helpers-QNV3KD2W.js → model-config-helpers-OQEZU2ZG.js} +4 -4
- package/dist/{openai-compat-adapter-GPZDIR3D.js → openai-compat-adapter-GRJA6RPZ.js} +7 -7
- package/dist/{outcome-store-adapter-WRONSTUD.js → outcome-store-adapter-7CKYWUZV.js} +4 -4
- package/dist/{pr-reviewer-helpers-7SMNNDFJ.js → pr-reviewer-helpers-LKHPRW3X.js} +6 -6
- package/dist/{registry-command-U7K6HIBK.js → registry-command-4LP4OSND.js} +4 -4
- package/dist/{repo-security-plan-LFWLVEL2.js → repo-security-plan-SOFUCHO4.js} +5 -5
- package/dist/research-helpers-synthesize-SXNAJWHX.js +16 -0
- package/dist/{routing-memory-OCIVXKOU.js → routing-memory-TWCXFPAD.js} +4 -4
- package/dist/{session-memory-4LMEWO3X.js → session-memory-YVT5M4AN.js} +5 -5
- package/dist/setup-config-JGHFR4P3.js +13 -0
- package/dist/{setup-custom-api-QEPUCHOX.js → setup-custom-api-EFAWNJ2D.js} +6 -6
- package/dist/setup-data-dir-Z4IQNMRC.js +34 -0
- package/dist/{tool-memory-EFGFXF65.js → tool-memory-5Y3HKHCB.js} +8 -8
- package/dist/{tool-memory-registry-adapters-FO6JVQSX.js → tool-memory-registry-adapters-IIORYBCC.js} +3 -3
- package/dist/unified-registry-IDUII5J7.js +35 -0
- package/dist/{weather-report-PMYQ3WBW.js → weather-report-KA5RT57Q.js} +4 -4
- package/dist/weather-report-KA5RT57Q.js.map +1 -0
- package/package.json +1 -1
- package/dist/chunk-KASBBKAQ.js.map +0 -1
- package/dist/chunk-KLSNDPU6.js.map +0 -1
- package/dist/chunk-LWOX65CP.js.map +0 -1
- package/dist/chunk-OMSKBBL7.js.map +0 -1
- package/dist/chunk-QRC3QLUQ.js.map +0 -1
- package/dist/chunk-SX2XWCGA.js.map +0 -1
- package/dist/chunk-TW6PJYTP.js.map +0 -1
- package/dist/chunk-UZJ6IPJR.js +0 -1057
- package/dist/chunk-UZJ6IPJR.js.map +0 -1
- package/dist/chunk-WKFPKPEM.js.map +0 -1
- package/dist/chunk-XDUTAUK3.js.map +0 -1
- package/dist/doctor-gateway-report-LRBPPJD4.js +0 -20
- package/dist/doctor-live-QFLT5SDB.js.map +0 -1
- package/dist/factory-KCKQQAEH.js +0 -31
- package/dist/factory-LUKUIB6C.js +0 -17
- package/dist/issue-triage-YBVJVAUU.js +0 -20
- package/dist/research-helpers-synthesize-X6MVAAKT.js +0 -16
- package/dist/setup-config-YNNNR4AE.js +0 -13
- package/dist/setup-data-dir-XM5DWH7R.js +0 -35
- package/dist/unified-registry-KLNC24HA.js +0 -36
- /package/dist/{ast-rule-runner-RI7ZJ4ZI.js.map → ast-rule-runner-3QRCP53C.js.map} +0 -0
- /package/dist/{child-mcp-config-73BX4OTR.js.map → child-mcp-config-NWPUDUVE.js.map} +0 -0
- /package/dist/{chunk-GAX75XCV.js.map → chunk-2BZXKNLN.js.map} +0 -0
- /package/dist/{chunk-QG3VDSWD.js.map → chunk-2HYJTIDY.js.map} +0 -0
- /package/dist/{chunk-VLOPXV47.js.map → chunk-53XY7YJD.js.map} +0 -0
- /package/dist/{chunk-23QA6GQD.js.map → chunk-66BE4KF4.js.map} +0 -0
- /package/dist/{chunk-TDUXUVUU.js.map → chunk-6LWUJOAS.js.map} +0 -0
- /package/dist/{chunk-JGY5HMWI.js.map → chunk-7HHXY7LM.js.map} +0 -0
- /package/dist/{chunk-J44RKRGQ.js.map → chunk-AFJE7K6Q.js.map} +0 -0
- /package/dist/{chunk-WD4DENPC.js.map → chunk-B3RAA47W.js.map} +0 -0
- /package/dist/{chunk-RIYFVDYN.js.map → chunk-CSDGNGAG.js.map} +0 -0
- /package/dist/{chunk-U37AITDA.js.map → chunk-DCPCO2XG.js.map} +0 -0
- /package/dist/{chunk-SS53PZO6.js.map → chunk-EFZRQ67X.js.map} +0 -0
- /package/dist/{chunk-7EV3QAKO.js.map → chunk-HUYVIUF3.js.map} +0 -0
- /package/dist/{chunk-V26YQQK4.js.map → chunk-I7VX7JWV.js.map} +0 -0
- /package/dist/{chunk-LXYT2P2Y.js.map → chunk-IGYZQEKM.js.map} +0 -0
- /package/dist/{chunk-XSAUVC6D.js.map → chunk-NLFCAEEK.js.map} +0 -0
- /package/dist/{chunk-QM5W3T3O.js.map → chunk-PZMODRBC.js.map} +0 -0
- /package/dist/{chunk-GX5ZHZR2.js.map → chunk-QARJHIQM.js.map} +0 -0
- /package/dist/{chunk-EJ5EBTAV.js.map → chunk-QTRPH2ZF.js.map} +0 -0
- /package/dist/{chunk-FNHVTDHZ.js.map → chunk-R75NZRON.js.map} +0 -0
- /package/dist/{chunk-UE7A2XHM.js.map → chunk-RUHKJC3X.js.map} +0 -0
- /package/dist/{chunk-4JJI34RV.js.map → chunk-S6XQFO4A.js.map} +0 -0
- /package/dist/{chunk-LP5ISXKM.js.map → chunk-SFWQ72ZS.js.map} +0 -0
- /package/dist/{chunk-D6E3LJGP.js.map → chunk-T4WTZTZY.js.map} +0 -0
- /package/dist/{chunk-JPFRRNRR.js.map → chunk-U7HL7FGS.js.map} +0 -0
- /package/dist/{chunk-2DYCHHWW.js.map → chunk-VVMSE5LF.js.map} +0 -0
- /package/dist/{chunk-QS5HGPCN.js.map → chunk-VZ7YZ2EF.js.map} +0 -0
- /package/dist/{chunk-PXYSAG5B.js.map → chunk-WFMMJWMO.js.map} +0 -0
- /package/dist/{chunk-FWQSOPBA.js.map → chunk-YOT3XEZM.js.map} +0 -0
- /package/dist/{chunk-JUUGLXSN.js.map → chunk-YRZV6KXZ.js.map} +0 -0
- /package/dist/{cli-auth-probe-A7ANEHJ7.js.map → cli-auth-probe-GUK3NXW3.js.map} +0 -0
- /package/dist/{cli-circuit-breaker-62YOQQT3.js.map → cli-circuit-breaker-VWARWNGU.js.map} +0 -0
- /package/dist/{cli-readiness-O6EOQV4L.js.map → cli-readiness-S23DWZCS.js.map} +0 -0
- /package/dist/{composite-router-YPZ3D5Y6.js.map → cli-server-gateway-DJXSHHXA.js.map} +0 -0
- /package/dist/{consensus-vote-RNQQUBXO.js.map → composite-router-3CBLBUGZ.js.map} +0 -0
- /package/dist/{context-retriever-HVPLM6DH.js.map → consensus-vote-JMWTJO4M.js.map} +0 -0
- /package/dist/{doctor-deep-UZQTD65D.js.map → context-retriever-P6ZE6UXS.js.map} +0 -0
- /package/dist/{doctor-gateway-report-LRBPPJD4.js.map → doctor-deep-HBIDPGWI.js.map} +0 -0
- /package/dist/{expert-bridge-O3UZXLGB.js.map → doctor-gateway-report-FWV7UN2Q.js.map} +0 -0
- /package/dist/{factory-KCKQQAEH.js.map → expert-bridge-3VWOHLC7.js.map} +0 -0
- /package/dist/{factory-LUKUIB6C.js.map → factory-A44THDRG.js.map} +0 -0
- /package/dist/{improvement-review-7IABFAU3.js.map → factory-YPJXDMN4.js.map} +0 -0
- /package/dist/{issue-triage-YBVJVAUU.js.map → improvement-review-FIKPTCPE.js.map} +0 -0
- /package/dist/{init-opencode-RYPBCM4P.js.map → init-opencode-YRHLGHIE.js.map} +0 -0
- /package/dist/{learning-persistence-FB4U5AX2.js.map → issue-triage-BLZHWC6U.js.map} +0 -0
- /package/dist/{model-config-helpers-QNV3KD2W.js.map → learning-persistence-X24OOBYY.js.map} +0 -0
- /package/dist/{openai-compat-adapter-GPZDIR3D.js.map → model-config-helpers-OQEZU2ZG.js.map} +0 -0
- /package/dist/{pr-reviewer-helpers-7SMNNDFJ.js.map → openai-compat-adapter-GRJA6RPZ.js.map} +0 -0
- /package/dist/{outcome-store-adapter-WRONSTUD.js.map → outcome-store-adapter-7CKYWUZV.js.map} +0 -0
- /package/dist/{repo-security-plan-LFWLVEL2.js.map → pr-reviewer-helpers-LKHPRW3X.js.map} +0 -0
- /package/dist/{registry-command-U7K6HIBK.js.map → registry-command-4LP4OSND.js.map} +0 -0
- /package/dist/{research-helpers-synthesize-X6MVAAKT.js.map → repo-security-plan-SOFUCHO4.js.map} +0 -0
- /package/dist/{routing-memory-OCIVXKOU.js.map → research-helpers-synthesize-SXNAJWHX.js.map} +0 -0
- /package/dist/{session-memory-4LMEWO3X.js.map → routing-memory-TWCXFPAD.js.map} +0 -0
- /package/dist/{setup-config-YNNNR4AE.js.map → session-memory-YVT5M4AN.js.map} +0 -0
- /package/dist/{setup-data-dir-XM5DWH7R.js.map → setup-config-JGHFR4P3.js.map} +0 -0
- /package/dist/{setup-custom-api-QEPUCHOX.js.map → setup-custom-api-EFAWNJ2D.js.map} +0 -0
- /package/dist/{tool-memory-EFGFXF65.js.map → setup-data-dir-Z4IQNMRC.js.map} +0 -0
- /package/dist/{tool-memory-registry-adapters-FO6JVQSX.js.map → tool-memory-5Y3HKHCB.js.map} +0 -0
- /package/dist/{unified-registry-KLNC24HA.js.map → tool-memory-registry-adapters-IIORYBCC.js.map} +0 -0
- /package/dist/{weather-report-PMYQ3WBW.js.map → unified-registry-IDUII5J7.js.map} +0 -0
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
import {
|
|
5
5
|
createLogger,
|
|
6
6
|
getTimeProvider
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-GPRYEKAA.js";
|
|
8
8
|
|
|
9
9
|
// src/mcp/tools/scanner-registry-fetcher.ts
|
|
10
10
|
import { z } from "zod";
|
|
@@ -740,4 +740,4 @@ export {
|
|
|
740
740
|
generateSecurityPlan,
|
|
741
741
|
buildPlanFromAnalysis
|
|
742
742
|
};
|
|
743
|
-
//# sourceMappingURL=chunk-
|
|
743
|
+
//# sourceMappingURL=chunk-7HHXY7LM.js.map
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
toSdkCallback,
|
|
8
8
|
toolStructuredError,
|
|
9
9
|
wrapToolWithTimeout
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-SFWQ72ZS.js";
|
|
11
11
|
import {
|
|
12
12
|
JsonlStore,
|
|
13
13
|
SECURITY_KEYWORDS,
|
|
@@ -21,11 +21,11 @@ import {
|
|
|
21
21
|
hasMeasuredCategory,
|
|
22
22
|
isDeclaredNeverDeprecate,
|
|
23
23
|
isRegisteredToolName
|
|
24
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-GPRYEKAA.js";
|
|
25
25
|
import {
|
|
26
26
|
getNexusDataDir,
|
|
27
27
|
nexusDataPath
|
|
28
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-CH6IBWXH.js";
|
|
29
29
|
|
|
30
30
|
// src/mcp/tools/improvement-review.ts
|
|
31
31
|
import { readFile } from "node:fs/promises";
|
|
@@ -2431,4 +2431,4 @@ export {
|
|
|
2431
2431
|
runImprovementReview,
|
|
2432
2432
|
registerImprovementReviewTool
|
|
2433
2433
|
};
|
|
2434
|
-
//# sourceMappingURL=chunk-
|
|
2434
|
+
//# sourceMappingURL=chunk-AFJE7K6Q.js.map
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
getTimeProvider
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-GPRYEKAA.js";
|
|
4
4
|
|
|
5
5
|
// src/version.ts
|
|
6
6
|
import semver from "semver";
|
|
7
|
-
var VERSION = true ? "
|
|
7
|
+
var VERSION = true ? "11.0.0" : "dev";
|
|
8
8
|
var NODE_ENGINE_RANGE = ">=24";
|
|
9
9
|
function isNodeVersionSupported(version) {
|
|
10
10
|
return semver.satisfies(version, NODE_ENGINE_RANGE);
|
|
@@ -106,4 +106,4 @@ export {
|
|
|
106
106
|
contextForLogging,
|
|
107
107
|
measuredTrustTier
|
|
108
108
|
};
|
|
109
|
-
//# sourceMappingURL=chunk-
|
|
109
|
+
//# sourceMappingURL=chunk-B3RAA47W.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli/cli-auth-probe.ts"],"sourcesContent":["/**\n * cli-auth-probe — real authentication probes for the four AI CLIs.\n *\n * The existing `adapter.healthCheck()` only confirms the CLI binary exists +\n * the version is supported. It does NOT probe whether the CLI can actually\n * authenticate. Result: doctor reports \"✓ Auth: CLI auth\" for installed-but-\n * unauthed CLIs, then downstream commands fail with unreadable errors.\n *\n * This module fixes that. Per-CLI probes use the cheapest reliable signal:\n *\n * - claude → presence + non-expired `~/.claude/.credentials.json`\n * (or env: ANTHROPIC_API_KEY)\n * - codex → `codex login status` (returns auth state)\n * (or env: OPENAI_API_KEY)\n * - gemini → presence of `agy` only, reported as `unknown` (#4391). The arm\n * runs Antigravity, which offers no non-interactive auth check\n * and does NOT use `~/.gemini/oauth_creds.json`.\n * - opencode → `opencode auth list` (parses credential count)\n *\n * No live API calls. Tokens are never decoded or sent anywhere — only their\n * presence/expiry is read locally. Where a CLI offers no signal we can read, the\n * probe reports `unknown` rather than guessing in either direction (#4391).\n *\n * Source: Issue #2447. Replaces the binary-detection-only path in doctor.ts\n * (Issue #2439 follow-up).\n */\n\nimport { execFile } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport { existsSync, readFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\n\nimport { CLI_SUBPROCESS_TIMEOUTS } from '../config/timeouts.js';\nimport type { CliName, RoutingArmId } from '../cli-adapters/types.js';\n\nconst execFileAsync = promisify(execFile);\n\n/**\n * Result of probing a single CLI's authentication state. Discriminated so\n * callers can render auth | needs-login | not-installed | error states\n * without re-checking flags.\n */\nexport type AuthProbeResult =\n | {\n readonly cli: CliName;\n readonly state: 'authenticated';\n /** How auth was satisfied — informational, not load-bearing. */\n readonly via: 'env-var' | 'cli-credentials';\n /** Optional metadata (e.g. expiry timestamp) if cheap to extract. */\n readonly meta?: Readonly<Record<string, unknown>>;\n }\n | {\n readonly cli: CliName;\n readonly state: 'needs-login';\n /** Short, human-readable reason to surface to the operator. */\n readonly reason: string;\n /** Exact command the operator should run to fix this. */\n readonly fixCommand: string;\n /** Optional canonical URL for credentials/console. */\n readonly fixUrl?: string;\n /** Env-var alternative if applicable. */\n readonly envFallback?: string;\n }\n | {\n readonly cli: CliName;\n readonly state: 'not-installed';\n readonly reason: string;\n }\n | {\n /**\n * The CLI is installed, but it exposes NO way to verify authentication\n * that we can use (#4391). Not a failure — an honest absence of evidence.\n *\n * Callers must admit these optimistically. Asserting `needs-login` here\n * excluded a working `agy` arm from routing for exactly this reason\n * (#4346), and asserting `authenticated` is how the retired gemini CLI\n * stayed selectable while failing every call (#4318). Real invocation\n * failures — the circuit breaker — do the excluding instead.\n */\n readonly cli: RoutingArmId;\n readonly state: 'unknown';\n readonly reason: string;\n }\n | {\n readonly cli: CliName;\n readonly state: 'error';\n readonly reason: string;\n };\n\nconst HOME = homedir();\n\n// ============================================================================\n// Per-CLI probes\n// ============================================================================\n\nfunction claudeNeedsLogin(reason: string): AuthProbeResult {\n return {\n cli: 'claude',\n state: 'needs-login',\n reason,\n fixCommand: 'claude /login',\n envFallback: 'ANTHROPIC_API_KEY',\n fixUrl: 'https://console.anthropic.com/account/keys',\n };\n}\n\nfunction probeClaude(): AuthProbeResult {\n if (process.env['ANTHROPIC_API_KEY'] !== undefined && process.env['ANTHROPIC_API_KEY'] !== '') {\n return { cli: 'claude', state: 'authenticated', via: 'env-var' };\n }\n const credPath = join(HOME, '.claude', '.credentials.json');\n if (!existsSync(credPath)) {\n return claudeNeedsLogin(\n 'No credentials at ~/.claude/.credentials.json and ANTHROPIC_API_KEY is not set'\n );\n }\n try {\n const parsed: unknown = JSON.parse(readFileSync(credPath, 'utf-8'));\n if (!isClaudeCredsShape(parsed)) {\n return claudeNeedsLogin(\n 'Credentials file present but not in expected shape (missing claudeAiOauth.accessToken)'\n );\n }\n const expiresAt = parsed.claudeAiOauth.expiresAt;\n if (typeof expiresAt === 'number' && expiresAt < Date.now()) {\n return claudeNeedsLogin(`OAuth token expired ${new Date(expiresAt).toISOString()}`);\n }\n return {\n cli: 'claude',\n state: 'authenticated',\n via: 'cli-credentials',\n ...(typeof expiresAt === 'number' ? { meta: { expiresAt } } : {}),\n };\n } catch (e: unknown) {\n return {\n cli: 'claude',\n state: 'error',\n reason: `Failed to read claude credentials: ${e instanceof Error ? e.message : String(e)}`,\n };\n }\n}\n\nfunction codexNeedsLogin(reason: string): AuthProbeResult {\n return {\n cli: 'codex',\n state: 'needs-login',\n reason,\n fixCommand: 'codex login',\n envFallback: 'OPENAI_API_KEY',\n fixUrl: 'https://platform.openai.com/api-keys',\n };\n}\n\nfunction classifyCodexStdout(stdout: string): AuthProbeResult {\n // Check 'not logged in' BEFORE 'logged in' — substring shadowing.\n if (/not logged/i.test(stdout) || /no.*token/i.test(stdout)) {\n return codexNeedsLogin(stdout.trim().split('\\n')[0] ?? 'Not logged in');\n }\n // Probe succeeded; treat unrecognized output as authed (defensive default).\n return { cli: 'codex', state: 'authenticated', via: 'cli-credentials' };\n}\n\nasync function probeCodex(): Promise<AuthProbeResult> {\n if (process.env['OPENAI_API_KEY'] !== undefined && process.env['OPENAI_API_KEY'] !== '') {\n return { cli: 'codex', state: 'authenticated', via: 'env-var' };\n }\n try {\n const { stdout } = await execFileAsync('codex', ['login', 'status'], {\n timeout: CLI_SUBPROCESS_TIMEOUTS.spawnMs,\n });\n return classifyCodexStdout(stdout);\n } catch (e: unknown) {\n const msg = e instanceof Error ? e.message : String(e);\n if (/ENOENT|not found/i.test(msg)) {\n return { cli: 'codex', state: 'not-installed', reason: 'codex binary not on PATH' };\n }\n return codexNeedsLogin('Not logged in (codex login status returned non-zero)');\n }\n}\n\n/**\n * Probe the gemini arm, which runs `agy` (Antigravity) since Google retired the\n * standalone gemini CLI (#4389).\n *\n * There is no usable auth signal to read, and that is a measured conclusion\n * rather than an assumption:\n *\n * - `agy models` HANGS when stdout is not a TTY — 90s and no output, confirmed\n * from both a shell pipe and `execFile` (#4393). Unusable programmatically.\n * - There is no `auth`, `login` or `whoami` subcommand.\n * - agy does not use `~/.gemini/oauth_creds.json`. Verified experimentally:\n * it served a completion while that file sat untouched with an expiry 5.5\n * hours in the past. The previous probe read exactly that file and so\n * reported `needs-login` for a working gateway.\n *\n * The only thing that proves agy works is a real completion, which costs ~17k\n * input tokens per call — far too expensive for a health check.\n *\n * So this reports `unknown`: the binary is there, and we decline to guess. Under\n * the admission policy that makes the arm selectable, and a genuinely broken\n * gateway is excluded by its own failures rather than by a check that cannot\n * see it.\n */\nasync function probeGemini(): Promise<AuthProbeResult> {\n try {\n // `--version` is local, instant, and TTY-independent — it establishes\n // presence without asserting anything about auth.\n await execFileAsync('agy', ['--version'], {\n timeout: CLI_SUBPROCESS_TIMEOUTS.spawnMs,\n maxBuffer: 64 * 1024,\n });\n return {\n cli: 'gemini',\n state: 'unknown',\n reason: 'agy exposes no non-interactive auth check; admitted unverified',\n };\n } catch (e: unknown) {\n const err = e as { code?: string | number };\n if (err.code === 'ENOENT') {\n return { cli: 'gemini', state: 'not-installed', reason: 'agy binary not found on PATH' };\n }\n // Deliberately does not echo stderr: gateway output can carry request\n // content or credential material.\n return { cli: 'gemini', state: 'error', reason: 'agy --version failed' };\n }\n}\n\nasync function probeOpencode(): Promise<AuthProbeResult> {\n // OpenCode's auth is per-provider (anthropic/openai/etc) configured in\n // ~/.config/opencode/opencode.json or via env. Probe by listing credentials.\n try {\n const { stdout } = await execFileAsync('opencode', ['auth', 'list'], {\n timeout: CLI_SUBPROCESS_TIMEOUTS.spawnMs,\n });\n if (/0 credentials/i.test(stdout)) {\n return {\n cli: 'opencode',\n state: 'needs-login',\n reason: 'No providers configured in opencode',\n fixCommand: 'opencode auth login',\n fixUrl: 'https://opencode.ai/docs/config',\n };\n }\n return { cli: 'opencode', state: 'authenticated', via: 'cli-credentials' };\n } catch (e: unknown) {\n const msg = e instanceof Error ? e.message : String(e);\n if (/ENOENT|not found/i.test(msg)) {\n return { cli: 'opencode', state: 'not-installed', reason: 'opencode binary not on PATH' };\n }\n return {\n cli: 'opencode',\n state: 'error',\n reason: msg.split('\\n')[0] ?? 'opencode auth list failed',\n };\n }\n}\n\n// ============================================================================\n// Type guards (no `any`, no untyped JSON)\n// ============================================================================\n\ninterface ClaudeCreds {\n readonly claudeAiOauth: {\n readonly accessToken: string;\n readonly expiresAt?: number;\n };\n}\n\nfunction isClaudeCredsShape(v: unknown): v is ClaudeCreds {\n if (typeof v !== 'object' || v === null) return false;\n const oauth = (v as { claudeAiOauth?: unknown }).claudeAiOauth;\n if (typeof oauth !== 'object' || oauth === null) return false;\n return typeof (oauth as { accessToken?: unknown }).accessToken === 'string';\n}\n\n// ============================================================================\n// Public API\n// ============================================================================\n\n/** Probe a single CLI. */\nexport async function probeCli(cli: CliName): Promise<AuthProbeResult> {\n switch (cli) {\n case 'claude':\n return Promise.resolve(probeClaude());\n case 'codex':\n return probeCodex();\n case 'gemini':\n return probeGemini();\n case 'opencode':\n return probeOpencode();\n }\n}\n\n/** Probe all four CLIs in parallel. Order in returned array matches input. */\nexport async function probeAllClis(): Promise<readonly AuthProbeResult[]> {\n const clis: readonly CliName[] = ['claude', 'gemini', 'codex', 'opencode'];\n return Promise.all(clis.map((c) => probeCli(c)));\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,3 +1,33 @@
|
|
|
1
|
+
// src/config/sandbox-detection.ts
|
|
2
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
+
function detectSandbox() {
|
|
4
|
+
const flavorRaw = process.env["NEXUS_SANDBOX"]?.trim();
|
|
5
|
+
const flavor = flavorRaw !== void 0 && flavorRaw !== "" ? flavorRaw : void 0;
|
|
6
|
+
const active = flavor !== void 0;
|
|
7
|
+
const rootRaw = process.env["NEXUS_SANDBOX_ROOT"]?.trim();
|
|
8
|
+
const root = rootRaw !== void 0 && rootRaw !== "" ? rootRaw : void 0;
|
|
9
|
+
return {
|
|
10
|
+
active,
|
|
11
|
+
flavor,
|
|
12
|
+
root,
|
|
13
|
+
heuristicMatch: detectContainerHeuristic()
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
function detectContainerHeuristic() {
|
|
17
|
+
try {
|
|
18
|
+
if (existsSync("/.dockerenv")) return "docker";
|
|
19
|
+
if (existsSync("/run/.containerenv")) return "podman";
|
|
20
|
+
if (existsSync("/proc/1/cgroup")) {
|
|
21
|
+
const cgroup = readFileSync("/proc/1/cgroup", "utf8");
|
|
22
|
+
if (/\bdocker\b/.test(cgroup)) return "docker";
|
|
23
|
+
if (/\bcontainerd\b/.test(cgroup)) return "docker";
|
|
24
|
+
}
|
|
25
|
+
return "unknown";
|
|
26
|
+
} catch {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
1
31
|
// src/config/defaults-env.ts
|
|
2
32
|
function parseIntEnv(envKey, fallback) {
|
|
3
33
|
const envValue = process.env[envKey];
|
|
@@ -40,36 +70,6 @@ import {
|
|
|
40
70
|
import { homedir as homedir2 } from "node:os";
|
|
41
71
|
import { dirname as dirname2, isAbsolute as isAbsolute2, join as join3, resolve as resolve3 } from "node:path";
|
|
42
72
|
|
|
43
|
-
// src/config/sandbox-detection.ts
|
|
44
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
45
|
-
function detectSandbox() {
|
|
46
|
-
const flavorRaw = process.env["NEXUS_SANDBOX"]?.trim();
|
|
47
|
-
const flavor = flavorRaw !== void 0 && flavorRaw !== "" ? flavorRaw : void 0;
|
|
48
|
-
const active = flavor !== void 0;
|
|
49
|
-
const rootRaw = process.env["NEXUS_SANDBOX_ROOT"]?.trim();
|
|
50
|
-
const root = rootRaw !== void 0 && rootRaw !== "" ? rootRaw : void 0;
|
|
51
|
-
return {
|
|
52
|
-
active,
|
|
53
|
-
flavor,
|
|
54
|
-
root,
|
|
55
|
-
heuristicMatch: detectContainerHeuristic()
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
function detectContainerHeuristic() {
|
|
59
|
-
try {
|
|
60
|
-
if (existsSync("/.dockerenv")) return "docker";
|
|
61
|
-
if (existsSync("/run/.containerenv")) return "podman";
|
|
62
|
-
if (existsSync("/proc/1/cgroup")) {
|
|
63
|
-
const cgroup = readFileSync("/proc/1/cgroup", "utf8");
|
|
64
|
-
if (/\bdocker\b/.test(cgroup)) return "docker";
|
|
65
|
-
if (/\bcontainerd\b/.test(cgroup)) return "docker";
|
|
66
|
-
}
|
|
67
|
-
return "unknown";
|
|
68
|
-
} catch {
|
|
69
|
-
return null;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
73
|
// src/config/repo-root-detection.ts
|
|
74
74
|
import { execFileSync } from "node:child_process";
|
|
75
75
|
import { existsSync as existsSync2, readFileSync as readFileSync2, realpathSync, statSync } from "node:fs";
|
|
@@ -509,4 +509,4 @@ export {
|
|
|
509
509
|
getPerRepoSubdirs,
|
|
510
510
|
sessionsDbPath
|
|
511
511
|
};
|
|
512
|
-
//# sourceMappingURL=chunk-
|
|
512
|
+
//# sourceMappingURL=chunk-CH6IBWXH.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/config/sandbox-detection.ts","../src/config/defaults-env.ts","../src/config/nexus-data-dir.ts","../src/config/repo-root-detection.ts","../src/config/portable-mode.ts"],"sourcesContent":["/**\n * Sandbox detection (#2501, child 1 of epic #2500).\n *\n * nexus-agents needs to know whether it's running inside a host-provided\n * sandbox (Docker Desktop Sandbox + OpenCode, Codex sandbox, locked-down\n * CI runner) so it can adjust behaviour:\n *\n * - default `NEXUS_DATA_DIR` to the multi-repo root rather than `~/...`\n * - skip CLI subprocess detection (binaries aren't there)\n * - fail-fast when the gateway is unreachable instead of degrading\n * - suppress diagnostics that don't apply\n *\n * The signal is **explicit**: the image author (Dockerfile, devcontainer,\n * harness wrapper) sets `NEXUS_SANDBOX=<flavor>`. The flavor string is\n * for diagnostics + per-flavor branching (`docker-opencode`, `codex`,\n * `claude-code`, `ci-restricted`, …); presence is the on/off signal.\n *\n * A heuristic check runs alongside for verification — if `NEXUS_SANDBOX`\n * claims `docker-opencode` but `/.dockerenv` is missing, that's a\n * misconfiguration worth surfacing to the operator.\n *\n * @module config/sandbox-detection\n */\n\nimport { existsSync, readFileSync } from 'node:fs';\n\n/**\n * Heuristic detection result. Independent from the explicit\n * `NEXUS_SANDBOX` env var; produced by inspecting filesystem markers.\n */\nexport type SandboxHeuristic = 'docker' | 'podman' | 'unknown' | null;\n\nexport interface SandboxInfo {\n /**\n * True iff `NEXUS_SANDBOX` is set + non-empty. The explicit signal\n * the rest of the codebase keys off — never `true` by heuristic alone.\n */\n readonly active: boolean;\n /**\n * Operator-supplied flavor string, e.g. `docker-opencode`. Undefined\n * when `active === false` or the env var is empty.\n */\n readonly flavor: string | undefined;\n /**\n * Multi-repo root the user mounted. From `NEXUS_SANDBOX_ROOT`. Undefined\n * when unset; consumers that need a default substitute `/`.\n */\n readonly root: string | undefined;\n /**\n * Independent heuristic match — `null` when we couldn't run the check,\n * `'unknown'` when ran but no marker matched. Used by `doctor` to flag\n * mismatches between the explicit signal and the runtime environment.\n */\n readonly heuristicMatch: SandboxHeuristic;\n}\n\n/**\n * Detect whether nexus-agents is running inside a host-provided sandbox.\n * Pure read of env + filesystem; no caching (cheap enough to recompute,\n * and tests routinely mutate process.env).\n */\nexport function detectSandbox(): SandboxInfo {\n const flavorRaw = process.env['NEXUS_SANDBOX']?.trim();\n const flavor = flavorRaw !== undefined && flavorRaw !== '' ? flavorRaw : undefined;\n const active = flavor !== undefined;\n\n const rootRaw = process.env['NEXUS_SANDBOX_ROOT']?.trim();\n const root = rootRaw !== undefined && rootRaw !== '' ? rootRaw : undefined;\n\n return {\n active,\n flavor,\n root,\n heuristicMatch: detectContainerHeuristic(),\n };\n}\n\n/**\n * Look for filesystem markers indicating a container runtime. Used as a\n * cross-check against the explicit `NEXUS_SANDBOX` signal.\n *\n * Order of checks:\n * 1. `/.dockerenv` (Docker)\n * 2. `/run/.containerenv` (Podman)\n * 3. `/proc/1/cgroup` containing `docker` or `containerd` strings\n *\n * Returns `null` when none of the checks could run (non-Linux host with\n * no `/proc`, sandbox blocks `existsSync`, etc.) — distinct from\n * `'unknown'` (checks ran, no marker matched) so the doctor message can\n * differentiate \"we couldn't tell\" from \"we checked, no container\".\n */\nfunction detectContainerHeuristic(): SandboxHeuristic {\n try {\n if (existsSync('/.dockerenv')) return 'docker';\n if (existsSync('/run/.containerenv')) return 'podman';\n if (existsSync('/proc/1/cgroup')) {\n const cgroup = readFileSync('/proc/1/cgroup', 'utf8');\n if (/\\bdocker\\b/.test(cgroup)) return 'docker';\n if (/\\bcontainerd\\b/.test(cgroup)) return 'docker';\n }\n return 'unknown';\n } catch {\n return null;\n }\n}\n","/**\n * Environment Variable Override Helpers\n *\n * Provides utilities for reading configuration from environment variables\n * with type-safe fallbacks to defaults.\n *\n * @module config/defaults-env\n */\n\nimport type { ToolRateLimitConfig } from './defaults-types.js';\n\n// ============================================================================\n// Environment Variable Parsers\n// ============================================================================\n\n/**\n * Parses an integer from an environment variable with fallback.\n */\nexport function parseIntEnv(envKey: string, fallback: number): number {\n const envValue = process.env[envKey];\n if (envValue === undefined) {\n return fallback;\n }\n const parsed = parseInt(envValue, 10);\n return isNaN(parsed) || parsed <= 0 ? fallback : parsed;\n}\n\n/**\n * Parses a float from an environment variable with fallback.\n */\nexport function parseFloatEnv(envKey: string, fallback: number): number {\n const envValue = process.env[envKey];\n if (envValue === undefined) {\n return fallback;\n }\n const parsed = parseFloat(envValue);\n return isNaN(parsed) || !isFinite(parsed) ? fallback : parsed;\n}\n\n/**\n * Parses a boolean flag value with fallback — the ONE accept-set for every\n * `NEXUS_*` boolean flag (#5155): `true`/`1` are truthy, `false`/`0` are\n * falsy, case-insensitively. Any other value (including `yes`/`no`/`on`/`off`\n * and the empty string) returns the fallback; `config/env-schema.ts`\n * (`boolLooseStr`) reports those at startup so the fallback is never silent.\n *\n * Pure so consumers that read an injected env object rather than\n * `process.env` (e.g. `cli-adapters/subprocess-env.ts`) share the same set.\n */\nexport function parseBoolValue(value: string | undefined, fallback: boolean): boolean {\n const normalized = value?.toLowerCase();\n if (normalized === undefined) {\n return fallback;\n }\n if (normalized === 'true' || normalized === '1') return true;\n if (normalized === 'false' || normalized === '0') return false;\n return fallback;\n}\n\n/**\n * Parses a boolean from an environment variable with fallback.\n * Delegates to {@link parseBoolValue}: only 'true'/'1' are truthy,\n * 'false'/'0' are falsy (case-insensitive); any other value returns the fallback.\n */\nexport function parseBoolEnv(envKey: string, fallback: boolean): boolean {\n return parseBoolValue(process.env[envKey], fallback);\n}\n\n// ============================================================================\n// Config Getter Types (for lazy initialization)\n// ============================================================================\n\n// The hand-written TimeoutDefaultsConst interface that lived here was removed\n// in #4939 with its last consumer (createGetTimeout); the exported type of the\n// same name is `typeof DEFAULTS.TIMEOUT_DEFAULTS` in config/defaults.ts.\n\n/** Tool rate limits type from DEFAULTS object */\nexport interface ToolRateLimitsConst {\n readonly orchestrate: ToolRateLimitConfig;\n readonly delegate: ToolRateLimitConfig;\n readonly workflow: ToolRateLimitConfig;\n readonly expert: ToolRateLimitConfig;\n}\n\n// ============================================================================\n// Config Getter Factory Functions\n// ============================================================================\n\n// createGetTimeout removed in #4939. It built `NEXUS_TIMEOUT_<KEY>` names at\n// runtime for a getter (getTimeout) with zero production callers, so the four\n// registered names — NEXUS_TIMEOUT_{CLI,API,WORKFLOW,MCP} — were read by nothing\n// that runs. Same class as the removals below.\n//\n// createGetWorkerConfig removed in #2977. createGetRetryConfig,\n// createGetRateLimitConfig and createGetCircuitBreakerConfig removed in #5903\n// for the same reason: their twelve NEXUS_* variables were registered,\n// documented and echoed back by `config get` as `Source: (env)`, and read by\n// nothing that runs. The rate limiter takes `enabled` from the config file\n// (`cli-server-tools.ts`), retry builds DEFAULT_RETRY_CONFIG from the static\n// DEFAULTS (`adapters/retry.ts`), and the production circuit breakers carry\n// their own config. See the comment in config/defaults.ts.\n\n/**\n * Creates a tool rate limit getter function bound to the DEFAULTS object.\n */\nexport function createGetToolRateLimit(\n toolRateLimits: ToolRateLimitsConst\n): (tool: keyof ToolRateLimitsConst) => ToolRateLimitConfig {\n return (tool: keyof ToolRateLimitsConst): ToolRateLimitConfig => {\n return toolRateLimits[tool];\n };\n}\n\n// createGetEnvVarDocumentation removed in #4939: its last remaining table was\n// the four NEXUS_TIMEOUT_* rows, all of which named unread variables.\n","/**\n * Nexus runtime data directory resolver (#2302, child of #2301).\n *\n * Returns the absolute path under which nexus-agents stores runtime state.\n * Two categories of state exist (epic #2872, vote #2876):\n *\n * - **Per-repo** state (sessions, checkpoints, traces, runs, audit,\n * pipeline, tasks) — tied to the work happening in a specific codebase.\n * When `NEXUS_REPO_PREFERRED=1` is set and the caller is inside a git\n * repo, these resolve to `<repo-root>/.nexus-agents/<subdir>/`.\n * - **Cross-repo** state (learning, voting, memory, weather, research,\n * auth, usage, models manifest) — accumulates across the operator's\n * whole workflow. Always resolves to `~/.nexus-agents/<subdir>/` so the\n * learning/routing feedback loop from #1389 / #1407 keeps working.\n *\n * Resolution order (first match wins):\n * 1. `NEXUS_DATA_DIR` env var if set + non-empty. Wins for both categories\n * — the operator's explicit choice overrides the split.\n * 2. Sandbox-mode default (#2501): when `NEXUS_SANDBOX` is set, use\n * `${NEXUS_SANDBOX_ROOT ?? '/'}/.nexus-agents`. Sandboxed deployments\n * typically mount a multi-repo root; state goes there, shared across\n * repo subfolders rather than buried inside one.\n * 3. Per-repo subdirs (when a repo root is known AND `NEXUS_REPO_PREFERRED`\n * is not explicitly `'0'`): `<repo-root>/.nexus-agents/<subdir>/`. The\n * repo root is the active workspace root when one has been set via\n * `setActiveWorkspaceRoot()` (the MCP server derives it from the client's\n * declared `roots`, MCP spec — see #3991), otherwise `findRepoRoot(cwd)`.\n * The active-root path exists because a globally-installed MCP server runs\n * with `process.cwd()` outside the repo, so cwd-based detection would\n * wrongly route per-repo state to homedir. Auto-adds `.nexus-agents/` to\n * the repo's `.gitignore` on first resolution (fail-closed).\n * 4. Cross-repo subdirs when `~/.nexus-agents/` is unwritable AND we're\n * in a repo: per-repo fallback at `<repo-root>/.nexus-agents/<subdir>/`\n * with one-time stderr announce. Issue #2888 — gives sandbox users\n * a working `research/`, `memory/`, etc. without env-var wrangling\n * while preserving the vote #2876 default for normal-machine users.\n * 5. `<homedir>/.nexus-agents` (cross-repo state on normal machines,\n * plus everything when not in a git repo or when\n * `NEXUS_REPO_PREFERRED=0`).\n *\n * `NEXUS_REPO_PREFERRED` defaults **ON** as of this release per vote\n * #2876. Escape hatches preserved:\n * - `NEXUS_REPO_PREFERRED=0` — fully opt out (homedir for everything).\n * - `NEXUS_DATA_DIR=~/.nexus-agents` — explicit override wins over\n * the tier and the categorization both.\n * - `nexus-agents migrate` (#2879) — relocate existing homedir state\n * into `<repo>/.nexus-agents/` before flipping in production.\n *\n * @module config/nexus-data-dir\n */\n\nimport {\n accessSync,\n constants as fsConstants,\n copyFileSync,\n existsSync,\n mkdirSync,\n realpathSync,\n renameSync,\n statSync,\n unlinkSync,\n} from 'node:fs';\nimport { homedir } from 'node:os';\nimport { dirname, isAbsolute, join, resolve } from 'node:path';\n\nimport { detectSandbox } from './sandbox-detection.js';\nimport { findRepoRoot, resolveMainCheckoutRoot } from './repo-root-detection.js';\nimport { ensureGitignored } from './portable-mode.js';\nimport { parseBoolEnv } from './defaults-env.js';\n\n/**\n * Subdirs scoped to a single repo's work (per the epic #2872 vote).\n * When `NEXUS_REPO_PREFERRED=1` and the caller is inside a git repo, these\n * route to `<repo-root>/.nexus-agents/`; otherwise homedir. The complement\n * (everything not in this set) always goes to homedir so cross-project\n * learning state stays intact.\n *\n * Source-of-truth for the categorization: vote #2876 conditions + audit\n * thread on epic #2872. Adding a new entry here is the appropriate way to\n * mark new state as per-repo — do that as a deliberate decision, not by\n * default.\n */\nconst PER_REPO_SUBDIRS: ReadonlySet<string> = new Set([\n // Scratch space for short-lived work (worktrees, prompt files, MCP configs).\n // Per-repo because a throwaway worktree of repo A is meaningless to repo B,\n // and because containing it here keeps it inside the already-gitignored\n // `.nexus-agents/` tree instead of accumulating in a shared `/tmp` nobody\n // reaps. A full `/tmp` is not a theoretical problem: it silently broke this\n // repo's own test suite, with ~1,100 files failing to COLLECT and zero\n // assertion failures, which reads like a code fault and is not one.\n 'tmp',\n 'sessions',\n 'checkpoints',\n 'traces',\n 'runs',\n 'audit',\n 'pipeline',\n 'tasks',\n // Async-mode job results (#3042 / epic #2631). Tied to work happening\n // in a specific codebase — a job dispatched on repo A shouldn't be\n // polled-against on repo B, which would happen if jobs/ were homedir-scoped.\n 'jobs',\n // CI health-check events (#3076 / #3084). Per-repo because outages\n // reported via `ci_health_check({ repo })` are repo-correlated; a wedge\n // on repo A's queue does not predict repo B's health.\n 'ci-health',\n // Governance artifacts — the runtime authentic-vote-record ledger\n // (`governance/vote-records.jsonl`, #3897/#3927/#3991). Per-repo because a\n // promotion proposal is scoped to the codebase being changed; with\n // `NEXUS_REPO_PREFERRED=1` (default) these route to\n // `<repo>/.nexus-agents/governance/` (gitignored), and to\n // `~/.nexus-agents/governance/` otherwise (global / non-repo install). This\n // is DISTINCT from the committed `<repo>/governance/vote-records.jsonl`\n // ledger the promotion gate reads — that artifact is reached only via the\n // explicit `NEXUS_VOTE_RECORDS_PATH` override (#3991).\n 'governance',\n]);\n\n/**\n * Per-repo subdirs that belong to the MAIN checkout even when the caller runs\n * inside a linked git worktree (#6531). A worktree is reaped when its work\n * lands; a governance record written into `<worktree>/.nexus-agents/` went with\n * it, while the CLI reported it written. Everything else per-repo stays\n * worktree-local: a quick panel chose governance-only (option B, 2-1) over\n * routing all per-repo state, to keep parallel worktrees' scratch, sessions and\n * jobs isolated. Must be a subset of {@link PER_REPO_SUBDIRS}.\n */\nconst MAIN_CHECKOUT_SUBDIRS: ReadonlySet<string> = new Set(['governance']);\n\n/** Returns the absolute path to the nexus-agents data directory. */\nexport function getNexusDataDir(): string {\n const fromEnv = process.env['NEXUS_DATA_DIR']?.trim();\n if (fromEnv !== undefined && fromEnv !== '') {\n return resolve(fromEnv);\n }\n const sandbox = detectSandbox();\n if (sandbox.active) {\n return resolve(sandbox.root ?? '/', '.nexus-agents');\n }\n return join(homedir(), '.nexus-agents');\n}\n\n/**\n * No-op kept for source-compatibility with consumers that called this\n * earlier in development. The resolver is no longer cached, so resetting\n * is unnecessary. Kept exported (rather than removed) to avoid breaking\n * imports in tests that may have already adopted it.\n */\nexport function resetNexusDataDirCache(): void {\n // intentionally empty — see module docstring\n}\n\n/**\n * Tracks per-process state for the auto-gitignore wiring: we only need\n * to (a) probe + append to `.gitignore` once per repo, and (b) bail\n * cleanly if the operator has explicitly silenced the auto-add via\n * `NEXUS_GITIGNORE_AUTO=false`. Reset in tests via the helper below.\n */\nconst gitignoredRoots = new Set<string>();\n\n/** Test helper — clears the auto-gitignore \"already did this\" memo. */\nexport function _resetGitignoreMemoForTests(): void {\n gitignoredRoots.clear();\n}\n\n/**\n * Active workspace root, when known from a source more authoritative than\n * `process.cwd()`. Set by the MCP server (#3991) from the client's declared\n * `roots` (MCP spec) once the initialize handshake completes, because a\n * globally-installed server runs with cwd OUTSIDE the repo being worked on —\n * so cwd-based repo detection would route per-repo `.nexus-agents` state to\n * homedir instead of `<repo>/.nexus-agents/`. CLI / in-repo callers never set\n * this, so they keep resolving via `findRepoRoot(cwd)` exactly as before.\n */\nlet activeWorkspaceRoot: string | undefined;\n\n/**\n * Records the active workspace root for repo-scoped data resolution. The\n * value is cross-trust-boundary input (it arrives over the MCP transport from\n * the client), so it is validated before use: resolved to an absolute path,\n * canonicalized via `realpathSync` (defeats symlink games), and required to\n * be an existing directory. Returns `true` when accepted, `false` when the\n * value is empty/invalid (in which case the previous root is left untouched).\n * Passing `null`/empty explicitly clears any previously-set root.\n */\nexport function setActiveWorkspaceRoot(root: string | null | undefined): boolean {\n const trimmed = root?.trim();\n if (trimmed === undefined || trimmed === '') {\n activeWorkspaceRoot = undefined;\n return false;\n }\n try {\n const canonical = realpathSync(resolve(trimmed));\n if (!isAbsolute(canonical) || !statSync(canonical).isDirectory()) return false;\n activeWorkspaceRoot = canonical;\n return true;\n } catch {\n // Nonexistent path, permission error, or broken symlink — reject and keep\n // whatever was set before so a bad late signal can't corrupt resolution.\n return false;\n }\n}\n\n/** Returns the active workspace root if one has been set, else `undefined`. */\nexport function getActiveWorkspaceRoot(): string | undefined {\n return activeWorkspaceRoot;\n}\n\n/** Test helper — clears the active workspace root memo. */\nexport function _resetActiveWorkspaceRootForTests(): void {\n activeWorkspaceRoot = undefined;\n}\n\n/**\n * Returns the repo-scoped `.nexus-agents/` directory if all of the\n * following hold: `NEXUS_REPO_PREFERRED` is NOT explicitly `'0'` (it\n * defaults ON as of vote #2876), `NEXUS_DATA_DIR` is not explicitly set\n * (explicit override always wins), no sandbox is active, and a repo root is\n * known — either the active workspace root (set via\n * `setActiveWorkspaceRoot()`) or an ancestor `.git` found by\n * `findRepoRoot(cwd)`.\n *\n * Side effect: on the first successful resolution per process per repo,\n * appends `.nexus-agents/` to the repo's `.gitignore` if not already\n * present. The operator can silence this with the boolean\n * `NEXUS_GITIGNORE_AUTO` set to `false`/`0` (e.g. on CI runners with a\n * frozen working tree).\n *\n * `options.mainCheckout` (#6531): when the root found is a linked git\n * worktree, return the `.nexus-agents/` of the MAIN checkout it belongs to\n * (see `resolveMainCheckoutRoot`), so the state outlives the worktree.\n *\n * Returns `null` when any precondition fails; callers fall back to\n * `getNexusDataDir()` (homedir).\n */\nexport function getNexusRepoDir(options: { readonly mainCheckout?: boolean } = {}): string | null {\n if (!parseBoolEnv('NEXUS_REPO_PREFERRED', true)) return null;\n const fromEnv = process.env['NEXUS_DATA_DIR']?.trim();\n if (fromEnv !== undefined && fromEnv !== '') return null;\n if (detectSandbox().active) return null;\n // Prefer the MCP-client-declared workspace root (#3991) when the server has\n // set it; fall back to walking up from cwd for CLI / in-repo callers.\n const found = activeWorkspaceRoot ?? findRepoRoot(process.cwd());\n if (found === null) return null;\n // #6531: `mainCheckout` maps a linked worktree to the checkout it belongs to.\n const root = options.mainCheckout === true ? resolveMainCheckoutRoot(found) : found;\n maybeAutoGitignore(root);\n return join(root, '.nexus-agents');\n}\n\n/**\n * Best-effort fail-closed wiring: once per process per repo, ensure\n * `.nexus-agents/` is in `<repo>/.gitignore`. Silenced via the boolean\n * `NEXUS_GITIGNORE_AUTO=false` (or `0`; default on — #5155). Failures are\n * non-fatal — the helper logs to stderr and continues.\n */\nfunction maybeAutoGitignore(repoRoot: string): void {\n if (!parseBoolEnv('NEXUS_GITIGNORE_AUTO', true)) return;\n if (gitignoredRoots.has(repoRoot)) return;\n gitignoredRoots.add(repoRoot);\n // Only attempt when the repo root looks real — avoid spamming stderr\n // from temp-dir test fixtures that race the lifecycle.\n if (!existsSync(repoRoot)) return;\n ensureGitignored(repoRoot, '.nexus-agents/');\n}\n\n/**\n * Per-process memo: was the homedir base directory found writable on first\n * probe? Used to short-circuit the sandbox-fallback decision in\n * `nexusDataPath()` without spending a syscall on every call.\n */\nlet homedirWritable: boolean | undefined;\n\n/** Per-process memo: have we already announced a fallback for this subdir? */\nconst announcedFallbacks = new Set<string>();\n\n/** Test helper — clears the homedir-writability + announcement memos. */\nexport function _resetWritabilityMemoForTests(): void {\n homedirWritable = undefined;\n announcedFallbacks.clear();\n}\n\n/**\n * Returns true if `getNexusDataDir()` resolves to a writable location. Probed\n * once per process and memo'd — flips the sandbox-fallback on/off for\n * cross-repo subdirs. `mkdirSync(recursive: true)` is the safest probe\n * because it both creates the dir if missing and surfaces ENOENT/EACCES\n * issues at the same point. Failures here are non-fatal — they just route\n * cross-repo state to the per-repo fallback (issue #2888).\n */\nfunction isHomedirBaseWritable(): boolean {\n if (homedirWritable !== undefined) return homedirWritable;\n const base = getNexusDataDir();\n try {\n mkdirSync(base, { recursive: true });\n accessSync(base, fsConstants.W_OK);\n homedirWritable = true;\n } catch {\n homedirWritable = false;\n }\n return homedirWritable;\n}\n\n/**\n * Emit a one-time stderr warning when a cross-repo subdir falls back to\n * the per-repo location. Operators in sandboxes get a clear signal about\n * what happened without per-call noise.\n */\nfunction announceCrossRepoFallback(subdir: string, repoPath: string): void {\n if (announcedFallbacks.has(subdir)) return;\n announcedFallbacks.add(subdir);\n process.stderr.write(\n `[nexus] ${subdir}: homedir ~/.nexus-agents is not writable; ` +\n `using per-repo fallback at ${repoPath}. See docs/guides/SANDBOXED-USAGE.md.\\n`\n );\n}\n\n/**\n * Returns a path joined under the appropriate data directory for the\n * given subdir.\n *\n * Routing decision (first match wins):\n * 1. **Per-repo subdir + in a repo + `NEXUS_REPO_PREFERRED` not '0'** →\n * `<repo>/.nexus-agents/<subdir>/...`. Standard behavior since #2884.\n * 2. **Cross-repo subdir + homedir unwritable + in a repo** →\n * `<repo>/.nexus-agents/<subdir>/...` as a sandbox-friendly fallback\n * (issue #2888). Emits a one-time stderr warning per subdir.\n * 3. **Otherwise** → `<homedir>/.nexus-agents/<subdir>/...`. If homedir\n * is also unwritable, the actual write will surface the error.\n *\n * Existing callers don't need to change — the routing decision is driven\n * by the first segment, not by caller-declared intent.\n */\nexport function nexusDataPath(...segments: string[]): string {\n const first = segments[0];\n\n // Tier 1: per-repo subdir + repo-preferred default.\n if (first !== undefined && PER_REPO_SUBDIRS.has(first)) {\n const repoDir = getNexusRepoDir({ mainCheckout: MAIN_CHECKOUT_SUBDIRS.has(first) });\n if (repoDir !== null) {\n return join(repoDir, ...segments);\n }\n }\n\n // Tier 2: cross-repo subdir but homedir isn't reachable. Fall back to\n // the per-repo location if we're in a repo, so sandbox users get a\n // working `research/`, `memory/`, etc. without manual env-var setup.\n // Vote #2876 preserved: this only fires when homedir is physically\n // unreachable; normal-machine users see no change.\n if (first !== undefined && !isHomedirBaseWritable()) {\n const repoDir = getNexusRepoDir();\n if (repoDir !== null) {\n const fallbackPath = join(repoDir, ...segments);\n announceCrossRepoFallback(first, fallbackPath);\n return fallbackPath;\n }\n // No repo to fall back to AND homedir unreachable — return the\n // homedir path anyway. The caller's eventual write will surface\n // the underlying EACCES/ENOENT, which is the right error to show\n // because the environment is genuinely broken at that point.\n }\n\n return join(getNexusDataDir(), ...segments);\n}\n\n/**\n * Like `nexusDataPath()` but eagerly creates the parent directory so\n * callers don't have to remember `mkdirSync(dirname(p), { recursive: true })`\n * before every write. Issue #2890. Use when the returned path will be\n * written to immediately; for read-only resolution prefer `nexusDataPath()`.\n *\n * If the call resolves to a directory (single segment or no segments),\n * the target IS the resolved path. If it resolves to a file (multiple\n * segments), the target is its parent.\n */\nexport function nexusDataPathEnsure(...segments: string[]): string {\n const resolved = nexusDataPath(...segments);\n const target = segments.length > 1 ? dirname(resolved) : resolved;\n mkdirSync(target, { recursive: true });\n return resolved;\n}\n\n/**\n * Explicitly homedir-scoped path. Use this in code that handles\n * cross-project state (routing memory, model registry cache, auth,\n * research catalog) where you want a hard guarantee that the resolver\n * never routes per-repo, even if a new subdir gets accidentally added\n * to the `PER_REPO_SUBDIRS` allowlist later.\n */\nexport function nexusSharedPath(...segments: string[]): string {\n return join(getNexusDataDir(), ...segments);\n}\n\n/**\n * Returns the read-only categorization used by `nexusDataPath()`.\n * Exposed for tests and for tooling that wants to introspect which\n * subdirs are per-repo (e.g. the `nexus-agents migrate` command).\n */\nexport function getPerRepoSubdirs(): ReadonlySet<string> {\n return PER_REPO_SUBDIRS;\n}\n\n/** Per-process memo — the legacy session-DB relocation runs at most once. */\nlet legacySessionsDbMigrated = false;\n\n/** Test helper — clears the session-DB migration \"already did this\" memo. */\nexport function _resetSessionsDbMigrationMemoForTests(): void {\n legacySessionsDbMigrated = false;\n}\n\n/** SQLite sidecar suffixes that must travel with the main DB file. */\nconst SQLITE_SIDECAR_SUFFIXES = ['', '-wal', '-shm', '-journal'] as const;\n\n/**\n * Resolves the session-database path, performing a one-time relocation\n * of any pre-#2902 database on the first call per process.\n *\n * The session DB is per-repo episodic state and belongs in the\n * `sessions/` bucket alongside the session journals — vote #2876\n * categorized `sessions/` as per-repo, and issue #2902 (consensus\n * 3/3) extended that to the DB. Before #2902 the DB resolved via\n * `nexusDataPath('sessions.db')`; because `sessions.db` is not a\n * per-repo first segment, that landed cross-repo at\n * `~/.nexus-agents/sessions.db`, so a DB started in one repo leaked\n * into every other repo.\n *\n * If a legacy DB exists at the old location and none exists at the\n * new one, the legacy file (and any SQLite sidecars) is moved so\n * existing session history is preserved rather than silently\n * orphaned. The move is best-effort: a failure leaves the legacy DB\n * untouched for manual recovery and the caller gets a fresh DB at the\n * new path. `NEXUS_DATA_DIR` / `NEXUS_REPO_PREFERRED` / the\n * `NEXUS_SESSIONS_DB` override all sit upstream of this resolver.\n */\nexport function sessionsDbPath(): string {\n const target = nexusDataPath('sessions', 'sessions.db');\n if (!legacySessionsDbMigrated) {\n legacySessionsDbMigrated = true;\n migrateLegacySessionsDb(target);\n }\n return target;\n}\n\n/** One-time relocation of a pre-#2902 session DB. Best-effort — see `sessionsDbPath`. */\nfunction migrateLegacySessionsDb(target: string): void {\n const legacy = nexusDataPath('sessions.db'); // pre-#2902 resolution\n if (legacy === target || !existsSync(legacy) || existsSync(target)) return;\n try {\n mkdirSync(dirname(target), { recursive: true });\n for (const suffix of SQLITE_SIDECAR_SUFFIXES) {\n const from = `${legacy}${suffix}`;\n if (existsSync(from)) moveFile(from, `${target}${suffix}`);\n }\n } catch {\n // Best-effort — a migration failure must never break session storage.\n }\n}\n\n/** Moves a file, falling back to copy+unlink across a filesystem boundary. */\nfunction moveFile(from: string, to: string): void {\n try {\n renameSync(from, to);\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== 'EXDEV') throw err;\n copyFileSync(from, to);\n unlinkSync(from);\n }\n}\n","/**\n * Repo-root detection — walks upward from a starting directory looking for\n * `.git` so callers can scope state to the current git repo. Built for\n * epic #2872 (issue #2882) to support the per-repo `.nexus-agents/` data\n * directory ratified by vote #2876.\n *\n * Defenses adopted:\n * - Walks upward but stops at filesystem root (no infinite loop on\n * symlinks-to-self).\n * - Stops at filesystem boundary (different `stat.dev`) — refuses to\n * escape across mount points. Prevents a sandboxed workdir from\n * resolving a `.git` on the host filesystem.\n * - Detects git worktrees: `.git` may be a *file* containing\n * `gitdir: <path>` rather than a directory.\n * - Realpath's the final root and rejects results that escape the\n * starting cwd by symlink.\n *\n * Deferred (tracked separately; not blocking #2882):\n * - CVE-2022-24765-style ownership check (refuse `.git` owned by a\n * different uid than the running process). The git CLI added\n * `safe.directory` for this; we don't have a comparable allowlist\n * surface yet. In CI the heuristic is too noisy to be useful\n * (runners often clone as a different uid than the workload).\n * File-system isolation (the `stat.dev` check above) covers the\n * bulk of the attack class.\n *\n * @module config/repo-root-detection\n */\n\nimport { execFileSync } from 'node:child_process';\nimport { existsSync, readFileSync, realpathSync, statSync } from 'node:fs';\nimport { dirname, isAbsolute, join, resolve } from 'node:path';\n\n/**\n * Walks upward from `start` looking for `.git` (file or directory).\n * Returns the absolute path to the directory containing it, or `null`.\n *\n * Worktrees: a `.git` file (not dir) with contents `gitdir: <path>` is\n * still recognised as marking the repo root. The pointed-to gitdir is\n * NOT followed — we just need the worktree's own root.\n *\n * Filesystem boundary: stops walking if the next ancestor lives on a\n * different filesystem (different `stat.dev`). This prevents a workdir\n * mounted inside a sandbox from finding a host-side `.git`.\n */\nexport function findRepoRoot(start: string): string | null {\n if (!isAbsolute(start)) {\n start = resolve(start);\n }\n\n let current: string;\n try {\n current = realpathSync(start);\n } catch {\n return null;\n }\n\n let startDev: number | undefined;\n try {\n startDev = statSync(current).dev;\n } catch {\n return null;\n }\n\n // Walk up. Depth cap is paranoid but bounds the loop on pathological\n // symlinks even though realpathSync above should already prevent cycles.\n for (let i = 0; i < 64; i++) {\n if (isRepoRoot(current)) {\n return current;\n }\n const parent = dirname(current);\n if (parent === current) {\n // Hit filesystem root.\n return null;\n }\n try {\n if (statSync(parent).dev !== startDev) {\n // Crossed a mount point; refuse to escape.\n return null;\n }\n } catch {\n return null;\n }\n current = parent;\n }\n return null;\n}\n\n/** True iff `dir/.git` exists as a directory or a git-worktree marker file. */\nexport function isRepoRoot(dir: string): boolean {\n const gitPath = `${dir}/.git`;\n if (!existsSync(gitPath)) return false;\n try {\n const st = statSync(gitPath);\n if (st.isDirectory()) return true;\n if (st.isFile()) {\n // Worktree: first line should be `gitdir: <path>`.\n const head = readFileSync(gitPath, 'utf-8').slice(0, 256);\n return head.startsWith('gitdir:');\n }\n } catch {\n return false;\n }\n return false;\n}\n\n/** Upper bound on a git metadata file read; real ones are one short line. */\nconst MAX_GIT_METADATA_BYTES = 4096;\n\n/** First line of a small file, trimmed; undefined when absent, oversized or empty. */\nfunction readGitMetadataLine(path: string): string | undefined {\n try {\n if (!statSync(path).isFile() || statSync(path).size > MAX_GIT_METADATA_BYTES) return undefined;\n const line = readFileSync(path, 'utf-8').split('\\n')[0]?.trim();\n return line === undefined || line === '' ? undefined : line;\n } catch {\n return undefined;\n }\n}\n\n/** realpath, or undefined when the path does not resolve. */\nfunction realpathOrUndefined(path: string): string | undefined {\n try {\n return realpathSync(path);\n } catch {\n return undefined;\n }\n}\n\n/** Bound on each git call; a hung git must not hang data-dir resolution. */\nconst GIT_TIMEOUT_MS = 5_000;\n\n/**\n * Run git and return trimmed stdout, or undefined on any failure (git absent,\n * not a repository, unset config key, timeout). `GIT_*` variables are dropped\n * so an inherited `GIT_DIR`/`GIT_WORK_TREE` cannot answer for a different\n * repository than the one asked about.\n */\nfunction gitOutput(args: readonly string[]): string | undefined {\n const env = Object.fromEntries(\n Object.entries(process.env).filter(([key]) => !key.startsWith('GIT_'))\n );\n try {\n const out = execFileSync('git', [...args], {\n encoding: 'utf-8',\n stdio: ['ignore', 'pipe', 'ignore'],\n timeout: GIT_TIMEOUT_MS,\n env,\n }).trim();\n return out === '' ? undefined : out;\n } catch {\n return undefined;\n }\n}\n\n/** git's common dir for the repository at `dir`, realpath'd. */\nfunction gitCommonDir(dir: string): string | undefined {\n const common = gitOutput(['-C', dir, 'rev-parse', '--path-format=absolute', '--git-common-dir']);\n return common === undefined ? undefined : realpathOrUndefined(common);\n}\n\n/**\n * The MAIN checkout that a linked worktree belongs to, or `repoRoot` itself\n * (#6531).\n *\n * `findRepoRoot` deliberately stops at a worktree's own root; this is the one\n * extra hop for state that must outlive the worktree. Every decision is git's\n * own answer (#6548 review), not a name heuristic:\n *\n * 1. Only a LINKED worktree moves: `repoRoot/.git` is a file whose admin dir\n * sits at `<common>/worktrees/<name>` and whose `gitdir` file points BACK\n * at this worktree (git writes it inside the main repository, not the\n * worktree — the guard against a forged marker redirecting writes, possibly\n * across a mount boundary). A submodule or `--separate-git-dir` checkout\n * also has a `.git` file, but no back-reference: it is a main checkout.\n * 2. `<common>` is `git rev-parse --git-common-dir`.\n * 3. `core.bare` in `<common>/config` → no main checkout; keep `repoRoot`.\n * 4. `core.worktree` set → that is the main checkout (a submodule: its common\n * dir is `<super>/.git/modules/<name>`). Otherwise the candidate is\n * `<common>`'s parent (the standard layout).\n * 5. The candidate is accepted only if git, run there, reports the same common\n * dir AND the candidate as its top level. A `--separate-git-dir` clone\n * records no path back to its checkout (git's own `worktree list` names the\n * git dir), so its worktrees fail here and keep their own root.\n */\nexport function resolveMainCheckoutRoot(repoRoot: string): string {\n const adminDir = backReferencedAdminDir(repoRoot);\n if (adminDir === undefined) return repoRoot;\n const commonDir = gitCommonDir(repoRoot);\n if (commonDir === undefined || dirname(adminDir) !== join(commonDir, 'worktrees')) {\n return repoRoot;\n }\n return mainCheckoutOf(commonDir) ?? repoRoot;\n}\n\n/**\n * The admin dir a worktree's `.git` marker names, only when that admin dir's\n * `gitdir` file points back at the same marker; undefined otherwise.\n */\nfunction backReferencedAdminDir(repoRoot: string): string | undefined {\n const dotGit = join(repoRoot, '.git');\n const target = readGitMetadataLine(dotGit)\n ?.match(/^gitdir:\\s*(.+)$/)?.[1]\n ?.trim();\n if (target === undefined) return undefined;\n const adminDir = resolveGitPath(repoRoot, target);\n if (adminDir === undefined) return undefined;\n const backRef = readGitMetadataLine(join(adminDir, 'gitdir'));\n if (backRef === undefined) return undefined;\n const pointsBack = resolveGitPath(adminDir, backRef) === realpathOrUndefined(dotGit);\n return pointsBack ? adminDir : undefined;\n}\n\n/** Resolve a git metadata path the way git does: relative to `base`, then realpath. */\nfunction resolveGitPath(base: string, value: string): string | undefined {\n return realpathOrUndefined(isAbsolute(value) ? value : resolve(base, value));\n}\n\n/** The checkout whose git dir is `commonDir`, per git; undefined when there is none. */\nfunction mainCheckoutOf(commonDir: string): string | undefined {\n const config = join(commonDir, 'config');\n if (gitOutput(['config', '--file', config, '--bool', '--get', 'core.bare']) === 'true') {\n return undefined;\n }\n const worktree = gitOutput(['config', '--file', config, '--get', 'core.worktree']);\n const candidate = realpathOrUndefined(\n worktree === undefined ? dirname(commonDir) : resolveUnder(commonDir, worktree)\n );\n if (candidate === undefined || gitCommonDir(candidate) !== commonDir) return undefined;\n const topLevel = gitOutput(['-C', candidate, 'rev-parse', '--show-toplevel']);\n if (topLevel === undefined || realpathOrUndefined(topLevel) !== candidate) return undefined;\n return candidate;\n}\n\n/** `value` resolved against `base` unless already absolute (core.worktree semantics). */\nfunction resolveUnder(base: string, value: string): string {\n return isAbsolute(value) ? value : resolve(base, value);\n}\n","/**\n * portable-mode — auto-detect sandboxed execution environments and\n * redirect nexus-agents data to a workspace-local directory.\n *\n * The existing `getNexusDataDir()` already respects `NEXUS_DATA_DIR` and\n * `NEXUS_PORTABLE_MODE`. This module wires up the *auto-detection* path\n * so operators running inside a Docker / restricted-FS / sandboxed\n * environment don't have to set the env vars manually — nexus-agents\n * notices and announces the flip.\n *\n * Source: Issue #2471 (epic #2467 child).\n *\n * Detection rules (first match wins):\n * 1. `NEXUS_DATA_DIR` already set → respect, no auto-detect\n * 2. `NEXUS_PORTABLE_MODE=0` → operator opted out, no auto-detect\n * 3. `NEXUS_PORTABLE_MODE=1` → portable mode, no detection needed\n * 4. Heuristic: home directory not writable\n * 5. Heuristic: container env vars set (KUBERNETES_SERVICE_HOST,\n * DOCKER_CONTAINER, ECS_CONTAINER_METADATA_URI, SANDBOX, etc.)\n *\n * When portable mode fires:\n * - Sets `process.env.NEXUS_DATA_DIR` to `<cwd>/.nexus-agents`\n * - Announces once on stderr (subsequent calls are silent)\n * - If cwd is a git repo, appends `.nexus-agents/` to .gitignore\n *\n * To force off: `NEXUS_PORTABLE_MODE=0`. To force on without heuristics:\n * `NEXUS_PORTABLE_MODE=1`. To override target dir: `NEXUS_DATA_DIR=...`.\n */\n\nimport { accessSync, constants, existsSync, readFileSync, appendFileSync, statSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join, resolve } from 'node:path';\n\n/** Container / sandbox environment variables that strongly imply portable mode. */\nconst SANDBOX_ENV_VARS: readonly string[] = [\n 'KUBERNETES_SERVICE_HOST',\n 'DOCKER_CONTAINER',\n 'ECS_CONTAINER_METADATA_URI',\n 'ECS_CONTAINER_METADATA_URI_V4',\n 'SANDBOX',\n 'NEXUS_SANDBOX',\n];\n\nlet DETECTED = false;\n\ninterface DetectionResult {\n readonly portable: boolean;\n readonly dataDir: string;\n readonly reason:\n 'env-data-dir' | 'env-opt-out' | 'env-opt-in' | 'home-unwritable' | 'container-env' | 'default';\n}\n\nfunction isHomeWritable(): boolean {\n try {\n const home = homedir();\n if (!existsSync(home)) return false;\n accessSync(home, constants.W_OK);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction inSandboxEnv(): boolean {\n for (const v of SANDBOX_ENV_VARS) {\n const val = process.env[v];\n if (val !== undefined && val !== '') return true;\n }\n return false;\n}\n\n/**\n * The operator-declared sandbox root, when set (#5026).\n *\n * Read directly rather than through `detectSandbox()` to keep this module free\n * of imports — it runs as a side effect of `cli-log-bootstrap` before anything\n * else loads.\n */\nfunction sandboxRootFromEnv(): string | undefined {\n const raw = process.env['NEXUS_SANDBOX_ROOT']?.trim();\n return raw !== undefined && raw !== '' ? resolve(raw) : undefined;\n}\n\nfunction checkExplicitEnv(cwd: string): DetectionResult | null {\n const fromEnv = process.env['NEXUS_DATA_DIR']?.trim();\n if (fromEnv !== undefined && fromEnv !== '') {\n return { portable: false, dataDir: resolve(fromEnv), reason: 'env-data-dir' };\n }\n const portableEnv = process.env['NEXUS_PORTABLE_MODE'];\n if (portableEnv === '0' || portableEnv === 'false') {\n return { portable: false, dataDir: join(homedir(), '.nexus-agents'), reason: 'env-opt-out' };\n }\n if (portableEnv === '1' || portableEnv === 'true') {\n return { portable: true, dataDir: join(cwd, '.nexus-agents'), reason: 'env-opt-in' };\n }\n return null;\n}\n\nfunction checkHeuristics(cwd: string): DetectionResult | null {\n if (!isHomeWritable()) {\n return { portable: true, dataDir: join(cwd, '.nexus-agents'), reason: 'home-unwritable' };\n }\n if (inSandboxEnv()) {\n // #5026: prefer an explicitly declared sandbox root over cwd. `NEXUS_SANDBOX`\n // is one of SANDBOX_ENV_VARS, so setting it makes this heuristic fire and\n // stamp `NEXUS_DATA_DIR = <cwd>/.nexus-agents` — which then short-circuits\n // `getNexusDataDir`'s own sandbox branch (`nexus-data-dir.ts:124-127`)\n // before it can honour `NEXUS_SANDBOX_ROOT`. The documented purpose of that\n // variable (\"default NEXUS_DATA_DIR to the multi-repo root\") therefore never\n // happened, and state fragmented per working directory across a multi-repo\n // mount. `doctor`'s dataDirInsideRepo check exists to detect exactly that\n // layout and reports it as operator misconfiguration.\n const declaredRoot = sandboxRootFromEnv();\n return {\n portable: true,\n dataDir: join(declaredRoot ?? cwd, '.nexus-agents'),\n reason: 'container-env',\n };\n }\n return null;\n}\n\n/**\n * Decide whether to flip nexus-agents to portable mode and what data dir\n * to use. Pure function; no side effects beyond reading env + filesystem.\n */\nexport function detectPortableMode(cwd: string = process.cwd()): DetectionResult {\n return (\n checkExplicitEnv(cwd) ??\n checkHeuristics(cwd) ?? {\n portable: false,\n dataDir: join(homedir(), '.nexus-agents'),\n reason: 'default',\n }\n );\n}\n\n/**\n * Apply detected portable mode at process startup. Idempotent (subsequent\n * calls are no-ops). Announces on stderr once when the auto-detection\n * heuristic triggers — env-opt-in/out doesn't announce because the\n * operator already knows.\n */\nexport function applyPortableMode(cwd: string = process.cwd()): void {\n if (DETECTED) return;\n DETECTED = true;\n\n const result = detectPortableMode(cwd);\n\n // No env, no heuristic match — nothing to do.\n if (!result.portable) return;\n\n // The heuristic-driven cases announce; explicit opt-in is silent.\n if (result.reason === 'home-unwritable' || result.reason === 'container-env') {\n process.stderr.write(\n `[portable-mode] Sandbox detected (${result.reason}). Using ${result.dataDir} for all nexus-agents data.\\n` +\n ` Set NEXUS_PORTABLE_MODE=0 to override; see docs/guides/SANDBOXED-USAGE.md\\n`\n );\n }\n\n process.env['NEXUS_DATA_DIR'] = result.dataDir;\n\n if (isInsideGitRepo(cwd)) {\n ensureGitignored(cwd, '.nexus-agents/');\n }\n}\n\nfunction isInsideGitRepo(cwd: string): boolean {\n try {\n const gitDir = join(cwd, '.git');\n return existsSync(gitDir) && statSync(gitDir).isDirectory();\n } catch {\n return false;\n }\n}\n\n/**\n * Append `entry` to `<cwd>/.gitignore` if not already present. Stderr\n * announce on the first append. Exported because the repo-preferred\n * resolver in `nexus-data-dir.ts` reuses the same auto-gitignore behavior\n * when `<repo>/.nexus-agents/` becomes the active data dir (epic #2872).\n */\nexport function ensureGitignored(cwd: string, entry: string): void {\n const path = join(cwd, '.gitignore');\n try {\n const existing = existsSync(path) ? readFileSync(path, 'utf-8') : '';\n const lines = existing.split('\\n').map((l) => l.trim());\n if (lines.includes(entry) || lines.includes(entry.replace(/\\/$/, ''))) return;\n const sep = existing.length > 0 && !existing.endsWith('\\n') ? '\\n' : '';\n appendFileSync(path, `${sep}${entry}\\n`, 'utf-8');\n process.stderr.write(\n `[portable-mode] Added '${entry}' to ${path} so nexus-agents data is gitignored.\\n`\n );\n } catch (e: unknown) {\n // Non-fatal — operator can add the line themselves.\n const msg = e instanceof Error ? e.message : String(e);\n process.stderr.write(\n `[portable-mode] Could not auto-update ${path} (${msg}). Add '${entry}' manually if you don't want nexus-agents data tracked by git.\\n`\n );\n }\n}\n\n/** Test-only reset of the once-only state. */\nexport function _resetDetectedForTests(): void {\n DETECTED = false;\n}\n"],"mappings":"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","names":["accessSync","existsSync","realpathSync","statSync","homedir","dirname","isAbsolute","join","resolve","existsSync","readFileSync","existsSync","readFileSync","statSync","join","resolve","resolve","join","homedir","realpathSync","resolve","isAbsolute","statSync","join","existsSync","accessSync","join","dirname","existsSync","dirname"]}
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
} from "./chunk-ROYGBFVE.js";
|
|
10
10
|
import {
|
|
11
11
|
nexusDataPath
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-CH6IBWXH.js";
|
|
13
13
|
|
|
14
14
|
// src/mcp/tools/tool-memory-registry-adapters.ts
|
|
15
15
|
var DEFAULT_SEARCH_LIMIT = 10;
|
|
@@ -102,4 +102,4 @@ export {
|
|
|
102
102
|
StatsOnlyAdapter,
|
|
103
103
|
ensureSharedMemoryRegistry
|
|
104
104
|
};
|
|
105
|
-
//# sourceMappingURL=chunk-
|
|
105
|
+
//# sourceMappingURL=chunk-CSDGNGAG.js.map
|
|
@@ -6,56 +6,7 @@ import {
|
|
|
6
6
|
createLogger,
|
|
7
7
|
err,
|
|
8
8
|
ok
|
|
9
|
-
} from "./chunk-
|
|
10
|
-
|
|
11
|
-
// src/adapters/gateway-redaction.ts
|
|
12
|
-
var GATEWAY_REDACTED = "<redacted>";
|
|
13
|
-
function redactGatewaySecrets(message, source, placeholder = GATEWAY_REDACTED) {
|
|
14
|
-
const secrets = [source.apiKey, ...Object.values(source.headers ?? {})].filter((s) => typeof s === "string" && s.trim() !== "").sort((a, b) => b.length - a.length);
|
|
15
|
-
return secrets.reduce((text, secret) => text.replaceAll(secret, placeholder), message);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
// src/adapters/sdk/gateway-env.ts
|
|
19
|
-
function readTrimmed(env, name) {
|
|
20
|
-
const value = env[name]?.trim();
|
|
21
|
-
return value === void 0 || value === "" ? void 0 : value;
|
|
22
|
-
}
|
|
23
|
-
function resolveGatewayEnv(env = process.env) {
|
|
24
|
-
return {
|
|
25
|
-
baseUrl: readTrimmed(env, OPENAI_COMPAT_URL_ENV),
|
|
26
|
-
apiKey: readTrimmed(env, OPENAI_COMPAT_KEY_ENV)
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
function readGatewayEnv(env = process.env) {
|
|
30
|
-
return resolveGatewayEnv(env);
|
|
31
|
-
}
|
|
32
|
-
var CUSTOM_API_SURFACE_ENV = "NEXUS_CUSTOM_API_SURFACE";
|
|
33
|
-
function readCustomApiSurface(env = process.env) {
|
|
34
|
-
const raw = env[CUSTOM_API_SURFACE_ENV]?.trim().toLowerCase() ?? "";
|
|
35
|
-
if (raw === "" || raw === "chat") return "chat";
|
|
36
|
-
if (raw === "responses") return "responses";
|
|
37
|
-
throw new ConfigError(
|
|
38
|
-
`${CUSTOM_API_SURFACE_ENV} must be one of: responses, chat (default chat); got an unrecognised value`
|
|
39
|
-
);
|
|
40
|
-
}
|
|
41
|
-
var OPENAI_BASE_URL_ENV = "OPENAI_BASE_URL";
|
|
42
|
-
var OPENAI_API_HOST = "api.openai.com";
|
|
43
|
-
function readDirectOpenAiSurface(env = process.env) {
|
|
44
|
-
const baseUrl = env[OPENAI_BASE_URL_ENV]?.trim() ?? "";
|
|
45
|
-
if (baseUrl === "" || hostnameOf(baseUrl) === OPENAI_API_HOST) return void 0;
|
|
46
|
-
return readCustomApiSurface(env);
|
|
47
|
-
}
|
|
48
|
-
function hostnameOf(baseUrl) {
|
|
49
|
-
try {
|
|
50
|
-
const host = new URL(baseUrl).hostname;
|
|
51
|
-
return host === "" ? "<no host>" : host;
|
|
52
|
-
} catch {
|
|
53
|
-
return "<unparseable url>";
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
function redactApiKey(message, apiKey) {
|
|
57
|
-
return redactGatewaySecrets(message, { apiKey });
|
|
58
|
-
}
|
|
9
|
+
} from "./chunk-GPRYEKAA.js";
|
|
59
10
|
|
|
60
11
|
// src/adapters/sdk/custom-api-validation.ts
|
|
61
12
|
import { isIPv4, isIPv6 } from "node:net";
|
|
@@ -295,14 +246,63 @@ function classifyIPv6(ip) {
|
|
|
295
246
|
return detail === null ? null : { reason: detail.reason, message: `${detail.message} via IPv6 (${ip})` };
|
|
296
247
|
}
|
|
297
248
|
|
|
249
|
+
// src/adapters/gateway-redaction.ts
|
|
250
|
+
var GATEWAY_REDACTED = "<redacted>";
|
|
251
|
+
function redactGatewaySecrets(message, source, placeholder = GATEWAY_REDACTED) {
|
|
252
|
+
const secrets = [source.apiKey, ...Object.values(source.headers ?? {})].filter((s) => typeof s === "string" && s.trim() !== "").sort((a, b) => b.length - a.length);
|
|
253
|
+
return secrets.reduce((text, secret) => text.replaceAll(secret, placeholder), message);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// src/adapters/sdk/gateway-env.ts
|
|
257
|
+
function readTrimmed(env, name) {
|
|
258
|
+
const value = env[name]?.trim();
|
|
259
|
+
return value === void 0 || value === "" ? void 0 : value;
|
|
260
|
+
}
|
|
261
|
+
function resolveGatewayEnv(env = process.env) {
|
|
262
|
+
return {
|
|
263
|
+
baseUrl: readTrimmed(env, OPENAI_COMPAT_URL_ENV),
|
|
264
|
+
apiKey: readTrimmed(env, OPENAI_COMPAT_KEY_ENV)
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
function readGatewayEnv(env = process.env) {
|
|
268
|
+
return resolveGatewayEnv(env);
|
|
269
|
+
}
|
|
270
|
+
var CUSTOM_API_SURFACE_ENV = "NEXUS_CUSTOM_API_SURFACE";
|
|
271
|
+
function readCustomApiSurface(env = process.env) {
|
|
272
|
+
const raw = env[CUSTOM_API_SURFACE_ENV]?.trim().toLowerCase() ?? "";
|
|
273
|
+
if (raw === "" || raw === "chat") return "chat";
|
|
274
|
+
if (raw === "responses") return "responses";
|
|
275
|
+
throw new ConfigError(
|
|
276
|
+
`${CUSTOM_API_SURFACE_ENV} must be one of: responses, chat (default chat); got an unrecognised value`
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
var OPENAI_BASE_URL_ENV = "OPENAI_BASE_URL";
|
|
280
|
+
var OPENAI_API_HOST = "api.openai.com";
|
|
281
|
+
function readDirectOpenAiSurface(env = process.env) {
|
|
282
|
+
const baseUrl = env[OPENAI_BASE_URL_ENV]?.trim() ?? "";
|
|
283
|
+
if (baseUrl === "" || hostnameOf(baseUrl) === OPENAI_API_HOST) return void 0;
|
|
284
|
+
return readCustomApiSurface(env);
|
|
285
|
+
}
|
|
286
|
+
function hostnameOf(baseUrl) {
|
|
287
|
+
try {
|
|
288
|
+
const host = new URL(baseUrl).hostname;
|
|
289
|
+
return host === "" ? "<no host>" : host;
|
|
290
|
+
} catch {
|
|
291
|
+
return "<unparseable url>";
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
function redactApiKey(message, apiKey) {
|
|
295
|
+
return redactGatewaySecrets(message, { apiKey });
|
|
296
|
+
}
|
|
297
|
+
|
|
298
298
|
export {
|
|
299
|
+
validateCustomApiBaseUrl,
|
|
300
|
+
assertCustomApiHostResolvesPublic,
|
|
299
301
|
redactGatewaySecrets,
|
|
300
302
|
readGatewayEnv,
|
|
301
303
|
readCustomApiSurface,
|
|
302
304
|
readDirectOpenAiSurface,
|
|
303
305
|
hostnameOf,
|
|
304
|
-
redactApiKey
|
|
305
|
-
validateCustomApiBaseUrl,
|
|
306
|
-
assertCustomApiHostResolvesPublic
|
|
306
|
+
redactApiKey
|
|
307
307
|
};
|
|
308
|
-
//# sourceMappingURL=chunk-
|
|
308
|
+
//# sourceMappingURL=chunk-D33IH5VS.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/adapters/sdk/custom-api-validation.ts","../src/adapters/gateway-redaction.ts","../src/adapters/sdk/gateway-env.ts"],"sourcesContent":["/**\n * Validation for the `custom-openai` SDK adapter's gateway URL.\n *\n * The base URL is user-provided, so without validation the adapter becomes\n * an SSRF vector: a malicious prompt that reshaped env vars, or a typo in\n * the config, could point nexus-agents at `http://169.254.169.254/` (AWS\n * metadata) or `http://localhost:5432/` (internal services). This module\n * rejects such URLs unless the user explicitly opts in via\n * `NEXUS_CUSTOM_API_ALLOW_PRIVATE=1`.\n *\n * Called out in the #2119 consensus vote by the Security Engineer role.\n *\n * @module adapters/sdk/custom-api-validation\n */\n\nimport { isIPv4, isIPv6 } from 'node:net';\nimport { lookup as dnsLookup } from 'node:dns/promises';\nimport { ConfigError, ok, err, createLogger, type Result } from '../../core/index.js';\nimport { CUSTOM_API_ALLOW_PRIVATE_ENV } from './types.js';\n\nconst logger = createLogger({ module: 'custom-api-validation' });\n\n/**\n * Why a given URL was rejected. Machine-readable so error messages can\n * distinguish root causes in downstream tooling.\n */\nexport type BaseUrlRejectionReason =\n | 'empty'\n | 'not_a_url'\n | 'not_http_https'\n | 'loopback'\n | 'link_local'\n | 'private_range'\n | 'reserved';\n\ninterface RejectionDetail {\n readonly reason: BaseUrlRejectionReason;\n readonly message: string;\n}\n\n/**\n * Validates a user-provided custom-gateway base URL. Returns the URL as an\n * `ok` Result if it passes, or a `ConfigError` with a machine-readable\n * reason if it fails.\n *\n * Pass `{ allowPrivate: true }` (or set the env var) to bypass the SSRF\n * checks — use this only when the gateway is on a trusted internal host\n * and you accept the risk.\n */\nexport function validateCustomApiBaseUrl(\n raw: string | undefined,\n opts: { readonly allowPrivate?: boolean } = {}\n): Result<URL, ConfigError> {\n if (raw === undefined || raw.trim() === '') {\n return err(\n new ConfigError(\n 'Custom API base URL is required but missing. Set NEXUS_OPENAI_COMPAT_URL or pass `baseUrl` in config.'\n )\n );\n }\n\n let url: URL;\n try {\n url = new URL(raw);\n } catch {\n // Not echoed: these messages reach the resilient-adapter warn line, and\n // the raw string can carry userinfo (#4392 inc 3).\n return err(new ConfigError('Custom API base URL is not a valid URL'));\n }\n\n if (url.protocol !== 'http:' && url.protocol !== 'https:') {\n return err(\n new ConfigError(`Custom API base URL must use http or https, got \"${url.protocol}\"`)\n );\n }\n\n const allowPrivate = opts.allowPrivate === true || resolveAllowPrivateFromEnv();\n if (!allowPrivate) {\n const rejection = classifyPrivateHost(url.hostname);\n if (rejection !== null) {\n return err(\n new ConfigError(\n `Custom API base URL rejected (SSRF guard, reason=\"${rejection.reason}\"): ${rejection.message}. ` +\n `Set ${CUSTOM_API_ALLOW_PRIVATE_ENV}=1 to bypass if the gateway runs on a trusted internal host.`\n )\n );\n }\n }\n\n return ok(url);\n}\n\nfunction resolveAllowPrivateFromEnv(): boolean {\n // Deliberately strict (case-sensitive, not parseBoolEnv): this disables an\n // SSRF guard, so we don't want extra case variants (e.g. `TRUE`) to loosen\n // the control. Fail-closed on anything but exact `1`/`true` (#3297).\n const v = process.env[CUSTOM_API_ALLOW_PRIVATE_ENV];\n return v === '1' || v === 'true';\n}\n\n/**\n * Returns a rejection reason if the hostname resolves (by string form) to\n * a loopback, link-local, or RFC 1918 private address; `null` otherwise.\n *\n * Note: this is a string-level check. It does NOT perform DNS resolution,\n * so a public DNS name that secretly resolves to a private IP will pass.\n * For that level of defense, callers should additionally run\n * {@link assertCustomApiHostResolvesPublic}, which performs a DNS lookup\n * and classifies each resolved address with the same range tables below.\n */\nfunction classifyPrivateHost(hostname: string): RejectionDetail | null {\n const normalized = normalizeHost(hostname);\n\n // Literal IP addresses — classify directly against the range tables.\n const literal = classifyIpAddress(normalized);\n if (literal !== 'not_an_ip') {\n return literal;\n }\n\n // Hostname literals that resolve to loopback without needing DNS\n if (\n normalized === 'localhost' ||\n normalized.endsWith('.localhost') ||\n normalized.endsWith('.local') // mDNS\n ) {\n return {\n reason: 'loopback',\n message: `hostname \"${hostname}\" resolves to loopback/mDNS`,\n };\n }\n\n return null;\n}\n\n/**\n * Strips IPv6 bracket wrapping (URL.hostname yields \"[::1]\") and lowercases,\n * giving the bare form the `node:net` checks expect. Shared by the sync and\n * async guards so the normalization rule lives in one place.\n */\nfunction normalizeHost(hostname: string): string {\n const stripped =\n hostname.startsWith('[') && hostname.endsWith(']') ? hostname.slice(1, -1) : hostname;\n return stripped.toLowerCase();\n}\n\n/**\n * Classifies a single (already normalized, bracket-stripped) address string.\n * Returns:\n * - a {@link RejectionDetail} if it is a private/loopback/link-local/reserved IP,\n * - `null` if it is a public IP literal (safe),\n * - the sentinel `'not_an_ip'` if the string is not an IP literal at all.\n *\n * This is the shared per-address classifier reused by both the sync\n * string-level guard ({@link classifyPrivateHost}) and the async\n * DNS-resolve-time guard ({@link assertCustomApiHostResolvesPublic}), so the\n * IPv4/IPv6 range tables live in exactly one place.\n */\nfunction classifyIpAddress(normalized: string): RejectionDetail | null | 'not_an_ip' {\n if (isIPv4(normalized)) {\n return classifyIPv4(normalized);\n }\n if (isIPv6(normalized)) {\n return classifyIPv6(normalized);\n }\n return 'not_an_ip';\n}\n\n/** Verdict for a host that is already an IP literal — no DNS required. */\nfunction classifyLiteral(hostname: string, normalized: string): Result<void, ConfigError> {\n const literal = classifyIpAddress(normalized);\n if (literal === null || literal === 'not_an_ip') return ok(undefined);\n return err(\n new ConfigError(\n `Custom API host '${hostname}' is a ${literal.reason} address: ${literal.message}`\n )\n );\n}\n\n/**\n * DNS-resolve-time SSRF guard for the custom-openai gateway.\n *\n * The sync {@link validateCustomApiBaseUrl} only inspects the hostname as a\n * string, so a PUBLIC DNS name that resolves to a PRIVATE/loopback/link-local\n * IP (e.g. an attacker-controlled `gateway.evil.test` pointing at\n * `169.254.169.254`) slips past it. This function closes that gap: it resolves\n * the hostname and runs the EXISTING IP classification against every returned\n * address, failing closed if ANY of them is private.\n *\n * Behaviour:\n * - `allowPrivate` (opt or env) → bypass, returns `ok` (matches the sync guard).\n * - hostname is already an IP literal → no DNS needed; the sync guard already\n * classified literals at construction, so return `ok`.\n * - otherwise `dns.lookup(hostname, { all: true })` and classify each address.\n * If ANY resolves private → `err(ConfigError)`.\n * - on a `dns.lookup` THROW (transient/NXDOMAIN/network error) → log debug and\n * return `ok`. Failing closed here is too aggressive: it would break legit\n * gateways on a flaky resolver, and this is additive defense-in-depth layered\n * on top of the sync string guard that already ran at construction. We only\n * REJECT on a SUCCESSFUL resolution to a private address.\n *\n * Residual risk (TOCTOU): this is RESOLVE-time, not socket-connect-time. A name\n * that passes here could rebind to a private IP before the actual connect\n * (DNS rebinding). A full fix needs a custom `fetch` `lookup` hook validating\n * the peer address at the socket layer — out of scope for this LOW\n * defense-in-depth pass (#3426).\n */\nexport async function assertCustomApiHostResolvesPublic(\n hostname: string,\n opts: { readonly allowPrivate?: boolean } = {}\n): Promise<Result<void, ConfigError>> {\n const allowPrivate = opts.allowPrivate === true || resolveAllowPrivateFromEnv();\n if (allowPrivate) {\n return ok(undefined);\n }\n\n const normalized = normalizeHost(hostname);\n\n // A literal needs no DNS, but it is still classified HERE rather than\n // assumed handled elsewhere. This used to return ok for any literal on the\n // grounds that the sync guard had classified it at construction — but\n // `discoverModels` calls only this guard, never the sync one, so on that\n // path each deferred to the other and neither ran. `http://169.254.169.254/`\n // reached IMDS, from a base URL that can come from a file rather than from\n // operator intent.\n if (isIPv4(normalized) || isIPv6(normalized)) {\n return classifyLiteral(hostname, normalized);\n }\n\n // Only `address` is read; the resolver's `family` is intentionally NOT\n // trusted — `classifyIpAddress` re-derives v4/v6 from the address string, so\n // a mislabeled family in the lookup result cannot let a private IP slip past.\n let addresses: ReadonlyArray<{ readonly address: string }>;\n try {\n addresses = await dnsLookup(hostname, { all: true });\n } catch (error) {\n // Fail OPEN: transient/NXDOMAIN/network errors must not break legit\n // gateways. The sync string guard already ran; this is additive only.\n logger.debug('custom-api SSRF resolve check: DNS lookup failed, skipping (fail-open)', {\n hostname,\n error: error instanceof Error ? error.message : String(error),\n });\n return ok(undefined);\n }\n\n const rejection = firstPrivateAddress(addresses);\n if (rejection !== null) {\n return err(\n new ConfigError(\n `Custom API base URL rejected (SSRF resolve guard, reason=\"${rejection.reason}\"): ` +\n `hostname \"${hostname}\" resolved to ${rejection.message}. ` +\n `Set ${CUSTOM_API_ALLOW_PRIVATE_ENV}=1 to bypass if the gateway runs on a trusted internal host.`\n )\n );\n }\n\n return ok(undefined);\n}\n\n/**\n * Returns the {@link RejectionDetail} for the first resolved address that\n * classifies as private/loopback/link-local/reserved, or `null` if every\n * address is public. Fail-closed: any single private hit trips the guard.\n */\nfunction firstPrivateAddress(\n addresses: ReadonlyArray<{ readonly address: string }>\n): RejectionDetail | null {\n for (const { address } of addresses) {\n const rejection = classifyIpAddress(address.toLowerCase());\n if (rejection !== null && rejection !== 'not_an_ip') {\n return rejection;\n }\n }\n return null;\n}\n\n/**\n * IPv4 ranges the SSRF guard rejects, in order of specificity.\n *\n * \"Not RFC1918\" is not the same as \"public\". The blocks below RFC1918 are\n * here because each is reachable from a host and serves something: 100.64/10\n * carries Alibaba Cloud's metadata endpoint, 192.0.0/24 carries a legacy\n * Oracle Cloud one, and multicast/reserved/broadcast reach the local segment\n * rather than the internet (second pass over #4884).\n */\nconst IPV4_RULES: ReadonlyArray<{\n readonly match: (a: number, b: number, c: number) => boolean;\n readonly reason: BaseUrlRejectionReason;\n readonly label: string;\n}> = [\n { match: (a) => a === 127, reason: 'loopback', label: 'IPv4 loopback' },\n { match: (a) => a === 10, reason: 'private_range', label: 'IPv4 private (10/8)' },\n {\n match: (a, b) => a === 172 && b >= 16 && b <= 31,\n reason: 'private_range',\n label: 'IPv4 private (172.16/12)',\n },\n {\n match: (a, b) => a === 192 && b === 168,\n reason: 'private_range',\n label: 'IPv4 private (192.168/16)',\n },\n {\n match: (a, b) => a === 169 && b === 254,\n reason: 'link_local',\n label: 'IPv4 link-local (169.254/16 — AWS IMDS)',\n },\n { match: (a) => a === 0, reason: 'reserved', label: 'IPv4 reserved (0/8)' },\n {\n match: (a, b) => a === 100 && b >= 64 && b <= 127,\n reason: 'private_range',\n label: 'IPv4 shared address space (100.64/10 — CGNAT, Alibaba metadata)',\n },\n {\n match: (a, b, c) => a === 192 && b === 0 && c === 0,\n reason: 'reserved',\n label: 'IPv4 IETF protocol assignments (192.0.0/24 — legacy Oracle metadata)',\n },\n {\n match: (a, b) => a === 198 && (b === 18 || b === 19),\n reason: 'reserved',\n label: 'IPv4 benchmarking (198.18/15)',\n },\n { match: (a) => a >= 224 && a <= 239, reason: 'reserved', label: 'IPv4 multicast (224/4)' },\n {\n match: (a) => a >= 240,\n reason: 'reserved',\n label: 'IPv4 reserved (240/4, includes 255.255.255.255 broadcast)',\n },\n];\n\nfunction classifyIPv4(ip: string): RejectionDetail | null {\n const parts = ip.split('.').map((p) => Number.parseInt(p, 10));\n if (parts.length !== 4 || parts.some((n) => Number.isNaN(n))) return null;\n const [a, b, c] = parts as [number, number, number, number];\n for (const rule of IPV4_RULES) {\n if (rule.match(a, b, c)) {\n return { reason: rule.reason, message: `${rule.label} (${ip})` };\n }\n }\n return null;\n}\n\n/**\n * Expand an IPv6 literal to its eight 16-bit groups, or null if unparseable.\n *\n * A trailing dotted quad (`::ffff:1.2.3.4`) is folded into two groups first,\n * so callers see one representation regardless of spelling.\n */\nfunction foldTrailingQuad(s: string): string | null {\n const dotted = /(?:^|:)((?:\\d{1,3}\\.){3}\\d{1,3})$/.exec(s);\n if (dotted?.[1] === undefined) return s;\n const q = dotted[1].split('.').map((n) => Number.parseInt(n, 10));\n if (q.length !== 4 || q.some((n) => Number.isNaN(n) || n < 0 || n > 255)) return null;\n const hi = (((q[0] ?? 0) << 8) | (q[1] ?? 0)).toString(16);\n const lo = (((q[2] ?? 0) << 8) | (q[3] ?? 0)).toString(16);\n return `${s.slice(0, s.length - dotted[1].length)}${hi}:${lo}`;\n}\n\nfunction toHextets(ip: string): number[] | null {\n const s = foldTrailingQuad(ip.toLowerCase());\n if (s === null) return null;\n const halves = s.split('::');\n if (halves.length > 2) return null;\n const head = halves[0] === '' || halves[0] === undefined ? [] : halves[0].split(':');\n const tail = halves[1] === '' || halves[1] === undefined ? [] : halves[1].split(':');\n const groups =\n halves.length === 1\n ? head\n : [...head, ...Array<string>(8 - head.length - tail.length).fill('0'), ...tail];\n if (groups.length !== 8) return null;\n const out = groups.map((g) => Number.parseInt(g === '' ? '0' : g, 16));\n return out.some((n) => Number.isNaN(n) || n < 0 || n > 0xffff) ? null : out;\n}\n\n/**\n * The IPv4 address embedded in an IPv6 literal, in dotted form, or null.\n *\n * Covers every prefix that carries a routable IPv4 payload: IPv4-mapped\n * (`::ffff:0:0/96`), IPv4-translated (`::ffff:0:0:0/96`), the NAT64\n * well-known prefix (`64:ff9b::/96`), 6to4 (`2002::/16`), and the deprecated\n * IPv4-compatible form (`::a.b.c.d`). Each is the same destination under a\n * different spelling, and the IPv4 rules must apply to all of them.\n */\nfunction embeddedIPv4(g: readonly number[]): string | null {\n // 6to4 is the odd one out: the payload sits in hextets 1-2, not 6-7, so it\n // cannot share the trailing-quad extraction below.\n if (g[0] === 0x2002) return hextetPairToDotted(g[1] ?? 0, g[2] ?? 0);\n const top = g.slice(0, 6);\n const prefixes: ReadonlyArray<readonly number[]> = [\n [0, 0, 0, 0, 0, 0xffff], // ::ffff:0:0/96 — IPv4-mapped\n [0, 0, 0, 0, 0xffff, 0], // ::ffff:0:0:0/96 — IPv4-translated\n [0x64, 0xff9b, 0, 0, 0, 0], // 64:ff9b::/96 — NAT64 well-known\n [0, 0, 0, 0, 0, 0], // ::a.b.c.d — deprecated IPv4-compatible\n ];\n const matched = prefixes.some((p) => p.every((v, i) => top[i] === v));\n if (!matched) return null;\n const g6 = g[6] ?? 0;\n const g7 = g[7] ?? 0;\n // `::` and `::1` are their own addresses, not embedded IPv4.\n if (top.every((v) => v === 0) && g6 === 0 && g7 <= 1) return null;\n return hextetPairToDotted(g6, g7);\n}\n\n/** The dotted IPv4 address carried by two consecutive 16-bit hextets. */\nfunction hextetPairToDotted(hi: number, lo: number): string {\n return [(hi >> 8) & 255, hi & 255, (lo >> 8) & 255, lo & 255].join('.');\n}\n\n/**\n * Classify an IPv6 address by its first hextet, or null if it is not in a\n * non-global prefix.\n *\n * Numeric rather than a string prefix test. The previous `startsWith('fe80:')`\n * covered 1/64th of fe80::/10 — `febf::1` is equally link-local and was\n * allowed — and site-local fec0::/10 had no check at all (second pass over #4884).\n */\nfunction classifyIPv6Prefix(first: number, ip: string): RejectionDetail | null {\n if (first >= 0xfc00 && first <= 0xfdff) {\n return { reason: 'private_range', message: `IPv6 unique-local (${ip}, fc00::/7)` };\n }\n if (first >= 0xfe80 && first <= 0xfebf) {\n return { reason: 'link_local', message: `IPv6 link-local (${ip}, fe80::/10)` };\n }\n if (first >= 0xfec0 && first <= 0xfeff) {\n return { reason: 'private_range', message: `IPv6 site-local (${ip}, fec0::/10)` };\n }\n return null;\n}\n\nfunction classifyIPv6(ip: string): RejectionDetail | null {\n const lower = ip.toLowerCase();\n if (lower === '::1') return { reason: 'loopback', message: `IPv6 loopback (${ip})` };\n\n const groups = toHextets(lower);\n if (groups === null) return null;\n\n const prefixDetail = classifyIPv6Prefix(groups[0] ?? 0, ip);\n if (prefixDetail !== null) return prefixDetail;\n\n if (groups.every((n) => n === 0)) {\n return { reason: 'reserved', message: `IPv6 unspecified (${ip}) — routes to localhost` };\n }\n\n // An IPv4 address written in IPv6 form is still that IPv4 address, and\n // `isIPv6` is true for it so it never reached the IPv4 rules — IMDS,\n // loopback and RFC1918 were all reachable this way. Note the URL parser\n // normalises the dotted spelling to hex, so the hex form is the one that\n // actually arrives.\n const v4 = embeddedIPv4(groups);\n if (v4 === null) return null;\n const detail = classifyIPv4(v4);\n return detail === null\n ? null\n : { reason: detail.reason, message: `${detail.message} via IPv6 (${ip})` };\n}\n","/**\n * Redaction of gateway credentials from error text.\n *\n * A gateway request carries the key (in `Authorization: Bearer` or in the\n * header `NEXUS_OPENAI_COMPAT_AUTH_HEADER` names) and every static header\n * from `NEXUS_OPENAI_COMPAT_EXTRA_HEADERS`. A gateway's error body may echo\n * any of them, and none has a vendor shape the pattern sanitizer knows, so\n * each is removed by exact match. One helper, so the discovery path, the\n * per-model adapter and `doctor --gateway` cannot disagree on what is secret.\n *\n * @module adapters/gateway-redaction\n */\n\n/** Replacement text for a redacted gateway credential. */\nconst GATEWAY_REDACTED = '<redacted>';\n\n/** The credentials one gateway call site sends. */\nexport interface GatewaySecretSource {\n /** The gateway key — also the auth-header value when a custom header carries it. */\n readonly apiKey?: string | undefined;\n /**\n * Headers sent on every request. EVERY value is treated as secret; a\n * `null` value removes a default header and carries nothing.\n */\n readonly headers?: Readonly<Record<string, string | null>> | undefined;\n}\n\n/**\n * `message` with the key and every header value replaced by `placeholder`.\n * Blank values are skipped (`replaceAll('')` would interleave the marker\n * between every character). Longer values are replaced first, so a value\n * that contains another is removed whole rather than leaving its remainder.\n */\nexport function redactGatewaySecrets(\n message: string,\n source: GatewaySecretSource,\n placeholder: string = GATEWAY_REDACTED\n): string {\n const secrets = [source.apiKey, ...Object.values(source.headers ?? {})]\n .filter((s): s is string => typeof s === 'string' && s.trim() !== '')\n .sort((a, b) => b.length - a.length);\n return secrets.reduce((text, secret) => text.replaceAll(secret, placeholder), message);\n}\n","/**\n * Canonical env resolver for the `custom-openai` gateway compatibility alias.\n *\n * The alias (`api:custom-openai`) and discovered arms (`api:<endpoint>`)\n * both read `NEXUS_OPENAI_COMPAT_URL` / `NEXUS_OPENAI_COMPAT_KEY`.\n *\n * @module adapters/sdk/gateway-env\n */\n\nimport { ConfigError } from '../../core/index.js';\nimport { OPENAI_COMPAT_KEY_ENV, OPENAI_COMPAT_URL_ENV } from './types.js';\nimport { redactGatewaySecrets } from '../gateway-redaction.js';\n\n/** The resolved canonical gateway pair. */\nexport interface GatewayEnv {\n /** `NEXUS_OPENAI_COMPAT_URL`, trimmed; empty is unset. */\n readonly baseUrl: string | undefined;\n /** `NEXUS_OPENAI_COMPAT_KEY`, trimmed; empty is unset. */\n readonly apiKey: string | undefined;\n}\n\n/** A set env value, trimmed; `undefined` for unset, empty, or whitespace-only. */\nfunction readTrimmed(env: NodeJS.ProcessEnv, name: string): string | undefined {\n const value = env[name]?.trim();\n return value === undefined || value === '' ? undefined : value;\n}\n\n/** Resolve the canonical gateway pair without logging. */\nexport function resolveGatewayEnv(env: NodeJS.ProcessEnv = process.env): GatewayEnv {\n return {\n baseUrl: readTrimmed(env, OPENAI_COMPAT_URL_ENV),\n apiKey: readTrimmed(env, OPENAI_COMPAT_KEY_ENV),\n };\n}\n\n/** The production reader for the canonical gateway pair. */\nexport function readGatewayEnv(env: NodeJS.ProcessEnv = process.env): GatewayEnv {\n return resolveGatewayEnv(env);\n}\n\n/**\n * The API surface the custom-openai alias and direct OpenAI gateway clients call:\n * `chat` is `POST <base>/chat/completions`, `responses` is `POST <base>/responses`.\n * Discovery-backed api:<endpoint> arms remain on chat.\n */\ntype CustomApiSurface = 'chat' | 'responses';\n\nconst CUSTOM_API_SURFACE_ENV = 'NEXUS_CUSTOM_API_SURFACE';\n\n/**\n * Read `NEXUS_CUSTOM_API_SURFACE` (#6645). Unset or empty means `chat`: the\n * AI SDK's default is the Responses API, but OpenAI-spec gateways commonly\n * serve `/chat/completions` only, so `responses` is the opt-in. Any other\n * value throws a `ConfigError` rather than guessing a surface.\n */\nexport function readCustomApiSurface(env: NodeJS.ProcessEnv = process.env): CustomApiSurface {\n const raw = env[CUSTOM_API_SURFACE_ENV]?.trim().toLowerCase() ?? '';\n if (raw === '' || raw === 'chat') return 'chat';\n if (raw === 'responses') return 'responses';\n throw new ConfigError(\n `${CUSTOM_API_SURFACE_ENV} must be one of: responses, chat (default chat); got an unrecognised value`\n );\n}\n\n/** Read by `@ai-sdk/openai` itself; the direct OpenAI adapter never passes a baseURL. */\nconst OPENAI_BASE_URL_ENV = 'OPENAI_BASE_URL';\nconst OPENAI_API_HOST = 'api.openai.com';\n\n/**\n * The surface the direct OpenAI adapter (`OPENAI_API_KEY`) calls (#6654).\n * `undefined` means the provider's own default (the Responses API), exactly\n * as before: `OPENAI_BASE_URL` unset, blank, or naming `api.openai.com`, and\n * `NEXUS_CUSTOM_API_SURFACE` is then not read at all. Any other host is an\n * OpenAI-compatible gateway, so it gets {@link readCustomApiSurface}'s answer:\n * chat completions unless `NEXUS_CUSTOM_API_SURFACE=responses`.\n */\nexport function readDirectOpenAiSurface(\n env: NodeJS.ProcessEnv = process.env\n): CustomApiSurface | undefined {\n const baseUrl = env[OPENAI_BASE_URL_ENV]?.trim() ?? '';\n if (baseUrl === '' || hostnameOf(baseUrl) === OPENAI_API_HOST) return undefined;\n return readCustomApiSurface(env);\n}\n\n/**\n * The host of a gateway base URL, for log lines and error messages. A base\n * URL can carry userinfo (`https://user:key@host/v1`), so the full string\n * must never reach a log. Two failure shapes, both replaced rather than\n * echoed: a string that does not parse at all, and a scheme-less paste such\n * as `key@host/v1`, which WHATWG parses with `key:` as the SCHEME and an\n * empty hostname (measured: `new URL('u:pw@host/v1').hostname === ''`).\n */\nexport function hostnameOf(baseUrl: string): string {\n try {\n const host = new URL(baseUrl).hostname;\n return host === '' ? '<no host>' : host;\n } catch {\n return '<unparseable url>';\n }\n}\n\n/**\n * Every exact occurrence of `apiKey` in `message` replaced with `<redacted>`.\n * The pattern-based sanitizer only knows vendor key shapes; a gateway key can\n * be any string, and a 401 body may echo the one it rejected. A blank key\n * matches nothing. The key-only case of {@link redactGatewaySecrets}.\n */\nexport function redactApiKey(message: string, apiKey: string | undefined): string {\n return redactGatewaySecrets(message, { apiKey });\n}\n"],"mappings":"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","names":[]}
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
err,
|
|
4
4
|
getErrorMessage,
|
|
5
5
|
ok
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-GPRYEKAA.js";
|
|
7
7
|
import {
|
|
8
8
|
CLI_SUBPROCESS_TIMEOUTS
|
|
9
9
|
} from "./chunk-4PN3UVZV.js";
|
|
@@ -283,4 +283,4 @@ export {
|
|
|
283
283
|
ScmError,
|
|
284
284
|
GitHubProvider
|
|
285
285
|
};
|
|
286
|
-
//# sourceMappingURL=chunk-
|
|
286
|
+
//# sourceMappingURL=chunk-DCPCO2XG.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
GitHubProvider,
|
|
3
3
|
ScmError
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-DCPCO2XG.js";
|
|
5
5
|
import {
|
|
6
6
|
ReputationCache,
|
|
7
7
|
assessReputation,
|
|
@@ -9,10 +9,10 @@ import {
|
|
|
9
9
|
resolveEnvMode,
|
|
10
10
|
resolveReputationGatingMode,
|
|
11
11
|
sanitizeInput
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-I7VX7JWV.js";
|
|
13
13
|
import {
|
|
14
14
|
hasToken
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-HUYVIUF3.js";
|
|
16
16
|
import {
|
|
17
17
|
toContentTrustProvenanceRecord
|
|
18
18
|
} from "./chunk-ZO6ZKR7L.js";
|
|
@@ -27,7 +27,7 @@ import {
|
|
|
27
27
|
getErrorMessage,
|
|
28
28
|
getTimeProvider,
|
|
29
29
|
ok
|
|
30
|
-
} from "./chunk-
|
|
30
|
+
} from "./chunk-GPRYEKAA.js";
|
|
31
31
|
import {
|
|
32
32
|
CLI_SUBPROCESS_TIMEOUTS
|
|
33
33
|
} from "./chunk-4PN3UVZV.js";
|
|
@@ -2645,4 +2645,4 @@ export {
|
|
|
2645
2645
|
IssueTriage,
|
|
2646
2646
|
createIssueTriage
|
|
2647
2647
|
};
|
|
2648
|
-
//# sourceMappingURL=chunk-
|
|
2648
|
+
//# sourceMappingURL=chunk-EFZRQ67X.js.map
|