nexus-agents 2.161.1 → 2.162.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. package/dist/{child-mcp-config-YBBGGRIQ.js → child-mcp-config-GXWEIDJH.js} +2 -2
  2. package/dist/{chunk-M43WDHS7.js → chunk-2U42PBUF.js} +2 -2
  3. package/dist/{chunk-KG7TUZCP.js → chunk-3KCSKD7P.js} +2 -2
  4. package/dist/{chunk-TAFGCQKM.js → chunk-7F6AMBM4.js} +4 -4
  5. package/dist/{chunk-X2K6JQXF.js → chunk-A4XTT4AG.js} +379 -293
  6. package/dist/chunk-A4XTT4AG.js.map +1 -0
  7. package/dist/{chunk-F64ZVDTS.js → chunk-BHMKXBCY.js} +4 -4
  8. package/dist/{chunk-KFURXCQX.js → chunk-BSGAU3WE.js} +3 -3
  9. package/dist/{chunk-A4IQ3WIV.js → chunk-CI5F3CJL.js} +2 -2
  10. package/dist/{chunk-YZ5FJXMZ.js → chunk-DQQTKATY.js} +11 -8
  11. package/dist/chunk-DQQTKATY.js.map +1 -0
  12. package/dist/{chunk-N4P5S6NC.js → chunk-DY5QOBYH.js} +2 -2
  13. package/dist/{chunk-22WY2UZO.js → chunk-EKGN6UIH.js} +34 -34
  14. package/dist/{chunk-MSLY2T7V.js → chunk-EPRHNYCO.js} +2 -2
  15. package/dist/{chunk-JTNG2GEO.js → chunk-FETTN45O.js} +2 -2
  16. package/dist/{chunk-M2MCHF2N.js → chunk-FTPCUJUK.js} +2 -2
  17. package/dist/{chunk-BYIK3SEP.js → chunk-G5KOZAHM.js} +2 -2
  18. package/dist/{chunk-OFNGKIRB.js → chunk-GMT6X3ED.js} +3 -3
  19. package/dist/{chunk-LZG6T6NF.js → chunk-IOWQ2KZK.js} +3 -3
  20. package/dist/{chunk-DSZPCPLU.js → chunk-L6PB3464.js} +2 -2
  21. package/dist/{chunk-5AGWDSRC.js → chunk-MYIJLJQ7.js} +5 -5
  22. package/dist/{chunk-A3ADATJQ.js → chunk-OFZYDOF3.js} +5 -5
  23. package/dist/{chunk-JVLGCODA.js → chunk-P23SETQP.js} +2 -2
  24. package/dist/{chunk-NNBOROVK.js → chunk-PYTWA2LR.js} +3 -3
  25. package/dist/{chunk-SVEMQWSS.js → chunk-RKLKUNRA.js} +2 -2
  26. package/dist/{chunk-HLIL4IAV.js → chunk-RQ4JK2YE.js} +3 -3
  27. package/dist/{chunk-VBFXKKSJ.js → chunk-TXRPJ6DJ.js} +4 -4
  28. package/dist/{chunk-47SHVEUS.js → chunk-V5MPFOT7.js} +2 -2
  29. package/dist/{chunk-QUPEN5ZT.js → chunk-VBL7BVUP.js} +2 -2
  30. package/dist/{chunk-WL74YS6H.js → chunk-VT562DIB.js} +7 -7
  31. package/dist/{chunk-KJ6JJ24E.js → chunk-Y6ZICYXS.js} +4 -4
  32. package/dist/{chunk-Q3VSFJMW.js → chunk-YAJBZKCC.js} +7 -7
  33. package/dist/{chunk-BO2OKI3L.js → chunk-YRGEW5HH.js} +5 -5
  34. package/dist/{chunk-VUEYMYJL.js → chunk-YXSRBCA2.js} +2 -2
  35. package/dist/{chunk-NJDGWKIG.js → chunk-Z6ORP7BL.js} +2 -2
  36. package/dist/{chunk-HTDA53XN.js → chunk-ZQP2VUQN.js} +8 -8
  37. package/dist/{chunk-SDBXLOV4.js → chunk-ZZJIUJT4.js} +3 -3
  38. package/dist/{cli-circuit-breaker-LRXSQ43Q.js → cli-circuit-breaker-MKP4EGJ3.js} +4 -4
  39. package/dist/cli.js +41 -41
  40. package/dist/{composite-router-5Q4PGWS6.js → composite-router-E5HK2SPX.js} +2 -2
  41. package/dist/{consensus-vote-EO5OR5VC.js → consensus-vote-53LNVJQX.js} +15 -15
  42. package/dist/{context-retriever-CEVBKMKI.js → context-retriever-ZQXEWMHS.js} +8 -8
  43. package/dist/{doctor-deep-IGJ742A2.js → doctor-deep-RW5FKXIQ.js} +3 -3
  44. package/dist/{expert-bridge-XP4CXZJD.js → expert-bridge-435SKR4I.js} +4 -4
  45. package/dist/{factory-YZ7GKC66.js → factory-BSELGB2U.js} +8 -8
  46. package/dist/{factory-6RKIL4XS.js → factory-PV4YEJXD.js} +5 -5
  47. package/dist/{improvement-review-LCWLZWSS.js → improvement-review-MO277SWX.js} +5 -5
  48. package/dist/index.d.ts +4 -0
  49. package/dist/index.js +30 -30
  50. package/dist/{init-opencode-LFHJR4NV.js → init-opencode-TH5B5PWD.js} +6 -6
  51. package/dist/{issue-triage-MG34EMJJ.js → issue-triage-ZAL27WBT.js} +6 -6
  52. package/dist/{pr-reviewer-helpers-PEKSWICD.js → pr-reviewer-helpers-Z6MIXEHD.js} +4 -4
  53. package/dist/{registry-command-ZASDTWMF.js → registry-command-YXXJ3EYA.js} +2 -2
  54. package/dist/{repo-security-plan-SVVLDD3L.js → repo-security-plan-LOYQKXWA.js} +3 -3
  55. package/dist/{research-helpers-synthesize-PUFJCIXW.js → research-helpers-synthesize-BIEZJYKV.js} +4 -4
  56. package/dist/{routing-memory-ZEHSEZQN.js → routing-memory-FD77RBPF.js} +2 -2
  57. package/dist/{session-memory-64GGDTTO.js → session-memory-ZWRYRPK2.js} +3 -3
  58. package/dist/{setup-command-LPNACGEN.js → setup-command-HTBWLS2P.js} +11 -11
  59. package/dist/{setup-config-I7EAJHBG.js → setup-config-HTZVHPW2.js} +3 -3
  60. package/dist/{setup-custom-api-KZUQVSAX.js → setup-custom-api-SHWRKXZG.js} +3 -3
  61. package/dist/{tool-memory-BGWJJRJK.js → tool-memory-2765TOOT.js} +5 -5
  62. package/dist/{unified-registry-DAFMWYGI.js → unified-registry-PBJ4ZVPL.js} +9 -9
  63. package/dist/{weather-report-4DZAU577.js → weather-report-SIF2QPOU.js} +2 -2
  64. package/package.json +1 -1
  65. package/dist/chunk-X2K6JQXF.js.map +0 -1
  66. package/dist/chunk-YZ5FJXMZ.js.map +0 -1
  67. /package/dist/{child-mcp-config-YBBGGRIQ.js.map → child-mcp-config-GXWEIDJH.js.map} +0 -0
  68. /package/dist/{chunk-M43WDHS7.js.map → chunk-2U42PBUF.js.map} +0 -0
  69. /package/dist/{chunk-KG7TUZCP.js.map → chunk-3KCSKD7P.js.map} +0 -0
  70. /package/dist/{chunk-TAFGCQKM.js.map → chunk-7F6AMBM4.js.map} +0 -0
  71. /package/dist/{chunk-F64ZVDTS.js.map → chunk-BHMKXBCY.js.map} +0 -0
  72. /package/dist/{chunk-KFURXCQX.js.map → chunk-BSGAU3WE.js.map} +0 -0
  73. /package/dist/{chunk-A4IQ3WIV.js.map → chunk-CI5F3CJL.js.map} +0 -0
  74. /package/dist/{chunk-N4P5S6NC.js.map → chunk-DY5QOBYH.js.map} +0 -0
  75. /package/dist/{chunk-22WY2UZO.js.map → chunk-EKGN6UIH.js.map} +0 -0
  76. /package/dist/{chunk-MSLY2T7V.js.map → chunk-EPRHNYCO.js.map} +0 -0
  77. /package/dist/{chunk-JTNG2GEO.js.map → chunk-FETTN45O.js.map} +0 -0
  78. /package/dist/{chunk-M2MCHF2N.js.map → chunk-FTPCUJUK.js.map} +0 -0
  79. /package/dist/{chunk-BYIK3SEP.js.map → chunk-G5KOZAHM.js.map} +0 -0
  80. /package/dist/{chunk-OFNGKIRB.js.map → chunk-GMT6X3ED.js.map} +0 -0
  81. /package/dist/{chunk-LZG6T6NF.js.map → chunk-IOWQ2KZK.js.map} +0 -0
  82. /package/dist/{chunk-DSZPCPLU.js.map → chunk-L6PB3464.js.map} +0 -0
  83. /package/dist/{chunk-5AGWDSRC.js.map → chunk-MYIJLJQ7.js.map} +0 -0
  84. /package/dist/{chunk-A3ADATJQ.js.map → chunk-OFZYDOF3.js.map} +0 -0
  85. /package/dist/{chunk-JVLGCODA.js.map → chunk-P23SETQP.js.map} +0 -0
  86. /package/dist/{chunk-NNBOROVK.js.map → chunk-PYTWA2LR.js.map} +0 -0
  87. /package/dist/{chunk-SVEMQWSS.js.map → chunk-RKLKUNRA.js.map} +0 -0
  88. /package/dist/{chunk-HLIL4IAV.js.map → chunk-RQ4JK2YE.js.map} +0 -0
  89. /package/dist/{chunk-VBFXKKSJ.js.map → chunk-TXRPJ6DJ.js.map} +0 -0
  90. /package/dist/{chunk-47SHVEUS.js.map → chunk-V5MPFOT7.js.map} +0 -0
  91. /package/dist/{chunk-QUPEN5ZT.js.map → chunk-VBL7BVUP.js.map} +0 -0
  92. /package/dist/{chunk-WL74YS6H.js.map → chunk-VT562DIB.js.map} +0 -0
  93. /package/dist/{chunk-KJ6JJ24E.js.map → chunk-Y6ZICYXS.js.map} +0 -0
  94. /package/dist/{chunk-Q3VSFJMW.js.map → chunk-YAJBZKCC.js.map} +0 -0
  95. /package/dist/{chunk-BO2OKI3L.js.map → chunk-YRGEW5HH.js.map} +0 -0
  96. /package/dist/{chunk-VUEYMYJL.js.map → chunk-YXSRBCA2.js.map} +0 -0
  97. /package/dist/{chunk-NJDGWKIG.js.map → chunk-Z6ORP7BL.js.map} +0 -0
  98. /package/dist/{chunk-HTDA53XN.js.map → chunk-ZQP2VUQN.js.map} +0 -0
  99. /package/dist/{chunk-SDBXLOV4.js.map → chunk-ZZJIUJT4.js.map} +0 -0
  100. /package/dist/{cli-circuit-breaker-LRXSQ43Q.js.map → cli-circuit-breaker-MKP4EGJ3.js.map} +0 -0
  101. /package/dist/{composite-router-5Q4PGWS6.js.map → composite-router-E5HK2SPX.js.map} +0 -0
  102. /package/dist/{consensus-vote-EO5OR5VC.js.map → consensus-vote-53LNVJQX.js.map} +0 -0
  103. /package/dist/{context-retriever-CEVBKMKI.js.map → context-retriever-ZQXEWMHS.js.map} +0 -0
  104. /package/dist/{doctor-deep-IGJ742A2.js.map → doctor-deep-RW5FKXIQ.js.map} +0 -0
  105. /package/dist/{expert-bridge-XP4CXZJD.js.map → expert-bridge-435SKR4I.js.map} +0 -0
  106. /package/dist/{factory-6RKIL4XS.js.map → factory-BSELGB2U.js.map} +0 -0
  107. /package/dist/{factory-YZ7GKC66.js.map → factory-PV4YEJXD.js.map} +0 -0
  108. /package/dist/{improvement-review-LCWLZWSS.js.map → improvement-review-MO277SWX.js.map} +0 -0
  109. /package/dist/{init-opencode-LFHJR4NV.js.map → init-opencode-TH5B5PWD.js.map} +0 -0
  110. /package/dist/{issue-triage-MG34EMJJ.js.map → issue-triage-ZAL27WBT.js.map} +0 -0
  111. /package/dist/{pr-reviewer-helpers-PEKSWICD.js.map → pr-reviewer-helpers-Z6MIXEHD.js.map} +0 -0
  112. /package/dist/{registry-command-ZASDTWMF.js.map → registry-command-YXXJ3EYA.js.map} +0 -0
  113. /package/dist/{repo-security-plan-SVVLDD3L.js.map → repo-security-plan-LOYQKXWA.js.map} +0 -0
  114. /package/dist/{research-helpers-synthesize-PUFJCIXW.js.map → research-helpers-synthesize-BIEZJYKV.js.map} +0 -0
  115. /package/dist/{routing-memory-ZEHSEZQN.js.map → routing-memory-FD77RBPF.js.map} +0 -0
  116. /package/dist/{session-memory-64GGDTTO.js.map → session-memory-ZWRYRPK2.js.map} +0 -0
  117. /package/dist/{setup-command-LPNACGEN.js.map → setup-command-HTBWLS2P.js.map} +0 -0
  118. /package/dist/{setup-config-I7EAJHBG.js.map → setup-config-HTZVHPW2.js.map} +0 -0
  119. /package/dist/{setup-custom-api-KZUQVSAX.js.map → setup-custom-api-SHWRKXZG.js.map} +0 -0
  120. /package/dist/{tool-memory-BGWJJRJK.js.map → tool-memory-2765TOOT.js.map} +0 -0
  121. /package/dist/{unified-registry-DAFMWYGI.js.map → unified-registry-PBJ4ZVPL.js.map} +0 -0
  122. /package/dist/{weather-report-4DZAU577.js.map → weather-report-SIF2QPOU.js.map} +0 -0
