@dotobokuri/fleet-console 1.45.0 → 1.47.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 (68) hide show
  1. package/dist/cli.d.ts +4 -0
  2. package/dist/cli.mjs +416 -899
  3. package/dist/client/assets/{_baseUniq-DtJH2bh8.js → _baseUniq-kGlxZURL.js} +1 -1
  4. package/dist/client/assets/{arc-DHCKMqqF.js → arc-0MFZHiK0.js} +1 -1
  5. package/dist/client/assets/{architectureDiagram-Q4EWVU46-BOuyINL8.js → architectureDiagram-Q4EWVU46--RIddBvw.js} +1 -1
  6. package/dist/client/assets/{blockDiagram-DXYQGD6D-cIre3_5n.js → blockDiagram-DXYQGD6D-BeAbiibf.js} +1 -1
  7. package/dist/client/assets/{c4Diagram-AHTNJAMY-DTX-qjSW.js → c4Diagram-AHTNJAMY-DRkTrRQf.js} +1 -1
  8. package/dist/client/assets/channel-Ba1lW0bI.js +1 -0
  9. package/dist/client/assets/{chunk-4BX2VUAB-DiBwpozK.js → chunk-4BX2VUAB-BpSILXNJ.js} +1 -1
  10. package/dist/client/assets/{chunk-4TB4RGXK-uCZ-B3-y.js → chunk-4TB4RGXK-ghYBwdri.js} +1 -1
  11. package/dist/client/assets/{chunk-55IACEB6-CmBu2sf0.js → chunk-55IACEB6-D-DtPH2C.js} +1 -1
  12. package/dist/client/assets/{chunk-EDXVE4YY-BXpw2VyB.js → chunk-EDXVE4YY-59RlsQG_.js} +1 -1
  13. package/dist/client/assets/{chunk-FMBD7UC4-Cn9sKTn5.js → chunk-FMBD7UC4-Hi26IR8N.js} +1 -1
  14. package/dist/client/assets/{chunk-OYMX7WX6-BDgH1zlW.js → chunk-OYMX7WX6-BRgJcTlv.js} +1 -1
  15. package/dist/client/assets/{chunk-QZHKN3VN-BOdvRibu.js → chunk-QZHKN3VN-BRc2Mimo.js} +1 -1
  16. package/dist/client/assets/{chunk-YZCP3GAM-BDIEapcU.js → chunk-YZCP3GAM-CpPUsJ-2.js} +1 -1
  17. package/dist/client/assets/classDiagram-6PBFFD2Q-CrjH2VLS.js +1 -0
  18. package/dist/client/assets/classDiagram-v2-HSJHXN6E-CrjH2VLS.js +1 -0
  19. package/dist/client/assets/clone-CSapyzZ9.js +1 -0
  20. package/dist/client/assets/{cose-bilkent-S5V4N54A-BVlc3Dad.js → cose-bilkent-S5V4N54A-CU7M4-h6.js} +1 -1
  21. package/dist/client/assets/{dagre-KV5264BT-raV4BK6w.js → dagre-KV5264BT-DsP2pvSV.js} +1 -1
  22. package/dist/client/assets/{diagram-5BDNPKRD-BMAh1LJt.js → diagram-5BDNPKRD-BIsHIEFX.js} +1 -1
  23. package/dist/client/assets/{diagram-G4DWMVQ6-AM5t2f3j.js → diagram-G4DWMVQ6-kszOBtB4.js} +1 -1
  24. package/dist/client/assets/{diagram-MMDJMWI5-DMw_UwWR.js → diagram-MMDJMWI5-Cn5sUEtw.js} +1 -1
  25. package/dist/client/assets/{diagram-TYMM5635-CiDAD3e9.js → diagram-TYMM5635-CDdfKXq-.js} +1 -1
  26. package/dist/client/assets/{erDiagram-SMLLAGMA-D1IXSwFZ.js → erDiagram-SMLLAGMA-CI6i4IOF.js} +1 -1
  27. package/dist/client/assets/{flowDiagram-DWJPFMVM-pEq1L6HP.js → flowDiagram-DWJPFMVM-7foBBYZ0.js} +1 -1
  28. package/dist/client/assets/{ganttDiagram-T4ZO3ILL-BAXTU16v.js → ganttDiagram-T4ZO3ILL-BtCT8Kyl.js} +1 -1
  29. package/dist/client/assets/{gitGraphDiagram-UUTBAWPF-PkEvpSio.js → gitGraphDiagram-UUTBAWPF-BnG43YXm.js} +1 -1
  30. package/dist/client/assets/{graph-OOLam9NE.js → graph-BJfoQM9L.js} +1 -1
  31. package/dist/client/assets/index-BQvUTYMt.js +482 -0
  32. package/dist/client/assets/index-DpllFOQw.css +1 -0
  33. package/dist/client/assets/{infoDiagram-42DDH7IO-CcLveXnw.js → infoDiagram-42DDH7IO-fSjQnpO3.js} +1 -1
  34. package/dist/client/assets/{ishikawaDiagram-UXIWVN3A-D-p_eX0W.js → ishikawaDiagram-UXIWVN3A-CikjAzZU.js} +1 -1
  35. package/dist/client/assets/{journeyDiagram-VCZTEJTY-D81ht6ZY.js → journeyDiagram-VCZTEJTY-CXlhn41B.js} +1 -1
  36. package/dist/client/assets/{kanban-definition-6JOO6SKY-D8SIIBWy.js → kanban-definition-6JOO6SKY-ZSgxxWjz.js} +1 -1
  37. package/dist/client/assets/{layout-CeMy87tl.js → layout-Cn9VNxzb.js} +1 -1
  38. package/dist/client/assets/{linear-C0Txkuhf.js → linear-D-4NsXnb.js} +1 -1
  39. package/dist/client/assets/{mermaid.core-zdTcCzmD.js → mermaid.core-DIF6Bqhc.js} +4 -4
  40. package/dist/client/assets/{min-BPp_9Vnv.js → min-BO2TWSJi.js} +1 -1
  41. package/dist/client/assets/{mindmap-definition-QFDTVHPH-DRJbMf8W.js → mindmap-definition-QFDTVHPH-Bmigbv3o.js} +1 -1
  42. package/dist/client/assets/{pieDiagram-DEJITSTG-CqPISwE-.js → pieDiagram-DEJITSTG-56EjQ8_u.js} +1 -1
  43. package/dist/client/assets/{quadrantDiagram-34T5L4WZ-BGLaujDD.js → quadrantDiagram-34T5L4WZ-B-mdpysI.js} +1 -1
  44. package/dist/client/assets/{requirementDiagram-MS252O5E-DWF6sFQ_.js → requirementDiagram-MS252O5E-C-16L-rK.js} +1 -1
  45. package/dist/client/assets/{sankeyDiagram-XADWPNL6-Dv2O23aj.js → sankeyDiagram-XADWPNL6-Dpi8Xvd6.js} +1 -1
  46. package/dist/client/assets/{sequenceDiagram-FGHM5R23-BXgYtweg.js → sequenceDiagram-FGHM5R23-Bb2L9XDw.js} +1 -1
  47. package/dist/client/assets/{stateDiagram-FHFEXIEX-COtDK2gC.js → stateDiagram-FHFEXIEX-z1AMjFur.js} +1 -1
  48. package/dist/client/assets/stateDiagram-v2-QKLJ7IA2-CKmEKJLy.js +1 -0
  49. package/dist/client/assets/{timeline-definition-GMOUNBTQ-B_-QYLHK.js → timeline-definition-GMOUNBTQ-DHXjAJin.js} +1 -1
  50. package/dist/client/assets/{vennDiagram-DHZGUBPP-BNVDRgAa.js → vennDiagram-DHZGUBPP-Cw9QDGZk.js} +1 -1
  51. package/dist/client/assets/{wardley-RL74JXVD-B3XwoW1A.js → wardley-RL74JXVD-CDHq7H5N.js} +1 -1
  52. package/dist/client/assets/{wardleyDiagram-NUSXRM2D-B6-OKLa-.js → wardleyDiagram-NUSXRM2D-B0P1pItE.js} +1 -1
  53. package/dist/client/assets/{xychartDiagram-5P7HB3ND-DepfDqrZ.js → xychartDiagram-5P7HB3ND-B5jXRxLe.js} +1 -1
  54. package/dist/client/index.html +2 -2
  55. package/dist/fleet-plugins/ledger/routes.mjs +45 -105
  56. package/dist/fleet-plugins/quota/routes.mjs +455 -64
  57. package/dist/fleet-plugins/repository/routes.mjs +26 -9
  58. package/dist/fleet-plugins/scuttlebutt/routes.mjs +150 -703
  59. package/dist/fleet-plugins/skills/routes.mjs +45 -105
  60. package/dist/fleet-plugins/terminal/routes.mjs +1436 -1076
  61. package/package.json +1 -1
  62. package/dist/client/assets/channel-BhciS8jR.js +0 -1
  63. package/dist/client/assets/classDiagram-6PBFFD2Q-BjRUnHce.js +0 -1
  64. package/dist/client/assets/classDiagram-v2-HSJHXN6E-BjRUnHce.js +0 -1
  65. package/dist/client/assets/clone-DygBt67n.js +0 -1
  66. package/dist/client/assets/index-BgHFwZy9.css +0 -1
  67. package/dist/client/assets/index-CvXOp1di.js +0 -452
  68. package/dist/client/assets/stateDiagram-v2-QKLJ7IA2-BzCFtEfV.js +0 -1
@@ -20150,225 +20150,6 @@ function inferBinName(packageName) {
20150
20150
  return lastSegment.replace(/@[^@/]+$/, "");
20151
20151
  }
20152
20152
 
20153
- // ../../packages/core-unified-agent/models.json
20154
- var models_default = {
20155
- version: 1,
20156
- updatedAt: "2026-07-25T00:00:00Z",
20157
- providers: {
20158
- claude: {
20159
- name: "Claude Code",
20160
- defaultModel: "opus[1m]",
20161
- models: [
20162
- { modelId: "haiku", name: "Claude Haiku", effort: { supported: true, levels: ["low", "medium", "high", "xhigh", "max"], default: "low" } },
20163
- { modelId: "sonnet", name: "Claude Sonnet", effort: { supported: true, levels: ["low", "medium", "high", "xhigh", "max"], default: "medium" } },
20164
- { modelId: "opus", name: "Claude Opus", effort: { supported: true, levels: ["low", "medium", "high", "xhigh", "max"], default: "xhigh" } },
20165
- { modelId: "opus[1m]", name: "Claude Opus [1M]", effort: { supported: true, levels: ["low", "medium", "high", "xhigh", "max"], default: "xhigh" } },
20166
- { modelId: "claude-opus-4-6[1m]", name: "Claude Opus 4.6 [1M]", effort: { supported: true, levels: ["low", "medium", "high", "xhigh", "max"], default: "high" } },
20167
- { modelId: "claude-opus-4-7[1m]", name: "Claude Opus 4.7 [1M]", effort: { supported: true, levels: ["low", "medium", "high", "xhigh", "max"], default: "high" } },
20168
- { modelId: "claude-opus-4-8[1m]", name: "Claude Opus 4.8 [1M]", effort: { supported: true, levels: ["low", "medium", "high", "xhigh", "max"], default: "high" } }
20169
- ]
20170
- },
20171
- codex: {
20172
- name: "Codex",
20173
- defaultModel: "gpt-5.6-sol",
20174
- models: [
20175
- { modelId: "gpt-5.6-sol", name: "GPT-5.6-Sol", effort: { supported: true, levels: ["low", "medium", "high", "xhigh", "max", "ultra"], default: "low" } },
20176
- { modelId: "gpt-5.6-sol-fast", name: "GPT-5.6-Sol Fast", providerModelId: "gpt-5.6-sol", serviceTier: "priority", effort: { supported: true, levels: ["low", "medium", "high", "xhigh", "max", "ultra"], default: "low" } },
20177
- { modelId: "gpt-5.6-terra", name: "GPT-5.6-Terra", effort: { supported: true, levels: ["low", "medium", "high", "xhigh", "max", "ultra"], default: "medium" } },
20178
- { modelId: "gpt-5.6-terra-fast", name: "GPT-5.6-Terra Fast", providerModelId: "gpt-5.6-terra", serviceTier: "priority", effort: { supported: true, levels: ["low", "medium", "high", "xhigh", "max", "ultra"], default: "medium" } },
20179
- { modelId: "gpt-5.6-luna", name: "GPT-5.6-Luna", effort: { supported: true, levels: ["low", "medium", "high", "xhigh", "max"], default: "medium" } },
20180
- { modelId: "gpt-5.6-luna-fast", name: "GPT-5.6-Luna Fast", providerModelId: "gpt-5.6-luna", serviceTier: "priority", effort: { supported: true, levels: ["low", "medium", "high", "xhigh", "max"], default: "medium" } },
20181
- { modelId: "gpt-5.5", name: "GPT-5.5", effort: { supported: true, levels: ["low", "medium", "high", "xhigh"], default: "high" } },
20182
- { modelId: "gpt-5.5-fast", name: "GPT-5.5 Fast", providerModelId: "gpt-5.5", serviceTier: "priority", effort: { supported: true, levels: ["low", "medium", "high", "xhigh"], default: "high" } }
20183
- ]
20184
- },
20185
- cursor: {
20186
- name: "Cursor Agent",
20187
- defaultModel: "auto",
20188
- models: [
20189
- { modelId: "auto", name: "Auto", effort: { supported: false } },
20190
- { modelId: "composer-2.5", name: "Composer 2.5", effort: { supported: false } },
20191
- { modelId: "composer-2.5-fast", name: "Composer 2.5 Fast", effort: { supported: false } },
20192
- { modelId: "gpt-5.6-sol", name: "GPT-5.6 Sol", spawnModelTemplate: "gpt-5.6-sol-{effort}", effort: { supported: true, levels: ["low", "medium", "high", "xhigh", "max"], default: "medium" } },
20193
- { modelId: "gpt-5.6-sol-fast", name: "GPT-5.6 Sol Fast", spawnModelTemplate: "gpt-5.6-sol-{effort}-fast", effort: { supported: true, levels: ["low", "medium", "high", "xhigh", "max"], default: "medium" } },
20194
- { modelId: "gpt-5.6-sol-none", name: "GPT-5.6 Sol None", effort: { supported: false } },
20195
- { modelId: "gpt-5.6-sol-none-fast", name: "GPT-5.6 Sol None Fast", effort: { supported: false } },
20196
- { modelId: "gpt-5.6-luna", name: "GPT-5.6 Luna", spawnModelTemplate: "gpt-5.6-luna-{effort}", effort: { supported: true, levels: ["low", "medium", "high", "xhigh", "max"], default: "medium" } },
20197
- { modelId: "gpt-5.6-luna-fast", name: "GPT-5.6 Luna Fast", spawnModelTemplate: "gpt-5.6-luna-{effort}-fast", effort: { supported: true, levels: ["low", "medium", "high", "xhigh", "max"], default: "medium" } },
20198
- { modelId: "gpt-5.6-luna-none", name: "GPT-5.6 Luna None", effort: { supported: false } },
20199
- { modelId: "gpt-5.6-luna-none-fast", name: "GPT-5.6 Luna None Fast", effort: { supported: false } },
20200
- { modelId: "gpt-5.6-terra", name: "GPT-5.6 Terra", spawnModelTemplate: "gpt-5.6-terra-{effort}", effort: { supported: true, levels: ["low", "medium", "high", "xhigh", "max"], default: "medium" } },
20201
- { modelId: "gpt-5.6-terra-fast", name: "GPT-5.6 Terra Fast", spawnModelTemplate: "gpt-5.6-terra-{effort}-fast", effort: { supported: true, levels: ["low", "medium", "high", "xhigh", "max"], default: "medium" } },
20202
- { modelId: "gpt-5.6-terra-none", name: "GPT-5.6 Terra None", effort: { supported: false } },
20203
- { modelId: "gpt-5.6-terra-none-fast", name: "GPT-5.6 Terra None Fast", effort: { supported: false } },
20204
- { modelId: "cursor-grok-4.5", name: "Cursor Grok 4.5", spawnModelTemplate: "cursor-grok-4.5-{effort}", effort: { supported: true, levels: ["low", "medium", "high"], default: "high" } },
20205
- { modelId: "cursor-grok-4.5-fast", name: "Cursor Grok 4.5 Fast", spawnModelTemplate: "cursor-grok-4.5-{effort}-fast", effort: { supported: true, levels: ["low", "medium", "high"], default: "high" } },
20206
- { modelId: "claude-opus-4-8", name: "Claude Opus 4.8", spawnModelTemplate: "claude-opus-4-8-{effort}", effort: { supported: true, levels: ["low", "medium", "high", "xhigh", "max"], default: "high" } },
20207
- { modelId: "claude-opus-4-8-fast", name: "Claude Opus 4.8 Fast", spawnModelTemplate: "claude-opus-4-8-{effort}-fast", effort: { supported: true, levels: ["low", "medium", "high", "xhigh", "max"], default: "high" } },
20208
- { modelId: "claude-opus-4-8-thinking", name: "Claude Opus 4.8 Thinking", spawnModelTemplate: "claude-opus-4-8-thinking-{effort}", effort: { supported: true, levels: ["low", "medium", "high", "xhigh", "max"], default: "xhigh" } },
20209
- { modelId: "claude-opus-4-8-thinking-fast", name: "Claude Opus 4.8 Thinking Fast", spawnModelTemplate: "claude-opus-4-8-thinking-{effort}-fast", effort: { supported: true, levels: ["low", "medium", "high", "xhigh", "max"], default: "xhigh" } },
20210
- { modelId: "claude-sonnet-5", name: "Claude Sonnet 5", spawnModelTemplate: "claude-sonnet-5-{effort}", effort: { supported: true, levels: ["low", "medium", "high", "xhigh", "max"], default: "high" } },
20211
- { modelId: "claude-sonnet-5-thinking", name: "Claude Sonnet 5 Thinking", spawnModelTemplate: "claude-sonnet-5-thinking-{effort}", effort: { supported: true, levels: ["low", "medium", "high", "xhigh", "max"], default: "xhigh" } },
20212
- { modelId: "claude-fable-5", name: "Claude Fable 5", description: "NO ZDR per cursor-agent model list", spawnModelTemplate: "claude-fable-5-{effort}", effort: { supported: true, levels: ["low", "medium", "high", "xhigh", "max"], default: "high" } },
20213
- { modelId: "claude-fable-5-thinking", name: "Claude Fable 5 Thinking", description: "NO ZDR per cursor-agent model list", spawnModelTemplate: "claude-fable-5-thinking-{effort}", effort: { supported: true, levels: ["low", "medium", "high", "xhigh", "max"], default: "xhigh" } },
20214
- { modelId: "gemini-3.1-pro", name: "Gemini 3.1 Pro", effort: { supported: false } },
20215
- { modelId: "gemini-3.5-flash", name: "Gemini 3.5 Flash", effort: { supported: false } },
20216
- { modelId: "kimi-k2.7-code", name: "Kimi K2.7 Code", effort: { supported: false } },
20217
- { modelId: "glm-5.2", name: "GLM 5.2", spawnModelTemplate: "glm-5.2-{effort}", effort: { supported: true, levels: ["high", "max"], default: "max" } }
20218
- ]
20219
- }
20220
- }
20221
- };
20222
-
20223
- // ../../packages/core-unified-agent/src/models/registry.ts
20224
- var EffortLevelSchema = external_exports.enum([
20225
- "low",
20226
- "medium",
20227
- "high",
20228
- "xhigh",
20229
- "max",
20230
- "ultra"
20231
- ]);
20232
- var EffortSchema = external_exports.union([
20233
- external_exports.object({
20234
- supported: external_exports.literal(true),
20235
- levels: external_exports.array(EffortLevelSchema).min(1),
20236
- default: EffortLevelSchema
20237
- }).strict(),
20238
- external_exports.object({
20239
- supported: external_exports.literal(false)
20240
- }).strict()
20241
- ]);
20242
- var ModelEntrySchema = external_exports.object({
20243
- /** 모델 고유 식별자 (session/set_model에 전달되는 값) */
20244
- modelId: external_exports.string(),
20245
- /** 사람이 읽을 수 있는 모델 이름 */
20246
- name: external_exports.string(),
20247
- /** 모델 설명 (선택) */
20248
- description: external_exports.string().optional(),
20249
- /** spawn 시 실제 CLI 모델 ID를 조립해야 하는 경우의 템플릿 */
20250
- spawnModelTemplate: external_exports.string().optional(),
20251
- /** 카탈로그 ID와 실제 provider 모델 ID가 다른 경우의 원본 모델 ID */
20252
- providerModelId: external_exports.string().optional(),
20253
- /** provider가 모델과 별도로 받는 서비스 티어 */
20254
- serviceTier: external_exports.string().optional(),
20255
- /** 모델의 컨텍스트 윈도우 크기 (토큰) */
20256
- contextWindow: external_exports.number().int().positive().optional(),
20257
- /** 모델별 effort 설정 */
20258
- effort: EffortSchema
20259
- }).check((ctx) => {
20260
- const effort = ctx.value.effort;
20261
- if (effort.supported && !effort.levels.includes(effort.default)) {
20262
- ctx.issues.push({
20263
- code: "custom",
20264
- input: effort.default,
20265
- message: `effort.default "${effort.default}"\uC740(\uB294) levels \uBAA9\uB85D\uC5D0 \uC874\uC7AC\uD574\uC57C \uD569\uB2C8\uB2E4`,
20266
- path: ["effort", "default"]
20267
- });
20268
- }
20269
- if (effort.supported && ctx.value.spawnModelTemplate && !ctx.value.spawnModelTemplate.includes("{effort}")) {
20270
- ctx.issues.push({
20271
- code: "custom",
20272
- input: ctx.value.spawnModelTemplate,
20273
- message: 'effort \uC9C0\uC6D0 \uBAA8\uB378\uC758 spawnModelTemplate\uC740 "{effort}" \uD50C\uB808\uC774\uC2A4\uD640\uB354\uB97C \uD3EC\uD568\uD574\uC57C \uD569\uB2C8\uB2E4',
20274
- path: ["spawnModelTemplate"]
20275
- });
20276
- }
20277
- if (ctx.value.serviceTier && !ctx.value.providerModelId) {
20278
- ctx.issues.push({
20279
- code: "custom",
20280
- input: ctx.value.serviceTier,
20281
- message: "serviceTier\uB97C \uC9C0\uC815\uD55C \uBAA8\uB378\uC740 providerModelId\uB3C4 \uC9C0\uC815\uD574\uC57C \uD569\uB2C8\uB2E4",
20282
- path: ["serviceTier"]
20283
- });
20284
- }
20285
- });
20286
- var ProviderSchema = external_exports.object({
20287
- /** 프로바이더 표시 이름 */
20288
- name: external_exports.string(),
20289
- /** 기본 모델 ID */
20290
- defaultModel: external_exports.string(),
20291
- /** 사용 가능한 모델 목록 */
20292
- models: external_exports.array(ModelEntrySchema).min(1)
20293
- }).check((ctx) => {
20294
- const ids = new Set(ctx.value.models.map((m) => m.modelId));
20295
- if (!ids.has(ctx.value.defaultModel)) {
20296
- ctx.issues.push({
20297
- code: "custom",
20298
- input: ctx.value.defaultModel,
20299
- message: `defaultModel "${ctx.value.defaultModel}"\uC740(\uB294) models \uBAA9\uB85D\uC5D0 \uC874\uC7AC\uD574\uC57C \uD569\uB2C8\uB2E4`,
20300
- path: ["defaultModel"]
20301
- });
20302
- }
20303
- });
20304
- var ModelMappingSchema = external_exports.record(external_exports.string(), external_exports.string());
20305
- var ModelsMapperEntrySchema = external_exports.object({
20306
- /** modelId와 연관된 매핑 모델 */
20307
- modelMapping: ModelMappingSchema,
20308
- /** 추가 환경변수 (선택) */
20309
- env: external_exports.record(external_exports.string(), external_exports.string()).optional()
20310
- });
20311
- external_exports.record(external_exports.string(), ModelsMapperEntrySchema);
20312
- var ModelsRegistrySchema = external_exports.object({
20313
- /** 스키마 버전 */
20314
- version: external_exports.number().int().positive(),
20315
- /** 최종 업데이트 시각 */
20316
- updatedAt: external_exports.string(),
20317
- /** 프로바이더별 모델 정보 */
20318
- providers: external_exports.record(external_exports.string(), ProviderSchema)
20319
- });
20320
- var registry2 = Object.freeze(ModelsRegistrySchema.parse(models_default));
20321
- function getProviderModels(cli) {
20322
- const provider = registry2.providers[cli];
20323
- if (!provider) {
20324
- throw new Error(`\uC54C \uC218 \uC5C6\uB294 \uD504\uB85C\uBC14\uC774\uB354: "${cli}"`);
20325
- }
20326
- return structuredClone(provider);
20327
- }
20328
- function getEffort(cli, modelId) {
20329
- const model = findProviderModel(cli, modelId);
20330
- return structuredClone(model.effort);
20331
- }
20332
- function getCursorSpawnEffortInfo(modelId) {
20333
- const model = findProviderModel("cursor", modelId);
20334
- if (!model.spawnModelTemplate || !model.effort.supported) {
20335
- return { supported: false, levels: [], default: null };
20336
- }
20337
- return {
20338
- supported: true,
20339
- levels: [...model.effort.levels],
20340
- default: model.effort.default
20341
- };
20342
- }
20343
- function resolveCursorSpawnModel(modelId, effort) {
20344
- const model = findProviderModel("cursor", modelId);
20345
- if (!model.spawnModelTemplate) {
20346
- return model.modelId;
20347
- }
20348
- if (!model.effort.supported) {
20349
- return model.spawnModelTemplate;
20350
- }
20351
- const level = effort ?? model.effort.default;
20352
- const resolvedLevel = model.effort.levels.includes(level) ? level : model.effort.default;
20353
- if (!model.effort.levels.includes(resolvedLevel)) {
20354
- throw new Error(
20355
- `cursor/${modelId} \uBAA8\uB378\uC740 effort "${level}"\uC744(\uB97C) \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uC0AC\uC6A9 \uAC00\uB2A5: ${model.effort.levels.join(", ")}`
20356
- );
20357
- }
20358
- return model.spawnModelTemplate.replace("{effort}", resolvedLevel);
20359
- }
20360
- function findProviderModel(cli, modelId) {
20361
- const provider = registry2.providers[cli];
20362
- if (!provider) {
20363
- throw new Error(`\uC54C \uC218 \uC5C6\uB294 \uD504\uB85C\uBC14\uC774\uB354: "${cli}"`);
20364
- }
20365
- const model = provider.models.find((m) => m.modelId === modelId);
20366
- if (!model) {
20367
- throw new Error(`\uC54C \uC218 \uC5C6\uB294 \uBAA8\uB378: "${cli}/${modelId}"`);
20368
- }
20369
- return model;
20370
- }
20371
-
20372
20153
  // ../../packages/core-unified-agent/src/config/CliConfigs.ts
