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
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli-adapters/cli-circuit-breaker.ts"],"sourcesContent":["/**\n * nexus-agents/cli-adapters - CLI Circuit Breaker Integration\n *\n * Wraps CLI adapter calls with circuit breaker pattern for resilient\n * multi-CLI execution with automatic fallback on failures.\n *\n * (Source: Issue #359 - Integrate circuit breaker with CLI adapters)\n */\n\nimport type { Result, ILogger } from '../core/index.js';\nimport { ok, err, createLogger, getTimeProvider, getErrorMessage } from '../core/index.js';\nimport type { TaskCategory } from '../config/task-specialization-types.js';\nimport type { FallbackTaskType } from './task-classifier.js';\nimport { getFallbackChainForCategory } from './fallback-chains.js';\nimport type {\n ICliAdapter,\n CliName,\n CliTask,\n CliResponse,\n CliError,\n ObservedArmId,\n} from './types.js';\nimport { breakerKeys } from './breaker-key.js';\nimport { gatewayServedSlotOf, resolveGatewayServedSlot } from './gateway-slot-arm.js';\nimport {\n CircuitBreakerRegistry,\n CircuitError,\n CircuitErrorCode,\n mapCliErrorToCategory,\n type CliCircuitBreaker,\n type CircuitBreakerConfig,\n type CircuitBreakerSnapshot,\n type CircuitStateChangeListener,\n} from './circuit-breaker.js';\nimport { isCallerInputCliError } from './cli-error-helpers.js';\nimport { isCallerCancelled } from '../adapters/abort-utils.js';\nimport { unenforcedAccessModeRefusal } from './access-mode.js';\n\n/** Maps canonical TaskCategory (10 types) to FallbackTaskType (5 types). */\nconst CATEGORY_TO_FALLBACK: Record<TaskCategory, FallbackTaskType> = {\n code_generation: 'code',\n code_review: 'code',\n testing: 'code',\n research: 'research',\n exploration: 'research',\n documentation: 'documentation',\n architecture: 'analysis',\n security_review: 'analysis',\n planning: 'analysis',\n devops: 'general',\n};\n\n/** Configuration for CLI circuit breaker integration. */\nexport interface CliCircuitBreakerConfig {\n readonly perCliConfig?: Partial<Record<CliName, Partial<CircuitBreakerConfig>>>;\n readonly fallbackChain?: ReadonlyArray<CliName>;\n readonly enableFallback?: boolean;\n readonly maxFallbackAttempts?: number;\n}\n\n/** Result of a circuit-protected execution with fallback info. */\nexport interface CircuitProtectedResult {\n readonly response: CliResponse;\n readonly executedBy: CliName;\n readonly usedFallback: boolean;\n readonly fallbackAttempts?: ReadonlyArray<CliName>;\n}\n\n/** Health status for all CLIs with circuit state. */\nexport interface CliCircuitHealthStatus {\n readonly clis: ReadonlyArray<{\n readonly name: CliName;\n readonly healthy: boolean;\n readonly circuitState: 'closed' | 'open' | 'half-open';\n readonly failureCount: number;\n readonly lastFailureTime: number | null;\n }>;\n readonly systemHealthy: boolean;\n readonly healthyCount: number;\n readonly timestamp: number;\n}\n\n/** Interface for CLI circuit breaker integration. */\nexport interface ICliCircuitBreakerIntegration {\n execute(\n adapter: ICliAdapter,\n task: CliTask,\n taskCategory?: TaskCategory\n ): Promise<Result<CircuitProtectedResult, CircuitError | CliError>>;\n getHealthStatus(): CliCircuitHealthStatus;\n getCircuitSnapshots(): Map<CliName, CircuitBreakerSnapshot>;\n resetCircuit(cliName: CliName): void;\n resetAllCircuits(): void;\n addStateChangeListener(listener: CircuitStateChangeListener): void;\n}\n\nconst DEFAULT_FALLBACK_CHAIN: ReadonlyArray<CliName> = ['claude', 'gemini', 'codex', 'opencode'];\nconst DEFAULT_CONFIG: Required<CliCircuitBreakerConfig> = {\n perCliConfig: {},\n fallbackChain: DEFAULT_FALLBACK_CHAIN,\n enableFallback: true,\n maxFallbackAttempts: 2,\n};\nconst defaultCliCircuitBreakerRegistry = new CircuitBreakerRegistry();\n\n// #4330: the shared registry now gates voter-panel availability, so a CLI can\n// vanish from a panel roster because its circuit opened. Log every transition —\n// otherwise the next person debugging a \"missing voter\" has no trail, and a\n// misclassified transient error silently costs the panel its diversity.\nconst registryLogger = createLogger({ component: 'cli-circuit-breaker' });\ndefaultCliCircuitBreakerRegistry.addGlobalStateChangeListener((event) => {\n registryLogger.warn('CLI circuit state changed', {\n cli: event.cliName,\n previousState: event.previousState,\n newState: event.newState,\n failureCount: event.failureCount,\n reason: event.reason,\n });\n});\n\n/** Returns the shared CLI circuit-breaker registry used by default integrations. */\nexport function getDefaultCliCircuitBreakerRegistry(): CircuitBreakerRegistry {\n return defaultCliCircuitBreakerRegistry;\n}\n\n/**\n * Reads the current snapshot for a CLI without creating a new breaker.\n *\n * `undefined` means no circuit state is known yet, so callers should fail open.\n */\nexport function getCliCircuitBreakerSnapshot(cliName: CliName): CircuitBreakerSnapshot | undefined {\n return defaultCliCircuitBreakerRegistry.getAllArmSnapshots().get(cliName);\n}\n\n/**\n * Integrates circuit breaker pattern with CLI adapters.\n * Provides automatic fallback when a CLI's circuit opens.\n */\nexport class CliCircuitBreakerIntegration implements ICliCircuitBreakerIntegration {\n private readonly registry: CircuitBreakerRegistry;\n private readonly adapters: Map<CliName, ICliAdapter> = new Map();\n private readonly config: Required<CliCircuitBreakerConfig>;\n private readonly logger: ILogger;\n\n constructor(\n adapters: ReadonlyArray<ICliAdapter>,\n config?: CliCircuitBreakerConfig,\n logger?: ILogger\n ) {\n this.config = { ...DEFAULT_CONFIG, ...config };\n this.logger = logger ?? createLogger({ component: 'cli-circuit-breaker-integration' });\n this.registry =\n config === undefined ? defaultCliCircuitBreakerRegistry : new CircuitBreakerRegistry();\n for (const adapter of adapters) {\n this.adapters.set(adapter.name, adapter);\n this.breakerFor(adapter);\n }\n }\n\n async execute(\n adapter: ICliAdapter,\n task: CliTask,\n taskCategory?: TaskCategory\n ): Promise<Result<CircuitProtectedResult, CircuitError | CliError>> {\n const primaryCli = adapter.name;\n const fallbackAttempts: CliName[] = [];\n let lastError: CircuitError | CliError | undefined;\n\n const primaryResult = await this.executeWithBreaker(adapter, task);\n if (primaryResult.ok) {\n return ok({ response: primaryResult.value, executedBy: primaryCli, usedFallback: false });\n }\n lastError = primaryResult.error;\n\n if (!this.config.enableFallback || !(lastError instanceof CircuitError)) {\n return err(lastError);\n }\n\n for (const cli of this.getFallbackClis(primaryCli, taskCategory, task).slice(\n 0,\n this.config.maxFallbackAttempts\n )) {\n const fallbackAdapter = this.adapters.get(cli);\n if (!fallbackAdapter) continue;\n fallbackAttempts.push(cli);\n this.logger.info('Attempting fallback', { from: primaryCli, to: cli });\n const result = await this.executeWithBreaker(fallbackAdapter, task);\n if (result.ok) {\n return ok({\n response: result.value,\n executedBy: cli,\n usedFallback: true,\n fallbackAttempts,\n });\n }\n lastError = result.error;\n }\n\n this.logger.warn('All fallback attempts failed', { primaryCli, fallbackAttempts });\n return err(lastError);\n }\n\n getHealthStatus(): CliCircuitHealthStatus {\n const clis: CliCircuitHealthStatus['clis'][number][] = [];\n let healthyCount = 0;\n for (const [name, adapter] of this.adapters) {\n const snapshot = this.breakerFor(adapter).breaker.getSnapshot();\n const healthy = snapshot.state === 'closed';\n if (healthy) healthyCount++;\n clis.push({\n name,\n healthy,\n circuitState: snapshot.state,\n failureCount: snapshot.failureCount,\n lastFailureTime: snapshot.lastFailureTime,\n });\n }\n return {\n clis,\n systemHealthy: healthyCount > 0,\n healthyCount,\n timestamp: getTimeProvider().now(),\n };\n }\n\n getCircuitSnapshots(): Map<CliName, CircuitBreakerSnapshot> {\n const snapshots = new Map<CliName, CircuitBreakerSnapshot>();\n for (const [name, adapter] of this.adapters) {\n snapshots.set(name, this.breakerFor(adapter).breaker.getSnapshot());\n }\n return snapshots;\n }\n\n resetCircuit(cliName: CliName): void {\n const adapter = this.adapters.get(cliName);\n this.registry.resetArm(adapter === undefined ? cliName : this.breakerFor(adapter).key);\n this.logger.info('Circuit reset', { cliName });\n }\n\n resetAllCircuits(): void {\n this.registry.resetAll();\n this.logger.info('All circuits reset');\n }\n\n addStateChangeListener(listener: CircuitStateChangeListener): void {\n this.registry.addGlobalStateChangeListener(listener);\n }\n\n private async executeWithBreaker(\n adapter: ICliAdapter,\n task: CliTask\n ): Promise<Result<CliResponse, CircuitError | CliError>> {\n let { key, breaker } = this.breakerFor(adapter, task);\n let execResult: Result<CliResponse, CliError>;\n let targetResolved = false;\n try {\n await resolveGatewayServedSlot(adapter);\n const refusal = await this.prepareImplicitRoute(adapter, task);\n if (refusal !== undefined) return err(refusal);\n targetResolved = true;\n ({ key, breaker } = this.breakerFor(adapter, task));\n const canRun = breaker.canExecute();\n if (!canRun.ok) return canRun;\n execResult = await adapter.execute(task);\n } catch (error) {\n // A rejected availability probe has not selected a failure domain.\n // Never charge the provisional plain CLI breaker for that probe.\n if (targetResolved) breaker.recordFailure('unknown');\n return err(\n new CircuitError(`CLI execution threw unexpectedly: ${getErrorMessage(error)}`, {\n circuitErrorCode: CircuitErrorCode.EXECUTION_FAILED,\n cliName: key,\n armId: key,\n circuitState: breaker.getState(),\n cause: error instanceof Error ? error : new Error(String(error)),\n })\n );\n }\n\n return this.recordOutcome(adapter, breaker, execResult);\n }\n\n /** Resolve an implicit OpenCode route before gating, preserving access refusals. */\n private async prepareImplicitRoute(\n adapter: ICliAdapter,\n task: CliTask\n ): Promise<CliError | undefined> {\n if (adapter.name !== 'opencode' || task.model !== undefined) return undefined;\n const refusal = unenforcedAccessModeRefusal(adapter, task);\n if (refusal !== undefined) return refusal;\n // A missing/cooled configured model omits --model and runs the default.\n await adapter.initialize();\n return undefined;\n }\n\n private recordOutcome(\n adapter: ICliAdapter,\n breaker: CliCircuitBreaker,\n execResult: Result<CliResponse, CliError>\n ): Result<CliResponse, CliError> {\n const recordsOutcome = this.ownsBreakerOutcome(adapter);\n if (!execResult.ok) {\n // #6613: caller-input errors (e.g. invalid model requested) must not count\n // against the breaker or exhaust half-open probe capacity. #6691: nor\n // must a call its caller cancelled.\n if (isCallerInputCliError(execResult.error) || isCallerCancelled(execResult.error)) {\n breaker.releaseHalfOpenProbe();\n return err(execResult.error);\n }\n if (recordsOutcome) breaker.recordFailure(mapCliErrorToCategory(execResult.error.code));\n return err(execResult.error);\n }\n\n if (recordsOutcome) breaker.recordSuccess();\n return ok(execResult.value);\n }\n\n private breakerFor(\n adapter: Pick<ICliAdapter, 'name'> & Partial<Pick<ICliAdapter, 'getModelInfo'>>,\n task?: CliTask\n ): {\n key: ObservedArmId;\n breaker: CliCircuitBreaker;\n } {\n const key = breakerKeys.forArm({\n name: adapter.name,\n model: task?.model ?? adapter.getModelInfo?.().id,\n gatewayArm: gatewayServedSlotOf(adapter)?.arm,\n });\n return {\n key,\n breaker: this.registry.getArmBreaker(key, this.config.perCliConfig[adapter.name]),\n };\n }\n\n /** Marked gateway slots record in the shared registry; private registries own their records. */\n private ownsBreakerOutcome(adapter: ICliAdapter): boolean {\n return (\n this.registry !== defaultCliCircuitBreakerRegistry ||\n gatewayServedSlotOf(adapter)?.arm === undefined\n );\n }\n\n private getFallbackClis(\n excludeCli: CliName,\n taskCategory?: TaskCategory,\n task?: CliTask\n ): CliName[] {\n const chain =\n taskCategory !== undefined\n ? getFallbackChainForCategory(taskCategory, CATEGORY_TO_FALLBACK[taskCategory])\n : this.config.fallbackChain;\n return [...chain].filter((cli) => {\n const adapter = this.adapters.get(cli);\n return (\n cli !== excludeCli &&\n adapter !== undefined &&\n !this.registry.isArmOpen(this.breakerFor(adapter, task).key)\n );\n });\n }\n}\n\n/** Creates a CLI circuit breaker integration with the specified adapters. */\nexport function createCliCircuitBreakerIntegration(\n adapters: ReadonlyArray<ICliAdapter>,\n config?: CliCircuitBreakerConfig,\n logger?: ILogger\n): CliCircuitBreakerIntegration {\n return new CliCircuitBreakerIntegration(adapters, config, logger);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAuCA,IAAM,uBAA+D;AAAA,EACnE,iBAAiB;AAAA,EACjB,aAAa;AAAA,EACb,SAAS;AAAA,EACT,UAAU;AAAA,EACV,aAAa;AAAA,EACb,eAAe;AAAA,EACf,cAAc;AAAA,EACd,iBAAiB;AAAA,EACjB,UAAU;AAAA,EACV,QAAQ;AACV;AA8CA,IAAM,yBAAiD,CAAC,UAAU,UAAU,SAAS,UAAU;AAC/F,IAAM,iBAAoD;AAAA,EACxD,cAAc,CAAC;AAAA,EACf,eAAe;AAAA,EACf,gBAAgB;AAAA,EAChB,qBAAqB;AACvB;AACA,IAAM,mCAAmC,IAAI,uBAAuB;AAMpE,IAAM,iBAAiB,aAAa,EAAE,WAAW,sBAAsB,CAAC;AACxE,iCAAiC,6BAA6B,CAAC,UAAU;AACvE,iBAAe,KAAK,6BAA6B;AAAA,IAC/C,KAAK,MAAM;AAAA,IACX,eAAe,MAAM;AAAA,IACrB,UAAU,MAAM;AAAA,IAChB,cAAc,MAAM;AAAA,IACpB,QAAQ,MAAM;AAAA,EAChB,CAAC;AACH,CAAC;AAGM,SAAS,sCAA8D;AAC5E,SAAO;AACT;AAOO,SAAS,6BAA6B,SAAsD;AACjG,SAAO,iCAAiC,mBAAmB,EAAE,IAAI,OAAO;AAC1E;AAMO,IAAM,+BAAN,MAA4E;AAAA,EAChE;AAAA,EACA,WAAsC,oBAAI,IAAI;AAAA,EAC9C;AAAA,EACA;AAAA,EAEjB,YACE,UACA,QACA,QACA;AACA,SAAK,SAAS,EAAE,GAAG,gBAAgB,GAAG,OAAO;AAC7C,SAAK,SAAS,UAAU,aAAa,EAAE,WAAW,kCAAkC,CAAC;AACrF,SAAK,WACH,WAAW,SAAY,mCAAmC,IAAI,uBAAuB;AACvF,eAAW,WAAW,UAAU;AAC9B,WAAK,SAAS,IAAI,QAAQ,MAAM,OAAO;AACvC,WAAK,WAAW,OAAO;AAAA,IACzB;AAAA,EACF;AAAA,EAEA,MAAM,QACJ,SACA,MACA,cACkE;AAClE,UAAM,aAAa,QAAQ;AAC3B,UAAM,mBAA8B,CAAC;AACrC,QAAI;AAEJ,UAAM,gBAAgB,MAAM,KAAK,mBAAmB,SAAS,IAAI;AACjE,QAAI,cAAc,IAAI;AACpB,aAAO,GAAG,EAAE,UAAU,cAAc,OAAO,YAAY,YAAY,cAAc,MAAM,CAAC;AAAA,IAC1F;AACA,gBAAY,cAAc;AAE1B,QAAI,CAAC,KAAK,OAAO,kBAAkB,EAAE,qBAAqB,eAAe;AACvE,aAAO,IAAI,SAAS;AAAA,IACtB;AAEA,eAAW,OAAO,KAAK,gBAAgB,YAAY,cAAc,IAAI,EAAE;AAAA,MACrE;AAAA,MACA,KAAK,OAAO;AAAA,IACd,GAAG;AACD,YAAM,kBAAkB,KAAK,SAAS,IAAI,GAAG;AAC7C,UAAI,CAAC,gBAAiB;AACtB,uBAAiB,KAAK,GAAG;AACzB,WAAK,OAAO,KAAK,uBAAuB,EAAE,MAAM,YAAY,IAAI,IAAI,CAAC;AACrE,YAAM,SAAS,MAAM,KAAK,mBAAmB,iBAAiB,IAAI;AAClE,UAAI,OAAO,IAAI;AACb,eAAO,GAAG;AAAA,UACR,UAAU,OAAO;AAAA,UACjB,YAAY;AAAA,UACZ,cAAc;AAAA,UACd;AAAA,QACF,CAAC;AAAA,MACH;AACA,kBAAY,OAAO;AAAA,IACrB;AAEA,SAAK,OAAO,KAAK,gCAAgC,EAAE,YAAY,iBAAiB,CAAC;AACjF,WAAO,IAAI,SAAS;AAAA,EACtB;AAAA,EAEA,kBAA0C;AACxC,UAAM,OAAiD,CAAC;AACxD,QAAI,eAAe;AACnB,eAAW,CAAC,MAAM,OAAO,KAAK,KAAK,UAAU;AAC3C,YAAM,WAAW,KAAK,WAAW,OAAO,EAAE,QAAQ,YAAY;AAC9D,YAAM,UAAU,SAAS,UAAU;AACnC,UAAI,QAAS;AACb,WAAK,KAAK;AAAA,QACR;AAAA,QACA;AAAA,QACA,cAAc,SAAS;AAAA,QACvB,cAAc,SAAS;AAAA,QACvB,iBAAiB,SAAS;AAAA,MAC5B,CAAC;AAAA,IACH;AACA,WAAO;AAAA,MACL;AAAA,MACA,eAAe,eAAe;AAAA,MAC9B;AAAA,MACA,WAAW,gBAAgB,EAAE,IAAI;AAAA,IACnC;AAAA,EACF;AAAA,EAEA,sBAA4D;AAC1D,UAAM,YAAY,oBAAI,IAAqC;AAC3D,eAAW,CAAC,MAAM,OAAO,KAAK,KAAK,UAAU;AAC3C,gBAAU,IAAI,MAAM,KAAK,WAAW,OAAO,EAAE,QAAQ,YAAY,CAAC;AAAA,IACpE;AACA,WAAO;AAAA,EACT;AAAA,EAEA,aAAa,SAAwB;AACnC,UAAM,UAAU,KAAK,SAAS,IAAI,OAAO;AACzC,SAAK,SAAS,SAAS,YAAY,SAAY,UAAU,KAAK,WAAW,OAAO,EAAE,GAAG;AACrF,SAAK,OAAO,KAAK,iBAAiB,EAAE,QAAQ,CAAC;AAAA,EAC/C;AAAA,EAEA,mBAAyB;AACvB,SAAK,SAAS,SAAS;AACvB,SAAK,OAAO,KAAK,oBAAoB;AAAA,EACvC;AAAA,EAEA,uBAAuB,UAA4C;AACjE,SAAK,SAAS,6BAA6B,QAAQ;AAAA,EACrD;AAAA,EAEA,MAAc,mBACZ,SACA,MACuD;AACvD,QAAI,EAAE,KAAK,QAAQ,IAAI,KAAK,WAAW,SAAS,IAAI;AACpD,QAAI;AACJ,QAAI,iBAAiB;AACrB,QAAI;AACF,YAAM,yBAAyB,OAAO;AACtC,YAAM,UAAU,MAAM,KAAK,qBAAqB,SAAS,IAAI;AAC7D,UAAI,YAAY,OAAW,QAAO,IAAI,OAAO;AAC7C,uBAAiB;AACjB,OAAC,EAAE,KAAK,QAAQ,IAAI,KAAK,WAAW,SAAS,IAAI;AACjD,YAAM,SAAS,QAAQ,WAAW;AAClC,UAAI,CAAC,OAAO,GAAI,QAAO;AACvB,mBAAa,MAAM,QAAQ,QAAQ,IAAI;AAAA,IACzC,SAAS,OAAO;AAGd,UAAI,eAAgB,SAAQ,cAAc,SAAS;AACnD,aAAO;AAAA,QACL,IAAI,aAAa,qCAAqC,gBAAgB,KAAK,CAAC,IAAI;AAAA,UAC9E,kBAAkB,iBAAiB;AAAA,UACnC,SAAS;AAAA,UACT,OAAO;AAAA,UACP,cAAc,QAAQ,SAAS;AAAA,UAC/B,OAAO,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC;AAAA,QACjE,CAAC;AAAA,MACH;AAAA,IACF;AAEA,WAAO,KAAK,cAAc,SAAS,SAAS,UAAU;AAAA,EACxD;AAAA;AAAA,EAGA,MAAc,qBACZ,SACA,MAC+B;AAC/B,QAAI,QAAQ,SAAS,cAAc,KAAK,UAAU,OAAW,QAAO;AACpE,UAAM,UAAU,4BAA4B,SAAS,IAAI;AACzD,QAAI,YAAY,OAAW,QAAO;AAElC,UAAM,QAAQ,WAAW;AACzB,WAAO;AAAA,EACT;AAAA,EAEQ,cACN,SACA,SACA,YAC+B;AAC/B,UAAM,iBAAiB,KAAK,mBAAmB,OAAO;AACtD,QAAI,CAAC,WAAW,IAAI;AAIlB,UAAI,sBAAsB,WAAW,KAAK,KAAK,kBAAkB,WAAW,KAAK,GAAG;AAClF,gBAAQ,qBAAqB;AAC7B,eAAO,IAAI,WAAW,KAAK;AAAA,MAC7B;AACA,UAAI,eAAgB,SAAQ,cAAc,sBAAsB,WAAW,MAAM,IAAI,CAAC;AACtF,aAAO,IAAI,WAAW,KAAK;AAAA,IAC7B;AAEA,QAAI,eAAgB,SAAQ,cAAc;AAC1C,WAAO,GAAG,WAAW,KAAK;AAAA,EAC5B;AAAA,EAEQ,WACN,SACA,MAIA;AACA,UAAM,MAAM,YAAY,OAAO;AAAA,MAC7B,MAAM,QAAQ;AAAA,MACd,OAAO,MAAM,SAAS,QAAQ,eAAe,EAAE;AAAA,MAC/C,YAAY,oBAAoB,OAAO,GAAG;AAAA,IAC5C,CAAC;AACD,WAAO;AAAA,MACL;AAAA,MACA,SAAS,KAAK,SAAS,cAAc,KAAK,KAAK,OAAO,aAAa,QAAQ,IAAI,CAAC;AAAA,IAClF;AAAA,EACF;AAAA;AAAA,EAGQ,mBAAmB,SAA+B;AACxD,WACE,KAAK,aAAa,oCAClB,oBAAoB,OAAO,GAAG,QAAQ;AAAA,EAE1C;AAAA,EAEQ,gBACN,YACA,cACA,MACW;AACX,UAAM,QACJ,iBAAiB,SACb,4BAA4B,cAAc,qBAAqB,YAAY,CAAC,IAC5E,KAAK,OAAO;AAClB,WAAO,CAAC,GAAG,KAAK,EAAE,OAAO,CAAC,QAAQ;AAChC,YAAM,UAAU,KAAK,SAAS,IAAI,GAAG;AACrC,aACE,QAAQ,cACR,YAAY,UACZ,CAAC,KAAK,SAAS,UAAU,KAAK,WAAW,SAAS,IAAI,EAAE,GAAG;AAAA,IAE/D,CAAC;AAAA,EACH;AACF;AAGO,SAAS,mCACd,UACA,QACA,QAC8B;AAC9B,SAAO,IAAI,6BAA6B,UAAU,QAAQ,MAAM;AAClE;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
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 } 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: CliName;\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":[]}
|