@@ -2612,7 +2612,7 @@ function setDefaultRegistry(registry) {
2612
2612
  }
2613
2613
  async function reloadDefaultRegistry() {
2614
2614
  globalRegistry = buildDefaultRegistry();
2615
- const { resetGlobalRegistry } = await import("./unified-registry-DAFMWYGI.js");
2615
+ const { resetGlobalRegistry } = await import("./unified-registry-PBJ4ZVPL.js");
2616
2616
  resetGlobalRegistry();
2617
2617
  return globalRegistry;
2618
2618
  }
@@ -2628,6 +2628,9 @@ function lookupInTree(modelId) {
2628
2628
  function inTreeById() {
2629
2629
  return new Map(buildInTreeEntries().map((e) => [e.id, e]));
2630
2630
  }
2631
+ function getModelPricing(modelId) {
2632
+ return lookupInTree(modelId)?.pricing;
2633
+ }
2631
2634
  function getModelContextWindow(modelId) {
2632
2635
  return lookupInTree(modelId)?.contextWindow ?? 8192;
2633
2636
  }
@@ -5481,6 +5484,198 @@ function createBudgetExceededError(budget, result, currentBudget) {
5481
5484
  };
5482
5485
  }
5483
5486
 
5487
+ // src/config/task-specialization-types.ts
5488
+ import { z as z4 } from "zod";
5489
+ var TASK_CATEGORIES = [
5490
+ "architecture",
5491
+ "code_generation",
5492
+ "code_review",
5493
+ "research",
5494
+ "security_review",
5495
+ "planning",
5496
+ "documentation",
5497
+ "testing",
5498
+ "devops",
5499
+ "exploration"
5500
+ ];
5501
+ var TaskCategorySchema = z4.enum(TASK_CATEGORIES);
5502
+ var TaskSpecializationSchema = z4.object({
5503
+ /** Task category identifier */
5504
+ category: TaskCategorySchema,
5505
+ /** Primary CLI recommendation */
5506
+ primaryCli: CliNameSchema,
5507
+ /** Secondary CLI fallback */
5508
+ secondaryCli: CliNameSchema,
5509
+ /** Why this CLI is preferred for this task type */
5510
+ reasoning: z4.string(),
5511
+ /** Keywords that trigger this category detection */
5512
+ keywords: z4.array(z4.string()).min(1),
5513
+ /** Bonus score applied when this category matches (0-20) */
5514
+ bonus: z4.number().min(0).max(20)
5515
+ });
5516
+
5517
+ // src/config/task-specialization.ts
5518
+ var TASK_SPECIALIZATION_MATRIX = [
5519
+ {
5520
+ category: "architecture",
5521
+ primaryCli: "gemini",
5522
+ secondaryCli: "claude",
5523
+ reasoning: "Gemini primary (66.7%, n=24) for architecture. Claude secondary (43.6%, n=220). Weather data 2026-03-09.",
5524
+ keywords: ["architect", "design", "system design", "trade-off", "adr"],
5525
+ bonus: 10
5526
+ },
5527
+ {
5528
+ category: "code_generation",
5529
+ primaryCli: "codex",
5530
+ secondaryCli: "claude",
5531
+ reasoning: "Codex primary (91.9%, n=408) for code generation with sandboxed execution. Confirmed per weather data (#1454)",
5532
+ keywords: ["implement", "generate code", "write function", "build feature"],
5533
+ bonus: 15
5534
+ },
5535
+ {
5536
+ category: "code_review",
5537
+ primaryCli: "codex",
5538
+ secondaryCli: "claude",
5539
+ reasoning: "Codex primary (88.3%, n=94) for code review; Claude secondary. Bonus aligned per weather data (#1454)",
5540
+ keywords: ["review code", "code review", "pull request", "pr review"],
5541
+ bonus: 15
5542
+ },
5543
+ {
5544
+ category: "research",
5545
+ primaryCli: "gemini",
5546
+ secondaryCli: "claude",
5547
+ reasoning: "Gemini has deep_research feature and 1M token context for synthesis",
5548
+ keywords: ["research", "investigate", "literature", "survey", "state of the art"],
5549
+ bonus: 15
5550
+ },
5551
+ {
5552
+ category: "security_review",
5553
+ primaryCli: "codex",
5554
+ secondaryCli: "claude",
5555
+ reasoning: "Weather 2026-03-09: claude 44.9% (n=385, declining), codex 60% (n=5), gemini 50% (n=14). Codex primary, claude secondary. Low bonus \u2014 no CLI is clearly dominant.",
5556
+ keywords: [
5557
+ "security review",
5558
+ "security analysis",
5559
+ "security audit",
5560
+ "security flaw",
5561
+ "vulnerability assessment",
5562
+ "threat model",
5563
+ "cve",
5564
+ "audit security",
5565
+ "owasp",
5566
+ "injection",
5567
+ "xss",
5568
+ "csrf",
5569
+ "security",
5570
+ "vulnerability"
5571
+ ],
5572
+ bonus: 5
5573
+ },
5574
+ {
5575
+ category: "planning",
5576
+ primaryCli: "claude",
5577
+ secondaryCli: "codex",
5578
+ reasoning: "Claude primary (92.0%, n=274) for planning and task decomposition. Confirmed per weather data (#1454)",
5579
+ keywords: ["plan", "sprint", "roadmap", "decompose", "prioritize"],
5580
+ bonus: 10
5581
+ },
5582
+ {
5583
+ category: "documentation",
5584
+ primaryCli: "gemini",
5585
+ secondaryCli: "claude",
5586
+ reasoning: "Gemini can process entire codebases (1M context) for comprehensive docs",
5587
+ keywords: ["document", "documentation", "readme", "api docs", "write docs"],
5588
+ bonus: 10
5589
+ },
5590
+ {
5591
+ category: "testing",
5592
+ primaryCli: "codex",
5593
+ secondaryCli: "claude",
5594
+ reasoning: "Codex primary (91.6%, n=143) for test writing with sandbox execution. Bonus aligned per weather data (#1454)",
5595
+ keywords: ["test", "write tests", "test coverage", "unit test", "integration test"],
5596
+ bonus: 15
5597
+ },
5598
+ {
5599
+ category: "devops",
5600
+ primaryCli: "claude",
5601
+ secondaryCli: "gemini",
5602
+ reasoning: "Claude excels at infrastructure reasoning and CI/CD configuration",
5603
+ keywords: [
5604
+ "devops",
5605
+ "ci/cd",
5606
+ "deploy",
5607
+ "infrastructure",
5608
+ "docker",
5609
+ "kubernetes",
5610
+ "pipeline",
5611
+ "helm",
5612
+ "terraform",
5613
+ "ansible",
5614
+ "makefile",
5615
+ "dockerfile",
5616
+ "github actions",
5617
+ "workflow",
5618
+ "concourse",
5619
+ "jenkins",
5620
+ "argocd",
5621
+ "vulnerability scan",
5622
+ "security scan",
5623
+ "sast",
5624
+ "dast",
5625
+ "zap",
5626
+ "semgrep",
5627
+ "grype",
5628
+ "fork",
5629
+ "kind",
5630
+ "cluster",
5631
+ "namespace",
5632
+ "ingress",
5633
+ "monitoring"
5634
+ ],
5635
+ bonus: 10
5636
+ },
5637
+ {
5638
+ category: "exploration",
5639
+ primaryCli: "gemini",
5640
+ secondaryCli: "codex",
5641
+ reasoning: "Gemini primary (98.5%, n=202) for exploration with 1M context. Claude removed as secondary (63.5% vs 98.5%, n=340) per #1462",
5642
+ keywords: ["explore", "navigate", "find", "discover", "scan codebase"],
5643
+ bonus: 15
5644
+ }
5645
+ ];
5646
+ var CATEGORY_INDEX = new Map(
5647
+ TASK_SPECIALIZATION_MATRIX.map((s) => [s.category, s])
5648
+ );
5649
+ function getSpecialization(category) {
5650
+ const spec = CATEGORY_INDEX.get(category);
5651
+ if (!spec) throw new Error(`Unknown category: ${category}`);
5652
+ return spec;
5653
+ }
5654
+ function detectTaskCategory(task) {
5655
+ const taskLower = task.toLowerCase();
5656
+ let bestSpec;
5657
+ let bestScore = 0;
5658
+ for (const spec of TASK_SPECIALIZATION_MATRIX) {
5659
+ let score = 0;
5660
+ for (const kw of spec.keywords) {
5661
+ if (taskLower.includes(kw)) {
5662
+ score += kw.split(/\s+/).length;
5663
+ }
5664
+ }
5665
+ if (score > bestScore) {
5666
+ bestScore = score;
5667
+ bestSpec = spec;
5668
+ }
5669
+ }
5670
+ if (bestSpec === void 0) return null;
5671
+ return {
5672
+ category: bestSpec.category,
5673
+ primaryCli: bestSpec.primaryCli,
5674
+ secondaryCli: bestSpec.secondaryCli,
5675
+ bonus: bestSpec.bonus
5676
+ };
5677
+ }
5678
+
5484
5679
  // src/cli-adapters/budget-router.ts