20373
20154
  var CLI_BACKENDS = {
20374
20155
  claude: {
@@ -20404,21 +20185,6 @@ var CLI_BACKENDS = {
20404
20185
  requiresModelAtSpawn: false,
20405
20186
  usesNpxBridge: false,
20406
20187
  defaultMaxTokens: 1e5
20407
- },
20408
- cursor: {
20409
- id: "cursor",
20410
- cliCommand: "cursor-agent",
20411
- protocol: "acp",
20412
- authRequired: true,
20413
- acpArgs: ["acp"],
20414
- modes: [
20415
- { id: "agent", label: "Agent" }
20416
- ],
20417
- supportsSessionClose: false,
20418
- supportsSessionLoad: true,
20419
- requiresModelAtSpawn: true,
20420
- usesNpxBridge: false,
20421
- defaultMaxTokens: 2e5
20422
20188
  }
20423
20189
  };
20424
20190
  function createSpawnConfig(cli, options) {
@@ -20449,9 +20215,6 @@ function createSpawnConfig(cli, options) {
20449
20215
  }
20450
20216
  const command = options.cliPath ?? backend.cliCommand;
20451
20217
  const args = backend.acpArgs ? [...backend.acpArgs] : [];
20452
- if (cli === "cursor" && options.model) {
20453
- args.unshift("--model", resolveCursorSpawnModel(options.model, options.effort));
20454
- }
20455
20218
  return {
20456
20219
  command,
20457
20220
  args,
@@ -20465,8 +20228,6 @@ function getYoloModeId(cli) {
20465
20228
  switch (cli) {
20466
20229
  case "claude":
20467
20230
  return "bypassPermissions";
20468
- case "cursor":
20469
- return "agent";
20470
20231
  case "codex":
20471
20232
  return "yolo";
20472
20233
  }
@@ -20665,6 +20426,152 @@ var CliDetector = class {
20665
20426
  }
20666
20427
  };
20667
20428
 
20429
+ // ../../packages/core-unified-agent/models.json
20430
+ var models_default = {
20431
+ version: 1,
20432
+ updatedAt: "2026-07-25T00:00:00Z",
20433
+ providers: {
20434
+ claude: {
20435
+ name: "Claude Code",
20436
+ defaultModel: "opus[1m]",
20437
+ models: [
20438
+ { modelId: "haiku", name: "Claude Haiku", effort: { supported: true, levels: ["low", "medium", "high", "xhigh", "max"], default: "low" } },
20439
+ { modelId: "sonnet", name: "Claude Sonnet", effort: { supported: true, levels: ["low", "medium", "high", "xhigh", "max"], default: "medium" } },
20440
+ { modelId: "opus", name: "Claude Opus", effort: { supported: true, levels: ["low", "medium", "high", "xhigh", "max"], default: "xhigh" } },
20441
+ { modelId: "opus[1m]", name: "Claude Opus [1M]", effort: { supported: true, levels: ["low", "medium", "high", "xhigh", "max"], default: "xhigh" } },
20442
+ { modelId: "claude-opus-4-6[1m]", name: "Claude Opus 4.6 [1M]", effort: { supported: true, levels: ["low", "medium", "high", "xhigh", "max"], default: "high" } },
20443
+ { modelId: "claude-opus-4-7[1m]", name: "Claude Opus 4.7 [1M]", effort: { supported: true, levels: ["low", "medium", "high", "xhigh", "max"], default: "high" } },
20444
+ { modelId: "claude-opus-4-8[1m]", name: "Claude Opus 4.8 [1M]", effort: { supported: true, levels: ["low", "medium", "high", "xhigh", "max"], default: "high" } }
20445
+ ]
20446
+ },
20447
+ codex: {
20448
+ name: "Codex",
20449
+ defaultModel: "gpt-5.6-sol",
20450
+ models: [
20451
+ { modelId: "gpt-5.6-sol", name: "GPT-5.6-Sol", effort: { supported: true, levels: ["low", "medium", "high", "xhigh", "max", "ultra"], default: "low" } },
20452
+ { modelId: "gpt-5.6-sol-fast", name: "GPT-5.6-Sol Fast", providerModelId: "gpt-5.6-sol", serviceTier: "priority", effort: { supported: true, levels: ["low", "medium", "high", "xhigh", "max", "ultra"], default: "low" } },
20453
+ { modelId: "gpt-5.6-terra", name: "GPT-5.6-Terra", effort: { supported: true, levels: ["low", "medium", "high", "xhigh", "max", "ultra"], default: "medium" } },
20454
+ { modelId: "gpt-5.6-terra-fast", name: "GPT-5.6-Terra Fast", providerModelId: "gpt-5.6-terra", serviceTier: "priority", effort: { supported: true, levels: ["low", "medium", "high", "xhigh", "max", "ultra"], default: "medium" } },
20455
+ { modelId: "gpt-5.6-luna", name: "GPT-5.6-Luna", effort: { supported: true, levels: ["low", "medium", "high", "xhigh", "max"], default: "medium" } },
20456
+ { modelId: "gpt-5.6-luna-fast", name: "GPT-5.6-Luna Fast", providerModelId: "gpt-5.6-luna", serviceTier: "priority", effort: { supported: true, levels: ["low", "medium", "high", "xhigh", "max"], default: "medium" } },
20457
+ { modelId: "gpt-5.5", name: "GPT-5.5", effort: { supported: true, levels: ["low", "medium", "high", "xhigh"], default: "high" } },
20458
+ { modelId: "gpt-5.5-fast", name: "GPT-5.5 Fast", providerModelId: "gpt-5.5", serviceTier: "priority", effort: { supported: true, levels: ["low", "medium", "high", "xhigh"], default: "high" } }
20459
+ ]
20460
+ }
20461
+ }
20462
+ };
20463
+
20464
+ // ../../packages/core-unified-agent/src/models/registry.ts
20465
+ var EffortLevelSchema = external_exports.enum([
20466
+ "low",
20467
+ "medium",
20468
+ "high",
20469
+ "xhigh",
20470
+ "max",
20471
+ "ultra"
20472
+ ]);
20473
+ var EffortSchema = external_exports.union([
20474
+ external_exports.object({
20475
+ supported: external_exports.literal(true),
20476
+ levels: external_exports.array(EffortLevelSchema).min(1),
20477
+ default: EffortLevelSchema
20478
+ }).strict(),
20479
+ external_exports.object({
20480
+ supported: external_exports.literal(false)
20481
+ }).strict()
20482
+ ]);
20483
+ var ModelEntrySchema = external_exports.object({
20484
+ /** 모델 고유 식별자 (session/set_model에 전달되는 값) */
20485
+ modelId: external_exports.string(),
20486
+ /** 사람이 읽을 수 있는 모델 이름 */
20487
+ name: external_exports.string(),
20488
+ /** 모델 설명 (선택) */
20489
+ description: external_exports.string().optional(),
20490
+ /** 카탈로그 ID와 실제 provider 모델 ID가 다른 경우의 원본 모델 ID */
20491
+ providerModelId: external_exports.string().optional(),
20492
+ /** provider가 모델과 별도로 받는 서비스 티어 */
20493
+ serviceTier: external_exports.string().optional(),
20494
+ /** 모델의 컨텍스트 윈도우 크기 (토큰) */
20495
+ contextWindow: external_exports.number().int().positive().optional(),
20496
+ /** 모델별 effort 설정 */
20497
+ effort: EffortSchema
20498
+ }).check((ctx) => {
20499
+ const effort = ctx.value.effort;
20500
+ if (effort.supported && !effort.levels.includes(effort.default)) {
20501
+ ctx.issues.push({
20502
+ code: "custom",
20503
+ input: effort.default,
20504
+ message: `effort.default "${effort.default}"\uC740(\uB294) levels \uBAA9\uB85D\uC5D0 \uC874\uC7AC\uD574\uC57C \uD569\uB2C8\uB2E4`,
20505
+ path: ["effort", "default"]
20506
+ });
20507
+ }
20508
+ if (ctx.value.serviceTier && !ctx.value.providerModelId) {
20509
+ ctx.issues.push({
20510
+ code: "custom",
20511
+ input: ctx.value.serviceTier,
20512
+ message: "serviceTier\uB97C \uC9C0\uC815\uD55C \uBAA8\uB378\uC740 providerModelId\uB3C4 \uC9C0\uC815\uD574\uC57C \uD569\uB2C8\uB2E4",
20513
+ path: ["serviceTier"]
20514
+ });
20515
+ }
20516
+ });
20517
+ var ProviderSchema = external_exports.object({
20518
+ /** 프로바이더 표시 이름 */
20519
+ name: external_exports.string(),
20520
+ /** 기본 모델 ID */
20521
+ defaultModel: external_exports.string(),
20522
+ /** 사용 가능한 모델 목록 */
20523
+ models: external_exports.array(ModelEntrySchema).min(1)
20524
+ }).check((ctx) => {
20525
+ const ids = new Set(ctx.value.models.map((m) => m.modelId));
20526
+ if (!ids.has(ctx.value.defaultModel)) {
20527
+ ctx.issues.push({
20528
+ code: "custom",
20529
+ input: ctx.value.defaultModel,
20530
+ message: `defaultModel "${ctx.value.defaultModel}"\uC740(\uB294) models \uBAA9\uB85D\uC5D0 \uC874\uC7AC\uD574\uC57C \uD569\uB2C8\uB2E4`,
20531
+ path: ["defaultModel"]
20532
+ });
20533
+ }
20534
+ });
20535
+ var ModelMappingSchema = external_exports.record(external_exports.string(), external_exports.string());
20536
+ var ModelsMapperEntrySchema = external_exports.object({
20537
+ /** modelId와 연관된 매핑 모델 */
20538
+ modelMapping: ModelMappingSchema,
20539
+ /** 추가 환경변수 (선택) */
20540
+ env: external_exports.record(external_exports.string(), external_exports.string()).optional()
20541
+ });
20542
+ external_exports.record(external_exports.string(), ModelsMapperEntrySchema);
20543
+ var ModelsRegistrySchema = external_exports.object({
20544
+ /** 스키마 버전 */
20545
+ version: external_exports.number().int().positive(),
20546
+ /** 최종 업데이트 시각 */
20547
+ updatedAt: external_exports.string(),
20548
+ /** 프로바이더별 모델 정보 */
20549
+ providers: external_exports.record(external_exports.string(), ProviderSchema)
20550
+ });
20551
+ var registry2 = Object.freeze(ModelsRegistrySchema.parse(models_default));
20552
+ function getProviderModels(cli) {
20553
+ const provider = registry2.providers[cli];
20554
+ if (!provider) {
20555
+ throw new Error(`\uC54C \uC218 \uC5C6\uB294 \uD504\uB85C\uBC14\uC774\uB354: "${cli}"`);
20556
+ }
20557
+ return structuredClone(provider);
20558
+ }
20559
+ function getEffort(cli, modelId) {
20560
+ const model = findProviderModel(cli, modelId);
20561
+ return structuredClone(model.effort);
20562
+ }
20563
+ function findProviderModel(cli, modelId) {
20564
+ const provider = registry2.providers[cli];
20565
+ if (!provider) {
20566
+ throw new Error(`\uC54C \uC218 \uC5C6\uB294 \uD504\uB85C\uBC14\uC774\uB354: "${cli}"`);
20567
+ }
20568
+ const model = provider.models.find((m) => m.modelId === modelId);
20569
+ if (!model) {
20570
+ throw new Error(`\uC54C \uC218 \uC5C6\uB294 \uBAA8\uB378: "${cli}/${modelId}"`);
20571
+ }
20572
+ return model;
20573
+ }
20574
+
20668
20575
  // ../../packages/core-unified-agent/src/client/UnifiedClaudeAgentClient.ts
20669
20576
  var UnifiedClaudeAgentClient = class extends EventEmitter {
20670
20577
  connection = null;
@@ -22396,453 +22303,6 @@ var UnifiedCodexAgentClient = class extends EventEmitter {
22396
22303
  };
22397
22304
  }
22398
22305
  };
22399
- var UnifiedCursorAgentClient = class extends EventEmitter {
22400
- connection = null;
22401
- sessionId = null;
22402
- sessionCwd = null;
22403
- currentSystemPrompt = null;
22404
- firstPromptPending = null;
22405
- currentConnectOptions = null;
22406
- detector = new CliDetector();
22407
- on(event, listener) {
22408
- return super.on(event, listener);
22409
- }
22410
- once(event, listener) {
22411
- return super.once(event, listener);
22412
- }
22413
- off(event, listener) {
22414
- return super.off(event, listener);
22415
- }
22416
- emitTyped(event, ...args) {
22417
- return super.emit(event, ...args);
22418
- }
22419
- async connect(options) {
22420
- await this.disconnect();
22421
- if (options.cli && options.cli !== "cursor") {
22422
- throw new Error("UnifiedCursorAgentClient\uB294 cursor CLI\uB9CC \uC9C0\uC6D0\uD569\uB2C8\uB2E4.");
22423
- }
22424
- const acpMcpServers = this.resolveMcpServers(options.mcpServers);
22425
- const spawnConfig = createSpawnConfig("cursor", options);
22426
- const cleanEnv = cleanEnvironment(process.env, options.env);
22427
- const env = { ...cleanEnv };
22428
- const connection = new AcpConnection({
22429
- command: spawnConfig.command,
22430
- args: spawnConfig.args,
22431
- cliType: "cursor",
22432
- cwd: options.cwd,
22433
- env,
22434
- requestTimeout: options.timeout,
22435
- initTimeout: options.timeout,
22436
- promptIdleTimeout: options.promptIdleTimeout,
22437
- clientInfo: options.clientInfo,
22438
- autoApprove: options.autoApprove,
22439
- fsAccess: options.fsAccess
22440
- });
22441
- this.connection = connection;
22442
- this.setupEventForwarding();
22443
- const recentLogs = [];
22444
- const collectLog = (message) => {
22445
- recentLogs.push(message);
22446
- if (recentLogs.length > 30) {
22447
- recentLogs.shift();
22448
- }
22449
- };
22450
- connection.on("log", collectLog);
22451
- let session;
22452
- try {
22453
- session = await connection.connect(
22454
- options.cwd,
22455
- options.sessionId,
22456
- acpMcpServers,
22457
- options.systemPrompt
22458
- );
22459
- } catch (error51) {
22460
- const connectionError = this.buildConnectionError(error51, recentLogs);
22461
- await this.cleanupFailedConnection();
22462
- throw connectionError;
22463
- } finally {
22464
- connection.off("log", collectLog);
22465
- }
22466
- return this.finalizeConnect(options, session);
22467
- }
22468
- async disconnect() {
22469
- if (!this.connection) {
22470
- this.clearSessionState();
22471
- return;
22472
- }
22473
- const conn = this.connection;
22474
- if (this.sessionId && conn.canResetSession) {
22475
- try {
22476
- await conn.endSession(this.sessionId);
22477
- } catch {
22478
- }
22479
- }
22480
- await conn.disconnect();
22481
- conn.removeAllListeners();
22482
- this.connection = null;
22483
- this.clearSessionState();
22484
- }
22485
- async endSession() {
22486
- if (!this.connection || !this.sessionId) {
22487
- throw new Error("\uC5F0\uACB0\uB418\uC5B4 \uC788\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4");
22488
- }
22489
- await this.connection.endSession(this.sessionId);
22490
- this.sessionId = null;
22491
- }
22492
- getConnectionInfo() {
22493
- return {
22494
- cli: this.connection ? "cursor" : null,
22495
- protocol: this.connection ? "acp" : null,
22496
- sessionId: this.sessionId,
22497
- state: this.connection ? this.connection.connectionState : "disconnected"
22498
- };
22499
- }
22500
- async detectClis() {
22501
- return this.detector.detectAll(true);
22502
- }
22503
- async sendMessage(content) {
22504
- if (!this.connection || !this.sessionId) {
22505
- throw new Error("\uC5F0\uACB0\uB418\uC5B4 \uC788\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4");
22506
- }
22507
- const systemPrompt = this.firstPromptPending;
22508
- if (!systemPrompt) {
22509
- return this.connection.sendPrompt(this.sessionId, content);
22510
- }
22511
- const userBlocks = typeof content === "string" ? [{ type: "text", text: content }] : content;
22512
- const response = await this.connection.sendPrompt(this.sessionId, [
22513
- { type: "text", text: systemPrompt },
22514
- ...userBlocks
22515
- ]);
22516
- this.firstPromptPending = null;
22517
- return response;
22518
- }
22519
- async cancelPrompt() {
22520
- if (!this.connection || !this.sessionId) {
22521
- throw new Error("\uC5F0\uACB0\uB418\uC5B4 \uC788\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4");
22522
- }
22523
- await this.connection.cancelSession(this.sessionId);
22524
- }
22525
- async setModel(model) {
22526
- if (!this.connection || !this.sessionId) {
22527
- throw new Error("\uC5F0\uACB0\uB418\uC5B4 \uC788\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4");
22528
- }
22529
- const reconnectOptions = this.buildReconnectOptions(model);
22530
- await this.reconnectWithRestore(reconnectOptions, "\uBAA8\uB378 \uBCC0\uACBD");
22531
- }
22532
- async setConfigOption(configId, value) {
22533
- if (!this.connection || !this.sessionId) {
22534
- throw new Error("\uC5F0\uACB0\uB418\uC5B4 \uC788\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4");
22535
- }
22536
- if (configId === "effort" || configId === "reasoning_effort") {
22537
- const reconnectOptions = this.buildEffortReconnectOptions(value);
22538
- if (!reconnectOptions) {
22539
- return;
22540
- }
22541
- await this.reconnectWithRestore(reconnectOptions, "effort \uBCC0\uACBD");
22542
- return;
22543
- }
22544
- if (configId === "model") {
22545
- return this.setModel(value);
22546
- }
22547
- await this.connection.setConfigOption(this.sessionId, configId, value);
22548
- }
22549
- async setMode(mode) {
22550
- if (!this.connection || !this.sessionId) {
22551
- throw new Error("\uC5F0\uACB0\uB418\uC5B4 \uC788\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4");
22552
- }
22553
- await this.connection.setMode(this.sessionId, mode);
22554
- }
22555
- async setYoloMode(enabled) {
22556
- return this.setMode(enabled ? getYoloModeId("cursor") : "default");
22557
- }
22558
- getAvailableModes() {
22559
- return getBackendConfig("cursor").modes ?? [];
22560
- }
22561
- getAvailableModels() {
22562
- return getProviderModels("cursor");
22563
- }
22564
- getCurrentSystemPrompt() {
22565
- return this.currentSystemPrompt;
22566
- }
22567
- async loadSession(sessionId, mcpServers) {
22568
- if (!this.connection) {
22569
- throw new Error("\uC5F0\uACB0\uB418\uC5B4 \uC788\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4");
22570
- }
22571
- await this.connection.loadSession({
22572
- sessionId,
22573
- cwd: this.sessionCwd ?? process.cwd(),
22574
- mcpServers: this.resolveMcpServers(mcpServers)
22575
- });
22576
- this.sessionId = sessionId;
22577
- this.currentSystemPrompt = null;
22578
- this.firstPromptPending = null;
22579
- }
22580
- async resetSession(cwd) {
22581
- if (!this.connection || !this.sessionId) {
22582
- throw new Error("\uC5F0\uACB0\uB418\uC5B4 \uC788\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4");
22583
- }
22584
- const targetCwd = cwd ?? this.sessionCwd ?? process.cwd();
22585
- if (!this.connection.canResetSession) {
22586
- throw new Error("[cursor] \uC138\uC158 \uB9AC\uC14B\uC744 \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. disconnect() \uD6C4 \uC7AC\uC5F0\uACB0\uD558\uC138\uC694.");
22587
- }
22588
- await this.connection.endSession(this.sessionId);
22589
- this.sessionId = null;
22590
- const session = this.currentSystemPrompt ? await this.connection.reconnectSession(
22591
- targetCwd,
22592
- void 0,
22593
- void 0,
22594
- this.currentSystemPrompt
22595
- ) : await this.connection.reconnectSession(targetCwd);
22596
- this.sessionId = session.sessionId;
22597
- this.sessionCwd = targetCwd;
22598
- this.firstPromptPending = this.currentSystemPrompt;
22599
- return {
22600
- cli: "cursor",
22601
- protocol: "acp",
22602
- session
22603
- };
22604
- }
22605
- resolveMcpServers(servers) {
22606
- return servers?.length ? mcpServerConfigsToAcp(servers) : [];
22607
- }
22608
- async finalizeConnect(options, session) {
22609
- if (options.yoloMode && session.sessionId) {
22610
- try {
22611
- await this.connection.setMode(session.sessionId, getYoloModeId("cursor"));
22612
- } catch {
22613
- }
22614
- }
22615
- this.sessionId = session.sessionId;
22616
- this.sessionCwd = options.cwd;
22617
- this.currentSystemPrompt = options.systemPrompt ?? null;
22618
- this.firstPromptPending = options.sessionId ? null : this.currentSystemPrompt;
22619
- this.currentConnectOptions = this.cloneConnectOptions(options);
22620
- return {
22621
- cli: "cursor",
22622
- protocol: "acp",
22623
- session
22624
- };
22625
- }
22626
- clearSessionState() {
22627
- this.sessionId = null;
22628
- this.sessionCwd = null;
22629
- this.currentSystemPrompt = null;
22630
- this.firstPromptPending = null;
22631
- this.currentConnectOptions = null;
22632
- }
22633
- buildReconnectOptions(model) {
22634
- if (!this.currentConnectOptions) {
22635
- throw new Error("[cursor] \uBAA8\uB378 \uC804\uD658\uC744 \uC704\uD55C \uAE30\uC874 \uC5F0\uACB0 \uC635\uC158\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. connect()\uB85C \uB2E4\uC2DC \uC5F0\uACB0\uD558\uC138\uC694.");
22636
- }
22637
- const options = this.cloneConnectOptions(this.currentConnectOptions);
22638
- const effort = this.resolveEffortForModel(model, options.effort);
22639
- delete options.sessionId;
22640
- if (effort) {
22641
- options.effort = effort;
22642
- } else {
22643
- delete options.effort;
22644
- }
22645
- return {
22646
- ...options,
22647
- cli: "cursor",
22648
- model
22649
- };
22650
- }
22651
- buildEffortReconnectOptions(effort) {
22652
- if (!this.currentConnectOptions) {
22653
- throw new Error("[cursor] effort \uC804\uD658\uC744 \uC704\uD55C \uAE30\uC874 \uC5F0\uACB0 \uC635\uC158\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. connect()\uB85C \uB2E4\uC2DC \uC5F0\uACB0\uD558\uC138\uC694.");
22654
- }
22655
- if (!this.currentConnectOptions.model) {
22656
- throw new Error("[cursor] effort \uC804\uD658\uC744 \uC704\uD55C \uD604\uC7AC \uBAA8\uB378 \uC815\uBCF4\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. \uBAA8\uB378\uC744 \uC9C0\uC815\uD574 \uB2E4\uC2DC \uC5F0\uACB0\uD558\uC138\uC694.");
22657
- }
22658
- if (this.currentConnectOptions.effort === effort) {
22659
- return null;
22660
- }
22661
- const effortInfo = getCursorSpawnEffortInfo(this.currentConnectOptions.model);
22662
- if (!effortInfo.supported) {
22663
- return null;
22664
- }
22665
- if (!effortInfo.levels.includes(effort)) {
22666
- throw new Error(
22667
- `[cursor] ${this.currentConnectOptions.model} \uBAA8\uB378\uC740 effort "${effort}"\uC744(\uB97C) \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uC0AC\uC6A9 \uAC00\uB2A5: ${effortInfo.levels.join(", ")}`
22668
- );
22669
- }
22670
- const options = this.cloneConnectOptions(this.currentConnectOptions);
22671
- delete options.sessionId;
22672
- return {
22673
- ...options,
22674
- cli: "cursor",
22675
- effort
22676
- };
22677
- }
22678
- resolveEffortForModel(model, effort) {
22679
- const effortInfo = getCursorSpawnEffortInfo(model);
22680
- if (!effortInfo.supported) {
22681
- return void 0;
22682
- }
22683
- if (effort && effortInfo.levels.includes(effort)) {
22684
- return effort;
22685
- }
22686
- return effortInfo.default ?? void 0;
22687
- }
22688
- async reconnectWithRestore(nextOptions, label) {
22689
- if (!this.currentConnectOptions) {
22690
- throw new Error(`[cursor] ${label}\uC744 \uC704\uD55C \uAE30\uC874 \uC5F0\uACB0 \uC635\uC158\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. connect()\uB85C \uB2E4\uC2DC \uC5F0\uACB0\uD558\uC138\uC694.`);
22691
- }
22692
- const previousOptions = this.cloneConnectOptions(this.currentConnectOptions);
22693
- try {
22694
- await this.connect(nextOptions);
22695
- } catch (error51) {
22696
- try {
22697
- await this.connect(previousOptions);
22698
- } catch (restoreError) {
22699
- throw new Error(
22700
- `[cursor] ${label} \uC2E4\uD328 \uD6C4 \uC774\uC804 \uC5F0\uACB0 \uBCF5\uAD6C\uB3C4 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4. ${label} \uC624\uB958: ${this.formatErrorMessage(error51)} / \uBCF5\uAD6C \uC624\uB958: ${this.formatErrorMessage(restoreError)}`
22701
- );
22702
- }
22703
- throw new Error(
22704
- `[cursor] ${label} \uC2E4\uD328\uB85C \uC774\uC804 \uC5F0\uACB0\uC744 \uBCF5\uAD6C\uD588\uC2B5\uB2C8\uB2E4. ${label} \uC624\uB958: ${this.formatErrorMessage(error51)}`
22705
- );
22706
- }
22707
- }
22708
- formatErrorMessage(error51) {
22709
- if (error51 instanceof Error) {
22710
- return error51.message;
22711
- }
22712
- return String(error51);
22713
- }
22714
- cloneConnectOptions(options) {
22715
- return {
22716
- ...options,
22717
- cli: "cursor",
22718
- env: options.env ? { ...options.env } : void 0,
22719
- clientInfo: options.clientInfo ? { ...options.clientInfo } : void 0,
22720
- mcpServers: options.mcpServers?.map((server) => ({
22721
- ...server,
22722
- headers: server.headers?.map((header) => ({ ...header }))
22723
- }))
22724
- };
22725
- }
22726
- setupEventForwarding() {
22727
- if (!this.connection) return;
22728
- this.connection.on("stateChange", (state) => {
22729
- this.emitTyped("stateChange", state);
22730
- });
22731
- this.connection.on("userMessageChunk", (text2, sessionId) => {
22732
- this.emitTyped("userMessageChunk", text2, sessionId);
22733
- });
22734
- this.connection.on("messageChunk", (text2, sessionId) => {
22735
- this.emitTyped("messageChunk", text2, sessionId);
22736
- });
22737
- this.connection.on("thoughtChunk", (text2, sessionId) => {
22738
- this.emitTyped("thoughtChunk", text2, sessionId);
22739
- });
22740
- this.connection.on("toolCall", (title, status, sessionId, data) => {
22741
- this.emitTyped("toolCall", title, status, sessionId, data);
22742
- });
22743
- this.connection.on("toolCallUpdate", (title, status, sessionId, data) => {
22744
- this.emitTyped("toolCallUpdate", title, status, sessionId, data);
22745
- });
22746
- this.connection.on("plan", (plan, sessionId) => {
22747
- this.emitTyped("plan", plan, sessionId);
22748
- });
22749
- this.connection.on("availableCommandsUpdate", (commands, sessionId) => {
22750
- this.emitTyped("availableCommandsUpdate", commands, sessionId);
22751
- });
22752
- this.connection.on("sessionUpdate", (update) => {
22753
- this.emitTyped("sessionUpdate", update);
22754
- });
22755
- this.connection.on("permissionRequest", (params, resolve3) => {
22756
- this.emitTyped("permissionRequest", params, resolve3);
22757
- });
22758
- this.connection.on("fileRead", (params, resolve3) => {
22759
- this.emitTyped("fileRead", params, resolve3);
22760
- });
22761
- this.connection.on("fileWrite", (params, resolve3) => {
22762
- this.emitTyped("fileWrite", params, resolve3);
22763
- });
22764
- this.connection.on("promptComplete", (sessionId) => {
22765
- this.emitTyped("promptComplete", sessionId);
22766
- });
22767
- this.connection.on("error", (err) => {
22768
- this.emitTyped("error", err);
22769
- });
22770
- this.connection.on("exit", (code, signal) => {
22771
- this.emitTyped("exit", code, signal);
22772
- });
22773
- this.connection.on("log", (msg) => {
22774
- this.emitTyped("log", msg);
22775
- });
22776
- this.connection.on("logEntry", (entry) => {
22777
- this.emitTyped("logEntry", entry);
22778
- });
22779
- }
22780
- async cleanupFailedConnection() {
22781
- if (!this.connection) {
22782
- return;
22783
- }
22784
- try {
22785
- await this.connection.disconnect();
22786
- } catch {
22787
- }
22788
- this.connection.removeAllListeners();
22789
- this.connection = null;
22790
- this.clearSessionState();
22791
- }
22792
- buildConnectionError(error51, recentLogs) {
22793
- if (getBackendConfig("cursor").authRequired && this.isAuthenticationError(error51, recentLogs)) {
22794
- return new Error(
22795
- "[cursor] \uC778\uC99D\uC774 \uD544\uC694\uD558\uAC70\uB098 \uC778\uC99D\uC774 \uB9CC\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \uBA3C\uC800 \uD574\uB2F9 CLI\uC5D0\uC11C \uB85C\uADF8\uC778/\uC778\uC99D\uC744 \uC644\uB8CC\uD55C \uB4A4 \uB2E4\uC2DC \uC2DC\uB3C4\uD574\uC8FC\uC138\uC694."
22796
- );
22797
- }
22798
- if (error51 instanceof Error) {
22799
- return error51;
22800
- }
22801
- if (typeof error51 === "object" && error51 !== null) {
22802
- const obj = error51;
22803
- if (typeof obj.message === "string") {
22804
- const code = typeof obj.code === "number" ? ` (code: ${obj.code})` : "";
22805
- const data = obj.data ? ` \u2014 ${JSON.stringify(obj.data)}` : "";
22806
- return new Error(`${obj.message}${code}${data}`);
22807
- }
22808
- return new Error(JSON.stringify(error51));
22809
- }
22810
- return new Error(String(error51));
22811
- }
22812
- isAuthenticationError(error51, recentLogs) {
22813
- const authPatterns = [
22814
- /auth_required/i,
22815
- /authentication required/i,
22816
- /not authenticated/i,
22817
- /please login/i,
22818
- /please log in/i,
22819
- /sign in/i,
22820
- /reauth/i,
22821
- /unauthorized/i,
22822
- /invalid api key/i
22823
- ];
22824
- if (this.matchAnyPattern(this.extractErrorText(error51), authPatterns)) {
22825
- return true;
22826
- }
22827
- return recentLogs.some((log) => this.matchAnyPattern(log, authPatterns));
22828
- }
22829
- extractErrorText(error51) {
22830
- if (error51 instanceof Error) {
22831
- const code = error51.code;
22832
- if (code === -32e3) {
22833
- return `auth_required ${error51.message}`;
22834
- }
22835
- return error51.message;
22836
- }
22837
- if (typeof error51 === "string") {
22838
- return error51;
22839
- }
22840
- return String(error51);
22841
- }
22842
- matchAnyPattern(text2, patterns) {
22843
- return patterns.some((pattern) => pattern.test(text2));
22844
- }
22845
- };
22846
22306
 
22847
22307
  // ../../packages/core-unified-agent/src/client/UnifiedAgent.ts
22848
22308
  var UnifiedAgent = {
@@ -22852,8 +22312,6 @@ var UnifiedAgent = {
22852
22312
  return new UnifiedClaudeAgentClient();
22853
22313
  case "codex":
22854
22314
  return new UnifiedCodexAgentClient();
22855
- case "cursor":
22856
- return new UnifiedCursorAgentClient();
22857
22315
  }
22858
22316
  },
22859
22317
  async build(options = {}) {
@@ -22866,7 +22324,7 @@ var UnifiedAgent = {
22866
22324
  const preferred = await new CliDetector().getPreferred();
22867
22325
  if (!preferred) {
22868
22326
  throw new Error(
22869
- "\uC0AC\uC6A9 \uAC00\uB2A5\uD55C CLI\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. claude, codex, cursor \uC911 \uD558\uB098\uB97C \uC124\uCE58\uD574\uC8FC\uC138\uC694."
22327
+ "\uC0AC\uC6A9 \uAC00\uB2A5\uD55C CLI\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. claude, codex \uC911 \uD558\uB098\uB97C \uC124\uCE58\uD574\uC8FC\uC138\uC694."
22870
22328
  );
22871
22329
  }
22872
22330
  return this.createClient(preferred.cli);
@@ -32753,7 +32211,7 @@ Both surfaces require the user's request. When the one the work needs is gated,
32753
32211
  ### Model Loadout
32754
32212
  Which model and effort a run uses is routing, so it stays with the host agent. Call the ${"`"}gateway_models${"`"} MCP tool before every run on either surface, then pick the identity this work needs — a measured role fit first, and the model's own allowance wherever measurement is silent. Never let the session's own model be the default answer; it is the most expensive way to obtain what any identity produces equally well, and an unpinned run spends that allowance too.
32755
32213
 
32756
- A staged workflow spreads its stages across identities and balances them against provider allowances instead of inheriting one model for every stage. The ${"`"}workflow${"`"} skill owns that procedure; what each roster field means stays in the tool's own metadata.
32214
+ A staged workflow spreads its stages across identities and balances them against provider allowances instead of inheriting one model for every stage — reading each allowance's own reported verdict rather than comparing raw percentages across windows that reset on different clocks. The ${"`"}workflow${"`"} skill owns that procedure; what each roster field means stays in the tool's own metadata.
32757
32215
 
32758
32216
  ### Skill Routing
32759
32217
  Load the ${"`"}workflow${"`"} skill before executing a stage skeleton or assigning models across runs. The skeleton itself belongs to the skill matching the work: ${"`"}architecture-review${"`"} to decide, ${"`"}codebase-research${"`"} to establish facts, ${"`"}implementation-run${"`"} to change files, ${"`"}quality-review${"`"} to judge what exists.`
@@ -32763,15 +32221,15 @@ var DEEP_DIVE2 = {
32763
32221
  name: "Deep Dive",
32764
32222
  prompt: String.raw`## Deep Dive Standing Order
32765
32223
 
32766
- A cross-cutting verification procedure that can be triggered **at any point** whenever results contain speculation, ambiguity, or insufficient evidence. It is not a phase of its own — it is a procedure that interrupts the current step, runs to completion, and then resumes that step. Throughout, never flatten uncertainty into confident-sounding summaries — preserve and surface ambiguity honestly.
32224
+ A cross-cutting verification procedure. It is not a phase of its own — it interrupts the current step, runs to completion, and then resumes that step. Throughout, never flatten uncertainty into confident-sounding summaries — preserve and surface ambiguity honestly.
32767
32225
 
32768
32226
  ### Trigger
32769
- Speculation-trigger handling is routed by the Result Integrity trigger mapping table.
32227
+ Entry is routed by the Result Integrity trigger mapping table. This order owns unverified claims only; ambiguity or thin evidence is a confidence gap and re-enters Context Confidence instead.
32770
32228
 
32771
32229
  ### Procedure
32772
- 1. **Surface scan** Look for obvious speculation markers (e.g., "likely", "probably", "I think", "may be", "not sure but…").
32773
- 2. **Speculation audit** If the result is lengthy, complex, or touches unfamiliar territory, skip your own scan and give the audit its own run:
32774
- - Give that run explicit instructions: *"Review the following analysis for speculative, assumed, or unverified claims. Flag each with evidence of why it is speculative and what verification is needed."*
32230
+ 1. **Choose the scan.** Scan it yourself when the result is short and confined to ground already read this session; give it its own run otherwise. These are alternatives, not steps.
32231
+ 2. **Run that scan.** Yours sweeps for speculation markers (e.g., "likely", "probably", "I think", "may be", "not sure but…"). Its own run gets explicit instructions:
32232
+ - *"Review the following analysis for speculative, assumed, or unverified claims. Flag each with evidence of why it is speculative and what verification is needed."*
32775
32233
  3. **Follow-up verification** — For each identified speculative element, start a verification run that seeks independent confirmation or refutation.
32776
32234
  4. **Repeat** until all speculative elements are either **confirmed with evidence** or explicitly flagged as **unresolvable unknowns**.
32777
32235
 
@@ -37524,7 +36982,9 @@ function projectClaudeContextInputTokens(inputTokens, advertisedContextWindow, u
37524
36982
  );
37525
36983
  }
37526
36984
  async function* projectAnthropicResponseUsage(chunks, options) {
37527
- if (!canProjectClaudeContextWindow(options.contextWindow)) {
36985
+ const contextWindow = canProjectClaudeContextWindow(options.contextWindow) ? options.contextWindow : void 0;
36986
+ const responseModel = typeof options.responseModel === "string" && options.responseModel.length > 0 ? options.responseModel : void 0;
36987
+ if (contextWindow === void 0 && responseModel === void 0) {
37528
36988
  yield* chunks;
37529
36989
  return;
37530
36990
  }
@@ -37532,7 +36992,8 @@ async function* projectAnthropicResponseUsage(chunks, options) {
37532
36992
  if (mediaType === "text/event-stream") {
37533
36993
  yield* projectSseUsage(
37534
36994
  chunks,
37535
- options.contextWindow,
36995
+ contextWindow,
36996
+ responseModel,
37536
36997
  positiveLimit(options.maxSseFrameBytes, DEFAULT_MAX_SSE_FRAME_BYTES)
37537
36998
  );
37538
36999
  return;
@@ -37540,14 +37001,15 @@ async function* projectAnthropicResponseUsage(chunks, options) {
37540
37001
  if (mediaType === "application/json" || mediaType?.endsWith("+json")) {
37541
37002
  yield* projectJsonUsage(
37542
37003
  chunks,
37543
- options.contextWindow,
37004
+ contextWindow,
37005
+ responseModel,
37544
37006
  positiveLimit(options.maxJsonBytes, DEFAULT_MAX_JSON_BYTES)
37545
37007
  );
37546
37008
  return;
37547
37009
  }
37548
37010
  yield* chunks;
37549
37011
  }
37550
- async function* projectJsonUsage(chunks, contextWindow, maxBytes) {
37012
+ async function* projectJsonUsage(chunks, contextWindow, responseModel, maxBytes) {
37551
37013
  const buffered = [];
37552
37014
  let bufferedBytes = 0;
37553
37015
  let passthrough = false;
@@ -37567,10 +37029,10 @@ async function* projectJsonUsage(chunks, contextWindow, maxBytes) {
37567
37029
  }
37568
37030
  if (passthrough || bufferedBytes === 0) return;
37569
37031
  const original = concatChunks(buffered, bufferedBytes);
37570
- const projected = projectJsonBytes(original, contextWindow);
37032
+ const projected = projectJsonBytes(original, contextWindow, responseModel);
37571
37033
  yield projected ?? original;
37572
37034
  }
37573
- async function* projectSseUsage(chunks, contextWindow, maxFrameBytes) {
37035
+ async function* projectSseUsage(chunks, contextWindow, responseModel, maxFrameBytes) {
37574
37036
  let buffered = new Uint8Array(0);
37575
37037
  let oversizedFrame = false;
37576
37038
  for await (const chunk of chunks) {
@@ -37597,7 +37059,7 @@ async function* projectSseUsage(chunks, contextWindow, maxFrameBytes) {
37597
37059
  const frame = buffered.slice(0, separator.index);
37598
37060
  const separatorBytes = buffered.slice(separator.index, separator.index + separator.length);
37599
37061
  const originalFrame = buffered.slice(0, separator.index + separator.length);
37600
- const projectedFrame = frame.byteLength > maxFrameBytes ? void 0 : projectSseFrame(frame, contextWindow);
37062
+ const projectedFrame = frame.byteLength > maxFrameBytes ? void 0 : projectSseFrame(frame, contextWindow, responseModel);
37601
37063
  yield projectedFrame === void 0 ? originalFrame : concatBytes(projectedFrame, separatorBytes);
37602
37064
  buffered = buffered.slice(separator.index + separator.length);
37603
37065
  }
@@ -37611,17 +37073,17 @@ async function* projectSseUsage(chunks, contextWindow, maxFrameBytes) {
37611
37073
  }
37612
37074
  if (buffered.byteLength > 0) yield buffered;
37613
37075
  }
37614
- function projectJsonBytes(bytes, contextWindow) {
37076
+ function projectJsonBytes(bytes, contextWindow, responseModel) {
37615
37077
  let parsed;
37616
37078
  try {
37617
37079
  parsed = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
37618
37080
  } catch {
37619
37081
  return void 0;
37620
37082
  }
37621
- const projected = projectAnthropicUsageEnvelope(parsed, contextWindow);
37083
+ const projected = projectAnthropicUsageEnvelope(parsed, contextWindow, responseModel);
37622
37084
  return projected.changed ? new TextEncoder().encode(JSON.stringify(projected.value)) : void 0;
37623
37085
  }
37624
- function projectSseFrame(frame, contextWindow) {
37086
+ function projectSseFrame(frame, contextWindow, responseModel) {
37625
37087
  let text2;
37626
37088
  try {
37627
37089
  text2 = new TextDecoder("utf-8", { fatal: true }).decode(frame);
@@ -37647,7 +37109,7 @@ function projectSseFrame(frame, contextWindow) {
37647
37109
  } catch {
37648
37110
  return void 0;
37649
37111
  }
37650
- const projected = projectAnthropicUsageEnvelope(parsed, contextWindow);
37112
+ const projected = projectAnthropicUsageEnvelope(parsed, contextWindow, responseModel);
37651
37113
  if (!projected.changed) return void 0;
37652
37114
  const firstDataIndex = dataIndexes[0];
37653
37115
  const dataIndexSet = new Set(dataIndexes);
@@ -37661,20 +37123,33 @@ function projectSseFrame(frame, contextWindow) {
37661
37123
  }
37662
37124
  return new TextEncoder().encode(outputLines.join(newline2));
37663
37125
  }
37664
- function projectAnthropicUsageEnvelope(value, contextWindow) {
37126
+ function projectAnthropicUsageEnvelope(value, contextWindow, responseModel) {
37665
37127
  if (!isRecord4(value)) return { changed: false, value };
37666
37128
  let projectedValue = value;
37667
37129
  let changed = false;
37668
- const topLevelUsage = projectUsageProperty(projectedValue, "usage", contextWindow);
37669
- if (topLevelUsage.changed) {
37670
- projectedValue = topLevelUsage.value;
37671
- changed = true;
37130
+ if (contextWindow !== void 0) {
37131
+ const topLevelUsage = projectUsageProperty(projectedValue, "usage", contextWindow);
37132
+ if (topLevelUsage.changed) {
37133
+ projectedValue = topLevelUsage.value;
37134
+ changed = true;
37135
+ }
37136
+ const message = projectedValue.message;
37137
+ if (isRecord4(message)) {
37138
+ const messageUsage = projectUsageProperty(message, "usage", contextWindow);
37139
+ if (messageUsage.changed) {
37140
+ projectedValue = { ...projectedValue, message: messageUsage.value };
37141
+ changed = true;
37142
+ }
37143
+ }
37672
37144
  }
37673
- const message = projectedValue.message;
37674
- if (isRecord4(message)) {
37675
- const messageUsage = projectUsageProperty(message, "usage", contextWindow);
37676
- if (messageUsage.changed) {
37677
- projectedValue = { ...projectedValue, message: messageUsage.value };
37145
+ if (responseModel !== void 0) {
37146
+ if (typeof projectedValue.model === "string" && projectedValue.model !== responseModel) {
37147
+ projectedValue = { ...projectedValue, model: responseModel };
37148
+ changed = true;
37149
+ }
37150
+ const message = projectedValue.message;
37151
+ if (isRecord4(message) && typeof message.model === "string" && message.model !== responseModel) {
37152
+ projectedValue = { ...projectedValue, message: { ...message, model: responseModel } };
37678
37153
  changed = true;
37679
37154
  }
37680
37155
  }
@@ -37740,7 +37215,7 @@ function isRecord4(value) {
37740
37215
  }
37741
37216
  var models_default2 = {
37742
37217
  version: 1,
37743
- updatedAt: "2026-08-01T00:00:00Z",
37218
+ updatedAt: "2026-08-03T00:00:00Z",
37744
37219
  providers: {
37745
37220
  codex: {
37746
37221
  name: "Codex",
@@ -38025,13 +37500,167 @@ var models_default2 = {
38025
37500
  ]
38026
37501
  }
38027
37502
  ]
37503
+ },
37504
+ opencode: {
37505
+ name: "OpenCode",
37506
+ defaultModel: "minimax-m3",
37507
+ source: "live GET https://opencode.ai/zen/go/v1/models + per-model wire probes on /messages, /responses, /chat/completions (2026-08-03); contextWindow cross-checked against models.dev api.json opencode-go entries and oh-my-pi packages/catalog/src/models.json (2026-08-03, both agree). Excluded: mimo-v2-pro/mimo-v2-omni (upstream server_error), hy3-preview (listed but Router.ModelNotFound)",
37508
+ models: [
37509
+ {
37510
+ modelId: "minimax-m3",
37511
+ name: "MiniMax-M3",
37512
+ contextWindow: 1e6
37513
+ },
37514
+ {
37515
+ modelId: "minimax-m2.7",
37516
+ name: "MiniMax-M2.7",
37517
+ contextWindow: 204800
37518
+ },
37519
+ {
37520
+ modelId: "minimax-m2.5",
37521
+ name: "MiniMax-M2.5",
37522
+ contextWindow: 204800
37523
+ },
37524
+ {
37525
+ modelId: "qwen3.8-max",
37526
+ name: "Qwen3.8-Max",
37527
+ contextWindow: 1e6
37528
+ },
37529
+ {
37530
+ modelId: "qwen3.7-max",
37531
+ name: "Qwen3.7-Max",
37532
+ contextWindow: 1e6
37533
+ },
37534
+ {
37535
+ modelId: "qwen3.7-plus",
37536
+ name: "Qwen3.7-Plus",
37537
+ contextWindow: 1e6
37538
+ },
37539
+ {
37540
+ modelId: "qwen3.6-plus",
37541
+ name: "Qwen3.6-Plus",
37542
+ contextWindow: 1e6
37543
+ },
37544
+ {
37545
+ modelId: "qwen3.5-plus",
37546
+ name: "Qwen3.5-Plus",
37547
+ contextWindow: 262144
37548
+ },
37549
+ {
37550
+ modelId: "gpt-5.6-luna",
37551
+ name: "GPT-5.6-Luna",
37552
+ wire: "responses",
37553
+ contextWindow: 105e4,
37554
+ effort: {
37555
+ supported: true,
37556
+ levels: [
37557
+ "low",
37558
+ "medium",
37559
+ "high",
37560
+ "xhigh",
37561
+ "max"
37562
+ ]
37563
+ }
37564
+ },
37565
+ {
37566
+ modelId: "grok-4.5",
37567
+ name: "Grok-4.5",
37568
+ wire: "responses",
37569
+ contextWindow: 5e5,
37570
+ effort: {
37571
+ supported: true,
37572
+ levels: [
37573
+ "low",
37574
+ "medium",
37575
+ "high"
37576
+ ]
37577
+ }
37578
+ },
37579
+ {
37580
+ modelId: "deepseek-v4-flash",
37581
+ name: "DeepSeek-V4-Flash",
37582
+ wire: "chat-completions",
37583
+ contextWindow: 1e6
37584
+ },
37585
+ {
37586
+ modelId: "deepseek-v4-pro",
37587
+ name: "DeepSeek-V4-Pro",
37588
+ wire: "chat-completions",
37589
+ contextWindow: 1e6
37590
+ },
37591
+ {
37592
+ modelId: "glm-5.2",
37593
+ name: "GLM-5.2",
37594
+ wire: "chat-completions",
37595
+ contextWindow: 1e6
37596
+ },
37597
+ {
37598
+ modelId: "glm-5.1",
37599
+ name: "GLM-5.1",
37600
+ wire: "chat-completions",
37601
+ contextWindow: 202752
37602
+ },
37603
+ {
37604
+ modelId: "glm-5",
37605
+ name: "GLM-5",
37606
+ wire: "chat-completions",
37607
+ contextWindow: 202752
37608
+ },
37609
+ {
37610
+ modelId: "kimi-k3",
37611
+ name: "Kimi-K3",
37612
+ wire: "chat-completions",
37613
+ contextWindow: 1048576
37614
+ },
37615
+ {
37616
+ modelId: "kimi-k2.7-code",
37617
+ name: "Kimi-K2.7-Code",
37618
+ wire: "chat-completions",
37619
+ contextWindow: 262144
37620
+ },
37621
+ {
37622
+ modelId: "kimi-k2.6",
37623
+ name: "Kimi-K2.6",
37624
+ wire: "chat-completions",
37625
+ contextWindow: 262144
37626
+ },
37627
+ {
37628
+ modelId: "kimi-k2.5",
37629
+ name: "Kimi-K2.5",
37630
+ wire: "chat-completions",
37631
+ contextWindow: 262144
37632
+ },
37633
+ {
37634
+ modelId: "mimo-v2.5-pro",
37635
+ name: "MiMo-V2.5-Pro",
37636
+ wire: "chat-completions",
37637
+ contextWindow: 1048576
37638
+ },
37639
+ {
37640
+ modelId: "mimo-v2.5",
37641
+ name: "MiMo-V2.5",
37642
+ wire: "chat-completions",
37643
+ contextWindow: 1e6
37644
+ },
37645
+ {
37646
+ modelId: "hy3",
37647
+ name: "HY3",
37648
+ wire: "chat-completions",
37649
+ contextWindow: 256e3
37650
+ }
37651
+ ]
38028
37652
  }
38029
37653
  }
38030
37654
  };
38031
37655
  var KIMI_AUTH_PROVIDER_ID = "Claude Code with Moonshot Kimi";
38032
37656
  var KIMI_CODE_API_BASE_URL = "https://api.kimi.com/coding";
38033
37657
  var KIMI_CODE_MODEL = "k3";
38034
- var GATEWAY_PROVIDERS = ["codex", "cursor", "kimi"];
37658
+ var GATEWAY_PROVIDERS = ["codex", "cursor", "kimi", "opencode"];
37659
+ var GATEWAY_MODEL_WIRES = ["anthropic", "responses", "chat-completions"];
37660
+ function isAnthropicPassthroughModel(model) {
37661
+ if (model.provider === "kimi") return true;
37662
+ return model.provider === "opencode" && (model.wire ?? "anthropic") === "anthropic";
37663
+ }
38035
37664
  var GATEWAY_REASONING_EFFORTS = [
38036
37665
  "low",
38037
37666
  "medium",
@@ -38064,6 +37693,7 @@ var GatewayModelEntrySchema = external_exports.object({
38064
37693
  serviceTier: external_exports.literal("priority").optional(),
38065
37694
  cursorMaxMode: external_exports.literal(true).optional(),
38066
37695
  quotaScope: external_exports.enum(GATEWAY_QUOTA_SCOPES).optional(),
37696
+ wire: external_exports.enum(GATEWAY_MODEL_WIRES).optional(),
38067
37697
  aliases: external_exports.array(external_exports.string().min(1)).optional(),
38068
37698
  contextWindow: external_exports.number().int().positive().optional(),
38069
37699
  effort: GatewayModelEffortSchema.optional()
@@ -38080,7 +37710,8 @@ var GatewayModelsRegistrySchema = external_exports.object({
38080
37710
  providers: external_exports.object({
38081
37711
  codex: GatewayProviderSchema,
38082
37712
  cursor: GatewayProviderSchema,
38083
- kimi: GatewayProviderSchema
37713
+ kimi: GatewayProviderSchema,
37714
+ opencode: GatewayProviderSchema
38084
37715
  }).strict()
38085
37716
  }).strict();
38086
37717
  var UNSUPPORTED_GATEWAY_MODEL_EFFORT = Object.freeze({ supported: false });
@@ -38105,6 +37736,7 @@ var GATEWAY_MODELS = Object.freeze(
38105
37736
  providerModels("codex");
38106
37737
  var CURSOR_SUBSCRIPTION_MODELS = providerModels("cursor");
38107
37738
  providerModels("kimi");
37739
+ providerModels("opencode");
38108
37740
  var GATEWAY_MODEL_ALIAS_PREFIX = "claude-gateway--";
38109
37741
  var CLAUDE_ONE_MILLION_MARKER = "[1m]";
38110
37742
  var CLAUDE_ONE_MILLION_DISPLAY_SUFFIX = " (1M Context)";
@@ -38113,7 +37745,7 @@ function toGatewayModelAlias(modelId) {
38113
37745
  }
38114
37746
  function toClaudeGatewayModelId(model) {
38115
37747
  const alias = toGatewayModelAlias(model.id);
38116
- if (canProjectClaudeContextWindow(model.contextWindow) && (model.provider !== "kimi" || isClaudeOneMillionContextWindow(model.contextWindow))) {
37748
+ if (canProjectClaudeContextWindow(model.contextWindow) && (!isAnthropicPassthroughModel(model) || isClaudeOneMillionContextWindow(model.contextWindow))) {
38117
37749
  return `${alias}${CLAUDE_ONE_MILLION_MARKER}`;
38118
37750
  }
38119
37751
  return alias;
@@ -38139,7 +37771,6 @@ function gatewayModelIdentity(model) {
38139
37771
  function buildGatewayModelConstraints(model) {
38140
37772
  const ladder = model.effort.supported ? model.effort.levels.filter((level) => ANTHROPIC_EFFORT_RUNGS.has(level)) : [];
38141
37773
  return {
38142
- identity: gatewayModelIdentity(model),
38143
37774
  provider: model.provider,
38144
37775
  ...model.contextWindow !== void 0 ? { contextWindow: model.contextWindow } : {},
38145
37776
  effortLadder: Object.freeze([...ladder]),
@@ -38213,6 +37844,7 @@ function toGatewayModel(provider, providerName, entry) {
38213
37844
  ...entry.serviceTier ? { serviceTier: entry.serviceTier } : {},
38214
37845
  ...entry.cursorMaxMode ? { cursorMaxMode: entry.cursorMaxMode } : {},
38215
37846
  ...entry.quotaScope ? { quotaScope: entry.quotaScope } : {},
37847
+ ...entry.wire ? { wire: entry.wire } : {},
38216
37848
  ...entry.description ? { description: entry.description } : {},
38217
37849
  ...entry.contextWindow ? { contextWindow: entry.contextWindow } : {},
38218
37850
  effort: freezeGatewayModelEffort(entry.effort),
@@ -38247,6 +37879,9 @@ function validateRegistry(value) {
38247
37879
  if (model.quotaScope && provider !== "cursor") {
38248
37880
  throw new Error(`Gateway quota scope is only supported by Cursor: ${provider}/${model.modelId}`);
38249
37881
  }
37882
+ if (model.wire && provider !== "opencode") {
37883
+ throw new Error(`Gateway model wire is only supported by OpenCode: ${provider}/${model.modelId}`);
37884
+ }
38250
37885
  if (model.effort?.supported) {
38251
37886
  if (new Set(model.effort.levels).size !== model.effort.levels.length) {
38252
37887
  throw new Error(`Gateway effort levels contain duplicates: ${provider}/${model.modelId}`);
@@ -40216,21 +39851,9 @@ var AgentServerMessageSchema = messageDesc(cursorAgentFile, 119);
40216
39851
  var ConversationStepSchema = messageDesc(cursorAgentFile, 53);
40217
39852
  var UserMessageSchema = messageDesc(cursorAgentFile, 63);
40218
39853
  var ConversationTurnStructureSchema = messageDesc(cursorAgentFile, 70);
40219
- var OPENAI_RESPONSES_URL = "https://api.openai.com/v1/responses";
40220
- var CHATGPT_CODEX_RESPONSES_URL = "https://chatgpt.com/backend-api/codex/responses";
40221
- var DEFAULT_MAX_UPSTREAM_BODY_BYTES = 64 * 1024 * 1024;
40222
- var DEFAULT_UPSTREAM_IDLE_TIMEOUT_MS = 3e4;
40223
- var CHATGPT_UNSUPPORTED_FIELDS = [
40224
- "max_output_tokens",
40225
- "temperature",
40226
- "top_p",
40227
- "stop",
40228
- "user",
40229
- "metadata"
40230
- ];
40231
39854
  var UpstreamBodyLimitError = class extends Error {
40232
39855
  constructor(maxBodyBytes) {
40233
- super(`OpenAI response exceeded ${maxBodyBytes} bytes`);
39856
+ super(`Upstream response exceeded ${maxBodyBytes} bytes`);
40234
39857
  this.maxBodyBytes = maxBodyBytes;
40235
39858
  this.name = "UpstreamBodyLimitError";
40236
39859
  }
@@ -40238,7 +39861,7 @@ var UpstreamBodyLimitError = class extends Error {
40238
39861
  };
40239
39862
  var UpstreamIdleTimeoutError = class extends Error {
40240
39863
  constructor(idleTimeoutMs) {
40241
- super(`OpenAI response was idle for ${idleTimeoutMs}ms`);
39864
+ super(`Upstream response was idle for ${idleTimeoutMs}ms`);
40242
39865
  this.idleTimeoutMs = idleTimeoutMs;
40243
39866
  this.name = "UpstreamIdleTimeoutError";
40244
39867
  }
@@ -40250,6 +39873,132 @@ var UpstreamProtocolError = class extends Error {
40250
39873
  this.name = "UpstreamProtocolError";
40251
39874
  }
40252
39875
  };
39876
+ async function readBoundedBody(body, options) {
39877
+ if (body === null) {
39878
+ return new Uint8Array();
39879
+ }
39880
+ const reader = body.getReader();
39881
+ const chunks = [];
39882
+ let byteLength3 = 0;
39883
+ try {
39884
+ while (true) {
39885
+ const result = await readWithIdleTimeout(reader, options);
39886
+ if (result.done) {
39887
+ break;
39888
+ }
39889
+ byteLength3 += result.value.byteLength;
39890
+ if (byteLength3 > options.maxBodyBytes) {
39891
+ const error51 = new UpstreamBodyLimitError(options.maxBodyBytes);
39892
+ options.controller.abort(error51);
39893
+ throw error51;
39894
+ }
39895
+ chunks.push(result.value);
39896
+ }
39897
+ } finally {
39898
+ await reader.cancel().catch(() => void 0);
39899
+ }
39900
+ const bodyBytes = new Uint8Array(byteLength3);
39901
+ let offset = 0;
39902
+ for (const chunk of chunks) {
39903
+ bodyBytes.set(chunk, offset);
39904
+ offset += chunk.byteLength;
39905
+ }
39906
+ return bodyBytes;
39907
+ }
39908
+ async function readWithIdleTimeout(reader, options) {
39909
+ return await new Promise((resolve3, reject) => {
39910
+ let settled = false;
39911
+ const finish = (callback, value) => {
39912
+ if (settled) {
39913
+ return;
39914
+ }
39915
+ settled = true;
39916
+ clearTimeout(timeout);
39917
+ options.controller.signal.removeEventListener("abort", abort);
39918
+ callback(value);
39919
+ };
39920
+ const abort = () => {
39921
+ const reason = options.controller.signal.reason instanceof Error ? options.controller.signal.reason : new DOMException("The operation was aborted", "AbortError");
39922
+ void reader.cancel(reason).catch(() => void 0);
39923
+ finish(reject, reason);
39924
+ };
39925
+ const timeout = setTimeout(() => {
39926
+ const error51 = new UpstreamIdleTimeoutError(options.idleTimeoutMs);
39927
+ options.controller.abort(error51);
39928
+ }, options.idleTimeoutMs);
39929
+ options.controller.signal.addEventListener("abort", abort, { once: true });
39930
+ if (options.controller.signal.aborted) {
39931
+ abort();
39932
+ return;
39933
+ }
39934
+ reader.read().then(
39935
+ (result) => {
39936
+ finish(resolve3, result);
39937
+ },
39938
+ (error51) => {
39939
+ finish(reject, error51);
39940
+ }
39941
+ );
39942
+ });
39943
+ }
39944
+ function nextEventBoundary(buffer) {
39945
+ const match = /\r\n\r\n|\n\n|\r\r/.exec(buffer);
39946
+ return match === null ? void 0 : { index: match.index, length: match[0].length };
39947
+ }
39948
+ function parseSseFrameFields(frame) {
39949
+ let eventName;
39950
+ const data = [];
39951
+ for (const line of frame.split(/\r\n|\n|\r/)) {
39952
+ if (line.startsWith(":")) {
39953
+ continue;
39954
+ }
39955
+ const separator = line.indexOf(":");
39956
+ const field = separator === -1 ? line : line.slice(0, separator);
39957
+ let value = separator === -1 ? "" : line.slice(separator + 1);
39958
+ if (value.startsWith(" ")) {
39959
+ value = value.slice(1);
39960
+ }
39961
+ if (field === "event") {
39962
+ eventName = value;
39963
+ } else if (field === "data") {
39964
+ data.push(value);
39965
+ }
39966
+ }
39967
+ return {
39968
+ ...eventName === void 0 ? {} : { event: eventName },
39969
+ data: data.join("\n")
39970
+ };
39971
+ }
39972
+ function linkAbortSignal(signal, controller) {
39973
+ if (signal === void 0) {
39974
+ return () => void 0;
39975
+ }
39976
+ const abort = () => controller.abort(signal.reason);
39977
+ if (signal.aborted) {
39978
+ abort();
39979
+ return () => void 0;
39980
+ }
39981
+ signal.addEventListener("abort", abort, { once: true });
39982
+ return () => signal.removeEventListener("abort", abort);
39983
+ }
39984
+ function positiveInteger(value, name) {
39985
+ if (!Number.isInteger(value) || value <= 0) {
39986
+ throw new TypeError(`${name} must be a positive integer`);
39987
+ }
39988
+ return value;
39989
+ }
39990
+ var OPENAI_RESPONSES_URL = "https://api.openai.com/v1/responses";
39991
+ var CHATGPT_CODEX_RESPONSES_URL = "https://chatgpt.com/backend-api/codex/responses";
39992
+ var DEFAULT_MAX_UPSTREAM_BODY_BYTES = 64 * 1024 * 1024;
39993
+ var DEFAULT_UPSTREAM_IDLE_TIMEOUT_MS = 3e4;
39994
+ var CHATGPT_UNSUPPORTED_FIELDS = [
39995
+ "max_output_tokens",
39996
+ "temperature",
39997
+ "top_p",
39998
+ "stop",
39999
+ "user",
40000
+ "metadata"
40001
+ ];
40253
40002
  var OpenAIResponsesAdapter = class {
40254
40003
  capabilities = { nativeTools: ["web_search"] };
40255
40004
  fetchImpl;
@@ -40396,38 +40145,6 @@ function forChatGptBackend(request) {
40396
40145
  copy.store = false;
40397
40146
  return copy;
40398
40147
  }
40399
- async function readBoundedBody(body, options) {
40400
- if (body === null) {
40401
- return new Uint8Array();
40402
- }
40403
- const reader = body.getReader();
40404
- const chunks = [];
40405
- let byteLength3 = 0;
40406
- try {
40407
- while (true) {
40408
- const result = await readWithIdleTimeout(reader, options);
40409
- if (result.done) {
40410
- break;
40411
- }
40412
- byteLength3 += result.value.byteLength;
40413
- if (byteLength3 > options.maxBodyBytes) {
40414
- const error51 = new UpstreamBodyLimitError(options.maxBodyBytes);
40415
- options.controller.abort(error51);
40416
- throw error51;
40417
- }
40418
- chunks.push(result.value);
40419
- }
40420
- } finally {
40421
- await reader.cancel().catch(() => void 0);
40422
- }
40423
- const bodyBytes = new Uint8Array(byteLength3);
40424
- let offset = 0;
40425
- for (const chunk of chunks) {
40426
- bodyBytes.set(chunk, offset);
40427
- offset += chunk.byteLength;
40428
- }
40429
- return bodyBytes;
40430
- }
40431
40148
  async function* parseOpenAIEventStream(body, options) {
40432
40149
  if (body === null) {
40433
40150
  options.onClose();
@@ -40473,71 +40190,14 @@ async function* parseOpenAIEventStream(body, options) {
40473
40190
  options.onClose();
40474
40191
  }
40475
40192
  }
40476
- async function readWithIdleTimeout(reader, options) {
40477
- return await new Promise((resolve3, reject) => {
40478
- let settled = false;
40479
- const finish = (callback, value) => {
40480
- if (settled) {
40481
- return;
40482
- }
40483
- settled = true;
40484
- clearTimeout(timeout);
40485
- options.controller.signal.removeEventListener("abort", abort);
40486
- callback(value);
40487
- };
40488
- const abort = () => {
40489
- const reason = options.controller.signal.reason instanceof Error ? options.controller.signal.reason : new DOMException("The operation was aborted", "AbortError");
40490
- void reader.cancel(reason).catch(() => void 0);
40491
- finish(reject, reason);
40492
- };
40493
- const timeout = setTimeout(() => {
40494
- const error51 = new UpstreamIdleTimeoutError(options.idleTimeoutMs);
40495
- options.controller.abort(error51);
40496
- }, options.idleTimeoutMs);
40497
- options.controller.signal.addEventListener("abort", abort, { once: true });
40498
- if (options.controller.signal.aborted) {
40499
- abort();
40500
- return;
40501
- }
40502
- reader.read().then(
40503
- (result) => {
40504
- finish(resolve3, result);
40505
- },
40506
- (error51) => {
40507
- finish(reject, error51);
40508
- }
40509
- );
40510
- });
40511
- }
40512
- function nextEventBoundary(buffer) {
40513
- const match = /\r\n\r\n|\n\n|\r\r/.exec(buffer);
40514
- return match === null ? void 0 : { index: match.index, length: match[0].length };
40515
- }
40516
40193
  function parseEventFrame(frame) {
40517
- let eventName;
40518
- const data = [];
40519
- for (const line of frame.split(/\r\n|\n|\r/)) {
40520
- if (line.startsWith(":")) {
40521
- continue;
40522
- }
40523
- const separator = line.indexOf(":");
40524
- const field = separator === -1 ? line : line.slice(0, separator);
40525
- let value = separator === -1 ? "" : line.slice(separator + 1);
40526
- if (value.startsWith(" ")) {
40527
- value = value.slice(1);
40528
- }
40529
- if (field === "event") {
40530
- eventName = value;
40531
- } else if (field === "data") {
40532
- data.push(value);
40533
- }
40534
- }
40535
- if (data.length === 0 || data.join("\n") === "[DONE]") {
40194
+ const { event: eventName, data } = parseSseFrameFields(frame);
40195
+ if (data.length === 0 || data === "[DONE]") {
40536
40196
  return void 0;
40537
40197
  }
40538
40198
  let parsed;
40539
40199
  try {
40540
- parsed = JSON.parse(data.join("\n"));
40200
+ parsed = JSON.parse(data);
40541
40201
  } catch (error51) {
40542
40202
  throw new UpstreamProtocolError(
40543
40203
  `OpenAI SSE contained invalid JSON: ${error51 instanceof Error ? error51.message : String(error51)}`
@@ -40669,7 +40329,6 @@ var STRICT_ALLOWED_KEYWORDS = /* @__PURE__ */ new Set([
40669
40329
  "$ref",
40670
40330
  "description",
40671
40331
  "title",
40672
- "format",
40673
40332
  "pattern",
40674
40333
  "minimum",
40675
40334
  "maximum",
@@ -40682,7 +40341,8 @@ var STRICT_ALLOWED_KEYWORDS = /* @__PURE__ */ new Set([
40682
40341
  "maxItems",
40683
40342
  // Dropped by `strictSchema` before the schema reaches the wire.
40684
40343
  "$schema",
40685
- "default"
40344
+ "default",
40345
+ "format"
40686
40346
  ]);
40687
40347
  function strictCompatible(schema) {
40688
40348
  if (!isRecord42(schema)) return true;
@@ -40721,11 +40381,19 @@ function strictSchema(schema) {
40721
40381
  const next = { ...schema };
40722
40382
  delete next.$schema;
40723
40383
  delete next.default;
40384
+ delete next.format;
40724
40385
  for (const key of ["anyOf", "oneOf", "allOf"]) {
40725
40386
  const branches = next[key];
40726
40387
  if (Array.isArray(branches)) next[key] = branches.map(strictSchema);
40727
40388
  }
40728
40389
  if (isRecord42(next.items)) next.items = strictSchema(next.items);
40390
+ if (isRecord42(next.$defs)) {
40391
+ const rewrittenDefs = {};
40392
+ for (const [name, value] of Object.entries(next.$defs)) {
40393
+ rewrittenDefs[name] = strictSchema(value);
40394
+ }
40395
+ next.$defs = rewrittenDefs;
40396
+ }
40729
40397
  const properties = next.properties;
40730
40398
  if (isRecord42(properties)) {
40731
40399
  const required2 = new Set(
@@ -40775,10 +40443,15 @@ function withoutNulls(value) {
40775
40443
  const out = {};
40776
40444
  for (const [key, entry] of Object.entries(value)) {
40777
40445
  if (entry === null) continue;
40778
- out[key] = isRecord42(entry) ? withoutNulls(entry) : entry;
40446
+ out[key] = withoutNullMembers(entry);
40779
40447
  }
40780
40448
  return out;
40781
40449
  }
40450
+ function withoutNullMembers(entry) {
40451
+ if (isRecord42(entry)) return withoutNulls(entry);
40452
+ if (Array.isArray(entry)) return entry.map(withoutNullMembers);
40453
+ return entry;
40454
+ }
40782
40455
  function outputItem(value) {
40783
40456
  const item = record2(value, "item");
40784
40457
  if (item.type === "message") {
@@ -40866,24 +40539,6 @@ function canonicalError(value) {
40866
40539
  const type = typeof error51.type === "string" && error51.type !== "error" ? error51.type : typeof error51.code === "string" ? error51.code : "api_error";
40867
40540
  return { type, message };
40868
40541
  }
40869
- function linkAbortSignal(signal, controller) {
40870
- if (signal === void 0) {
40871
- return () => void 0;
40872
- }
40873
- const abort = () => controller.abort(signal.reason);
40874
- if (signal.aborted) {
40875
- abort();
40876
- return () => void 0;
40877
- }
40878
- signal.addEventListener("abort", abort, { once: true });
40879
- return () => signal.removeEventListener("abort", abort);
40880
- }
40881
- function positiveInteger(value, name) {
40882
- if (!Number.isInteger(value) || value <= 0) {
40883
- throw new TypeError(`${name} must be a positive integer`);
40884
- }
40885
- return value;
40886
- }
40887
40542
  function isRecord42(value) {
40888
40543
  return typeof value === "object" && value !== null && !Array.isArray(value);
40889
40544
  }
@@ -43297,6 +42952,478 @@ async function resolveCodexCredentials(deps) {
43297
42952
  return null;
43298
42953
  }
43299
42954
  }
42955
+ var DEFAULT_CHAT_MAX_UPSTREAM_BODY_BYTES = 64 * 1024 * 1024;
42956
+ var DEFAULT_CHAT_UPSTREAM_IDLE_TIMEOUT_MS = 3e4;
42957
+ var OpenAIChatCompletionsAdapter = class {
42958
+ fetchImpl;
42959
+ maxBodyBytes;
42960
+ idleTimeoutMs;
42961
+ url;
42962
+ extraHeaders;
42963
+ constructor(options) {
42964
+ this.url = options.url;
42965
+ this.extraHeaders = options.headers ?? {};
42966
+ this.fetchImpl = options.fetch ?? globalThis.fetch.bind(globalThis);
42967
+ this.maxBodyBytes = positiveInteger(
42968
+ options.maxBodyBytes ?? DEFAULT_CHAT_MAX_UPSTREAM_BODY_BYTES,
42969
+ "maxBodyBytes"
42970
+ );
42971
+ this.idleTimeoutMs = positiveInteger(
42972
+ options.idleTimeoutMs ?? DEFAULT_CHAT_UPSTREAM_IDLE_TIMEOUT_MS,
42973
+ "idleTimeoutMs"
42974
+ );
42975
+ }
42976
+ async stream(request, options) {
42977
+ if (options.apiKey.length === 0) {
42978
+ throw new TypeError("apiKey must not be empty");
42979
+ }
42980
+ const controller = new AbortController();
42981
+ const unlinkAbort = linkAbortSignal(options.signal, controller);
42982
+ const payload = forChatCompletionsBackend(request);
42983
+ wireLog("openai-chat.wire.request", { url: this.url, payload });
42984
+ let response;
42985
+ try {
42986
+ response = await this.fetchImpl(this.url, {
42987
+ method: "POST",
42988
+ headers: {
42989
+ accept: "text/event-stream",
42990
+ authorization: `Bearer ${options.apiKey}`,
42991
+ "content-type": "application/json",
42992
+ ...this.extraHeaders
42993
+ },
42994
+ body: JSON.stringify(payload),
42995
+ signal: controller.signal
42996
+ });
42997
+ } catch (error51) {
42998
+ unlinkAbort();
42999
+ throw error51;
43000
+ }
43001
+ const readOptions = {
43002
+ controller,
43003
+ idleTimeoutMs: this.idleTimeoutMs,
43004
+ maxBodyBytes: this.maxBodyBytes
43005
+ };
43006
+ if (!response.ok) {
43007
+ try {
43008
+ const body = await readBoundedBody(response.body, readOptions);
43009
+ return { ok: false, status: response.status, headers: response.headers, body };
43010
+ } finally {
43011
+ unlinkAbort();
43012
+ }
43013
+ }
43014
+ return {
43015
+ ok: true,
43016
+ status: response.status,
43017
+ headers: response.headers,
43018
+ events: translateChatCompletionsStream(response.body, {
43019
+ ...readOptions,
43020
+ onClose: unlinkAbort
43021
+ })
43022
+ };
43023
+ }
43024
+ };
43025
+ function forChatCompletionsBackend(request) {
43026
+ const messages = [];
43027
+ if (request.instructions !== void 0 && request.instructions.length > 0) {
43028
+ messages.push({ role: "system", content: request.instructions });
43029
+ }
43030
+ let pendingToolCalls = [];
43031
+ let pendingAssistantText;
43032
+ let deferredMessages = [];
43033
+ const flushToolCalls = () => {
43034
+ if (pendingToolCalls.length === 0) return;
43035
+ messages.push({
43036
+ role: "assistant",
43037
+ content: pendingAssistantText ?? null,
43038
+ tool_calls: pendingToolCalls
43039
+ });
43040
+ pendingToolCalls = [];
43041
+ pendingAssistantText = void 0;
43042
+ };
43043
+ const flushDeferredMessages = () => {
43044
+ if (deferredMessages.length === 0) return;
43045
+ messages.push(...deferredMessages);
43046
+ deferredMessages = [];
43047
+ };
43048
+ for (const item of request.input) {
43049
+ if (item.type === "function_call") {
43050
+ flushDeferredMessages();
43051
+ pendingToolCalls.push({
43052
+ id: item.call_id,
43053
+ type: "function",
43054
+ function: { name: item.name, arguments: item.arguments }
43055
+ });
43056
+ continue;
43057
+ }
43058
+ if (item.type === "function_call_output") {
43059
+ flushToolCalls();
43060
+ messages.push({ role: "tool", tool_call_id: item.call_id, content: item.output });
43061
+ continue;
43062
+ }
43063
+ if (pendingToolCalls.length > 0) {
43064
+ if (item.role === "assistant") {
43065
+ const text2 = canonicalMessageText(item.content);
43066
+ if (text2.length > 0) {
43067
+ pendingAssistantText = pendingAssistantText === void 0 ? text2 : `${pendingAssistantText}
43068
+
43069
+ ${text2}`;
43070
+ }
43071
+ } else {
43072
+ deferredMessages.push(chatWireMessage(item));
43073
+ }
43074
+ continue;
43075
+ }
43076
+ flushDeferredMessages();
43077
+ messages.push(chatWireMessage(item));
43078
+ }
43079
+ flushToolCalls();
43080
+ flushDeferredMessages();
43081
+ const payload = {
43082
+ model: request.model,
43083
+ messages,
43084
+ stream: true,
43085
+ stream_options: { include_usage: true }
43086
+ };
43087
+ const tools = (request.tools ?? []).map((tool) => ({
43088
+ type: "function",
43089
+ function: {
43090
+ name: tool.name,
43091
+ ...tool.description === void 0 ? {} : { description: tool.description },
43092
+ parameters: tool.parameters
43093
+ }
43094
+ }));
43095
+ if (tools.length > 0) {
43096
+ payload.tools = tools;
43097
+ }
43098
+ const toolChoice = request.tool_choice;
43099
+ if (toolChoice !== void 0) {
43100
+ payload.tool_choice = typeof toolChoice === "string" ? toolChoice : { type: "function", function: { name: toolChoice.name } };
43101
+ }
43102
+ if (request.parallel_tool_calls !== void 0 && tools.length > 0) {
43103
+ payload.parallel_tool_calls = request.parallel_tool_calls;
43104
+ }
43105
+ if (request.max_output_tokens !== void 0) {
43106
+ payload.max_tokens = request.max_output_tokens;
43107
+ }
43108
+ return payload;
43109
+ }
43110
+ function chatWireMessage(item) {
43111
+ const role = item.role === "developer" ? "system" : item.role;
43112
+ if (typeof item.content === "string") {
43113
+ return { role, content: item.content };
43114
+ }
43115
+ if (role !== "user") {
43116
+ return { role, content: canonicalMessageText(item.content) };
43117
+ }
43118
+ const parts = item.content.map((part) => {
43119
+ if (part.type === "input_text") {
43120
+ return { type: "text", text: part.text };
43121
+ }
43122
+ return {
43123
+ type: "image_url",
43124
+ image_url: {
43125
+ url: part.image_url,
43126
+ // Chat 와이어의 detail 집합에는 original이 없다. 축소 없이 보내는 데 가장
43127
+ // 가까운 값은 high다.
43128
+ ...part.detail === void 0 ? {} : { detail: part.detail === "original" ? "high" : part.detail }
43129
+ }
43130
+ };
43131
+ });
43132
+ return { role, content: parts };
43133
+ }
43134
+ async function* translateChatCompletionsStream(body, options) {
43135
+ if (body === null) {
43136
+ options.onClose();
43137
+ throw new UpstreamProtocolError("Chat Completions streaming response had no body");
43138
+ }
43139
+ const reader = body.getReader();
43140
+ const decoder = new TextDecoder();
43141
+ let buffer = "";
43142
+ let byteLength3 = 0;
43143
+ let responseId;
43144
+ let responseModel;
43145
+ let createdEmitted = false;
43146
+ let textSeen = false;
43147
+ let accumulatedText = "";
43148
+ let usage2 = null;
43149
+ let failed = false;
43150
+ const toolCalls = /* @__PURE__ */ new Map();
43151
+ const MESSAGE_ITEM_ID = "chat_message_0";
43152
+ function* consumeChunk(value) {
43153
+ if (!isRecord7(value)) {
43154
+ throw new UpstreamProtocolError("Chat Completions SSE event was not an object");
43155
+ }
43156
+ if (isRecord7(value.error)) {
43157
+ failed = true;
43158
+ yield { type: "error", error: chatCanonicalError(value.error) };
43159
+ return;
43160
+ }
43161
+ if (typeof value.id === "string" && responseId === void 0) {
43162
+ responseId = value.id;
43163
+ }
43164
+ if (typeof value.model === "string" && responseModel === void 0) {
43165
+ responseModel = value.model;
43166
+ }
43167
+ if (!createdEmitted) {
43168
+ createdEmitted = true;
43169
+ yield {
43170
+ type: "response.created",
43171
+ response: {
43172
+ id: responseId ?? "chat_response",
43173
+ model: responseModel ?? "",
43174
+ usage: null
43175
+ }
43176
+ };
43177
+ }
43178
+ if (isRecord7(value.usage)) {
43179
+ usage2 = chatUsage(value.usage);
43180
+ }
43181
+ const choices = Array.isArray(value.choices) ? value.choices : [];
43182
+ for (const choice of choices) {
43183
+ if (!isRecord7(choice)) continue;
43184
+ const delta = isRecord7(choice.delta) ? choice.delta : {};
43185
+ if (typeof delta.content === "string" && delta.content.length > 0) {
43186
+ textSeen = true;
43187
+ accumulatedText += delta.content;
43188
+ yield {
43189
+ type: "response.output_text.delta",
43190
+ item_id: MESSAGE_ITEM_ID,
43191
+ output_index: 0,
43192
+ content_index: 0,
43193
+ delta: delta.content
43194
+ };
43195
+ }
43196
+ const wireToolCalls = Array.isArray(delta.tool_calls) ? delta.tool_calls : [];
43197
+ for (const call of wireToolCalls) {
43198
+ if (!isRecord7(call)) continue;
43199
+ const index = typeof call.index === "number" ? call.index : 0;
43200
+ const pending = toolCalls.get(index) ?? { arguments: "" };
43201
+ if (typeof call.id === "string" && call.id.length > 0) {
43202
+ pending.id ??= call.id;
43203
+ }
43204
+ const fn = isRecord7(call.function) ? call.function : {};
43205
+ if (typeof fn.name === "string" && fn.name.length > 0) {
43206
+ pending.name ??= fn.name;
43207
+ }
43208
+ if (typeof fn.arguments === "string") {
43209
+ pending.arguments += fn.arguments;
43210
+ }
43211
+ toolCalls.set(index, pending);
43212
+ }
43213
+ }
43214
+ }
43215
+ function* finish() {
43216
+ if (failed) return;
43217
+ if (!createdEmitted) {
43218
+ createdEmitted = true;
43219
+ yield {
43220
+ type: "response.created",
43221
+ response: { id: responseId ?? "chat_response", model: responseModel ?? "", usage: null }
43222
+ };
43223
+ }
43224
+ if (textSeen) {
43225
+ yield {
43226
+ type: "response.output_text.done",
43227
+ item_id: MESSAGE_ITEM_ID,
43228
+ output_index: 0,
43229
+ content_index: 0,
43230
+ text: accumulatedText
43231
+ };
43232
+ }
43233
+ let outputIndex = 1;
43234
+ for (const [index, pending] of [...toolCalls.entries()].sort(([a], [b]) => a - b)) {
43235
+ if (pending.name === void 0) {
43236
+ throw new UpstreamProtocolError(`Chat Completions tool call ${index} ended without a name`);
43237
+ }
43238
+ const id = pending.id ?? `chat_call_${index}`;
43239
+ const item = {
43240
+ id,
43241
+ type: "function_call",
43242
+ call_id: id,
43243
+ name: pending.name,
43244
+ arguments: pending.arguments
43245
+ };
43246
+ yield { type: "response.output_item.added", output_index: outputIndex, item: { ...item, arguments: "" } };
43247
+ yield {
43248
+ type: "response.function_call_arguments.done",
43249
+ item_id: id,
43250
+ output_index: outputIndex,
43251
+ arguments: pending.arguments
43252
+ };
43253
+ yield { type: "response.output_item.done", output_index: outputIndex, item };
43254
+ outputIndex += 1;
43255
+ }
43256
+ yield {
43257
+ type: "response.completed",
43258
+ response: {
43259
+ id: responseId ?? "chat_response",
43260
+ model: responseModel ?? "",
43261
+ // 하류 message_delta는 usage가 필수다. include_usage에도 usage 청크를 주지
43262
+ // 않는 백엔드에서는 0-usage로 완결하고, 실제 회계는 provider 콘솔이 맡는다.
43263
+ usage: usage2 ?? { input_tokens: 0, output_tokens: 0 }
43264
+ }
43265
+ };
43266
+ }
43267
+ try {
43268
+ while (true) {
43269
+ const result = await readWithIdleTimeout(reader, options);
43270
+ if (result.done) {
43271
+ buffer += decoder.decode();
43272
+ break;
43273
+ }
43274
+ byteLength3 += result.value.byteLength;
43275
+ if (byteLength3 > options.maxBodyBytes) {
43276
+ const error51 = new UpstreamBodyLimitError(options.maxBodyBytes);
43277
+ options.controller.abort(error51);
43278
+ throw error51;
43279
+ }
43280
+ buffer += decoder.decode(result.value, { stream: true });
43281
+ let boundary = nextEventBoundary(buffer);
43282
+ while (boundary !== void 0) {
43283
+ const frame = buffer.slice(0, boundary.index);
43284
+ buffer = buffer.slice(boundary.index + boundary.length);
43285
+ const data = chatFrameData(frame);
43286
+ if (data !== void 0) {
43287
+ yield* consumeChunk(data);
43288
+ }
43289
+ boundary = nextEventBoundary(buffer);
43290
+ }
43291
+ }
43292
+ if (buffer.trim().length > 0) {
43293
+ const data = chatFrameData(buffer);
43294
+ if (data !== void 0) {
43295
+ yield* consumeChunk(data);
43296
+ }
43297
+ }
43298
+ yield* finish();
43299
+ } finally {
43300
+ await reader.cancel().catch(() => void 0);
43301
+ options.onClose();
43302
+ }
43303
+ }
43304
+ function chatFrameData(frame) {
43305
+ const { data } = parseSseFrameFields(frame);
43306
+ if (data.length === 0 || data === "[DONE]") {
43307
+ return void 0;
43308
+ }
43309
+ try {
43310
+ return JSON.parse(data);
43311
+ } catch (error51) {
43312
+ throw new UpstreamProtocolError(
43313
+ `Chat Completions SSE contained invalid JSON: ${error51 instanceof Error ? error51.message : String(error51)}`
43314
+ );
43315
+ }
43316
+ }
43317
+ function chatUsage(value) {
43318
+ const inputTokens = nonNegativeOrZero(value.prompt_tokens);
43319
+ const outputTokens = nonNegativeOrZero(value.completion_tokens);
43320
+ const promptDetails = isRecord7(value.prompt_tokens_details) ? value.prompt_tokens_details : void 0;
43321
+ const completionDetails = isRecord7(value.completion_tokens_details) ? value.completion_tokens_details : void 0;
43322
+ const cachedInputTokens = promptDetails === void 0 ? void 0 : optionalNonNegative(promptDetails.cached_tokens);
43323
+ const reasoningOutputTokens = completionDetails === void 0 ? void 0 : optionalNonNegative(completionDetails.reasoning_tokens);
43324
+ const totalTokens = optionalNonNegative(value.total_tokens);
43325
+ return {
43326
+ input_tokens: inputTokens,
43327
+ output_tokens: outputTokens,
43328
+ ...cachedInputTokens === void 0 ? {} : { cached_input_tokens: cachedInputTokens },
43329
+ ...reasoningOutputTokens === void 0 ? {} : { reasoning_output_tokens: reasoningOutputTokens },
43330
+ ...totalTokens === void 0 ? {} : { total_tokens: totalTokens }
43331
+ };
43332
+ }
43333
+ function chatCanonicalError(error51) {
43334
+ const message = typeof error51.message === "string" ? error51.message : JSON.stringify(error51);
43335
+ const type = typeof error51.type === "string" && error51.type !== "error" ? error51.type : typeof error51.code === "string" ? error51.code : "api_error";
43336
+ return { type, message };
43337
+ }
43338
+ function nonNegativeOrZero(value) {
43339
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : 0;
43340
+ }
43341
+ function optionalNonNegative(value) {
43342
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : void 0;
43343
+ }
43344
+ function isRecord7(value) {
43345
+ return typeof value === "object" && value !== null && !Array.isArray(value);
43346
+ }
43347
+ var OPENCODE_AUTH_PROVIDER_ID = "Claude Code with OpenCode Go";
43348
+ var OPENCODE_GO_API_BASE_URL = "https://opencode.ai/zen/go";
43349
+ var OPENCODE_GO_MODEL = "minimax-m2.5";
43350
+ var OPENCODE_GO_MESSAGES_URL = `${OPENCODE_GO_API_BASE_URL}/v1/messages`;
43351
+ var OPENCODE_GO_RESPONSES_URL = `${OPENCODE_GO_API_BASE_URL}/v1/responses`;
43352
+ var OPENCODE_GO_CHAT_COMPLETIONS_URL = `${OPENCODE_GO_API_BASE_URL}/v1/chat/completions`;
43353
+ function opencodeGoWire(model) {
43354
+ return model.wire ?? "anthropic";
43355
+ }
43356
+ function createOpencodeGoAdapter(wire, options = {}) {
43357
+ if (wire === "responses") {
43358
+ return new OpenAIResponsesAdapter({
43359
+ url: OPENCODE_GO_RESPONSES_URL,
43360
+ ...options.fetch ? { fetch: options.fetch } : {}
43361
+ });
43362
+ }
43363
+ return new OpenAIChatCompletionsAdapter({
43364
+ url: OPENCODE_GO_CHAT_COMPLETIONS_URL,
43365
+ ...options.fetch ? { fetch: options.fetch } : {}
43366
+ });
43367
+ }
43368
+
43369
+ // ../../packages/fleet-admiral/src/agent-cli/gateway-agents.ts
43370
+ var GENERAL_PURPOSE_AGENT_PROMPT = [
43371
+ "You are a Fleet execution agent. Do the assigned work directly; do not re-delegate the whole assignment.",
43372
+ 'Treat host objective/scope/constraints/references as binding contracts. Do not silently re-plan, expand scope, or substitute a "cleaner" design \u2014 finish as instructed, then optionally suggest alternatives. On ambiguity or conflict, stop and report the blocker instead of guessing.',
43373
+ "",
43374
+ "Pick ONE mode from the task and stay in it:",
43375
+ "- recon: read-only facts; least-invasive evidence path; cite path:line",
43376
+ "- decide: read-only; one simplest viable recommendation; no implementation checklist",
43377
+ "- implement: edit within scope; verify what you changed; report compliance and any deviations",
43378
+ "- verify: hunt real defects with evidence+impact; PASS/FAIL; fix only if asked",
43379
+ "",
43380
+ "Search only as needed for the chosen mode. Prefer known paths over broad sweeps. Do not default to exhaustive multi-strategy hunting.",
43381
+ "NEVER create files unless they are absolutely necessary. ALWAYS prefer editing an existing file to creating a new one.",
43382
+ "NEVER proactively create documentation files (*.md) or README files. Only create documentation files if explicitly requested.",
43383
+ "Final reply: concise essentials only \u2014 mode, what changed or found, key evidence (path:line when relevant), and blockers/deviations."
43384
+ ].join("\n");
43385
+ function buildGatewayCustomAgents(exposed) {
43386
+ const agents = {};
43387
+ for (const model of exposed) {
43388
+ const modelId = toClaudeGatewayModelId(model);
43389
+ const constraints = buildGatewayModelConstraints(model);
43390
+ if (constraints.effortSupported) {
43391
+ for (const effort of constraints.effortLadder) {
43392
+ const name2 = toGatewayAgentName(modelId, effort);
43393
+ agents[name2] = {
43394
+ description: gatewayAgentDescription(modelId, name2, effort),
43395
+ prompt: GENERAL_PURPOSE_AGENT_PROMPT,
43396
+ model: modelId,
43397
+ effort
43398
+ };
43399
+ }
43400
+ continue;
43401
+ }
43402
+ const name = toGatewayAgentName(modelId);
43403
+ agents[name] = {
43404
+ description: gatewayAgentDescription(modelId, name),
43405
+ prompt: GENERAL_PURPOSE_AGENT_PROMPT,
43406
+ model: modelId
43407
+ };
43408
+ }
43409
+ return agents;
43410
+ }
43411
+ function toGatewayAgentName(modelId, effort) {
43412
+ const stripped = modelId.startsWith("claude-gateway--") ? modelId.slice("claude-gateway--".length) : modelId;
43413
+ const base = stripped.replace(/\[1m\]/g, "-1m").replace(/[^A-Za-z0-9]+/g, "-").replace(/^-+|-+$/g, "").toLowerCase();
43414
+ const stem = base.length > 0 ? base : "model";
43415
+ return effort === void 0 ? stem : `${stem}-${effort}`;
43416
+ }
43417
+ function gatewayAgentDescription(modelId, name, effort) {
43418
+ const effortPart = effort === void 0 ? "no effort control" : `effort ${effort}`;
43419
+ return [
43420
+ `Gateway model ${modelId}, ${effortPart}.`,
43421
+ "Fleet execution agent that runs one mode \u2014 recon, decide, implement, or verify. Name the mode in the task.",
43422
+ "Use after calling gateway_models when this roster entry fits the stage.",
43423
+ `Select this identity by the agent type name ${name}. The model id above is a value for a model field and is rejected wherever a name is expected.`,
43424
+ ...effort === void 0 ? [] : [`That name already carries ${effort}, so pinning a reasoning effort alongside it is redundant.`]
43425
+ ].join(" ");
43426
+ }
43300
43427
 
43301
43428
  // ../../packages/fleet-admiral/src/ai-gateway/role-fit.ts
43302
43429
  var ROLE_FIT = Object.freeze({
@@ -43363,37 +43490,115 @@ function gatewayRoleFit(identity) {
43363
43490
  var UNSUPPORTED_QUOTA = Object.freeze({ status: "unsupported" });
43364
43491
  var PARENT_PROVIDER_ID = "claude";
43365
43492
  function buildGatewayLoadout(input) {
43366
- const models = input.exposed.map((model) => toLoadoutModel(model, input.defaultModel));
43493
+ const placed = input.exposed.map((model) => ({
43494
+ provider: model.provider,
43495
+ entry: toLoadoutModel(model, input.defaultModel)
43496
+ }));
43367
43497
  return {
43368
- revision: loadoutRevision(models),
43498
+ revision: loadoutRevision(placed.map(({ entry }) => entry)),
43369
43499
  catalogUpdatedAt: GATEWAY_MODELS_UPDATED_AT,
43370
- models,
43371
- providers: buildProviders(input.exposed, input.quota)
43500
+ providers: buildProviders(placed, input.quota, input.now ?? Date.now)
43372
43501
  };
43373
43502
  }
43374
43503
  function toLoadoutModel(model, defaultModel) {
43504
+ const modelId = toClaudeGatewayModelId(model);
43505
+ const { provider: _provider, ...constraints } = buildGatewayModelConstraints(model);
43375
43506
  return {
43376
- id: toClaudeGatewayModelId(model),
43377
- displayName: model.displayName,
43378
- constraints: buildGatewayModelConstraints(model),
43507
+ agentTypes: toAgentTypeSelectors(modelId, constraints),
43508
+ modelId,
43509
+ constraints,
43379
43510
  roleFit: gatewayRoleFit(gatewayModelIdentity(model)) ?? null,
43380
43511
  isSessionDefault: defaultModel !== void 0 && defaultModel.id === model.id
43381
43512
  };
43382
43513
  }
43383
- function buildProviders(exposed, quota) {
43514
+ function toAgentTypeSelectors(id, constraints) {
43515
+ if (!constraints.effortSupported) {
43516
+ return Object.freeze({ none: toGatewayAgentName(id) });
43517
+ }
43518
+ return Object.freeze(Object.fromEntries(
43519
+ constraints.effortLadder.map((effort) => [effort, toGatewayAgentName(id, effort)])
43520
+ ));
43521
+ }
43522
+ function buildProviders(placed, quota, now) {
43384
43523
  const ids = [PARENT_PROVIDER_ID];
43385
- for (const model of exposed) {
43386
- if (!ids.includes(model.provider)) ids.push(model.provider);
43524
+ for (const { provider } of placed) {
43525
+ if (!ids.includes(provider)) ids.push(provider);
43387
43526
  }
43388
43527
  for (const id of Object.keys(quota ?? {})) {
43389
43528
  if (!ids.includes(id)) ids.push(id);
43390
43529
  }
43391
- return ids.map((id) => ({ id, quota: quota?.[id] ?? UNSUPPORTED_QUOTA }));
43530
+ return Object.freeze(Object.fromEntries(ids.map((id) => [id, {
43531
+ quota: enrichProviderQuota(quota?.[id], now) ?? UNSUPPORTED_QUOTA,
43532
+ models: Object.freeze(
43533
+ placed.filter((candidate) => candidate.provider === id).map(({ entry }) => entry)
43534
+ )
43535
+ }])));
43536
+ }
43537
+ var MS_PER_HOUR = 36e5;
43538
+ var CADENCE_SESSION_MAX_MS = 20 * MS_PER_HOUR;
43539
+ var CADENCE_DAILY_MAX_MS = 3 * 24 * MS_PER_HOUR;
43540
+ var CADENCE_WEEKLY_MAX_MS = 20 * 24 * MS_PER_HOUR;
43541
+ var MIN_ELAPSED_FRACTION = 0.05;
43542
+ var PACE_CRITICAL = 1.5;
43543
+ var PACE_ELEVATED = 1.1;
43544
+ var USED_CRITICAL_PERCENT = 95;
43545
+ var USED_ELEVATED_PERCENT = 80;
43546
+ function enrichProviderQuota(quota, now) {
43547
+ if (!quota) return void 0;
43548
+ const { windows, ...rest } = quota;
43549
+ if (!windows || windows.length === 0) return rest;
43550
+ const at = typeof quota.fetchedAt === "number" && Number.isFinite(quota.fetchedAt) ? quota.fetchedAt : now();
43551
+ return { ...rest, windows: windows.map((window) => enrichQuotaWindow(window, at)) };
43552
+ }
43553
+ function windowCadence(durationMs) {
43554
+ if (durationMs <= CADENCE_SESSION_MAX_MS) return "session";
43555
+ if (durationMs <= CADENCE_DAILY_MAX_MS) return "daily";
43556
+ if (durationMs <= CADENCE_WEEKLY_MAX_MS) return "weekly";
43557
+ return "monthly";
43558
+ }
43559
+ function windowPressure(usedPercent, paceRatio) {
43560
+ if (usedPercent >= USED_CRITICAL_PERCENT || paceRatio !== void 0 && paceRatio >= PACE_CRITICAL) {
43561
+ return "critical";
43562
+ }
43563
+ if (usedPercent >= USED_ELEVATED_PERCENT || paceRatio !== void 0 && paceRatio >= PACE_ELEVATED) {
43564
+ return "elevated";
43565
+ }
43566
+ return "ok";
43567
+ }
43568
+ function enrichQuotaWindow(window, at) {
43569
+ const durationMs = window.period?.durationMs;
43570
+ if (typeof durationMs !== "number" || !Number.isFinite(durationMs) || durationMs <= 0) {
43571
+ return { ...window, pressure: windowPressure(window.usedPercent, void 0) };
43572
+ }
43573
+ const startsAt = window.period?.startsAt ?? (window.resetsAt !== void 0 && window.resetsAt > durationMs ? window.resetsAt - durationMs : void 0);
43574
+ const resetBoundary = window.resetsAt ?? (startsAt !== void 0 ? startsAt + durationMs : void 0);
43575
+ const stale = resetBoundary !== void 0 && at > resetBoundary;
43576
+ let paceRatio;
43577
+ let projectedExhaustionAt;
43578
+ if (startsAt !== void 0 && resetBoundary !== void 0 && !stale && at > startsAt) {
43579
+ const elapsed = Math.min(1, (at - startsAt) / durationMs);
43580
+ if (elapsed >= MIN_ELAPSED_FRACTION) {
43581
+ const used = Math.min(1, Math.max(0, window.usedPercent / 100));
43582
+ paceRatio = Math.round(used / elapsed * 100) / 100;
43583
+ if (used > 0) {
43584
+ const exhaustionAt = startsAt + Math.round((at - startsAt) / used);
43585
+ if (exhaustionAt < resetBoundary) projectedExhaustionAt = exhaustionAt;
43586
+ }
43587
+ }
43588
+ }
43589
+ return {
43590
+ ...window,
43591
+ cadence: windowCadence(durationMs),
43592
+ ...paceRatio !== void 0 ? { paceRatio } : {},
43593
+ ...projectedExhaustionAt !== void 0 ? { projectedExhaustionAt } : {},
43594
+ recoveryHalfLifeMs: Math.round(durationMs / 2),
43595
+ pressure: windowPressure(window.usedPercent, paceRatio)
43596
+ };
43392
43597
  }
43393
43598
  function loadoutRevision(models) {
43394
43599
  const material = [
43395
43600
  GATEWAY_MODELS_UPDATED_AT,
43396
- ...models.map((model) => `${model.id}:${model.isSessionDefault ? "1" : "0"}`).sort()
43601
+ ...models.map((model) => `${model.modelId}:${model.isSessionDefault ? "1" : "0"}`).sort()
43397
43602
  ].join("\n");
43398
43603
  return createHash("sha256").update(material).digest("hex").slice(0, 12);
43399
43604
  }
@@ -43414,16 +43619,17 @@ var GATEWAY_MODELS_DOCTRINE = {
43414
43619
  `Do not carry an earlier read forward as if it were still current. The revision tracks roster and default changes only, never allowance movement, so equal revisions do not mean equal quotas.`,
43415
43620
  `Do not treat the response as a recommendation to act on unread. It reports facts; the choice and its justification stay with you.`
43416
43621
  ],
43622
+ // 이 목록은 필드 사전이 아니라 판정 규칙이다. 응답을 보면 알 수 있는 것은 빼고,
43623
+ // 틀리면 조용히 실패하는 것만 남긴다 — 길어질수록 읽히지 않고, 읽히지 않으면 없는 것과 같다.
43417
43624
  usageGuidelines: [
43418
- `models[] contains only the exposed models. A model absent here is one the user turned off \u2014 the gateway still executes it, so pinning it would quietly override that choice with no error.`,
43419
- `constraints.effortLadder lists the only reasoning levels that survive; a level outside it is clamped upstream without notice. Ladders differ per model, and some models have no effort control at all.`,
43420
- `roleFit is null when the axis was never measured. Unmeasured is not unsuitable \u2014 it means quality gives no reason to prefer one identity, so the choice falls to allowance rather than back to the session's own model.`,
43421
- `constraints.quotaScope names the sub-allowance a model is billed against. Read the provider window whose scope matches it; the scope-less window is the sum of pools and can look healthy while that model's own pool is spent.`,
43422
- `A provider quota of status "unsupported" means the allowance cannot be read, never that it is plentiful.`,
43423
- `An unpinned stage spends whatever this session is currently running on, so its provider is the baseline an offload is measured against. The roster cannot identify that provider \u2014 it is registered once per runtime and cannot see which model a given session launched with \u2014 so match it yourself against the model you are running, and read that provider's window.`,
43424
- `isSessionDefault reflects what Settings currently designates, not necessarily what an already-running session launched with; the two diverge when the setting is changed mid-session.`,
43425
- `constraints.homolineage marks a model sharing the parent session's lineage. It can move spend off that allowance, but adds no independence to a panel whose value comes from differing judgement.`,
43426
- `constraints.identity collapses service-tier siblings, so a measurement recorded against one covers the other.`
43625
+ `Two spellings, never interchangeable. agentTypes names this identity once per reasoning rung, so selecting by name already carries the level and nothing further pins effort; modelId is the model as a value. Neither derives from the other \u2014 the transform behind a name collapses ".", "[1m]", and "--" all into "-". Prefer a name: a wrong name fails loudly, while modelId reaches whatever the catalog holds, including a model the user turned off, in silence.`,
43626
+ `Names are registered once at session start; this roster is re-read live. A model exposed mid-session therefore appears here under a name that will not resolve until a new session \u2014 that, and not a stale roster, is what an unknown-name failure means.`,
43627
+ `Each provider entry pairs one allowance with the exposed models it serves, so the window to read against a model is in the same entry. claude serves none by design: it is what an unpinned run spends, and the baseline an offload is measured against. A model in no entry is one the user turned off.`,
43628
+ `effortLadder is the whole set of levels that survive \u2014 anything outside it is clamped upstream with no signal, and some models have no effort control at all.`,
43629
+ `roleFit null means unmeasured, never unsuitable: quality then gives no reason to prefer an identity, so the choice falls to allowance and never back to this session's own model. homolineage marks shared lineage with that session \u2014 useful for moving spend, useless where independent judgement is the product.`,
43630
+ `Read pressure before doing arithmetic of your own; it is the verdict on one window, combining headroom with burn pace against that window's own clock, and it never says which model to choose. Compare usedPercent only within one cadence \u2014 a shared window id does not mean a shared length. Where a provider splits into pools, quotaScope picks the window that applies and the isAggregate one stays out of headroom math. recoveryHalfLifeMs prices the drain: weeks of lockout for a monthly window, hours for a session one.`,
43631
+ `Absence is never safety. A missing derived field means the reading could not support it, and status "unsupported" means the allowance could not be read at all.`,
43632
+ `The roster cannot tell which provider this session itself runs on \u2014 it is registered once per runtime \u2014 so make that match yourself and read that window. isSessionDefault reflects Settings as it stands now, not what an already-running session launched with.`
43427
43633
  ]
43428
43634
  };
43429
43635
  function buildGatewayModelsToolSpec(deps) {
@@ -43496,17 +43702,31 @@ function getExecutorMcpTools(registry4, carrierRuntime, carrierId) {
43496
43702
 
43497
43703
  // ../../packages/fleet-admiral/src/ai-gateway/auth.ts
43498
43704
  async function validateKimiAuthKey(apiKey) {
43499
- const validation = await validateAnthropicCompatibleApiKey({
43705
+ return validateAnthropicCompatibleAuthKey(apiKey, {
43500
43706
  providerId: KIMI_AUTH_PROVIDER_ID,
43501
- apiKey,
43502
43707
  baseUrl: KIMI_CODE_API_BASE_URL,
43503
43708
  model: KIMI_CODE_MODEL
43504
43709
  });
43710
+ }
43711
+ async function validateOpencodeGoAuthKey(apiKey) {
43712
+ return validateAnthropicCompatibleAuthKey(apiKey, {
43713
+ providerId: OPENCODE_AUTH_PROVIDER_ID,
43714
+ baseUrl: OPENCODE_GO_API_BASE_URL,
43715
+ model: OPENCODE_GO_MODEL
43716
+ });
43717
+ }
43718
+ async function validateAnthropicCompatibleAuthKey(apiKey, coordinates) {
43719
+ const validation = await validateAnthropicCompatibleApiKey({
43720
+ providerId: coordinates.providerId,
43721
+ apiKey,
43722
+ baseUrl: coordinates.baseUrl,
43723
+ model: coordinates.model
43724
+ });
43505
43725
  if (isAuthValidationSuccess(validation)) {
43506
- return { providerId: KIMI_AUTH_PROVIDER_ID, status: "success" };
43726
+ return { providerId: coordinates.providerId, status: "success" };
43507
43727
  }
43508
43728
  return {
43509
- providerId: KIMI_AUTH_PROVIDER_ID,
43729
+ providerId: coordinates.providerId,
43510
43730
  status: validation.status,
43511
43731
  detail: validation.detail
43512
43732
  };
@@ -43676,63 +43896,6 @@ function buildClaudeMcpConfig(servers) {
43676
43896
  });
43677
43897
  }
43678
43898
 
43679
- // ../../packages/fleet-admiral/src/agent-cli/gateway-agents.ts
43680
- var GENERAL_PURPOSE_AGENT_PROMPT = [
43681
- "You are a Fleet execution agent. Do the assigned work directly; do not re-delegate the whole assignment.",
43682
- 'Treat host objective/scope/constraints/references as binding contracts. Do not silently re-plan, expand scope, or substitute a "cleaner" design \u2014 finish as instructed, then optionally suggest alternatives. On ambiguity or conflict, stop and report the blocker instead of guessing.',
43683
- "",
43684
- "Pick ONE mode from the task and stay in it:",
43685
- "- recon: read-only facts; least-invasive evidence path; cite path:line",
43686
- "- decide: read-only; one simplest viable recommendation; no implementation checklist",
43687
- "- implement: edit within scope; verify what you changed; report compliance and any deviations",
43688
- "- verify: hunt real defects with evidence+impact; PASS/FAIL; fix only if asked",
43689
- "",
43690
- "Search only as needed for the chosen mode. Prefer known paths over broad sweeps. Do not default to exhaustive multi-strategy hunting.",
43691
- "NEVER create files unless they are absolutely necessary. ALWAYS prefer editing an existing file to creating a new one.",
43692
- "NEVER proactively create documentation files (*.md) or README files. Only create documentation files if explicitly requested.",
43693
- "Final reply: concise essentials only \u2014 mode, what changed or found, key evidence (path:line when relevant), and blockers/deviations."
43694
- ].join("\n");
43695
- function buildGatewayCustomAgents(exposed) {
43696
- const agents = {};
43697
- for (const model of exposed) {
43698
- const modelId = toClaudeGatewayModelId(model);
43699
- const constraints = buildGatewayModelConstraints(model);
43700
- if (constraints.effortSupported) {
43701
- for (const effort of constraints.effortLadder) {
43702
- const name2 = toGatewayAgentName(modelId, effort);
43703
- agents[name2] = {
43704
- description: gatewayAgentDescription(model, modelId, effort),
43705
- prompt: GENERAL_PURPOSE_AGENT_PROMPT,
43706
- model: modelId,
43707
- effort
43708
- };
43709
- }
43710
- continue;
43711
- }
43712
- const name = toGatewayAgentName(modelId);
43713
- agents[name] = {
43714
- description: gatewayAgentDescription(model, modelId),
43715
- prompt: GENERAL_PURPOSE_AGENT_PROMPT,
43716
- model: modelId
43717
- };
43718
- }
43719
- return agents;
43720
- }
43721
- function toGatewayAgentName(modelId, effort) {
43722
- const stripped = modelId.startsWith("claude-gateway--") ? modelId.slice("claude-gateway--".length) : modelId;
43723
- const base = stripped.replace(/\[1m\]/g, "-1m").replace(/[^A-Za-z0-9]+/g, "-").replace(/^-+|-+$/g, "").toLowerCase();
43724
- const stem = base.length > 0 ? base : "model";
43725
- return effort === void 0 ? stem : `${stem}-${effort}`;
43726
- }
43727
- function gatewayAgentDescription(model, modelId, effort) {
43728
- const effortPart = effort === void 0 ? "no effort control" : `effort ${effort}`;
43729
- return [
43730
- `Gateway model ${model.displayName} (${modelId}), ${effortPart}.`,
43731
- "Fleet execution agent that runs one mode \u2014 recon, decide, implement, or verify. Name the mode in the task.",
43732
- "Use after calling gateway_models when this roster entry fits the stage."
43733
- ].join(" ");
43734
- }
43735
-
43736
43899
  // ../../packages/fleet-admiral/src/agent-cli/assets.generated.ts
43737
43900
  var EMBEDDED_AGENT_CLI_SKILL_ASSETS = [
43738
43901
  { relativePath: "assumption-audit/SKILL.md", content: "---\nname: assumption-audit\ndescription: Resolve decision-shaped blocking gaps one at a time \u2014 Context Confidence gate failures during an active protocol, or pre-engagement requirements ambiguity routed by the Command Integrity Standing Order.\n---\n\nUse this auxiliary skill only when a decision-shaped blocking gap has been found: either the active protocol or Context Confidence re-entry path surfaced it, or the Command Integrity Standing Order routed a pre-engagement requirements ambiguity here before a protocol mode loads. This skill is not a protocol mode, does not replace the active protocol, and cannot declare the planning boundary passed by itself.\n\nFor each unresolved blocking gap, triage the gap before questioning:\n\n- **Scout-shaped**: the answer should come from direct file reads, focused reconnaissance, carrier scouting, or another verifiable evidence source. Send the workflow back to that evidence-gathering path instead of asking the user to decide.\n- **Decision-shaped**: the answer depends on preference, scope, risk appetite, product intent, or authority that evidence alone cannot settle. Ask exactly one question for this gap.\n- **Escalation-shaped**: the answer requires authority beyond the current operator, changes the mission boundary, repeatedly fails to resolve, or would weaken the active protocol's required gate. Escalate to the user.\n\nWhen a gap is decision-shaped, ask one question at a time. Present your recommended answer first, then give one or two concrete alternatives when useful. Walk decision dependencies one branch at a time until the current gap is resolved; do not bundle unrelated gaps into the same question.\n\nAfter the gap is answered, report the resolved decision in one short line and return control to the caller: the active protocol or Context Confidence Standing Order on re-entry, or the Protocol Gate when invoked pre-engagement. An active workflow must re-evaluate confidence and re-apply the required planning boundary gate before planning proceeds.\n" },
@@ -43809,7 +43972,7 @@ If the intended Carrier is unavailable or carrier_dispatch rejects the requested
43809
43972
  { relativePath: "gateway/codebase-research/SKILL.md", content: "---\nname: codebase-research\ndescription: Answer a question about a codebase or an external subject by fanning out independent searches, reading sources directly, and separating what was verified from what was only claimed. Load before orchestrating reconnaissance across many files, subsystems, or external sources. Skip for a single lookup you can perform directly.\n---\n\n# Codebase Research\n\nReconnaissance whose product is **evidence, not a summary**. The run's value comes from covering angles a single reader would miss and from being explicit about what it failed to establish.\n\nExecuting this skeleton \u2014 the surface it runs on, the wiring between stages, and model and effort assignment \u2014 belongs to `workflow`; this skill owns the shape of the run.\n\n## When Not To Use\n\n- A fact one grep or one file read settles. Fanning out costs more than the answer is worth.\n- Work that will change files. Use `implementation-run`.\n- Judging code that already exists against a standard. Use `quality-review`.\n\n## Stage Skeleton\n\n| Stage | Role | Fan | Returns |\n|---|---|---|---|\n| Scope | decompose | 1 | 3-6 angles, each a distinct search strategy \u2014 not paraphrases of one query |\n| Sweep | scan | one per angle | Located candidates with a path or URL and why each is relevant |\n| Read | extract | one per surviving candidate | Claims, each with a verbatim quote and its exact source |\n| Reconcile | synthesize | 1 | Merged findings, ranked, with contradictions kept visible |\n\nRun Sweep and Read as a pipeline. A barrier between them buys nothing: each candidate can be read the moment its angle finds it. Insert a barrier only before Reconcile, which genuinely needs the whole set.\n\n## Rules\n\n- **Angles must differ in method, not wording.** By-name, by-caller, by-test, by-history, by-config are different angles. Three rephrasings of one query is one angle run three times.\n- **A claim without a quote is a lead, not a finding.** Require the source and the literal text; report the count of leads that never became findings.\n- **Deduplicate before reading, not after.** Deduplicate on a normalized identity (path, or host plus path for a URL) so the same source is not read once per angle.\n- **Contradictions survive to the report.** When two sources disagree, say so and name both. Collapsing them into whichever sounds more confident destroys the run's most valuable output.\n- **Name what you failed to reach.** Blocked networks, unreadable files, and truncated searches are results. A report that omits them reads as exhaustive when it is not.\n\n## Stopping\n\nStop when a full sweep round adds no source you had not already read. Do not keep spawning searchers because the subject is large \u2014 spawn them because the last round found something new.\n\n## Gotchas\n\n- **Symptom:** The report is confident and short, and every finding traces to one or two sources.\n **Action:** Check whether the angles actually differed. Re-run with methods, not phrasings.\n **Why:** Similar queries return the same top results, so the fan-out produced redundancy that reads as corroboration.\n\n- **Symptom:** A cited file path or symbol does not exist.\n **Action:** Treat the whole finding as unverified and re-read the source before keeping it.\n **Why:** A stage that could not reach a source may still produce a plausible path; requiring a verbatim quote is what makes this detectable.\n" },
43810
43973
  { relativePath: "gateway/implementation-run/SKILL.md", content: '---\nname: implementation-run\ndescription: Apply one decided change across many files, packages, or call sites by discovering the sites, transforming each in isolation, and inspecting the artifacts rather than the reports. Load before a migration, a sweeping refactor, or a multi-package edit. Skip when the change fits in a few files you will edit directly, or when the approach is not yet decided.\n---\n\n# Implementation Run\n\nThe only stage shape here that **writes**. Its risk is not failure \u2014 a failed edit is visible \u2014 but convergence: many branches each producing something reasonable that together do not match the codebase.\n\nExecuting this skeleton \u2014 the surface it runs on, the wiring between stages, and model and effort assignment \u2014 belongs to `workflow`; this skill owns the shape of the run.\n\n## When Not To Use\n\n- The approach is undecided. Decide first with `architecture-review`; a stage handed an open decision will close it for you, differently in each branch.\n- A handful of files you can edit directly. The per-stage overhead exceeds the work.\n- Judging existing code. Use `quality-review`.\n\n## Stage Skeleton\n\n| Stage | Role | Fan | Returns |\n|---|---|---|---|\n| Discover | map | 1-3 | Every site that must change, each with a path and why it qualifies |\n| **Decide** | \u2014 | **host only** | The literal values every site will use. Decided here, never in a stage. |\n| Apply | implement | one per site or coherent group, `isolation: \'worktree\'` | Files changed, and which existing conventions were matched |\n| Inspect | verify | host reads the diff | Accept or reject per site |\n\nDiscover and Apply pipeline naturally, but **Decide is a barrier by necessity** \u2014 the literals must exist before any site is touched, or each branch invents its own.\n\n## Decisions Travel as Literals\n\nBefore starting any branch, close every judgment gap. Ask both:\n\n1. Must the stage choose a concrete value?\n2. Does it lack the doctrine or convention context to justify that choice?\n\nIf both are yes, **the host chooses the value and passes it verbatim**. This covers design tokens, API paths, setting keys, protocol tokens, names, error message text, thresholds, and constants \u2014 not an exhaustive list.\n\nNever leave a choice to a stage behind phrases like "match the existing style", "pick a consistent name", "follow the convention", or "\uC801\uC808\uD788". A stage on another model has no feel for this repository and will produce something defensible but foreign.\n\n## Rules\n\n- **Isolate every writing branch.** Parallel edits to a shared tree corrupt each other. Worktree isolation costs setup time and disk; pay it whenever more than one branch writes.\n- **Inspect artifacts, never narratives.** Read the actual diff for each site. A stage\'s summary of what it did is evidence of what it believed, not of what it wrote.\n- **Verbatim match or defect.** A literal you sent must appear exactly. An equivalent-looking substitution \u2014 a synonym token, a reformatted path, a renamed key \u2014 is a defect, not a variation.\n- **A site that needs a new decision stops.** When Apply discovers a case Decide did not cover, it returns that fact instead of choosing. Resolve it on the host and start that branch again with the value; do not let one branch set precedent for the rest.\n- **Reject rather than patch.** A branch whose output drifted is re-run with a sharper prompt. Fixing its output by hand hides that the prompt was insufficient, and the next site will drift the same way.\n\n## Scope Warning\n\nMeasurement covered only **local, well-precedented edits** \u2014 a couple of files with an obvious existing pattern to follow. Every model tested handled those correctly. Nothing establishes that this holds for sweeping or cross-package work, where convention drift compounds and each branch sees only its own slice. Treat wide runs as unproven: keep groups small, inspect every diff, and keep a structural change on the host rather than spreading it across branches that each see one slice.\n\n## Stopping\n\nStop when every discovered site is either accepted or explicitly deferred with a reason. Do not accept a run with unexamined sites because the count is large \u2014 an unexamined site is an unknown edit.\n\n## Gotchas\n\n- **Symptom:** Tests pass and the build is green, but the change reads as foreign to the surrounding code.\n **Action:** Diff the produced values against the literals you sent. Re-run the drifted sites with the literal spelled out.\n **Why:** Green checks confirm the code runs, not that it belongs; convention is invisible to a compiler.\n\n- **Symptom:** Different sites solved the same sub-problem differently.\n **Action:** That sub-problem belonged in Decide. Choose once on the host and re-run the affected sites with the value.\n **Why:** Each branch resolved an open decision independently, which is exactly what the Decide barrier exists to prevent.\n\n- **Symptom:** A branch reports success but changed nothing.\n **Action:** Check the returned file list against the actual diff before accepting.\n **Why:** A branch that could not find its target may report the intent as done; only the artifact settles it.\n' },
43811
43974
  { relativePath: "gateway/quality-review/SKILL.md", content: "---\nname: quality-review\ndescription: Review existing code or a change set by splitting the work into independent dimensions, hunting within each, then adversarially verifying every finding before it is reported. Load before a correctness, security, or quality pass over a diff or subsystem. Skip when you already know the defect and only need it fixed.\n---\n\n# Quality Review\n\nThe output is a **judged finding list, not a fix list**. A reviewer that also repairs what it finds loses the independence that made the finding worth having, and repairs things that were never broken.\n\nExecuting this skeleton \u2014 the surface it runs on, the wiring between stages, and model and effort assignment \u2014 belongs to `workflow`; this skill owns the shape of the run.\n\n## When Not To Use\n\n- The defect is known and only the repair remains. Use `implementation-run`.\n- Deciding between designs. Use `architecture-review`.\n- Establishing facts with no standard to judge against. Use `codebase-research`.\n\n## Stage Skeleton\n\n| Stage | Role | Fan | Returns |\n|---|---|---|---|\n| Split | decompose | 1 | The dimensions this review will cover, each with its own standard |\n| Hunt | scan | one per dimension | Candidate findings, each with a file, a line, and a concrete failing scenario |\n| Verify | verify | 2-3 per finding, mixed lineage, prompted to refute | Refuted or survived, with the specific evidence |\n| Adjudicate | \u2014 | **host only** | Confirmed / declined / deferred, with the reason |\n\nPipeline Hunt into Verify \u2014 a dimension's findings can be verified while another dimension is still hunting. Nothing here needs a global barrier.\n\n## Dimensions Stay Separate\n\nNever combine security auditing with functional or end-to-end review in one hunt. Measured outcome: the combined run drops the functional pass \u2014 security findings are more legible, so the agent spends its budget there and reports the run as complete. Give each dimension its own hunter with its own standard.\n\nTypical dimensions, chosen per target rather than run wholesale: correctness, security and input trust, boundary and ownership rules, error and failure handling, test coverage, and convention conformance.\n\n## Verify Is Adversarial\n\nVerifiers are prompted to **refute**, not to confirm. A finding survives only when the refutation attempt fails.\n\n- Default to refuted when uncertain. An unreproduced finding is a hypothesis.\n- Require a concrete failing scenario: inputs or state, and the wrong result. \"This could break\" is not a finding.\n- Distinguish three outcomes. Survived, refuted on merit, and **unverifiable because the verifier errored** are different; collapsing the third into \"refuted\" silently discards real findings when infrastructure fails.\n- Mix lineage across a finding's verifiers. Identical models produce correlated verdicts, which reads as agreement.\n\n## Adjudication Stays on the Host\n\nA surviving finding is evidence, not an instruction. For each one the host decides:\n\n- **Confirm** when it occurs on a path a real workflow reaches, is in scope, and the repair costs less than the defect.\n- **Decline** when it is hypothetical, overfit to the reviewer's reading, outside scope, or contradicts an intended trade-off. Record the reason; a silent skip is indistinguishable from an oversight.\n- **Defer** when it is real but belongs to different work. Say why it is real and why not here.\n\nSeverity never decides disposition. A reviewer's P1 on a path nothing reaches is still a decline.\n\n## Stopping\n\nStop when a hunting round produces no finding that survives verification. Two consecutive dry rounds end the run. A reviewer can always generate another suggestion, so waiting for it to fall silent is an unbounded loop.\n\n## Gotchas\n\n- **Symptom:** The run reports many findings and all of them survived.\n **Action:** Check that verifiers were prompted to refute rather than to assess. A confirming verifier confirms.\n **Why:** Adversarial framing is the entire mechanism; without it the verify stage is a second opinion that agrees by default.\n\n- **Symptom:** Fixing one finding produced the next round's findings.\n **Action:** Roll back the fix rather than widening it. That is evidence the repair was over-scoped.\n **Why:** A repair that breeds findings changed more than the defect required.\n\n- **Symptom:** The security dimension is thorough and the functional one is a sentence.\n **Action:** Re-run the functional dimension on its own hunter.\n **Why:** Combined dimensions do not split budget evenly; the more legible one absorbs it.\n" },
43812
- { relativePath: "gateway/workflow/SKILL.md", content: "---\nname: workflow\ndescription: Run a staged multi-agent operation \u2014 map a stage skeleton onto the workflow execution surface, choose pipeline or barrier between stages, keep failures visible, and assign each stage its model and reasoning effort. Load before executing any skeleton from architecture-review, codebase-research, implementation-run, or quality-review, and before pinning a model or effort anywhere. Skip when the work is one stage you will perform directly.\n---\n\n# Workflow\n\nThe other gateway skills each own the *shape* of a run \u2014 which stages exist, what each returns, where the judgment stays on the host. This skill owns **turning that shape into an actual run**: the surface it executes on, how stages are wired to each other, and what each stage runs on.\n\nA skeleton that is never executed as stages is not a cheaper version of the run. It is a single reader doing every job in one context, which is the failure mode the skeleton exists to prevent.\n\n## Execution Surface\n\nStaged execution requires the workflow execution surface \u2014 the one that runs a script of stages, wires them together, and lets each stage carry its own model and effort. Inspect the live tool surface before concluding anything about it; tools may be lazy-loaded.\n\nThis skill covers that surface only, and that surface is not the default. An Agent \u2014 one run, or a named teammate you can continue \u2014 carries work that needs no wiring between its parts, and the Orchestration Policy Standing Order makes it the default for exactly that reason. A staged run is what the user asks for on top of it, and what it buys is the wiring: data flowing between stages, barriers, fan-out, and a fleet of different models working the same problem at once. Model and effort assignment below applies to both surfaces.\n\n**A surface gated behind user opt-in is unavailable until that opt-in exists.** Some workflow surfaces refuse to run unless the user explicitly asked for a multi-agent run. That refusal is not a defect and it is not a reason to quietly do the whole thing yourself in one context. Report the gate, say what the staged run would cost and what it would buy, and wait \u2014 the same way you would report any unavailable surface.\n\nTwo things stay out of this skill on purpose:\n\n- **Call mechanics.** Argument names, script syntax, return shapes, and which values a field accepts live in the live tool description. Read them there every time. Anything restated here would be a copy that goes stale silently.\n- **Whether to run at all.** That is the Orchestration Policy Standing Order's call, not this skill's.\n\n## Reading a Stage Skeleton\n\nEvery gateway skeleton is a table of `Stage | Role | Fan | Returns`. Read it as an execution plan:\n\n- **Role** is the one-word job \u2014 map, propose, implement, verify, synthesize, transform. It is also the input to model assignment below.\n- **Fan** is how many parallel branches that stage runs. `1` is one branch. `one per <item>` is a fan-out sized by the previous stage's output, not by a number you pick. **`host only` is not a stage you hand off** \u2014 it is a barrier where you do the work yourself, and handing it off defeats the skeleton.\n- **Returns** is the contract. When a stage returns structured data, declare the schema rather than parsing prose; a stage that must fill a shape will retry against it, while a stage asked to write prose will improvise.\n\n## Pipeline by Default\n\nBetween two stages, the choice is pipeline or barrier, and **pipeline is the default**.\n\nA barrier \u2014 waiting for every branch of stage N before starting stage N+1 \u2014 is correct only when stage N+1 genuinely needs the whole set at once: deduplicating across all results before expensive downstream work, deciding literals every later branch must share, early-exit when the total is zero, or a prompt that compares one result against the others.\n\nA barrier is **not** justified by needing to flatten, map, or filter between stages \u2014 do that inside a stage \u2014 nor by the stages feeling conceptually separate, nor by the script reading more cleanly. Each unjustified barrier costs the difference between the slowest branch and the fastest, on every item, for nothing.\n\nEach skill's skeleton already names its own barriers, and they are the load-bearing part of that shape. `implementation-run`'s Decide barrier and `quality-review`'s Adjudicate barrier are the two places the run stops being parallel because a single decision must exist before anything downstream. Do not optimize them away.\n\n## Failures Must Be Loud\n\nFan-out helpers routinely turn a failed branch into an empty result rather than an error. A run that lost three of eight branches then looks like a run that found less, which is indistinguishable from a thorough run over a quiet subject.\n\n- Have each stage **return its failure as a value** \u2014 a result that says it failed and why \u2014 instead of throwing into the helper.\n- Before synthesizing, check the branch count against what you started. A missing branch is a finding.\n- Never report coverage you did not verify. If the run capped, sampled, or dropped anything, say so in the report; silent truncation reads as completeness.\n\n## Model and Effort Assignment\n\nDistribution is the default. Concentrating a run on the model this session happens to run on is the exception, and the exception carries the burden of proof \u2014 the binding rule is in the Orchestration Policy Standing Order. This is the procedure that discharges it.\n\n**Call `gateway_models` first, every time.** Not once per session: allowances move while work is in flight, and a roster entry can be enabled or disabled between two runs.\n\nWork through these in order.\n\n1. **Name the role.** Take it from the skeleton's Role column. If you cannot name it in one word, the stage boundary is wrong; fix the split before choosing a model.\n2. **Name the dominant risk.** What would ruin *this* stage: too little context, unreliable tool use, correlated judgment, drift from repository convention, or incomplete coverage. One risk, not a list.\n3. **Look for a measured fit.** Read `roleFit` for that risk. A declared `fit` is a reason to prefer an identity and a declared `unfit` a reason to avoid it. `null` means unmeasured: it says nothing about quality, and it is never a reason to fall back to the session model.\n4. **Spread the rest by allowance.** For every stage with no measured fit, choose by cost. Read the window that belongs to the model \u2014 the one whose `scope` matches `constraints.quotaScope` when the model declares one, and the provider's scope-less window when it does not \u2014 then send stages toward the lower `usedPercent`. A scope is declared only where one subscription splits into pools; there, and only there, the scope-less figure is a sum that can read healthy while the model's own pool is spent. Move off a provider as it approaches exhaustion instead of discovering it mid-run.\n5. **Re-pick effort for the model you chose.** Ladders differ between identities. A level a model does not advertise is clamped down to the next rung below it with no signal to you, and rejected outright when nothing is below. Take a rung the target's `effortLadder` actually lists, and check the stage's input against the target's `contextWindow`.\n6. **Diversify where disagreement is the product.** A majority-vote or judging stage wants different lineages \u2014 a verifier sharing its subject's lineage inherits the same blind spots. `homolineage: true` marks an identity sharing the parent Claude session's lineage: useful for moving spend, useless for independence.\n7. **Confirm the name exists on both sides.** Roster membership resolves live, but Agent names were fixed when the session started. Pick only a name present in both; a model enabled mid-session is unreachable until restart. `400 unknown model` means re-read the roster.\n8. **Do not choose the load-bearing stage by allowance alone.** When everything downstream rests on one stage \u2014 the contract survey, the final synthesis, the integrating judgment \u2014 let measured fit and lineage independence decide it, and let cost break ties only after those.\n9. **Record the split.** One line per run: which identities carried which stages, and what decided it. A distribution nobody can audit is indistinguishable from a random one.\n\n### What Measurement Actually Showed\n\nTwo measurements, both on 2026-08-02.\n\nThree models against seven stage roles were **indistinguishable on five of them**: structured output, repository search, adversarial judgment, mechanical transformation, and a small implementation task.\n\nTwelve identities were then given one identical mapping task \u2014 twelve files, exact line counts, exact export symbols. **All twelve answered it perfectly**: full coverage, no fabricated file, and every one caught the trap entry whose correct answer was an empty list. What separated them was spend. The cheapest finished on 176k total tokens over 5 tool calls; the most expensive spent 5.20M over 29 for the same answer. Output tokens alone ran 1.7k to 20.3k, so this is not a cache-read artifact.\n\nRead the two together. Quality parity is the prior \u2014 and parity is exactly what makes cost the deciding axis. **Indistinguishable never meant \"inherit\"; it means the expensive choice buys nothing.** The roster declares fit only where a measurement separated the models, and reports `null` everywhere else \u2014 `null` means unmeasured, never unsuitable.\n\n### Rules That Measurement Refuted\n\n- **A larger context window does not mean better reading.** Asked to map a 22-file subsystem, the 1M-window model opened 16 files and a 372k-window model opened all 22. What separated them was thoroughness in tool use, which no catalog field predicts. Use the window as a floor \u2014 can this model hold the input at all \u2014 not as a ranking.\n- **Raising effort does not reliably improve judgment.** The same verification task at the lowest and highest rungs produced the same verdict with equal reasoning quality. Effort pays only once a task is hard enough to need it; raising it by habit buys nothing and costs throughput.\n- **A local, well-precedented edit does not need the session model.** Every model tested landed the change in the right files, found the package's existing export pattern instead of inventing one, and matched the surrounding comment language. This does **not** generalize to sweeping or multi-package work, where convention drift compounds and goes unseen.\n\n### Handing Work to a Different Model\n\nA stage running on another model has no feel for this repository's conventions, so decisions must travel as literal values, not as descriptions. Name the exact token, path, setting key, or constant; never write \"match the existing style\" or \"pick something consistent\". On return, check the artifacts against the literals you sent \u2014 an equivalent-looking substitution is a defect, not a variation.\n\n## Gotchas\n\n- **Symptom:** A run that pinned several models produced uniform-looking results, or one stage's output is missing with no error.\n **Action:** Check whether that branch failed rather than ran. Confirm each pinned id is still in the roster and return branch failures as values instead of letting the helper collapse them.\n **Why:** A de-selected or mistyped id fails at the gateway, but the fan-out helper turns a failed branch into an empty slot, so a heterogeneous run silently becomes a partial one.\n\n- **Symptom:** A stage ran at a different reasoning level than the one requested.\n **Action:** Read that model's ladder from the roster and request a level it actually advertises.\n **Why:** Ladders are not uniform \u2014 some models have no `medium`, others no effort control at all \u2014 and an off-ladder level is clamped upstream without any signal.\n\n- **Symptom:** A provider looked like it had room, but its requests began failing.\n **Action:** Read the window whose `scope` matches the model's `quotaScope`, not the provider's combined figure.\n **Why:** One subscription can bill through separate pools; the sum can read comfortable while the pool a given model draws from is nearly spent.\n\n- **Symptom:** A stage returned nothing at all \u2014 no result, no error you can quote \u2014 while other stages on the same provider succeeded.\n **Action:** Treat a high `usedPercent` on that model's own window as the explanation and move those stages to another provider. Do not wait for a message that says exhausted.\n **Why:** There is no exhaustion status. `status` distinguishes *reading* failures \u2014 not connected, signed out, expired, no subscription, stale, error \u2014 and a spent pool is visible only as `usedPercent` near 100. A stage dying after retries with an empty return is what exhaustion actually looks like from here.\n\n- **Symptom:** A model you just enabled is in `gateway_models` but every attempt to run a stage on it fails as an unknown Agent.\n **Action:** Use only names present in both the live roster and the Agent names this session started with. Reaching a newly enabled model requires a new session.\n **Why:** The roster re-reads the user's selection on every call, but Agent names were serialized once at session start. The two drift apart the moment settings change mid-session.\n\n- **Symptom:** The run took as long as doing it yourself, with the same total cost.\n **Action:** Count the barriers. Each one that no stage actually needed becomes wall-clock spent waiting for the slowest branch.\n **Why:** Staging buys overlap; a skeleton executed as a sequence of barriers pays the coordination cost and collects none of it back.\n\n- **Symptom:** A stage came back asking what to do, or made a choice the skeleton reserved for the host.\n **Action:** Move that decision to the preceding host-only barrier and run the stage again with the value spelled out.\n **Why:** A stage handed an open decision always closes it, differently in each branch \u2014 which is the failure the barrier was placed there to prevent.\n" },
43975
+ { relativePath: "gateway/workflow/SKILL.md", content: "---\nname: workflow\ndescription: Run a staged multi-agent operation \u2014 map a stage skeleton onto the workflow execution surface, choose pipeline or barrier between stages, keep failures visible, and assign each stage its model and reasoning effort. Load before executing any skeleton from architecture-review, codebase-research, implementation-run, or quality-review, and before pinning a model or effort anywhere. Skip when the work is one stage you will perform directly.\n---\n\n# Workflow\n\nThe other gateway skills each own the *shape* of a run \u2014 which stages exist, what each returns, where the judgment stays on the host. This skill owns **turning that shape into an actual run**: the surface it executes on, how stages are wired to each other, and what each stage runs on.\n\nA skeleton that is never executed as stages is not a cheaper version of the run. It is a single reader doing every job in one context, which is the failure mode the skeleton exists to prevent.\n\n## Execution Surface\n\nStaged execution requires the workflow execution surface \u2014 the one that runs a script of stages, wires them together, and lets each stage carry its own model and effort. Inspect the live tool surface before concluding anything about it; tools may be lazy-loaded.\n\nThis skill covers that surface only, and that surface is not the default. An Agent \u2014 one run, or a named teammate you can continue \u2014 carries work that needs no wiring between its parts, and the Orchestration Policy Standing Order makes it the default for exactly that reason. A staged run is what the user asks for on top of it, and what it buys is the wiring: data flowing between stages, barriers, fan-out, and a fleet of different models working the same problem at once. Model and effort assignment below applies to both surfaces.\n\n**A surface gated behind user opt-in is unavailable until that opt-in exists.** Some workflow surfaces refuse to run unless the user explicitly asked for a multi-agent run. That refusal is not a defect and it is not a reason to quietly do the whole thing yourself in one context. Report the gate, say what the staged run would cost and what it would buy, and wait \u2014 the same way you would report any unavailable surface.\n\nTwo things stay out of this skill on purpose:\n\n- **Call mechanics.** Argument names, script syntax, return shapes, and which values a field accepts live in the live tool description. Read them there every time. Anything restated here would be a copy that goes stale silently.\n- **Whether to run at all.** That is the Orchestration Policy Standing Order's call, not this skill's.\n\n## Reading a Stage Skeleton\n\nEvery gateway skeleton is a table of `Stage | Role | Fan | Returns`. Read it as an execution plan:\n\n- **Role** is the one-word job \u2014 map, propose, implement, verify, synthesize, transform. It is also the input to model assignment below.\n- **Fan** is how many parallel branches that stage runs. `1` is one branch. `one per <item>` is a fan-out sized by the previous stage's output, not by a number you pick. **`host only` is not a stage you hand off** \u2014 it is a barrier where you do the work yourself, and handing it off defeats the skeleton.\n- **Returns** is the contract. When a stage returns structured data, declare the schema rather than parsing prose; a stage that must fill a shape will retry against it, while a stage asked to write prose will improvise.\n\n## Pipeline by Default\n\nBetween two stages, the choice is pipeline or barrier, and **pipeline is the default**.\n\nA barrier \u2014 waiting for every branch of stage N before starting stage N+1 \u2014 is correct only when stage N+1 genuinely needs the whole set at once: deduplicating across all results before expensive downstream work, deciding literals every later branch must share, early-exit when the total is zero, or a prompt that compares one result against the others.\n\nA barrier is **not** justified by needing to flatten, map, or filter between stages \u2014 do that inside a stage \u2014 nor by the stages feeling conceptually separate, nor by the script reading more cleanly. Each unjustified barrier costs the difference between the slowest branch and the fastest, on every item, for nothing.\n\nEach skill's skeleton already names its own barriers, and they are the load-bearing part of that shape. `implementation-run`'s Decide barrier and `quality-review`'s Adjudicate barrier are the two places the run stops being parallel because a single decision must exist before anything downstream. Do not optimize them away.\n\n## Failures Must Be Loud\n\nFan-out helpers routinely turn a failed branch into an empty result rather than an error. A run that lost three of eight branches then looks like a run that found less, which is indistinguishable from a thorough run over a quiet subject.\n\n- Have each stage **return its failure as a value** \u2014 a result that says it failed and why \u2014 instead of throwing into the helper.\n- Before synthesizing, check the branch count against what you started. A missing branch is a finding.\n- Never report coverage you did not verify. If the run capped, sampled, or dropped anything, say so in the report; silent truncation reads as completeness.\n\n## Model and Effort Assignment\n\nDistribution is the default. Concentrating a run on the model this session happens to run on is the exception, and the exception carries the burden of proof \u2014 the binding rule is in the Orchestration Policy Standing Order. This is the procedure that discharges it.\n\n**Call `gateway_models` first, every time.** Not once per session: allowances move while work is in flight, and a roster entry can be enabled or disabled between two runs.\n\nWork through these in order.\n\n1. **Name the role.** Take it from the skeleton's Role column. If you cannot name it in one word, the stage boundary is wrong; fix the split before choosing a model.\n2. **Name the dominant risk.** What would ruin *this* stage: too little context, unreliable tool use, correlated judgment, drift from repository convention, or incomplete coverage. One risk, not a list.\n3. **Look for a measured fit.** Read `roleFit` for that risk. A declared `fit` is a reason to prefer an identity and a declared `unfit` a reason to avoid it. `null` means unmeasured: it says nothing about quality, and it is never a reason to fall back to the session model.\n4. **Spread the rest by allowance.** For every stage with no measured fit, choose by cost. Read the window that belongs to the model \u2014 the one whose `scope` matches `constraints.quotaScope` when the model declares one, and the provider's scope-less window when it does not \u2014 and let the roster's own verdict lead: prefer windows at `pressure: \"ok\"`, treat `\"elevated\"` as a reason to route elsewhere, and send nothing to `\"critical\"` unless every alternative is worse. Break a tie between windows that share a `cadence` by the lower `usedPercent`, and never compare percentages across cadences \u2014 a weekly window at 49% early in its week burns hotter than a monthly one at 78% near its reset, and `paceRatio` above 1.0 says so directly. On an older reading that carries none of the derived fields, treat percentages as comparable only within a single provider's windows \u2014 a shared id like `cycle` does not mean a shared length \u2014 and across providers trust only the extreme: a window near 100 is spent whatever its clock. A scope is declared only where one subscription splits into pools; there the scope-less figure is marked `isAggregate` \u2014 a sum that can read healthy while the model's own pool is spent, and one that stays out of headroom math. Move off a provider as its windows go elevated instead of discovering exhaustion mid-run.\n5. **Re-pick effort for the model you chose.** Ladders differ between identities. A level a model does not advertise is clamped down to the next rung below it with no signal to you, and rejected outright when nothing is below. Take a rung the target's `effortLadder` actually lists, and check the stage's input against the target's `contextWindow`.\n6. **Diversify where disagreement is the product.** A majority-vote or judging stage wants different lineages \u2014 a verifier sharing its subject's lineage inherits the same blind spots. `homolineage: true` marks an identity sharing the parent Claude session's lineage: useful for moving spend, useless for independence.\n7. **Confirm the name exists on both sides.** Roster membership resolves live, but Agent names were fixed when the session started. Pick only a name present in both; a model enabled mid-session is unreachable until restart. `400 unknown model` means re-read the roster.\n8. **Do not choose the load-bearing stage by allowance alone.** When everything downstream rests on one stage \u2014 the contract survey, the final synthesis, the integrating judgment \u2014 let measured fit and lineage independence decide it, and let cost break ties only after those.\n9. **Record the split.** One line per run: which identities carried which stages, and what decided it. A distribution nobody can audit is indistinguishable from a random one.\n\n### What Measurement Actually Showed\n\nTwo measurements, both on 2026-08-02.\n\nThree models against seven stage roles were **indistinguishable on five of them**: structured output, repository search, adversarial judgment, mechanical transformation, and a small implementation task.\n\nTwelve identities were then given one identical mapping task \u2014 twelve files, exact line counts, exact export symbols. **All twelve answered it perfectly**: full coverage, no fabricated file, and every one caught the trap entry whose correct answer was an empty list. What separated them was spend. The cheapest finished on 176k total tokens over 5 tool calls; the most expensive spent 5.20M over 29 for the same answer. Output tokens alone ran 1.7k to 20.3k, so this is not a cache-read artifact.\n\nRead the two together. Quality parity is the prior \u2014 and parity is exactly what makes cost the deciding axis. **Indistinguishable never meant \"inherit\"; it means the expensive choice buys nothing.** The roster declares fit only where a measurement separated the models, and reports `null` everywhere else \u2014 `null` means unmeasured, never unsuitable.\n\n### Rules That Measurement Refuted\n\n- **A larger context window does not mean better reading.** Asked to map a 22-file subsystem, the 1M-window model opened 16 files and a 372k-window model opened all 22. What separated them was thoroughness in tool use, which no catalog field predicts. Use the window as a floor \u2014 can this model hold the input at all \u2014 not as a ranking.\n- **Raising effort does not reliably improve judgment.** The same verification task at the lowest and highest rungs produced the same verdict with equal reasoning quality. Effort pays only once a task is hard enough to need it; raising it by habit buys nothing and costs throughput.\n- **A local, well-precedented edit does not need the session model.** Every model tested landed the change in the right files, found the package's existing export pattern instead of inventing one, and matched the surrounding comment language. This does **not** generalize to sweeping or multi-package work, where convention drift compounds and goes unseen.\n\n### Handing Work to a Different Model\n\nA stage running on another model has no feel for this repository's conventions, so decisions must travel as literal values, not as descriptions. Name the exact token, path, setting key, or constant; never write \"match the existing style\" or \"pick something consistent\". On return, check the artifacts against the literals you sent \u2014 an equivalent-looking substitution is a defect, not a variation.\n\n## Gotchas\n\n- **Symptom:** A run that pinned several models produced uniform-looking results, or one stage's output is missing with no error.\n **Action:** Check whether that branch failed rather than ran. Confirm each pinned id is still in the roster and return branch failures as values instead of letting the helper collapse them.\n **Why:** A de-selected or mistyped id fails at the gateway, but the fan-out helper turns a failed branch into an empty slot, so a heterogeneous run silently becomes a partial one.\n\n- **Symptom:** A stage ran at a different reasoning level than the one requested.\n **Action:** Read that model's ladder from the roster and request a level it actually advertises.\n **Why:** Ladders are not uniform \u2014 some models have no `medium`, others no effort control at all \u2014 and an off-ladder level is clamped upstream without any signal.\n\n- **Symptom:** A provider looked like it had room, but its requests began failing.\n **Action:** Read the window whose `scope` matches the model's `quotaScope`, not the provider's combined figure.\n **Why:** One subscription can bill through separate pools; the sum can read comfortable while the pool a given model draws from is nearly spent.\n\n- **Symptom:** A stage returned nothing at all \u2014 no result, no error you can quote \u2014 while other stages on the same provider succeeded.\n **Action:** Treat a `\"critical\"` pressure \u2014 or a `usedPercent` near 100 \u2014 on that model's own window as the explanation and move those stages to another provider. Do not wait for a message that says exhausted.\n **Why:** There is no exhaustion status. `status` distinguishes *reading* failures \u2014 not connected, signed out, expired, no subscription, stale, error \u2014 and a spent pool is visible only in its own window's figures. A stage dying after retries with an empty return is what exhaustion actually looks like from here.\n\n- **Symptom:** A model you just enabled is in `gateway_models` but every attempt to run a stage on it fails as an unknown Agent.\n **Action:** Use only names present in both the live roster and the Agent names this session started with. Reaching a newly enabled model requires a new session.\n **Why:** The roster re-reads the user's selection on every call, but Agent names were serialized once at session start. The two drift apart the moment settings change mid-session.\n\n- **Symptom:** The run took as long as doing it yourself, with the same total cost.\n **Action:** Count the barriers. Each one that no stage actually needed becomes wall-clock spent waiting for the slowest branch.\n **Why:** Staging buys overlap; a skeleton executed as a sequence of barriers pays the coordination cost and collects none of it back.\n\n- **Symptom:** A stage came back asking what to do, or made a choice the skeleton reserved for the host.\n **Action:** Move that decision to the preceding host-only barrier and run the stage again with the value spelled out.\n **Why:** A stage handed an open decision always closes it, differently in each branch \u2014 which is the failure the barrier was placed there to prevent.\n" },
43813
43976
  { relativePath: "protocol-baseline/SKILL.md", content: "---\nname: protocol-baseline\ndescription: Use the compact Fleet protocol mode for simple, reversible, single-surface work.\n---\n\n# Fleet Protocol: Baseline\n\nUse this mode only for simple, reversible, single-surface operational work.\n\nAt any point during the work, if a Downward Guard trigger appears, stop and re-classify.\n\n## Checkpoints\n\nNone. Selecting baseline implies Mission Anchor Compact Mode.\n\n## Reporting Cadence\n\nAs you move through this protocol, report progress to the user in order.\n\n1. Brief in one line how the Procedure will proceed. \u2192 report `brief: <\u2026>`\n2. State that execution is beginning and run the Procedure. \u2192 report `status: executing`\n\n## General Quarters\n\nConfirm each readiness check below before the Procedure. Work through them in order and report each as you confirm it, then proceed to the Objective anchor. These checks prepare the work; they do not gate entry.\n\n- [ ] **Common** \u2014 objective stated (Mission Anchor), mode-fit holds (Mode Gate), Standing Orders binding. \u2192 report `common: ready`\n- [ ] **Single surface** \u2014 the exact file, command, or fact is identified. \u2192 report `surface: <x>`\n- [ ] **Reversibility** \u2014 the change is trivially reversible. \u2192 report `reversible: yes`\n\n## Procedure\n\n1. Objective statement: state the Mission Anchor objective in one line.\n2. Exact fact/file verification: verify the exact file, command, or fact needed for the request.\n3. Execution: make the smallest reversible change or run the exact requested command.\n4. Result verification: check the touched surface or command result.\n5. One-line report: report what changed, verification, and any skipped escalation trigger.\n" },
43814
43977
  { relativePath: "protocol-frontline/SKILL.md", content: "---\nname: protocol-frontline\ndescription: Use the coordinated Fleet protocol mode for multi-carrier or parallel ownership work.\n---\n\n# Fleet Protocol: Frontline\n\nUse this mode when operational work requires multiple Carriers, independent parallel workstreams, cross-carrier review loops, or file ownership coordination. If the work is high risk but single-owner, use `protocol-redline` instead.\n\n## Checkpoints\n\nDecomposition, Dispatch, Integration, Verification.\n\n## Reporting Cadence\n\nAs you move through this protocol, report progress to the user in order \u2014 each step on its own line with its report token.\n\n1. Brief how the Procedure will proceed \u2014 name (a) the Procedure steps that will run, (b) each carrier's file or responsibility ownership, and (c) the dispatch wave sequencing. \u2192 report `brief: <\u2026>`\n2. State that execution is beginning and run the Procedure. \u2192 report `status: executing`\n\n## General Quarters\n\nConfirm each readiness check below before the Procedure. Work through them in order and report each as you confirm it, then proceed to reconnaissance and decomposition. These checks prepare the work; they do not gate entry.\n\n- [ ] **Common** \u2014 objective stated (Mission Anchor), mode-fit holds (Mode Gate), Standing Orders binding. \u2192 report `common: ready`\n- [ ] **Impact radius** \u2014 flag public-surface or API impact, irreversibility, and any security-sensitive surface. \u2192 report `impact: <\u2026>`\n- [ ] **Rollback** \u2014 identify a rollback-safe checkpoint and any user approval point before execution begins. \u2192 report `rollback: <\u2026>`\n- [ ] **Carrier availability** \u2014 confirm the intended carriers are actually exposed and available this session. \u2192 report `carriers: <\u2026>`\n- [ ] **Ownership** \u2014 pre-sketch each carrier's file or responsibility boundary. \u2192 report `ownership: <\u2026>`\n- [ ] **Shared resources** \u2014 flag shared mutable resources (same files, lock files, or a singleton test environment). \u2192 report `shared: <\u2026|none>`\n- [ ] **Dependencies** \u2014 pre-classify parallel versus sequential work before decomposition and dispatch. \u2192 report `dependencies: <parallel|sequenced: \u2026>`\n\n## Procedure\n\n1. Reconnaissance and decomposition: audit known facts, identify gaps, map affected surfaces, and split work into independently verifiable missions.\n2. Ownership graph: assign each Carrier a clear file or responsibility boundary, note dependencies, and identify shared mutable resources.\n3. Host-authored structured planning boundary: `Apply the Context Confidence Standing Order \u2014 entry requires complete`. Resolve all blocking and confirmatory gaps before the host authors the dispatch plan.\n4. Parallel dispatch: use the `carrier-operations` skill's sequencing rules to launch independent Carrier work in parallel; sequence only for explicit dependencies or shared resources.\n5. Integration: re-read files before editing or accepting Carrier output, reconcile overlaps, and preserve unrelated user or Carrier changes.\n6. Cross-carrier review loop: route implementation outputs to review Carriers, send actionable findings back to owners, and re-review changed surfaces.\n7. Verification: run integrated tests and apply Deep Dive to speculative or conflicting Carrier claims.\n8. Documentation and completion report: update directly affected docs and report executed waves, Carrier ownership, QA, unresolved risks, and final Result Integrity checks.\n\n## Cross-Carrier Feedback Patterns\n\nWhen composing waves and review loops, select the structured feedback pattern that fits the task:\n\n| Pattern | Flow | When |\n|---------|------|------|\n| **Build \u2192 Review** | implementation carrier \u2192 review carrier \u2192 findings back to implementation carrier \u2192 re-review | Standard implementation cycle |\n| **Analyze \u2192 Execute** | implementation or refactoring carrier \u2192 review carrier verifies | Refactoring workflow |\n| **Decide \u2192 Host Planning \u2192 Execute** | optional judgment carrier \u2192 host-authored plan \u2192 execution carrier | Complex features |\n| **Research \u2192 Act** | reconnaissance carrier \u2192 appropriate follow-up carrier from the active roster | Unknown scope tasks |\n" },
43815
43978
  { relativePath: "protocol-midline/SKILL.md", content: "---\nname: protocol-midline\ndescription: Use the normal Fleet protocol mode for bounded operational work without downward-guard triggers.\n---\n\n# Fleet Protocol: Midline\n\nUse this mode for ordinary bounded operational work.\n\nAt any point during the work, if a Downward Guard trigger appears, stop and re-classify.\n\n## Checkpoints\n\nReconnaissance, Plan, Execution, Verification.\n\n## Reporting Cadence\n\nAs you move through this protocol, report progress to the user in order \u2014 each step on its own line with its report token.\n\n1. Brief how the Procedure will proceed \u2014 name (a) the Procedure steps that will run, (b) the target surfaces, and (c) the verification command. \u2192 report `brief: <\u2026>`\n2. State that execution is beginning and run the Procedure. \u2192 report `status: executing`\n\n## General Quarters\n\nConfirm each readiness check below before the Procedure. Work through them in order and report each as you confirm it, then proceed to focused reconnaissance. These checks prepare the work; they do not gate entry.\n\n- [ ] **Common** \u2014 objective stated (Mission Anchor), mode-fit holds (Mode Gate), Standing Orders binding. \u2192 report `common: ready`\n- [ ] **Target surfaces** \u2014 provisionally name the minimal modules or files reconnaissance will touch; confirm or revise in the brief after reconnaissance. \u2192 report `surfaces: <\u2026>`\n- [ ] **Verification** \u2014 provisionally pre-load the test, build, or check command that will prove the work done; confirm or revise in the brief after reconnaissance. \u2192 report `verify: <cmd>`\n- [ ] **Carrier** \u2014 declare whether a carrier dispatch is needed. \u2192 report `carrier: <none|\u2026>`\n\n## Procedure\n\n1. Focused reconnaissance: audit known facts, identify blocking and confirmatory gaps, and inspect the minimal relevant surfaces.\n2. Host-authored planning boundary: `Apply the Context Confidence Standing Order \u2014 entry requires sufficient`. Resolve all blocking gaps before the host plans.\n3. Host-authored inline plan: state objective, targets, execution steps, and done criteria.\n4. Execution: implement the plan in narrow batches, using Carrier Operations Policy when delegation is appropriate.\n5. Verification and review: run targeted checks, apply Deep Dive to speculative results, and fix actionable issues.\n6. Documentation and final report: update directly affected docs only when behavior or operator guidance changed, then summarize changes and QA.\n" },
@@ -43999,6 +44162,16 @@ function claudeHooks(options) {
43999
44162
  const userPromptSubmitExecs = [options.captureSessionHookExec, options.turnStartHookExec, options.autoNameHookExec].filter((exec2) => exec2 !== void 0);
44000
44163
  const stopExecs = [options.turnEndHookExec].filter((exec2) => exec2 !== void 0);
44001
44164
  const inputWaitingExec = options.inputWaitingHookExec;
44165
+ const preToolUse = [
44166
+ ...inputWaitingExec ? [{
44167
+ matcher: "AskUserQuestion",
44168
+ hooks: [claudeCommandHook(inputWaitingExec)]
44169
+ }] : [],
44170
+ ...options.backgroundSpawnHookExec ? [{
44171
+ matcher: "Task|Agent|Workflow",
44172
+ hooks: [claudeCommandHook(options.backgroundSpawnHookExec)]
44173
+ }] : []
44174
+ ];
44002
44175
  return {
44003
44176
  hooks: {
44004
44177
  ...userPromptSubmitExecs.length > 0 ? {
@@ -44011,15 +44184,17 @@ function claudeHooks(options) {
44011
44184
  hooks: stopExecs.map(claudeCommandHook)
44012
44185
  }]
44013
44186
  } : {},
44187
+ ...preToolUse.length > 0 ? { PreToolUse: preToolUse } : {},
44014
44188
  ...inputWaitingExec ? {
44015
- PreToolUse: [{
44016
- matcher: "AskUserQuestion",
44017
- hooks: [claudeCommandHook(inputWaitingExec)]
44018
- }],
44019
44189
  Notification: [{
44020
44190
  matcher: "permission_prompt|elicitation_dialog",
44021
44191
  hooks: [claudeCommandHook(inputWaitingExec)]
44022
44192
  }]
44193
+ } : {},
44194
+ ...options.backgroundStopHookExec ? {
44195
+ SubagentStop: [{
44196
+ hooks: [claudeCommandHook(options.backgroundStopHookExec)]
44197
+ }]
44023
44198
  } : {}
44024
44199
  }
44025
44200
  };
@@ -44267,6 +44442,8 @@ async function injectAgentCliProfile(profile, options) {
44267
44442
  turnStartHookExec: options.turnStartHookExec,
44268
44443
  turnEndHookExec: options.turnEndHookExec,
44269
44444
  inputWaitingHookExec: options.inputWaitingHookExec,
44445
+ backgroundSpawnHookExec: options.backgroundSpawnHookExec,
44446
+ backgroundStopHookExec: options.backgroundStopHookExec,
44270
44447
  autoNameHookExec: options.autoNameHookExec,
44271
44448
  withMarketplaceLock: options.withMarketplaceLock
44272
44449
  });
@@ -51351,6 +51528,9 @@ function createTerminalSessionManager(deps) {
51351
51528
  touchActivity(session);
51352
51529
  session.pty.resize(session.cols, session.rows);
51353
51530
  replayScrollback(session, socket);
51531
+ if (socket.readyState === WS_OPEN_STATE) {
51532
+ socket.send(Buffer.from(JSON.stringify({ type: "replay_end" }), "utf8"), { binary: false });
51533
+ }
51354
51534
  socket.on("message", (data, isBinary) => handleSocketMessage(session, data, isBinary));
51355
51535
  socket.once("close", () => detachSocket(session, socket));
51356
51536
  }
@@ -51485,13 +51665,15 @@ function createTerminalSessionManager(deps) {
51485
51665
  function handlePtyData(session, data) {
51486
51666
  touchActivity(session);
51487
51667
  const buffer = Buffer.from(data, "utf8");
51488
- respondToTerminalQueries(session, buffer);
51668
+ const liveSocket = session.activeSocket?.readyState === WS_OPEN_STATE ? session.activeSocket : null;
51669
+ const queryResponses = scanTerminalQueries(session, buffer);
51670
+ if (!liveSocket) {
51671
+ for (const response of queryResponses) writeTerminalQueryResponse(session, response);
51672
+ }
51489
51673
  observeOscTitles(session, buffer);
51490
51674
  session.scrollback.push(buffer);
51491
51675
  while (session.scrollback.length > scrollbackLimit) session.scrollback.shift();
51492
- if (session.activeSocket && session.activeSocket.readyState === WS_OPEN_STATE) {
51493
- session.activeSocket.send(buffer, { binary: true });
51494
- }
51676
+ liveSocket?.send(buffer, { binary: true });
51495
51677
  }
51496
51678
  function observeOscTitles(session, buffer) {
51497
51679
  if (!session.titleParser || !session.titleListener) return;
@@ -51637,8 +51819,9 @@ function clearGraceTimer(session) {
51637
51819
  clearTimeout(session.graceTimer);
51638
51820
  session.graceTimer = null;
51639
51821
  }
51640
- function respondToTerminalQueries(session, buffer) {
51822
+ function scanTerminalQueries(session, buffer) {
51641
51823
  const text2 = `${session.terminalQueryResidual}${buffer.toString("utf8")}`;
51824
+ const responses = [];
51642
51825
  session.terminalQueryResidual = "";
51643
51826
  let cursor = 0;
51644
51827
  while (cursor < text2.length) {
@@ -51652,9 +51835,11 @@ function respondToTerminalQueries(session, buffer) {
51652
51835
  session.terminalQueryResidual = trimTerminalQueryResidual(text2.slice(start));
51653
51836
  break;
51654
51837
  }
51655
- writeTerminalQueryResponse(session, resolveTerminalQueryResponse(session, text2.slice(start, end + 1)));
51838
+ const response = resolveTerminalQueryResponse(session, text2.slice(start, end + 1));
51839
+ if (response) responses.push(response);
51656
51840
  cursor = end + 1;
51657
51841
  }
51842
+ return responses;
51658
51843
  }
51659
51844
  function readTrailingEscape(text2) {
51660
51845
  return text2.endsWith(ANSI_ESCAPE) ? ANSI_ESCAPE : "";
@@ -51926,12 +52111,11 @@ function hashablePath(entry) {
51926
52111
  return filePath;
51927
52112
  }
51928
52113
  var AGENT_CLI_PATHS_STORAGE_KEY = "agent-cli-paths";
51929
- var AGENT_CLI_COMMANDS = ["claude", "cursor-agent"];
51930
- var STORED_AGENT_CLI_COMMANDS = ["claude", "codex", "cursor-agent"];
52114
+ var AGENT_CLI_COMMANDS = ["claude"];
52115
+ var STORED_AGENT_CLI_COMMANDS = ["claude", "codex"];
51931
52116
  var OVERRIDE_ENV_BY_COMMAND = {
51932
52117
  claude: "CLAUDE_BIN",
51933
- codex: "CODEX_BIN",
51934
- "cursor-agent": "CURSOR_AGENT_BIN"
52118
+ codex: "CODEX_BIN"
51935
52119
  };
51936
52120
  var agentCliPathsWriteTail = Promise.resolve();
51937
52121
  function createAgentCliPathStore(storage, pluginId) {
@@ -51960,7 +52144,7 @@ function serializeAgentCliPathsWrite(write) {
51960
52144
  return result;
51961
52145
  }
51962
52146
  function normalizeAgentCliPaths(value) {
51963
- if (!isRecord7(value) || value.version !== 1 || !isRecord7(value.paths)) {
52147
+ if (!isRecord8(value) || value.version !== 1 || !isRecord8(value.paths)) {
51964
52148
  return { version: 1, paths: {} };
51965
52149
  }
51966
52150
  const paths = {};
@@ -52006,7 +52190,6 @@ function validateUserAgentCliPath(executablePath, env, platform = process.platfo
52006
52190
  }
52007
52191
  function agentCliCommandForId(cliId) {
52008
52192
  if (cliId === "claude" || cliId === "claude-native" || cliId === "claude-gateway") return "claude";
52009
- if (cliId === "cursor") return "cursor-agent";
52010
52193
  return null;
52011
52194
  }
52012
52195
  function applyAgentCliPathEnvOverlay(env, cliId, userPaths) {
@@ -52084,7 +52267,7 @@ function readPathEntries(env, platform) {
52084
52267
  const separator = platform === "win32" ? ";" : path23__default.delimiter;
52085
52268
  return value.split(separator).filter((entry) => entry.length > 0);
52086
52269
  }
52087
- function isRecord7(value) {
52270
+ function isRecord8(value) {
52088
52271
  return typeof value === "object" && value !== null && !Array.isArray(value);
52089
52272
  }
52090
52273
  function isNodeError(error51) {
@@ -52093,8 +52276,7 @@ function isNodeError(error51) {
52093
52276
 
52094
52277
  // ../fleet-plugins/terminal/server/agent-api/agent-cli-detect.ts
52095
52278
  var BINARY_DISPLAY_NAMES = {
52096
- claude: "Claude Code",
52097
- "cursor-agent": "Cursor Agent"
52279
+ claude: "Claude Code"
52098
52280
  };
52099
52281
  var VERSION_PROBE_TIMEOUT_MS = 5e3;
52100
52282
  var SEMVER_PATTERN = /(\d+\.\d+\.\d+)/;
@@ -52241,6 +52423,36 @@ async function readConsoleQuotaSnapshot(origin, fetchImpl = fetch) {
52241
52423
  function record3(value) {
52242
52424
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
52243
52425
  }
52426
+ var AMOUNT_PATTERN = /^\d{1,15}$/;
52427
+ var MAX_WINDOW_DURATION_MS = 400 * 24 * 36e5;
52428
+ var DURATION_BASES = /* @__PURE__ */ new Set(["upstream", "catalog"]);
52429
+ var START_BASES = /* @__PURE__ */ new Set(["upstream", "derived"]);
52430
+ function toWindowPeriod(value) {
52431
+ const period = record3(value);
52432
+ if (!period) return void 0;
52433
+ const durationMs = period.durationMs;
52434
+ const durationBasis = period.durationBasis;
52435
+ if (typeof durationMs !== "number" || !Number.isFinite(durationMs) || durationMs <= 0) return void 0;
52436
+ if (durationMs > MAX_WINDOW_DURATION_MS) return void 0;
52437
+ if (typeof durationBasis !== "string" || !DURATION_BASES.has(durationBasis)) return void 0;
52438
+ const startsAt = typeof period.startsAt === "number" && Number.isFinite(period.startsAt) ? period.startsAt : void 0;
52439
+ const startsAtBasis = typeof period.startsAtBasis === "string" && START_BASES.has(period.startsAtBasis) ? period.startsAtBasis : void 0;
52440
+ return {
52441
+ durationMs,
52442
+ durationBasis,
52443
+ ...startsAt !== void 0 ? { startsAt } : {},
52444
+ ...startsAt !== void 0 && startsAtBasis !== void 0 ? { startsAtBasis } : {}
52445
+ };
52446
+ }
52447
+ function toWindowAmounts(value) {
52448
+ const amounts = record3(value);
52449
+ if (!amounts) return void 0;
52450
+ const used = amounts.used;
52451
+ const limit = amounts.limit;
52452
+ if (typeof used !== "string" || typeof limit !== "string") return void 0;
52453
+ if (!AMOUNT_PATTERN.test(used) || !AMOUNT_PATTERN.test(limit)) return void 0;
52454
+ return { used, limit };
52455
+ }
52244
52456
  function toQuotaSnapshot(payload) {
52245
52457
  const providers = record3(record3(payload)?.providers);
52246
52458
  if (!providers) return void 0;
@@ -52251,11 +52463,17 @@ function toQuotaSnapshot(payload) {
52251
52463
  const windows = Array.isArray(provider.windows) ? provider.windows.flatMap((entry) => {
52252
52464
  const window = record3(entry);
52253
52465
  if (!window || typeof window.id !== "string" || typeof window.usedPercent !== "number") return [];
52466
+ const period = toWindowPeriod(window.period);
52467
+ const amounts = toWindowAmounts(window.amounts);
52254
52468
  return [{
52255
52469
  id: window.id,
52256
52470
  ...typeof window.scope === "string" ? { scope: window.scope } : {},
52471
+ ...typeof window.label === "string" ? { label: window.label } : {},
52257
52472
  usedPercent: window.usedPercent,
52258
- ...typeof window.resetsAt === "number" ? { resetsAt: window.resetsAt } : {}
52473
+ ...typeof window.resetsAt === "number" ? { resetsAt: window.resetsAt } : {},
52474
+ ...period ? { period } : {},
52475
+ ...window.isAggregate === true ? { isAggregate: true } : {},
52476
+ ...amounts ? { amounts } : {}
52259
52477
  }];
52260
52478
  }) : void 0;
52261
52479
  snapshot[id] = {
@@ -52270,8 +52488,8 @@ function toQuotaSnapshot(payload) {
52270
52488
  // ../fleet-plugins/terminal/server/ai-gateway-settings.ts
52271
52489
  var AI_GATEWAY_SETTINGS_STORAGE_KEY = "ai-gateway";
52272
52490
  function normalizeAiGatewaySettings(value) {
52273
- if (!isRecord8(value) || value.version !== 1) return { version: 1 };
52274
- const models = Array.isArray(value.models) ? value.models.filter((entry) => isRecord8(entry) && typeof entry.id === "string" && entry.id.length > 0).map((entry) => ({ id: entry.id })) : [];
52491
+ if (!isRecord9(value) || value.version !== 1) return { version: 1 };
52492
+ const models = Array.isArray(value.models) ? value.models.filter((entry) => isRecord9(entry) && typeof entry.id === "string" && entry.id.length > 0).map((entry) => ({ id: entry.id })) : [];
52275
52493
  const defaultModel = typeof value.defaultModel === "string" && value.defaultModel.length > 0 ? value.defaultModel : void 0;
52276
52494
  return {
52277
52495
  version: 1,
@@ -52316,7 +52534,7 @@ function serializeAiGatewaySettingsWrite(write) {
52316
52534
  );
52317
52535
  return result;
52318
52536
  }
52319
- function isRecord8(value) {
52537
+ function isRecord9(value) {
52320
52538
  return typeof value === "object" && value !== null && !Array.isArray(value);
52321
52539
  }
52322
52540
  function resolveAiGatewaySelection(settings2) {
@@ -52481,6 +52699,9 @@ var MARKETPLACE_LOCK_DIR_SUFFIX = ".lock";
52481
52699
  function buildConsoleTurnHookCommand(entry, phase) {
52482
52700
  return buildConsoleCliHookExec(entry, ["hook", phase === "start" ? "turn-start" : "turn-end"]);
52483
52701
  }
52702
+ function buildConsoleBackgroundHookCommand(entry, kind) {
52703
+ return buildConsoleCliHookExec(entry, ["hook", kind === "spawn" ? "background-spawn" : "background-stop"]);
52704
+ }
52484
52705
  function buildConsoleAttentionHookCommand(entry) {
52485
52706
  return buildConsoleCliHookExec(entry, ["hook", "attention"]);
52486
52707
  }
@@ -52638,6 +52859,8 @@ async function createAgentCliLaunchSpec(options) {
52638
52859
  ),
52639
52860
  turnStartHookExec: buildConsoleTurnHookCommand(options.hookEntry, "start"),
52640
52861
  turnEndHookExec: buildConsoleTurnHookCommand(options.hookEntry, "end"),
52862
+ backgroundSpawnHookExec: buildConsoleBackgroundHookCommand(options.hookEntry, "spawn"),
52863
+ backgroundStopHookExec: buildConsoleBackgroundHookCommand(options.hookEntry, "stop"),
52641
52864
  inputWaitingHookExec: buildConsoleAttentionHookCommand(options.hookEntry),
52642
52865
  autoNameHookExec: buildConsoleAutoNameHookCommand(options.hookEntry),
52643
52866
  onCleanup: (cleanup) => cleanupStack.push(cleanup),
@@ -52790,6 +53013,7 @@ var TENANT_EVENT_LIMIT = 1e3;
52790
53013
  var TENANT_FINALIZED_JOB_LIMIT = 100;
52791
53014
  var TENANT_JOB_LIMIT = 200;
52792
53015
  var EVENT_TEXT_RETENTION_LIMIT = 8192;
53016
+ var BACKGROUND_PENDING_TTL_MS = 30 * 6e4;
52793
53017
  var REDACTED_REQUEST_PATH = "[redacted path]";
52794
53018
  function createConsoleObservabilityStore(deps = {}) {
52795
53019
  const now = deps.now ?? Date.now;
@@ -52899,6 +53123,8 @@ function createConsoleObservabilityStore(deps = {}) {
52899
53123
  const createdAt = input.createdAt ?? now();
52900
53124
  const canonicalCwd = canonicalizeTheaterPath(input.cwd);
52901
53125
  const theaterId = workspaceHash(canonicalCwd);
53126
+ const previous = terminalSessionsById.get(input.sessionId);
53127
+ if (previous) clearTerminalSessionBackgroundPending(previous);
52902
53128
  const state = {
52903
53129
  sessionId: input.sessionId,
52904
53130
  cwd: input.cwd,
@@ -52914,6 +53140,8 @@ function createConsoleObservabilityStore(deps = {}) {
52914
53140
  return toTerminalSessionInfo(state);
52915
53141
  }
52916
53142
  function injectDormantOperation(operation) {
53143
+ const previous = terminalSessionsById.get(operation.sessionId);
53144
+ if (previous) clearTerminalSessionBackgroundPending(previous);
52917
53145
  const state = {
52918
53146
  sessionId: operation.sessionId,
52919
53147
  cwd: operation.cwd,
@@ -52973,6 +53201,9 @@ function createConsoleObservabilityStore(deps = {}) {
52973
53201
  delete session.modelActivity;
52974
53202
  delete session.attentionPending;
52975
53203
  }
53204
+ if (status === "dormant" || status === "closed" || status === "error") {
53205
+ clearTerminalSessionBackgroundPending(session);
53206
+ }
52976
53207
  return toTerminalSessionInfo(session);
52977
53208
  }
52978
53209
  function setTerminalSessionTurnState(sessionId, turnState) {
@@ -52983,6 +53214,25 @@ function createConsoleObservabilityStore(deps = {}) {
52983
53214
  delete session.attentionPending;
52984
53215
  return toTerminalSessionInfo(session);
52985
53216
  }
53217
+ function setTerminalSessionBackgroundEvent(sessionId, event) {
53218
+ const session = terminalSessionsById.get(sessionId);
53219
+ if (!session) return null;
53220
+ if (session.status === "dormant" || session.status === "closed" || session.status === "error") return null;
53221
+ const count = session.backgroundPendingCount ?? 0;
53222
+ session.backgroundPendingCount = event === "spawn" ? count + 1 : Math.max(0, count - 1);
53223
+ if (session.backgroundPendingCount === 0) {
53224
+ clearTerminalSessionBackgroundPending(session);
53225
+ return toTerminalSessionInfo(session);
53226
+ }
53227
+ if (session.backgroundPendingExpiry) clearTimeout(session.backgroundPendingExpiry);
53228
+ session.backgroundPendingExpiry = setTimeout(() => {
53229
+ session.backgroundPendingCount = 0;
53230
+ delete session.backgroundPendingExpiry;
53231
+ notifySessionUpdated(toTerminalSessionInfo(session));
53232
+ }, BACKGROUND_PENDING_TTL_MS);
53233
+ if (typeof session.backgroundPendingExpiry.unref === "function") session.backgroundPendingExpiry.unref();
53234
+ return toTerminalSessionInfo(session);
53235
+ }
52986
53236
  function setTerminalSessionModelActivity(sessionId, modelActivity) {
52987
53237
  const session = terminalSessionsById.get(sessionId);
52988
53238
  if (!session) return null;
@@ -53003,6 +53253,7 @@ function createConsoleObservabilityStore(deps = {}) {
53003
53253
  session.status = "dormant";
53004
53254
  session.providerSession = providerSession;
53005
53255
  delete session.modelActivity;
53256
+ clearTerminalSessionBackgroundPending(session);
53006
53257
  return toTerminalSessionInfo(session);
53007
53258
  }
53008
53259
  function renameTerminalSession(sessionId, rawLabel) {
@@ -53070,7 +53321,14 @@ function createConsoleObservabilityStore(deps = {}) {
53070
53321
  };
53071
53322
  for (const listener of allListeners) listener(event);
53072
53323
  }
53324
+ function clearTerminalSessionBackgroundPending(session) {
53325
+ if (session.backgroundPendingExpiry) clearTimeout(session.backgroundPendingExpiry);
53326
+ delete session.backgroundPendingExpiry;
53327
+ session.backgroundPendingCount = 0;
53328
+ }
53073
53329
  function removeTerminalSession(sessionId) {
53330
+ const session = terminalSessionsById.get(sessionId);
53331
+ if (session) clearTerminalSessionBackgroundPending(session);
53074
53332
  const workspace = workspacesByCliRunId.get(sessionId);
53075
53333
  if (workspace?.terminalSessionId === sessionId) {
53076
53334
  removeWorkspaceIndexes(workspace);
@@ -53079,6 +53337,7 @@ function createConsoleObservabilityStore(deps = {}) {
53079
53337
  return terminalSessionsById.delete(sessionId);
53080
53338
  }
53081
53339
  function clear() {
53340
+ for (const session of terminalSessionsById.values()) clearTerminalSessionBackgroundPending(session);
53082
53341
  workspacesByCliRunId.clear();
53083
53342
  workspacesByRegistrationId.clear();
53084
53343
  eventsByTenant.clear();
@@ -53115,6 +53374,7 @@ function createConsoleObservabilityStore(deps = {}) {
53115
53374
  clearTerminalSessionProviderSession,
53116
53375
  updateTerminalSessionStatus,
53117
53376
  setTerminalSessionTurnState,
53377
+ setTerminalSessionBackgroundEvent,
53118
53378
  setTerminalSessionModelActivity,
53119
53379
  transitionTerminalSessionToDormant,
53120
53380
  removeTerminalSession,
@@ -53189,6 +53449,7 @@ function toTerminalSessionInfo(state) {
53189
53449
  turnState: state.turnState ?? "none",
53190
53450
  ...state.modelActivity ? { modelActivity: state.modelActivity } : {},
53191
53451
  ...state.attentionPending === true ? { attentionPending: true } : {},
53452
+ ...state.backgroundPendingCount && state.backgroundPendingCount > 0 ? { backgroundPending: true } : {},
53192
53453
  createdAt: state.createdAt,
53193
53454
  theaterId: state.theaterId,
53194
53455
  registrationId: state.registrationId,
@@ -53693,6 +53954,7 @@ function sweepIdleAgentSessions(deps) {
53693
53954
  if (session.status !== "registered" && session.status !== "terminal-only") continue;
53694
53955
  if (session.modelActivity === "working") continue;
53695
53956
  if (session.modelActivity === void 0 && session.turnState === "running") continue;
53957
+ if (session.backgroundPending === true) continue;
53696
53958
  if (deps.hasActiveCarrierJob(session.sessionId)) continue;
53697
53959
  if (!deps.hasProviderSessionCapture(session.sessionId)) continue;
53698
53960
  const lastActivityAt = deps.getSessionLastActivityAt(session.sessionId);
@@ -54004,6 +54266,7 @@ function createAgentApi(ctx, terminalRuntime, deps) {
54004
54266
  }
54005
54267
  async function handleSessionItem(req, res, sessionId, action) {
54006
54268
  if (action === "turn") return handleTurn(req, res, sessionId);
54269
+ if (action === "background") return handleBackground(req, res, sessionId);
54007
54270
  if (action === "attention") return handleAttention(req, res, sessionId);
54008
54271
  if (action === "auto-name") return handleAutoName(req, res, sessionId);
54009
54272
  if (action === "capture") return handleCapture(req, res, sessionId);
@@ -54142,6 +54405,24 @@ function createAgentApi(ctx, terminalRuntime, deps) {
54142
54405
  if (turnState === "ended") scheduleIdentityRefresh(sessionId);
54143
54406
  return true;
54144
54407
  }
54408
+ async function handleBackground(req, res, sessionId) {
54409
+ if (req.method !== "POST") return methodNotAllowed2(res);
54410
+ if (!ctx.host.security.isLockAuthorized(req)) return unauthorized(res);
54411
+ const body = await ctx.host.http.readJsonBody(req);
54412
+ const event = body?.event === "spawn" || body?.event === "stop" ? body.event : null;
54413
+ if (event === null) {
54414
+ ctx.host.http.writeJson(res, 400, { error: "invalid_event" });
54415
+ return true;
54416
+ }
54417
+ const updated = observability.setTerminalSessionBackgroundEvent(sessionId, event);
54418
+ if (!updated) {
54419
+ ctx.host.http.writeJson(res, 404, { error: "terminal_session_not_found" });
54420
+ return true;
54421
+ }
54422
+ observability.notifySessionUpdated(updated);
54423
+ ctx.host.http.writeJson(res, 200, { ok: true });
54424
+ return true;
54425
+ }
54145
54426
  async function handleAttention(req, res, sessionId) {
54146
54427
  if (req.method !== "POST") return methodNotAllowed2(res);
54147
54428
  if (!ctx.host.security.isLockAuthorized(req)) return unauthorized(res);
@@ -55259,8 +55540,7 @@ var ANALYSIS_ERROR_CODES = {
55259
55540
  sessionBusy: "analysis_session_busy"
55260
55541
  };
55261
55542
  var ANALYST_CLI_ENTRIES = [
55262
- { binaryId: "claude", cliId: "claude" },
55263
- { binaryId: "cursor-agent", cliId: "cursor" }
55543
+ { binaryId: "claude", cliId: "claude" }
55264
55544
  ];
55265
55545
  function buildAnalysisCatalog(statuses, modelsFor) {
55266
55546
  const statusById = new Map(statuses.map((status) => [status.id, status]));
@@ -55285,7 +55565,7 @@ function analysisError(code, message) {
55285
55565
  return { error: { code, message } };
55286
55566
  }
55287
55567
  function isAnalysisSelection(catalog, value) {
55288
- if (!isRecord9(value) || !hasExactKeys(value, ["cliId", "model", "effort", "language"]) || typeof value.cliId !== "string" || typeof value.model !== "string" || value.effort !== void 0 && typeof value.effort !== "string" || value.language !== void 0 && value.language !== "en" && value.language !== "ko") return false;
55568
+ if (!isRecord10(value) || !hasExactKeys(value, ["cliId", "model", "effort", "language"]) || typeof value.cliId !== "string" || typeof value.model !== "string" || value.effort !== void 0 && typeof value.effort !== "string" || value.language !== void 0 && value.language !== "en" && value.language !== "ko") return false;
55289
55569
  const cli = catalog.clis.find((candidate) => candidate.cliId === value.cliId);
55290
55570
  if (!cli?.available) return false;
55291
55571
  const model = cli.models.find((candidate) => candidate.id === value.model);
@@ -55294,9 +55574,9 @@ function isAnalysisSelection(catalog, value) {
55294
55574
  return typeof value.effort === "string" && value.effort.length > 0 && model.effortLevels.includes(value.effort);
55295
55575
  }
55296
55576
  function isMessageBody(value) {
55297
- return isRecord9(value) && hasExactKeys(value, ["text"]) && typeof value.text === "string" && value.text.trim().length > 0;
55577
+ return isRecord10(value) && hasExactKeys(value, ["text"]) && typeof value.text === "string" && value.text.trim().length > 0;
55298
55578
  }
55299
- function isRecord9(value) {
55579
+ function isRecord10(value) {
55300
55580
  return typeof value === "object" && value !== null && !Array.isArray(value);
55301
55581
  }
55302
55582
  function hasExactKeys(value, keys) {
@@ -55981,6 +56261,146 @@ async function ignoreMissing(operation) {
55981
56261
  }
55982
56262
  }
55983
56263
 
56264
+ // ../fleet-plugins/terminal/server/ai-gateway-proxy.ts
56265
+ var HOP_BY_HOP_HEADERS = /* @__PURE__ */ new Set([
56266
+ "connection",
56267
+ "content-encoding",
56268
+ "content-length",
56269
+ "keep-alive",
56270
+ "proxy-authenticate",
56271
+ "proxy-authorization",
56272
+ "te",
56273
+ "trailer",
56274
+ "transfer-encoding",
56275
+ "upgrade"
56276
+ ]);
56277
+ async function proxyAnthropicMessages(res, body, options) {
56278
+ const upstream = await options.fetchImpl(options.url, {
56279
+ method: "POST",
56280
+ headers: options.headers,
56281
+ body: JSON.stringify(body),
56282
+ signal: options.signal
56283
+ });
56284
+ const responseHeaders = {};
56285
+ upstream.headers.forEach((value, key) => {
56286
+ if (!HOP_BY_HOP_HEADERS.has(key)) responseHeaders[key] = value;
56287
+ });
56288
+ res.writeHead(upstream.status, responseHeaders);
56289
+ if (!upstream.body) {
56290
+ res.end();
56291
+ return;
56292
+ }
56293
+ const rawBody = readResponseBody(upstream.body);
56294
+ const responseBody = options.contextWindow === void 0 && options.responseModel === void 0 ? rawBody : projectAnthropicResponseUsage(rawBody, {
56295
+ contentType: upstream.headers.get("content-type"),
56296
+ contextWindow: options.contextWindow,
56297
+ responseModel: options.responseModel
56298
+ });
56299
+ for await (const chunk of responseBody) {
56300
+ if (!res.write(chunk)) await drain(res);
56301
+ }
56302
+ res.end();
56303
+ }
56304
+ async function* readResponseBody(body) {
56305
+ const reader = body.getReader();
56306
+ try {
56307
+ for (; ; ) {
56308
+ const { done, value } = await reader.read();
56309
+ if (done) return;
56310
+ yield value;
56311
+ }
56312
+ } finally {
56313
+ reader.releaseLock();
56314
+ }
56315
+ }
56316
+ function eagerAnthropicRequestBody(body, model) {
56317
+ return {
56318
+ ...body,
56319
+ model,
56320
+ messages: body.messages.map(eagerAnthropicMessage),
56321
+ ...body.tools === void 0 ? {} : { tools: body.tools.map(eagerAnthropicTool) }
56322
+ };
56323
+ }
56324
+ function eagerAnthropicTool(tool) {
56325
+ if (!("input_schema" in tool)) return tool;
56326
+ const { defer_loading: _deferLoading, ...eagerTool } = tool;
56327
+ return eagerTool;
56328
+ }
56329
+ function eagerAnthropicMessage(message) {
56330
+ if (typeof message.content === "string") return message;
56331
+ return {
56332
+ ...message,
56333
+ content: message.content.map((block) => {
56334
+ if (block.type !== "tool_result" || !Array.isArray(block.content)) return block;
56335
+ return {
56336
+ ...block,
56337
+ content: block.content.map((result) => {
56338
+ if (result.type !== "tool_reference") return result;
56339
+ const toolName = typeof result.tool_name === "string" && result.tool_name.length > 0 ? result.tool_name : "(invalid reference)";
56340
+ return { type: "text", text: `Tool available: ${toolName}` };
56341
+ })
56342
+ };
56343
+ })
56344
+ };
56345
+ }
56346
+ async function drain(res) {
56347
+ await new Promise((resolve3) => res.once("drain", resolve3));
56348
+ }
56349
+ function writeAnthropicError(res, status, type, message) {
56350
+ res.writeHead(status, { "content-type": "application/json" });
56351
+ res.end(JSON.stringify({ type: "error", error: { type, message } }));
56352
+ }
56353
+ function writeSseErrorFrame(res, type, message) {
56354
+ try {
56355
+ const data = JSON.stringify({ type: "error", error: { type, message } });
56356
+ res.write(`
56357
+
56358
+ event: error
56359
+ data: ${data}
56360
+
56361
+ `);
56362
+ } catch {
56363
+ }
56364
+ }
56365
+ function errorMessage(error51) {
56366
+ return error51 instanceof Error ? error51.message : String(error51);
56367
+ }
56368
+
56369
+ // ../fleet-plugins/terminal/server/ai-gateway-opencode.ts
56370
+ function isOpencodeAnthropicPassthrough(model) {
56371
+ return opencodeGoWire(model) === "anthropic";
56372
+ }
56373
+ function createOpencodeGateway(wire) {
56374
+ return new AnthropicMessagesGateway(createOpencodeGoAdapter(wire));
56375
+ }
56376
+ async function proxyToOpencode(requestHeaders, res, body, model, contextWindow, apiKey, fetchImpl, signal) {
56377
+ const headers = {
56378
+ "content-type": "application/json",
56379
+ "anthropic-version": typeof requestHeaders["anthropic-version"] === "string" ? requestHeaders["anthropic-version"] : "2023-06-01",
56380
+ "x-api-key": apiKey
56381
+ };
56382
+ for (const name of ["anthropic-beta", "user-agent"]) {
56383
+ const value = requestHeaders[name];
56384
+ if (typeof value === "string") headers[name] = value;
56385
+ }
56386
+ const responseModel = typeof body.model === "string" ? body.model : void 0;
56387
+ await proxyAnthropicMessages(res, opencodeRequestBody(body, model), {
56388
+ contextWindow,
56389
+ responseModel,
56390
+ fetchImpl,
56391
+ headers,
56392
+ signal,
56393
+ url: OPENCODE_GO_MESSAGES_URL
56394
+ });
56395
+ }
56396
+ function opencodeRequestBody(body, model) {
56397
+ const eagerBody = eagerAnthropicRequestBody(body, model);
56398
+ if (body.output_config === void 0) return eagerBody;
56399
+ const { effort: _effort, ...outputConfig } = body.output_config;
56400
+ const { output_config: _outputConfig, ...withoutOutputConfig } = eagerBody;
56401
+ return Object.keys(outputConfig).length > 0 ? { ...withoutOutputConfig, output_config: outputConfig } : withoutOutputConfig;
56402
+ }
56403
+
55984
56404
  // ../fleet-plugins/terminal/server/ai-gateway-routes.ts
55985
56405
  var AI_GATEWAY_ROUTE_SEGMENT = "ai-gateway";
55986
56406
  var AI_GATEWAY_MODEL_ENV = "FLEET_AI_GATEWAY_MODEL";
@@ -56083,6 +56503,13 @@ function createAiGatewayRouter(deps = {}) {
56083
56503
  return true;
56084
56504
  }
56085
56505
  credential = kimiApiKey;
56506
+ } else if (target2?.provider === "opencode") {
56507
+ const opencodeApiKey = await deps.readOpencodeApiKey?.();
56508
+ if (!opencodeApiKey) {
56509
+ writeAnthropicError(res, 401, "authentication_error", "No OpenCode Go API key was found. Sign in to OpenCode Go first.");
56510
+ return true;
56511
+ }
56512
+ credential = opencodeApiKey;
56086
56513
  }
56087
56514
  const controller = new AbortController();
56088
56515
  const abort = () => controller.abort(new Error("client disconnected"));
@@ -56106,7 +56533,20 @@ function createAiGatewayRouter(deps = {}) {
56106
56533
  );
56107
56534
  return true;
56108
56535
  }
56109
- const gateway = deps.gateway ?? (target2.provider === "cursor" ? ownedCursorGateway : createGatewayFor(target2, chatgptAccountId));
56536
+ if (target2.provider === "opencode" && isOpencodeAnthropicPassthrough(target2)) {
56537
+ await proxyToOpencode(
56538
+ req.headers,
56539
+ res,
56540
+ body,
56541
+ upstreamModelId(target2),
56542
+ claudeContextWindow,
56543
+ credential,
56544
+ fetchImpl,
56545
+ controller.signal
56546
+ );
56547
+ return true;
56548
+ }
56549
+ const gateway = deps.gateway ?? (target2.provider === "cursor" ? ownedCursorGateway : target2.provider === "opencode" ? createOpencodeGateway(opencodeGoWire(target2)) : createGatewayFor(target2, chatgptAccountId));
56110
56550
  const diagnosticsEnabled = target2.provider === "cursor" ? await cursorDiagnosticsEnabled() : void 0;
56111
56551
  const modelContextWindow = typeof target2.contextWindow === "number" && Number.isFinite(target2.contextWindow) && target2.contextWindow > 0 ? target2.contextWindow : void 0;
56112
56552
  const upstream = await gateway.stream(body, {
@@ -56185,8 +56625,10 @@ async function proxyToKimi(requestHeaders, res, body, model, contextWindow, apiK
56185
56625
  const value = requestHeaders[name];
56186
56626
  if (typeof value === "string") headers[name] = value;
56187
56627
  }
56628
+ const responseModel = typeof body.model === "string" ? body.model : void 0;
56188
56629
  await proxyAnthropicMessages(res, kimiRequestBody(body, model), {
56189
56630
  contextWindow,
56631
+ responseModel,
56190
56632
  fetchImpl,
56191
56633
  headers,
56192
56634
  signal,
@@ -56195,12 +56637,7 @@ async function proxyToKimi(requestHeaders, res, body, model, contextWindow, apiK
56195
56637
  }
56196
56638
  var KIMI_REASONING_EFFORTS = ["low", "high", "max"];
56197
56639
  function kimiRequestBody(body, model) {
56198
- const eagerBody = {
56199
- ...body,
56200
- model,
56201
- messages: body.messages.map(kimiEagerMessage),
56202
- ...body.tools === void 0 ? {} : { tools: body.tools.map(kimiEagerTool) }
56203
- };
56640
+ const eagerBody = eagerAnthropicRequestBody(body, model);
56204
56641
  const effort = reasoningEffortFromOutputConfig(body.output_config);
56205
56642
  if (effort === void 0) {
56206
56643
  return eagerBody;
@@ -56213,78 +56650,6 @@ function kimiRequestBody(body, model) {
56213
56650
  }
56214
56651
  };
56215
56652
  }
56216
- function kimiEagerTool(tool) {
56217
- if (!("input_schema" in tool)) return tool;
56218
- const { defer_loading: _deferLoading, ...eagerTool } = tool;
56219
- return eagerTool;
56220
- }
56221
- function kimiEagerMessage(message) {
56222
- if (typeof message.content === "string") return message;
56223
- return {
56224
- ...message,
56225
- content: message.content.map((block) => {
56226
- if (block.type !== "tool_result" || !Array.isArray(block.content)) return block;
56227
- return {
56228
- ...block,
56229
- content: block.content.map((result) => {
56230
- if (result.type !== "tool_reference") return result;
56231
- const toolName = typeof result.tool_name === "string" && result.tool_name.length > 0 ? result.tool_name : "(invalid reference)";
56232
- return { type: "text", text: `Tool available: ${toolName}` };
56233
- })
56234
- };
56235
- })
56236
- };
56237
- }
56238
- var HOP_BY_HOP_HEADERS = /* @__PURE__ */ new Set([
56239
- "connection",
56240
- "content-encoding",
56241
- "content-length",
56242
- "keep-alive",
56243
- "proxy-authenticate",
56244
- "proxy-authorization",
56245
- "te",
56246
- "trailer",
56247
- "transfer-encoding",
56248
- "upgrade"
56249
- ]);
56250
- async function proxyAnthropicMessages(res, body, options) {
56251
- const upstream = await options.fetchImpl(options.url, {
56252
- method: "POST",
56253
- headers: options.headers,
56254
- body: JSON.stringify(body),
56255
- signal: options.signal
56256
- });
56257
- const responseHeaders = {};
56258
- upstream.headers.forEach((value, key) => {
56259
- if (!HOP_BY_HOP_HEADERS.has(key)) responseHeaders[key] = value;
56260
- });
56261
- res.writeHead(upstream.status, responseHeaders);
56262
- if (!upstream.body) {
56263
- res.end();
56264
- return;
56265
- }
56266
- const rawBody = readResponseBody(upstream.body);
56267
- const responseBody = options.contextWindow === void 0 ? rawBody : projectAnthropicResponseUsage(rawBody, {
56268
- contentType: upstream.headers.get("content-type"),
56269
- contextWindow: options.contextWindow
56270
- });
56271
- for await (const chunk of responseBody) {
56272
- if (!res.write(chunk)) await drain(res);
56273
- }
56274
- res.end();
56275
- }
56276
- async function* readResponseBody(body) {
56277
- const reader = body.getReader();
56278
- try {
56279
- for (; ; ) {
56280
- const { done, value } = await reader.read();
56281
- if (done) return;
56282
- yield value;
56283
- }
56284
- } finally {
56285
- reader.releaseLock();
56286
- }
56287
- }
56288
56653
  function createGatewayFor(model, chatgptAccountId) {
56289
56654
  if (model.provider !== "codex") {
56290
56655
  throw new TypeError(`Unsupported translated gateway provider: ${model.provider}`);
@@ -56321,28 +56686,6 @@ function headerEntries(headers) {
56321
56686
  });
56322
56687
  return entries;
56323
56688
  }
56324
- async function drain(res) {
56325
- await new Promise((resolve3) => res.once("drain", resolve3));
56326
- }
56327
- function writeAnthropicError(res, status, type, message) {
56328
- res.writeHead(status, { "content-type": "application/json" });
56329
- res.end(JSON.stringify({ type: "error", error: { type, message } }));
56330
- }
56331
- function writeSseErrorFrame(res, type, message) {
56332
- try {
56333
- const data = JSON.stringify({ type: "error", error: { type, message } });
56334
- res.write(`
56335
-
56336
- event: error
56337
- data: ${data}
56338
-
56339
- `);
56340
- } catch {
56341
- }
56342
- }
56343
- function errorMessage(error51) {
56344
- return error51 instanceof Error ? error51.message : String(error51);
56345
- }
56346
56689
 
56347
56690
  // ../fleet-plugins/terminal/server/carrier-settings-routes.ts
56348
56691
  var CARRIER_SETTINGS_PRESENTATION_LOCALES = CARRIER_PRESENTATION_LOCALES;
@@ -56776,23 +57119,38 @@ function registerGlobalShellRoutes(ctx, runtime) {
56776
57119
  }
56777
57120
 
56778
57121
  // ../fleet-plugins/terminal/server/model-auth-state.ts
57122
+ var MODEL_AUTH_STORE_IDS = Object.freeze({
57123
+ kimi: KIMI_AUTH_PROVIDER_ID,
57124
+ opencode: OPENCODE_AUTH_PROVIDER_ID
57125
+ });
57126
+ var MODEL_AUTH_DISPLAY_NAMES = Object.freeze({
57127
+ kimi: "Kimi for AI Gateway",
57128
+ opencode: "OpenCode Go for AI Gateway"
57129
+ });
57130
+ function isTerminalModelAuthProviderId(value) {
57131
+ return value in MODEL_AUTH_STORE_IDS;
57132
+ }
56779
57133
  async function buildModelAuthState(authService) {
56780
57134
  const signedInIds = new Set(await authService.listProviderIds());
56781
57135
  return {
56782
- providers: [{
56783
- provider: "kimi",
56784
- displayName: "Kimi for AI Gateway",
56785
- signedIn: signedInIds.has(KIMI_AUTH_PROVIDER_ID)
56786
- }]
57136
+ providers: Object.keys(MODEL_AUTH_STORE_IDS).map((provider) => ({
57137
+ provider,
57138
+ displayName: MODEL_AUTH_DISPLAY_NAMES[provider],
57139
+ signedIn: signedInIds.has(MODEL_AUTH_STORE_IDS[provider])
57140
+ }))
56787
57141
  };
56788
57142
  }
56789
57143
 
56790
57144
  // ../fleet-plugins/terminal/server/model-auth-routes.ts
56791
57145
  var UPSTREAM_FAILURE_STATUSES = /* @__PURE__ */ new Set(["timeout", "network", "server"]);
57146
+ var MODEL_AUTH_VALIDATORS = {
57147
+ kimi: validateKimiAuthKey,
57148
+ opencode: validateOpencodeGoAuthKey
57149
+ };
56792
57150
  function registerTerminalModelAuthRoutes(ctx, deps) {
56793
57151
  registerRouter(ctx, "model-auth", createTerminalModelAuthRouter(ctx, {
56794
57152
  ...deps,
56795
- validateApiKey: validateKimiAuthKey
57153
+ validateApiKey: (provider, apiKey) => MODEL_AUTH_VALIDATORS[provider](apiKey)
56796
57154
  }));
56797
57155
  }
56798
57156
  function createTerminalModelAuthRouter(ctx, deps) {
@@ -56808,11 +57166,11 @@ function createTerminalModelAuthRouter(ctx, deps) {
56808
57166
  }
56809
57167
  const providerId = parseProviderPath(path44);
56810
57168
  if (!providerId) return false;
56811
- if (providerId !== "kimi") {
57169
+ if (!isTerminalModelAuthProviderId(providerId)) {
56812
57170
  ctx.host.http.writeJson(res, 404, { error: "provider_not_found" });
56813
57171
  return true;
56814
57172
  }
56815
- const provider = await findProvider(deps);
57173
+ const provider = await findProvider(deps, providerId);
56816
57174
  if (!provider) {
56817
57175
  ctx.host.http.writeJson(res, 404, { error: "provider_not_found" });
56818
57176
  return true;
@@ -56822,7 +57180,7 @@ function createTerminalModelAuthRouter(ctx, deps) {
56822
57180
  return true;
56823
57181
  }
56824
57182
  if (req.method === "DELETE") {
56825
- await signOutProvider(ctx, req, res, deps);
57183
+ await signOutProvider(ctx, req, res, deps, provider.provider);
56826
57184
  return true;
56827
57185
  }
56828
57186
  ctx.host.http.writeJson(res, 405, { error: "Method not allowed" });
@@ -56848,7 +57206,7 @@ async function signInProvider(ctx, req, res, deps, provider) {
56848
57206
  return;
56849
57207
  }
56850
57208
  const apiKey = body.apiKey.trim();
56851
- const validation = await deps.validateApiKey(apiKey);
57209
+ const validation = await deps.validateApiKey(provider.provider, apiKey);
56852
57210
  if (validation.status !== "success") {
56853
57211
  ctx.host.http.writeJson(res, UPSTREAM_FAILURE_STATUSES.has(validation.status) ? 502 : 400, {
56854
57212
  error: formatSignInFailureMessage(provider.displayName, validation.status),
@@ -56856,22 +57214,23 @@ async function signInProvider(ctx, req, res, deps, provider) {
56856
57214
  });
56857
57215
  return;
56858
57216
  }
56859
- await deps.authService.setApiKey(KIMI_AUTH_PROVIDER_ID, apiKey);
57217
+ await deps.authService.setApiKey(MODEL_AUTH_STORE_IDS[provider.provider], apiKey);
56860
57218
  await writeMutationState2(ctx, res, deps);
56861
57219
  }
56862
- async function signOutProvider(ctx, req, res, deps) {
57220
+ async function signOutProvider(ctx, req, res, deps, provider) {
56863
57221
  if (!ctx.host.security.isTerminalAuthorized(req)) {
56864
57222
  ctx.host.http.writeJson(res, 401, { error: "unauthorized" });
56865
57223
  return;
56866
57224
  }
56867
- await deps.authService.deleteApiKey(KIMI_AUTH_PROVIDER_ID);
57225
+ await deps.authService.deleteApiKey(MODEL_AUTH_STORE_IDS[provider]);
56868
57226
  await writeMutationState2(ctx, res, deps);
56869
57227
  }
56870
57228
  async function writeMutationState2(ctx, res, deps) {
56871
57229
  ctx.host.http.writeJson(res, 200, { state: await buildModelAuthState(deps.authService) });
56872
57230
  }
56873
- async function findProvider(deps) {
56874
- return (await buildModelAuthState(deps.authService)).providers[0] ?? null;
57231
+ async function findProvider(deps, providerId) {
57232
+ const state = await buildModelAuthState(deps.authService);
57233
+ return state.providers.find((provider) => provider.provider === providerId) ?? null;
56875
57234
  }
56876
57235
  function parseProviderPath(path44) {
56877
57236
  const parts = path44.split("/").filter(Boolean);
@@ -57031,7 +57390,8 @@ var routes_default = definePlugin({
57031
57390
  registerTerminalModelAuthRoutes(ctx, { authService: infraServices.authService });
57032
57391
  registerAiGatewayRoutes(ctx, {
57033
57392
  readAiGatewaySettings: aiGatewayStore.read,
57034
- readKimiApiKey: () => infraServices.authService.getApiKey(KIMI_AUTH_PROVIDER_ID)
57393
+ readKimiApiKey: () => infraServices.authService.getApiKey(KIMI_AUTH_PROVIDER_ID),
57394
+ readOpencodeApiKey: () => infraServices.authService.getApiKey(OPENCODE_AUTH_PROVIDER_ID)
57035
57395
  });
57036
57396
  registerCarrierSettingsRoutes(ctx, { registry: carrierRegistry });
57037
57397
  const agentCliPathStore = createAgentCliPathStore(ctx.host.storage, ctx.pluginId);