5485
5680
  var logger4 = createLogger({ component: "budget-router" });
5486
5681
  var DEFAULT_OPTIONS = {
@@ -5500,8 +5695,15 @@ var DEFAULT_OPTIONS = {
5500
5695
  warning: 75,
5501
5696
  critical: 90
5502
5697
  },
5503
- enforceHardLimits: true
5698
+ enforceHardLimits: true,
5699
+ // #4196: per-task-class cost ceilings default OFF (no ceiling configured).
5700
+ taskClassCostCeilings: {}
5504
5701
  };
5702
+ function estimateRegistryCostUsd(slot, inputTokens, outputTokens) {
5703
+ const pricing = getModelPricing(getDefaultModelForCli(slot));
5704
+ if (pricing === void 0) return void 0;
5705
+ return inputTokens / 1e6 * pricing.inputPer1M + outputTokens / 1e6 * pricing.outputPer1M;
5706
+ }
5505
5707
  var BudgetRouter = class {
5506
5708
  adapters;
5507
5709
  options;
@@ -5603,6 +5805,47 @@ var BudgetRouter = class {
5603
5805
  projectedBudget
5604
5806
  };
5605
5807
  }
5808
+ /**
5809
+ * Per-task-class cost ceiling filter (#4196, epic #4175).
5810
+ *
5811
+ * Resolves the task's class via `detectTaskCategory`; when a ceiling is
5812
+ * configured for that class, each candidate's cost is estimated with
5813
+ * canonical registry pricing and candidates above the ceiling are dropped.
5814
+ *
5815
+ * BINDING fail direction: a candidate with MISSING registry pricing FAILS
5816
+ * the check (fail-closed) — unknown cost must not slip under a configured
5817
+ * ceiling. This is deliberately NOT the return-all-candidates fallback of
5818
+ * `filterByPreferenceTier` (composite-router-helpers.ts).
5819
+ *
5820
+ * Billing-mode gating (api only) is the caller's responsibility
5821
+ * (`applyBudgetFilter`); plan mode never invokes this.
5822
+ */
5823
+ filterByTaskClassCeiling(task, candidates) {
5824
+ const ceiling = this.resolveTaskClassCeiling(task);
5825
+ if (ceiling === void 0) return candidates;
5826
+ const inputTokens = estimateTokens2(task.content);
5827
+ const outputTokens = task.maxTokens ?? inputTokens * 2;
5828
+ return candidates.filter((arm) => {
5829
+ const cost = estimateRegistryCostUsd(routingArmDisplaySlot(arm), inputTokens, outputTokens);
5830
+ if (cost === void 0) {
5831
+ logger4.debug("Cost ceiling: missing registry pricing \u2014 failing closed", { arm, ceiling });
5832
+ return false;
5833
+ }
5834
+ const within = cost <= ceiling;
5835
+ if (!within) {
5836
+ logger4.debug("Cost ceiling: candidate excluded", { arm, cost, ceiling });
5837
+ }
5838
+ return within;
5839
+ });
5840
+ }
5841
+ /** Resolve the configured ceiling for the task's detected class, if any (#4196). */
5842
+ resolveTaskClassCeiling(task) {
5843
+ const ceilings = this.options.taskClassCostCeilings;
5844
+ if (Object.keys(ceilings).length === 0) return void 0;
5845
+ const match = detectTaskCategory(task.content);
5846
+ if (match === null) return void 0;
5847
+ return ceilings[match.category];
5848
+ }
5606
5849
  /**
5607
5850
  * Route task with budget awareness.
5608
5851
  */
@@ -5825,6 +6068,24 @@ var TASK_CATEGORY_PLAN_CRITERIA = {
5825
6068
  ],
5826
6069
  general: PLAN_BILLING_TOPSIS_CRITERIA
5827
6070
  };
6071
+ var DIFFICULTY_QUALITY_HEAVY_THRESHOLD = 7;
6072
+ var DIFFICULTY_COST_HEAVY_THRESHOLD = 4;
6073
+ var DIFFICULTY_WEIGHT_SHIFT = 0.15;
6074
+ function applyDifficultyCostWeighting(criteria, reasoningComplexity) {
6075
+ const direction = reasoningComplexity > DIFFICULTY_QUALITY_HEAVY_THRESHOLD ? 1 : reasoningComplexity < DIFFICULTY_COST_HEAVY_THRESHOLD ? -1 : 0;
6076
+ if (direction === 0) return criteria;
6077
+ const quality = criteria.find((c) => c.name === "quality");
6078
+ const cost = criteria.find((c) => c.name === "cost");
6079
+ if (quality === void 0 || cost === void 0) return criteria;
6080
+ const donorWeight = direction === 1 ? cost.weight : quality.weight;
6081
+ const shift = Math.min(DIFFICULTY_WEIGHT_SHIFT, donorWeight);
6082
+ if (shift <= 0) return criteria;
6083
+ return criteria.map((c) => {
6084
+ if (c.name === "quality") return { ...c, weight: c.weight + direction * shift };
6085
+ if (c.name === "cost") return { ...c, weight: c.weight - direction * shift };
6086
+ return c;
6087
+ });
6088
+ }
5828
6089
  function getCriteriaForTaskCategory(taskType, billingMode = "api") {
5829
6090
  const map2 = billingMode === "plan" ? TASK_CATEGORY_PLAN_CRITERIA : TASK_CATEGORY_TOPSIS_CRITERIA;
5830
6091
  return map2[taskType] ?? (billingMode === "plan" ? PLAN_BILLING_TOPSIS_CRITERIA : DEFAULT_TOPSIS_CRITERIA);
@@ -6086,19 +6347,19 @@ var TopsisRouter = class {
6086
6347
  };
6087
6348
 
6088
6349
  // src/cli-adapters/budget-router-types.ts
6089
- import { z as z4 } from "zod";
6090
- var BudgetConstraintSchema = z4.object({
6091
- maxTokens: z4.number().int().positive().optional(),
6092
- maxCostUSD: z4.number().positive().optional(),
6093
- maxLatencyMs: z4.number().positive().optional()
6350
+ import { z as z5 } from "zod";
6351
+ var BudgetConstraintSchema = z5.object({
6352
+ maxTokens: z5.number().int().positive().optional(),
6353
+ maxCostUSD: z5.number().positive().optional(),
6354
+ maxLatencyMs: z5.number().positive().optional()
6094
6355
  });
6095
- var SessionBudgetSchema = z4.object({
6096
- totalTokens: z4.number().int().positive(),
6097
- totalCostUSD: z4.number().positive(),
6098
- usedTokens: z4.number().int().min(0).default(0),
6099
- usedCostUSD: z4.number().min(0).default(0),
6100
- startTime: z4.number().int().positive(),
6101
- sessionId: z4.string().min(1)
6356
+ var SessionBudgetSchema = z5.object({
6357
+ totalTokens: z5.number().int().positive(),
6358
+ totalCostUSD: z5.number().positive(),
6359
+ usedTokens: z5.number().int().min(0).default(0),
6360
+ usedCostUSD: z5.number().min(0).default(0),
6361
+ startTime: z5.number().int().positive(),
6362
+ sessionId: z5.string().min(1)
6102
6363
  });
6103
6364
  var DEFAULT_LINUCB_CONFIG = {
6104
6365
  numArms: 4,
@@ -6106,11 +6367,11 @@ var DEFAULT_LINUCB_CONFIG = {
6106
6367
  alpha: 1,
6107
6368
  lambda: 1
6108
6369
  };
6109
- var LinUCBConfigSchema = z4.object({
6110
- numArms: z4.number().int().positive().default(4),
6111
- featureDim: z4.number().int().positive().default(6),
6112
- alpha: z4.number().positive().default(1),
6113
- lambda: z4.number().positive().default(1)
6370
+ var LinUCBConfigSchema = z5.object({
6371
+ numArms: z5.number().int().positive().default(4),
6372
+ featureDim: z5.number().int().positive().default(6),
6373
+ alpha: z5.number().positive().default(1),
6374
+ lambda: z5.number().positive().default(1)
6114
6375
  });
6115
6376
 
6116
6377
  // src/cli-adapters/linucb-math.ts
@@ -6491,7 +6752,7 @@ var LinUCBBandit = class {
6491
6752
  import { randomUUID as randomUUID4 } from "crypto";
6492
6753
 
6493
6754
  // src/cli-adapters/preference-router-types.ts
6494
- import { z as z5 } from "zod";
6755
+ import { z as z6 } from "zod";
6495
6756
  var DEFAULT_PREFERENCE_ROUTER_CONFIG = {
6496
6757
  strongModel: {
6497
6758
  tier: "strong",
@@ -6512,24 +6773,24 @@ var DEFAULT_PREFERENCE_ROUTER_CONFIG = {
6512
6773
  maxDataPoints: 1e4,
6513
6774
  enableOnlineLearning: true
6514
6775
  };
6515
- var PreferenceRouterConfigSchema = z5.object({
6516
- strongModel: z5.object({
6517
- tier: z5.literal("strong"),
6518
- cli: z5.enum(["claude", "gemini", "codex", "opencode"]),
6519
- costPerMillionTokens: z5.number().positive(),
6520
- qualityBaseline: z5.number().min(0).max(1)
6776
+ var PreferenceRouterConfigSchema = z6.object({
6777
+ strongModel: z6.object({
6778
+ tier: z6.literal("strong"),
6779
+ cli: z6.enum(["claude", "gemini", "codex", "opencode"]),
6780
+ costPerMillionTokens: z6.number().positive(),
6781
+ qualityBaseline: z6.number().min(0).max(1)
6521
6782
  }),
6522
- weakModel: z5.object({
6523
- tier: z5.literal("weak"),
6524
- cli: z5.enum(["claude", "gemini", "codex", "opencode"]),
6525
- costPerMillionTokens: z5.number().positive(),
6526
- qualityBaseline: z5.number().min(0).max(1)
6783
+ weakModel: z6.object({
6784
+ tier: z6.literal("weak"),
6785
+ cli: z6.enum(["claude", "gemini", "codex", "opencode"]),
6786
+ costPerMillionTokens: z6.number().positive(),
6787
+ qualityBaseline: z6.number().min(0).max(1)
6527
6788
  }),
6528
- routingThreshold: z5.number().min(0).max(1).default(0.5),
6529
- minDataPoints: z5.number().int().positive().default(10),
6530
- maxDataPoints: z5.number().int().positive().default(1e4),
6531
- enableOnlineLearning: z5.boolean().default(true),
6532
- domainThresholds: z5.record(z5.string(), z5.number().min(0).max(1)).optional()
6789
+ routingThreshold: z6.number().min(0).max(1).default(0.5),
6790
+ minDataPoints: z6.number().int().positive().default(10),
6791
+ maxDataPoints: z6.number().int().positive().default(1e4),
6792
+ enableOnlineLearning: z6.boolean().default(true),
6793
+ domainThresholds: z6.record(z6.string(), z6.number().min(0).max(1)).optional()
6533
6794
  });
6534
6795
 
6535
6796
  // src/cli-adapters/preference-router-store.ts
@@ -6871,8 +7132,8 @@ function createPreferenceRouter(config, dataStore) {
6871
7132
  }
6872
7133
 
6873
7134
  // src/cli-adapters/zero-router-types.ts
6874
- import { z as z6 } from "zod";
6875
- var DifficultyDimensionSchema = z6.enum([
7135
+ import { z as z7 } from "zod";
7136
+ var DifficultyDimensionSchema = z7.enum([
6876
7137
  "reasoning",
6877
7138
  "knowledge",
6878
7139
  "creativity",
@@ -6886,17 +7147,17 @@ var DIFFICULTY_DIMENSIONS = [
6886
7147
  "precision",
6887
7148
  "context_length"
6888
7149
  ];
6889
- var DifficultySpaceSchema = z6.object({
7150
+ var DifficultySpaceSchema = z7.object({
6890
7151
  /** Reasoning difficulty: logical complexity, multi-step inference (0-1) */
6891
- reasoning: z6.number().min(0).max(1),
7152
+ reasoning: z7.number().min(0).max(1),
6892
7153
  /** Knowledge difficulty: domain expertise required (0-1) */
6893
- knowledge: z6.number().min(0).max(1),
7154
+ knowledge: z7.number().min(0).max(1),
6894
7155
  /** Creativity difficulty: novel generation, open-endedness (0-1) */
6895
- creativity: z6.number().min(0).max(1),
7156
+ creativity: z7.number().min(0).max(1),
6896
7157
  /** Precision difficulty: accuracy requirements, error tolerance (0-1) */
6897
- precision: z6.number().min(0).max(1),
7158
+ precision: z7.number().min(0).max(1),
6898
7159
  /** Context length difficulty: amount of context to process (0-1) */
6899
- context_length: z6.number().min(0).max(1)
7160
+ context_length: z7.number().min(0).max(1)
6900
7161
  });
6901
7162
  var DEFAULT_DIFFICULTY_THRESHOLDS = {
6902
7163
  easyUpperBound: 0.3,
@@ -6912,12 +7173,12 @@ var DEFAULT_TIER_TO_CLIS = {
6912
7173
  balanced: ["codex", "opencode", "gemini", "claude"],
6913
7174
  powerful: ["claude", "codex", "opencode", "gemini"]
6914
7175
  };
6915
- var DifficultyWeightsSchema = z6.object({
6916
- reasoning: z6.number().min(0).max(1),
6917
- knowledge: z6.number().min(0).max(1),
6918
- creativity: z6.number().min(0).max(1),
6919
- precision: z6.number().min(0).max(1),
6920
- context_length: z6.number().min(0).max(1)
7176
+ var DifficultyWeightsSchema = z7.object({
7177
+ reasoning: z7.number().min(0).max(1),
7178
+ knowledge: z7.number().min(0).max(1),
7179
+ creativity: z7.number().min(0).max(1),
7180
+ precision: z7.number().min(0).max(1),
7181
+ context_length: z7.number().min(0).max(1)
6921
7182
  });
6922
7183
  var DEFAULT_DIFFICULTY_WEIGHTS = {
6923
7184
  reasoning: 0.3,
@@ -6926,29 +7187,29 @@ var DEFAULT_DIFFICULTY_WEIGHTS = {
6926
7187
  precision: 0.25,
6927
7188
  context_length: 0.15
6928
7189
  };
6929
- var ZeroRouterConfigSchema = z6.object({
7190
+ var ZeroRouterConfigSchema = z7.object({
6930
7191
  /** Difficulty thresholds for level classification */
6931
- thresholds: z6.object({
6932
- easyUpperBound: z6.number().min(0).max(1),
6933
- hardLowerBound: z6.number().min(0).max(1)
7192
+ thresholds: z7.object({
7193
+ easyUpperBound: z7.number().min(0).max(1),
7194
+ hardLowerBound: z7.number().min(0).max(1)
6934
7195
  }).default(DEFAULT_DIFFICULTY_THRESHOLDS),
6935
7196
  /** Weights for difficulty aggregation */
6936
7197
  weights: DifficultyWeightsSchema.default(DEFAULT_DIFFICULTY_WEIGHTS),
6937
7198
  /** Mapping from difficulty level to model tier */
6938
- difficultyToTier: z6.record(z6.enum(["easy", "medium", "hard"]), z6.enum(["fast", "balanced", "powerful"])).default(DEFAULT_DIFFICULTY_TO_TIER),
7199
+ difficultyToTier: z7.record(z7.enum(["easy", "medium", "hard"]), z7.enum(["fast", "balanced", "powerful"])).default(DEFAULT_DIFFICULTY_TO_TIER),
6939
7200
  /** Mapping from model tier to CLI preference order */
6940
- tierToClis: z6.record(
6941
- z6.enum(["fast", "balanced", "powerful"]),
6942
- z6.array(z6.enum(["claude", "gemini", "codex", "opencode"]))
7201
+ tierToClis: z7.record(
7202
+ z7.enum(["fast", "balanced", "powerful"]),
7203
+ z7.array(z7.enum(["claude", "gemini", "codex", "opencode"]))
6943
7204
  ).default(DEFAULT_TIER_TO_CLIS),
6944
7205
  /** Enable adaptive calibration from outcomes */
6945
- enableCalibration: z6.boolean().default(true),
7206
+ enableCalibration: z7.boolean().default(true),
6946
7207
  /** Maximum outcomes to store for calibration */
6947
- maxCalibrationOutcomes: z6.number().int().positive().default(1e3),
7208
+ maxCalibrationOutcomes: z7.number().int().positive().default(1e3),
6948
7209
  /** Minimum outcomes before applying calibration adjustments */
6949
- minCalibrationOutcomes: z6.number().int().positive().default(50),
7210
+ minCalibrationOutcomes: z7.number().int().positive().default(50),
6950
7211
  /** Verbose logging */
6951
- verbose: z6.boolean().default(false)
7212
+ verbose: z7.boolean().default(false)
6952
7213
  });
6953
7214
  var DEFAULT_ZERO_ROUTER_CONFIG = {
6954
7215
  thresholds: DEFAULT_DIFFICULTY_THRESHOLDS,
@@ -7472,16 +7733,16 @@ var ZeroRouter = class {
7472
7733
  };
7473
7734
 
7474
7735
  // src/cli-adapters/latency-tracker-types.ts
7475
- import { z as z7 } from "zod";
7476
- var LatencyTrackerConfigSchema = z7.object({
7736
+ import { z as z8 } from "zod";
7737
+ var LatencyTrackerConfigSchema = z8.object({
7477
7738
  /** Maximum number of samples to keep per CLI (default: 100) */
7478
- windowSize: z7.number().int().positive().default(100),
7739
+ windowSize: z8.number().int().positive().default(100),
7479
7740
  /** Time-weighted decay factor (0-1, higher = more weight to recent) (default: 0.95) */
7480
- decayFactor: z7.number().min(0).max(1).default(0.95),
7741
+ decayFactor: z8.number().min(0).max(1).default(0.95),
7481
7742
  /** Maximum age of samples in milliseconds before forced eviction (default: 3600000 = 1 hour) */
7482
- maxSampleAgeMs: z7.number().int().positive().default(36e5),
7743
+ maxSampleAgeMs: z8.number().int().positive().default(36e5),
7483
7744
  /** Percentiles to calculate (default: [50, 95, 99]) */
7484
- percentiles: z7.array(z7.number().min(0).max(100)).default([50, 95, 99])
7745
+ percentiles: z8.array(z8.number().min(0).max(100)).default([50, 95, 99])
7485
7746
  });
7486
7747
  var EMPTY_LATENCY_STATS = {
7487
7748
  count: 0,
@@ -7716,19 +7977,19 @@ var LatencyTracker = class {
7716
7977
  };
7717
7978
 
7718
7979
  // src/cli-adapters/routing/router-stage.ts
7719
- import { z as z8 } from "zod";
7720
- var StageConfigSchema = z8.object({
7721
- enabled: z8.boolean().default(true),
7722
- priority: z8.number().int().min(0).max(100).default(50),
7723
- options: z8.record(z8.string(), z8.unknown()).optional()
7980
+ import { z as z9 } from "zod";
7981
+ var StageConfigSchema = z9.object({
7982
+ enabled: z9.boolean().default(true),
7983
+ priority: z9.number().int().min(0).max(100).default(50),
7984
+ options: z9.record(z9.string(), z9.unknown()).optional()
7724
7985
  });
7725
- var RoutingOutcomeSchema = z8.object({
7986
+ var RoutingOutcomeSchema = z9.object({
7726
7987
  selectedCli: CliNameSchema,
7727
- task: z8.string(),
7728
- success: z8.boolean(),
7729
- qualityScore: z8.number().min(0).max(1).optional(),
7730
- latencyMs: z8.number().int().positive().optional(),
7731
- tokensUsed: z8.number().int().positive().optional()
7988
+ task: z9.string(),
7989
+ success: z9.boolean(),
7990
+ qualityScore: z9.number().min(0).max(1).optional(),
7991
+ latencyMs: z9.number().int().positive().optional(),
7992
+ tokensUsed: z9.number().int().positive().optional()
7732
7993
  });
7733
7994
  function createRoutingContext(task, availableClis = ["claude", "gemini", "codex", "opencode"], metadata) {
7734
7995
  return {
@@ -7924,198 +8185,6 @@ var ConfidenceCascadeStage = class {
7924
8185
  }
7925
8186
  };
7926
8187
 
7927
- // src/config/task-specialization-types.ts
7928
- import { z as z9 } from "zod";
7929
- var TASK_CATEGORIES = [
7930
- "architecture",
7931
- "code_generation",
7932
- "code_review",
7933
- "research",
7934
- "security_review",
7935
- "planning",
7936
- "documentation",
7937
- "testing",
7938
- "devops",
7939
- "exploration"
7940
- ];
7941
- var TaskCategorySchema = z9.enum(TASK_CATEGORIES);
7942
- var TaskSpecializationSchema = z9.object({
7943
- /** Task category identifier */
7944
- category: TaskCategorySchema,
7945
- /** Primary CLI recommendation */
7946
- primaryCli: CliNameSchema,
7947
- /** Secondary CLI fallback */
7948
- secondaryCli: CliNameSchema,
7949
- /** Why this CLI is preferred for this task type */
7950
- reasoning: z9.string(),
7951
- /** Keywords that trigger this category detection */
7952
- keywords: z9.array(z9.string()).min(1),
7953
- /** Bonus score applied when this category matches (0-20) */
7954
- bonus: z9.number().min(0).max(20)
7955
- });
7956
-
7957
- // src/config/task-specialization.ts
7958
- var TASK_SPECIALIZATION_MATRIX = [
7959
- {
7960
- category: "architecture",
7961
- primaryCli: "gemini",
7962
- secondaryCli: "claude",
7963
- reasoning: "Gemini primary (66.7%, n=24) for architecture. Claude secondary (43.6%, n=220). Weather data 2026-03-09.",
7964
- keywords: ["architect", "design", "system design", "trade-off", "adr"],
7965
- bonus: 10
7966
- },
7967
- {
7968
- category: "code_generation",
7969
- primaryCli: "codex",
7970
- secondaryCli: "claude",
7971
- reasoning: "Codex primary (91.9%, n=408) for code generation with sandboxed execution. Confirmed per weather data (#1454)",
7972
- keywords: ["implement", "generate code", "write function", "build feature"],
7973
- bonus: 15
7974
- },
7975
- {
7976
- category: "code_review",
7977
- primaryCli: "codex",
7978
- secondaryCli: "claude",
7979
- reasoning: "Codex primary (88.3%, n=94) for code review; Claude secondary. Bonus aligned per weather data (#1454)",
7980
- keywords: ["review code", "code review", "pull request", "pr review"],
7981
- bonus: 15
7982
- },
7983
- {
7984
- category: "research",
7985
- primaryCli: "gemini",
7986
- secondaryCli: "claude",
7987
- reasoning: "Gemini has deep_research feature and 1M token context for synthesis",
7988
- keywords: ["research", "investigate", "literature", "survey", "state of the art"],
7989
- bonus: 15
7990
- },
7991
- {
7992
- category: "security_review",
7993
- primaryCli: "codex",
7994
- secondaryCli: "claude",
7995
- reasoning: "Weather 2026-03-09: claude 44.9% (n=385, declining), codex 60% (n=5), gemini 50% (n=14). Codex primary, claude secondary. Low bonus \u2014 no CLI is clearly dominant.",
7996
- keywords: [
7997
- "security review",
7998
- "security analysis",
7999
- "security audit",
8000
- "security flaw",
8001
- "vulnerability assessment",
8002
- "threat model",
8003
- "cve",
8004
- "audit security",
8005
- "owasp",
8006
- "injection",
8007
- "xss",
8008
- "csrf",
8009
- "security",
8010
- "vulnerability"
8011
- ],
8012
- bonus: 5
8013
- },
8014
- {
8015
- category: "planning",
8016
- primaryCli: "claude",
8017
- secondaryCli: "codex",
8018
- reasoning: "Claude primary (92.0%, n=274) for planning and task decomposition. Confirmed per weather data (#1454)",
8019
- keywords: ["plan", "sprint", "roadmap", "decompose", "prioritize"],
8020
- bonus: 10
8021
- },
8022
- {
8023
- category: "documentation",
8024
- primaryCli: "gemini",
8025
- secondaryCli: "claude",
8026
- reasoning: "Gemini can process entire codebases (1M context) for comprehensive docs",
8027
- keywords: ["document", "documentation", "readme", "api docs", "write docs"],
8028
- bonus: 10
8029
- },
8030
- {
8031
- category: "testing",
8032
- primaryCli: "codex",
8033
- secondaryCli: "claude",
8034
- reasoning: "Codex primary (91.6%, n=143) for test writing with sandbox execution. Bonus aligned per weather data (#1454)",
8035
- keywords: ["test", "write tests", "test coverage", "unit test", "integration test"],
8036
- bonus: 15
8037
- },
8038
- {
8039
- category: "devops",
8040
- primaryCli: "claude",
8041
- secondaryCli: "gemini",
8042
- reasoning: "Claude excels at infrastructure reasoning and CI/CD configuration",
8043
- keywords: [
8044
- "devops",
8045
- "ci/cd",
8046
- "deploy",
8047
- "infrastructure",
8048
- "docker",
8049
- "kubernetes",
8050
- "pipeline",
8051
- "helm",
8052
- "terraform",
8053
- "ansible",
8054
- "makefile",
8055
- "dockerfile",
8056
- "github actions",
8057
- "workflow",
8058
- "concourse",
8059
- "jenkins",
8060
- "argocd",
8061
- "vulnerability scan",
8062
- "security scan",
8063
- "sast",
8064
- "dast",
8065
- "zap",
8066
- "semgrep",
8067
- "grype",
8068
- "fork",
8069
- "kind",
8070
- "cluster",
8071
- "namespace",
8072
- "ingress",
8073
- "monitoring"
8074
- ],
8075
- bonus: 10
8076
- },
8077
- {
8078
- category: "exploration",
8079
- primaryCli: "gemini",
8080
- secondaryCli: "codex",
8081
- reasoning: "Gemini primary (98.5%, n=202) for exploration with 1M context. Claude removed as secondary (63.5% vs 98.5%, n=340) per #1462",
8082
- keywords: ["explore", "navigate", "find", "discover", "scan codebase"],
8083
- bonus: 15
8084
- }
8085
- ];
8086
- var CATEGORY_INDEX = new Map(
8087
- TASK_SPECIALIZATION_MATRIX.map((s) => [s.category, s])
8088
- );
8089
- function getSpecialization(category) {
8090
- const spec = CATEGORY_INDEX.get(category);
8091
- if (!spec) throw new Error(`Unknown category: ${category}`);
8092
- return spec;
8093
- }
8094
- function detectTaskCategory(task) {
8095
- const taskLower = task.toLowerCase();
8096
- let bestSpec;
8097
- let bestScore = 0;
8098
- for (const spec of TASK_SPECIALIZATION_MATRIX) {
8099
- let score = 0;
8100
- for (const kw of spec.keywords) {
8101
- if (taskLower.includes(kw)) {
8102
- score += kw.split(/\s+/).length;
8103
- }
8104
- }
8105
- if (score > bestScore) {
8106
- bestScore = score;
8107
- bestSpec = spec;
8108
- }
8109
- }
8110
- if (bestSpec === void 0) return null;
8111
- return {
8112
- category: bestSpec.category,
8113
- primaryCli: bestSpec.primaryCli,
8114
- secondaryCli: bestSpec.secondaryCli,
8115
- bonus: bestSpec.bonus
8116
- };
8117
- }
8118
-
8119
8188
  // src/cli-adapters/routing/stages/capability-match-stage.ts
8120
8189
  var CLI_CAPABILITIES = {
8121
8190
  claude: { reasoning: 10, codeGeneration: 8, speed: 5, costEfficiency: 3 },
@@ -9933,7 +10002,11 @@ var CompositeRouterConfigSchema = z11.object({
9933
10002
  budgetConstraints: z11.object({
9934
10003
  maxTokens: z11.number().positive(),
9935
10004
  maxCostUsd: z11.number().positive(),
9936
- maxLatencyMs: z11.number().positive()
10005
+ maxLatencyMs: z11.number().positive(),
10006
+ /** Per-task-class cost ceilings in USD, keyed by TaskCategory (#4196).
10007
+ * Default: absent → no ceiling (OFF/unlimited). Enforced only under
10008
+ * billingMode 'api'; missing candidate pricing fails CLOSED. */
10009
+ taskClassMaxCostUsd: z11.record(z11.string(), z11.number().positive())
9937
10010
  }).partial().optional(),
9938
10011
  /** LinUCB exploration parameter (default: 1.0) */
9939
10012
  linucbAlpha: z11.number().positive().default(1),
@@ -10013,6 +10086,7 @@ function calculateConfidence(topsisScore, ucbScore, candidateCount) {
10013
10086
  const avgScore = scores.reduce((a, b) => a + b, 0) / scores.length;
10014
10087
  return 0.3 * baseConfidence + 0.7 * avgScore;
10015
10088
  }
10089
+ var PLAN_MODE_COST_ANNOTATION = "cost weighting disabled: plan mode";
10016
10090
  function buildReason(options) {
10017
10091
  const { selectedCli, stages, topsisScore, ucbScore, preferenceScore, difficultyTier } = options;
10018
10092
  const difficultyScore = options.difficultyScore;
@@ -10024,6 +10098,7 @@ function buildReason(options) {
10024
10098
  if (preferenceScore !== void 0) parts.push("preference " + preferenceScore.toFixed(2));
10025
10099
  if (topsisScore !== void 0) parts.push("TOPSIS score " + topsisScore.toFixed(2));
10026
10100
  if (ucbScore !== void 0) parts.push("UCB score " + ucbScore.toFixed(2));
10101
+ if (options.billingMode === "plan") parts.push(PLAN_MODE_COST_ANNOTATION);
10027
10102
  return parts.join(", ");
10028
10103
  }
10029
10104
  function filterByPreferenceTier(candidates, tier) {
@@ -10041,9 +10116,7 @@ function cliTaskToTask(cliTask) {
10041
10116
  };
10042
10117
  }
10043
10118
  function applyBudgetFilter(task, candidates, budgetRouter, config) {
10044
- if (budgetRouter === void 0) {
10045
- return { eligible: candidates, withinBudget: true };
10046
- }
10119
+ if (budgetRouter === void 0) return { eligible: candidates, withinBudget: true };
10047
10120
  const rawConstraints = config.budgetConstraints;
10048
10121
  const constraint = {};
10049
10122
  if (rawConstraints?.maxTokens !== void 0) {
@@ -10056,21 +10129,18 @@ function applyBudgetFilter(task, candidates, budgetRouter, config) {
10056
10129
  constraint.maxLatencyMs = rawConstraints.maxLatencyMs;
10057
10130
  }
10058
10131
  const result = budgetRouter.checkBudget(task, constraint);
10059
- return { eligible: result.withinBudget ? candidates : [], withinBudget: result.withinBudget };
10132
+ if (!result.withinBudget) return { eligible: [], withinBudget: false };
10133
+ if (config.billingMode !== "api") return { eligible: candidates, withinBudget: true };
10134
+ return { eligible: budgetRouter.filterByTaskClassCeiling(task, candidates), withinBudget: true };
10060
10135
  }
10061
10136
  var TOPSIS_TOLERANCE_BAND_PERCENT = 0.05;
10062
- function selectTopsisRouter(router, billingMode, taskType) {
10063
- if (taskType !== void 0) {
10064
- const mode = billingMode === "plan" ? "plan" : "api";
10065
- const criteria = getCriteriaForTaskCategory(taskType, mode);
10066
- const defaultCriteria = mode === "plan" ? PLAN_BILLING_TOPSIS_CRITERIA : DEFAULT_TOPSIS_CRITERIA;
10067
- if (criteria !== defaultCriteria) {
10068
- return new TopsisRouter({ criteria });
10069
- }
10070
- }
10071
- if (billingMode === "plan") {
10072
- return new TopsisRouter({ criteria: PLAN_BILLING_TOPSIS_CRITERIA });
10073
- }
10137
+ function selectTopsisRouter(router, billingMode, taskType, reasoningComplexity) {
10138
+ const mode = billingMode === "plan" ? "plan" : "api";
10139
+ const defaultCriteria = mode === "plan" ? PLAN_BILLING_TOPSIS_CRITERIA : DEFAULT_TOPSIS_CRITERIA;
10140
+ const base = taskType !== void 0 ? getCriteriaForTaskCategory(taskType, mode) : defaultCriteria;
10141
+ const criteria = mode === "api" && reasoningComplexity !== void 0 ? applyDifficultyCostWeighting(base, reasoningComplexity) : base;
10142
+ if (criteria !== defaultCriteria) return new TopsisRouter({ criteria });
10143
+ if (mode === "plan") return new TopsisRouter({ criteria: PLAN_BILLING_TOPSIS_CRITERIA });
10074
10144
  return router;
10075
10145
  }
10076
10146
  var PERFORMANCE_FLOOR_THRESHOLD = 0.5;
@@ -10125,7 +10195,12 @@ function applyTopsisRanking(taskProfile, candidates, topsisRouter, options) {
10125
10195
  return { ranking: candidates, topScore: 1 };
10126
10196
  }
10127
10197
  const billingMode = options?.billingMode ?? "api";
10128
- const router = selectTopsisRouter(topsisRouter, billingMode, taskProfile.taskType);
10198
+ const router = selectTopsisRouter(
10199
+ topsisRouter,
10200
+ billingMode,
10201
+ taskProfile.taskType,
10202
+ taskProfile.reasoningComplexity
10203
+ );
10129
10204
  const adjustedProfiles = buildAdjustedProfiles(taskProfile, candidates, options);
10130
10205
  const result = router.selectModel({ profiles: adjustedProfiles });
10131
10206
  const scoreMap = new Map(result.scores.map((s) => [s.cliName, s.closenessScore]));
@@ -10196,6 +10271,7 @@ function buildDecisionFields(ctx) {
10196
10271
  const reason = buildReason({
10197
10272
  selectedCli: ctx.selectedCli,
10198
10273
  stages: ctx.stagesExecuted,
10274
+ ...ctx.billingMode !== void 0 ? { billingMode: ctx.billingMode } : {},
10199
10275
  ...ctx.topsisScore !== void 0 ? { topsisScore: ctx.topsisScore } : {},
10200
10276
  ...ctx.ucbScore !== void 0 ? { ucbScore: ctx.ucbScore } : {},
10201
10277
  ...ctx.preferenceScore !== void 0 ? { preferenceScore: ctx.preferenceScore } : {},
@@ -13864,7 +13940,7 @@ var CompositeRouter = class _CompositeRouter {
13864
13940
  }
13865
13941
  initializeCoreRouters(adapters, preferenceConfig, zeroConfig, latencyConfig) {
13866
13942
  if (this.config.enableBudgetFilter && adapters.size > 0) {
13867
- this.budgetRouter = new BudgetRouter(adapters);
13943
+ this.budgetRouter = this.buildBudgetRouter(adapters);
13868
13944
  }
13869
13945
  if (this.config.enableZeroRouter) this.zeroRouter = new ZeroRouter(zeroConfig, this.logger);
13870
13946
  if (this.config.enablePreferenceRouting)
@@ -13876,6 +13952,12 @@ var CompositeRouter = class _CompositeRouter {
13876
13952
  }
13877
13953
  if (this.config.enableLatencyTracking) this.latencyTracker = new LatencyTracker(latencyConfig);
13878
13954
  }
13955
+ /** #4196: plumb per-task-class cost ceilings into the BudgetRouter.
13956
+ * Absent → defaults (no ceiling configured). */
13957
+ buildBudgetRouter(adapters) {
13958
+ const ceilings = this.config.budgetConstraints?.taskClassMaxCostUsd;
13959
+ return ceilings !== void 0 ? new BudgetRouter(adapters, { taskClassCostCeilings: ceilings }) : new BudgetRouter(adapters);
13960
+ }
13879
13961
  initializeMemoryAndStages(routingMemoryConfig, stageConfigs) {
13880
13962
  if (this.config.enableRoutingMemory) {
13881
13963
  this.routingMemory = new RoutingMemory(routingMemoryConfig);
@@ -13994,7 +14076,7 @@ var CompositeRouter = class _CompositeRouter {
13994
14076
  */
13995
14077
  async consultUnifiedContext(task) {
13996
14078
  try {
13997
- const { getContextForTask, inferTaskCategory } = await import("./context-retriever-CEVBKMKI.js");
14079
+ const { getContextForTask, inferTaskCategory } = await import("./context-retriever-ZQXEWMHS.js");
13998
14080
  const ctx = await getContextForTask({
13999
14081
  task: task.content,
14000
14082
  category: inferTaskCategory(task.content),
@@ -14218,7 +14300,11 @@ var CompositeRouter = class _CompositeRouter {
14218
14300
  }
14219
14301
  const decisionTimeMs = getTimeProvider().now() - params.startTime;
14220
14302
  this.updateStats(params.selectedCli, decisionTimeMs);
14221
- const { confidence, reason, alternatives } = buildDecisionFields({ ...params, decisionTimeMs });
14303
+ const { confidence, reason, alternatives } = buildDecisionFields({
14304
+ ...params,
14305
+ decisionTimeMs,
14306
+ billingMode: this.config.billingMode
14307
+ });
14222
14308
  const model = isRouteModelSelectionEnabled() && params.difficultyTier !== void 0 ? resolveModelForTier(routingArmDisplaySlot(params.selectedCli), params.difficultyTier) : void 0;
14223
14309
  return ok({
14224
14310
  adapter: selectedAdapter,
@@ -15499,4 +15585,4 @@ export {
15499
15585
  AgentCapability,
15500
15586
  OrchestratorError
15501
15587
  };
15502
- //# sourceMappingURL=chunk-X2K6JQXF.js.map
15588
+ //# sourceMappingURL=chunk-A4XTT4AG.js.map