@cuongtran001/kanna 1.2.0 → 1.3.1

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 (196) hide show
  1. package/dist/client/assets/{arc-BLk1CZKB.js → arc-DXtN3EWJ.js} +1 -1
  2. package/dist/client/assets/{architectureDiagram-3BPJPVTR-B01ZQYte.js → architectureDiagram-3BPJPVTR-CIGqW1z-.js} +1 -1
  3. package/dist/client/assets/{blockDiagram-GPEHLZMM-diL6a3cq.js → blockDiagram-GPEHLZMM-BafNS_H6.js} +1 -1
  4. package/dist/client/assets/{c4Diagram-AAUBKEIU-BY1verL8.js → c4Diagram-AAUBKEIU-YdAgRSis.js} +1 -1
  5. package/dist/client/assets/channel-BEYTrUAm.js +1 -0
  6. package/dist/client/assets/{chunk-2J33WTMH-psdZuajw.js → chunk-2J33WTMH-eiP5y5G-.js} +1 -1
  7. package/dist/client/assets/{chunk-4BX2VUAB-CrwQVBc5.js → chunk-4BX2VUAB-CvLGzPMF.js} +1 -1
  8. package/dist/client/assets/{chunk-55IACEB6-9-aj4jKO.js → chunk-55IACEB6-Y-S9Fb8A.js} +1 -1
  9. package/dist/client/assets/{chunk-727SXJPM-1FNK9_nB.js → chunk-727SXJPM-6sJH1RI9.js} +1 -1
  10. package/dist/client/assets/{chunk-AQP2D5EJ-DFhvd_Ek.js → chunk-AQP2D5EJ-hV1r_IXO.js} +1 -1
  11. package/dist/client/assets/{chunk-FMBD7UC4-B7MLrDa7.js → chunk-FMBD7UC4-CC3L28S5.js} +1 -1
  12. package/dist/client/assets/{chunk-ND2GUHAM-D4Df05QE.js → chunk-ND2GUHAM-BZ3czhdH.js} +1 -1
  13. package/dist/client/assets/{chunk-QZHKN3VN-Iq17Dznm.js → chunk-QZHKN3VN-Bjyd6Yr-.js} +1 -1
  14. package/dist/client/assets/classDiagram-4FO5ZUOK-CvnF5Ud6.js +1 -0
  15. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-CvnF5Ud6.js +1 -0
  16. package/dist/client/assets/{cose-bilkent-S5V4N54A-BmaZhtXy.js → cose-bilkent-S5V4N54A-CfxdDghE.js} +1 -1
  17. package/dist/client/assets/{dagre-BM42HDAG-BCmpfnfP.js → dagre-BM42HDAG-B8CddsbB.js} +1 -1
  18. package/dist/client/assets/{diagram-2AECGRRQ-BIEcKowS.js → diagram-2AECGRRQ-F0yS0fVB.js} +1 -1
  19. package/dist/client/assets/{diagram-5GNKFQAL-CRBABBfv.js → diagram-5GNKFQAL-cW0i0qlh.js} +1 -1
  20. package/dist/client/assets/{diagram-KO2AKTUF-BRxUk16N.js → diagram-KO2AKTUF-BzAKMeJT.js} +1 -1
  21. package/dist/client/assets/{diagram-LMA3HP47-DPBs4xl1.js → diagram-LMA3HP47-qdC4XqIl.js} +1 -1
  22. package/dist/client/assets/{diagram-OG6HWLK6-BE9rgVv8.js → diagram-OG6HWLK6-CGcpi6Dh.js} +1 -1
  23. package/dist/client/assets/{erDiagram-TEJ5UH35-D3vgA1xJ.js → erDiagram-TEJ5UH35-CxNTDicM.js} +1 -1
  24. package/dist/client/assets/{flowDiagram-I6XJVG4X-Cgu-8XXC.js → flowDiagram-I6XJVG4X-qkGa5TqY.js} +1 -1
  25. package/dist/client/assets/{ganttDiagram-6RSMTGT7-CgjXVyTR.js → ganttDiagram-6RSMTGT7-D9mVPoQi.js} +1 -1
  26. package/dist/client/assets/{gitGraphDiagram-PVQCEYII-zD5D8B-i.js → gitGraphDiagram-PVQCEYII-DA0grAXe.js} +1 -1
  27. package/dist/client/assets/{index-DBTdhMbJ.js → index-BqUY1Bm4.js} +1 -1
  28. package/dist/client/assets/index-CLsOWXff.css +32 -0
  29. package/dist/client/assets/{index-zQLLeAfl.js → index-TU1k6ov6.js} +253 -243
  30. package/dist/client/assets/{infoDiagram-5YYISTIA-BaFHqT4u.js → infoDiagram-5YYISTIA-CGljtLs3.js} +1 -1
  31. package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-2yAC1br2.js → ishikawaDiagram-YF4QCWOH-CtC0xrmG.js} +1 -1
  32. package/dist/client/assets/{journeyDiagram-JHISSGLW-DacQ-ND5.js → journeyDiagram-JHISSGLW-URLnVZeN.js} +1 -1
  33. package/dist/client/assets/{kanban-definition-UN3LZRKU-Qlbwm3z4.js → kanban-definition-UN3LZRKU-C2Q6aqQh.js} +1 -1
  34. package/dist/client/assets/{linear-DeafqpgO.js → linear-ByKHtsIx.js} +1 -1
  35. package/dist/client/assets/{mermaid.core-KYiCPJDj.js → mermaid.core-Dl_f2KLc.js} +4 -4
  36. package/dist/client/assets/{mindmap-definition-RKZ34NQL-B1V-T6d3.js → mindmap-definition-RKZ34NQL-CaJ9IDLI.js} +1 -1
  37. package/dist/client/assets/{pieDiagram-4H26LBE5-DOF9OFRu.js → pieDiagram-4H26LBE5-mmYaf2Oa.js} +1 -1
  38. package/dist/client/assets/{quadrantDiagram-W4KKPZXB-lH1mB7ko.js → quadrantDiagram-W4KKPZXB-B_PO6OCC.js} +1 -1
  39. package/dist/client/assets/{requirementDiagram-4Y6WPE33-BaTsJ_sS.js → requirementDiagram-4Y6WPE33-M4huwJt1.js} +1 -1
  40. package/dist/client/assets/{sankeyDiagram-5OEKKPKP-Pfxib5iI.js → sankeyDiagram-5OEKKPKP-B0hb1sWh.js} +1 -1
  41. package/dist/client/assets/{sequenceDiagram-3UESZ5HK-BQMEKkBC.js → sequenceDiagram-3UESZ5HK-BAqSptnA.js} +1 -1
  42. package/dist/client/assets/{stateDiagram-AJRCARHV-CuldNnzp.js → stateDiagram-AJRCARHV-DTNkvPi5.js} +1 -1
  43. package/dist/client/assets/stateDiagram-v2-BHNVJYJU-CVN3oGTF.js +1 -0
  44. package/dist/client/assets/{timeline-definition-PNZ67QCA-DP7u0YAg.js → timeline-definition-PNZ67QCA-MgAjHz8Q.js} +1 -1
  45. package/dist/client/assets/{vennDiagram-CIIHVFJN-QlrjpNeQ.js → vennDiagram-CIIHVFJN-DmKoO51W.js} +1 -1
  46. package/dist/client/assets/{wardley-L42UT6IY-BqdJ_sAM.js → wardley-L42UT6IY-CWcyHnYs.js} +1 -1
  47. package/dist/client/assets/{wardleyDiagram-YWT4CUSO-Wi3kaQ5X.js → wardleyDiagram-YWT4CUSO-BGIGmFP9.js} +1 -1
  48. package/dist/client/assets/{xychartDiagram-2RQKCTM6-D-KXMyCG.js → xychartDiagram-2RQKCTM6-D20YIvEl.js} +1 -1
  49. package/dist/client/index.html +2 -2
  50. package/package.json +1 -1
  51. package/src/server/agent-coordinator-types.ts +158 -0
  52. package/src/server/agent-coordinator.ts +919 -0
  53. package/src/server/agent-deps-builders.ts +425 -0
  54. package/src/server/agent.oauth-rotation.test.ts +101 -0
  55. package/src/server/agent.test.ts +24 -8
  56. package/src/server/agent.ts +35 -4963
  57. package/src/server/auto-continue/read-model.test.ts +2 -2
  58. package/src/server/auto-continue/read-model.ts +4 -1
  59. package/src/server/claude-autocontinue-commands.test.ts +494 -0
  60. package/src/server/claude-autocontinue-commands.ts +256 -0
  61. package/src/server/claude-cancel-handler.test.ts +472 -0
  62. package/src/server/claude-cancel-handler.ts +241 -0
  63. package/src/server/claude-chat-management.test.ts +395 -0
  64. package/src/server/claude-chat-management.ts +251 -0
  65. package/src/server/claude-harness-stream.test.ts +273 -0
  66. package/src/server/claude-harness-stream.ts +158 -0
  67. package/src/server/claude-loop-orch-commands.test.ts +579 -0
  68. package/src/server/claude-loop-orch-commands.ts +466 -0
  69. package/src/server/claude-message-normalizer.test.ts +213 -0
  70. package/src/server/claude-message-normalizer.ts +436 -0
  71. package/src/server/claude-prompt-helpers.test.ts +248 -0
  72. package/src/server/claude-prompt-helpers.ts +139 -0
  73. package/src/server/claude-sdk-queue.test.ts +169 -0
  74. package/src/server/claude-sdk-queue.ts +82 -0
  75. package/src/server/claude-send-command.test.ts +414 -0
  76. package/src/server/claude-send-command.ts +420 -0
  77. package/src/server/claude-session-config-helpers.test.ts +279 -0
  78. package/src/server/claude-session-config-helpers.ts +151 -0
  79. package/src/server/claude-session-config.test.ts +250 -0
  80. package/src/server/claude-session-config.ts +159 -0
  81. package/src/server/claude-session-error-handler.test.ts +498 -0
  82. package/src/server/claude-session-error-handler.ts +361 -0
  83. package/src/server/claude-session-lifecycle.test.ts +669 -0
  84. package/src/server/claude-session-lifecycle.ts +283 -0
  85. package/src/server/claude-session-rebuild.test.ts +224 -0
  86. package/src/server/claude-session-rebuild.ts +101 -0
  87. package/src/server/claude-session-runner.test.ts +523 -0
  88. package/src/server/claude-session-runner.ts +521 -0
  89. package/src/server/claude-session-spawner.test.ts +401 -0
  90. package/src/server/claude-session-spawner.ts +401 -0
  91. package/src/server/claude-session-start.test.ts +205 -0
  92. package/src/server/claude-session-start.ts +277 -0
  93. package/src/server/claude-session-state-queries.test.ts +344 -0
  94. package/src/server/claude-session-state-queries.ts +160 -0
  95. package/src/server/claude-session-state.ts +104 -0
  96. package/src/server/claude-slash-commands.test.ts +277 -0
  97. package/src/server/claude-slash-commands.ts +234 -0
  98. package/src/server/claude-spawn-helpers.test.ts +221 -0
  99. package/src/server/claude-spawn-helpers.ts +179 -0
  100. package/src/server/claude-steer-log.test.ts +243 -0
  101. package/src/server/claude-steer-log.ts +58 -0
  102. package/src/server/claude-subagent-tool-response.test.ts +213 -0
  103. package/src/server/claude-subagent-tool-response.ts +165 -0
  104. package/src/server/claude-subagent-wiring.test.ts +263 -0
  105. package/src/server/claude-subagent-wiring.ts +324 -0
  106. package/src/server/claude-tool-respond.test.ts +321 -0
  107. package/src/server/claude-tool-respond.ts +142 -0
  108. package/src/server/claude-turn-runner.test.ts +310 -0
  109. package/src/server/claude-turn-runner.ts +228 -0
  110. package/src/server/claude-turn-starter.test.ts +266 -0
  111. package/src/server/claude-turn-starter.ts +566 -0
  112. package/src/server/claude-usage-math.test.ts +169 -0
  113. package/src/server/claude-usage-math.ts +132 -0
  114. package/src/server/diff-store-branch-ops.adapter.test.ts +332 -0
  115. package/src/server/diff-store-branch-ops.adapter.ts +602 -0
  116. package/src/server/diff-store-commit-ops.adapter.test.ts +358 -0
  117. package/src/server/diff-store-commit-ops.adapter.ts +258 -0
  118. package/src/server/diff-store-errors.ts +85 -0
  119. package/src/server/diff-store-file-ops.adapter.ts +212 -0
  120. package/src/server/diff-store-git-branch.adapter.ts +686 -0
  121. package/src/server/diff-store-io.adapter.ts +27 -0
  122. package/src/server/diff-store-parse.test.ts +212 -0
  123. package/src/server/diff-store-parse.ts +122 -0
  124. package/src/server/diff-store-state.ts +82 -0
  125. package/src/server/diff-store.ts +76 -1854
  126. package/src/server/event-store-apply.test.ts +280 -0
  127. package/src/server/event-store-apply.ts +132 -0
  128. package/src/server/event-store-chat-lifecycle.test.ts +408 -0
  129. package/src/server/event-store-chat-lifecycle.ts +441 -0
  130. package/src/server/event-store-entity-write.ts +356 -0
  131. package/src/server/event-store-helpers.test.ts +230 -0
  132. package/src/server/event-store-helpers.ts +235 -0
  133. package/src/server/event-store-init.ts +276 -0
  134. package/src/server/event-store-messages.adapter.test.ts +365 -0
  135. package/src/server/event-store-messages.adapter.ts +213 -0
  136. package/src/server/event-store-orch-subscription.ts +124 -0
  137. package/src/server/event-store-orch.test.ts +462 -0
  138. package/src/server/event-store-orch.ts +312 -0
  139. package/src/server/event-store-peripheral-events.adapter.test.ts +254 -0
  140. package/src/server/event-store-peripheral-events.adapter.ts +105 -0
  141. package/src/server/event-store-snapshot.test.ts +597 -0
  142. package/src/server/event-store-snapshot.ts +658 -0
  143. package/src/server/event-store-subagent.test.ts +408 -0
  144. package/src/server/event-store-subagent.ts +198 -0
  145. package/src/server/event-store-tool-requests.test.ts +210 -0
  146. package/src/server/event-store-tool-requests.ts +131 -0
  147. package/src/server/event-store-transcript-write.adapter.ts +285 -0
  148. package/src/server/event-store-write-ops.test.ts +554 -0
  149. package/src/server/event-store-write-ops.ts +529 -0
  150. package/src/server/event-store.test.ts +35 -0
  151. package/src/server/event-store.ts +251 -2284
  152. package/src/server/events.ts +3 -0
  153. package/src/server/harness-types.ts +15 -1
  154. package/src/server/oauth-errors.ts +19 -0
  155. package/src/server/read-models.ts +26 -1
  156. package/src/server/subagent-orchestrator.ts +3 -0
  157. package/src/server/ws-router-agent-ctrl.test.ts +187 -0
  158. package/src/server/ws-router-agent-ctrl.ts +137 -0
  159. package/src/server/ws-router-broadcast.ts +488 -0
  160. package/src/server/ws-router-chat.test.ts +461 -0
  161. package/src/server/ws-router-chat.ts +279 -0
  162. package/src/server/ws-router-defaults.test.ts +283 -0
  163. package/src/server/ws-router-defaults.ts +434 -0
  164. package/src/server/ws-router-diff.test.ts +167 -0
  165. package/src/server/ws-router-diff.ts +250 -0
  166. package/src/server/ws-router-envelope.ts +329 -0
  167. package/src/server/ws-router-misc.test.ts +422 -0
  168. package/src/server/ws-router-misc.ts +265 -0
  169. package/src/server/ws-router-orch.test.ts +190 -0
  170. package/src/server/ws-router-orch.ts +93 -0
  171. package/src/server/ws-router-project.test.ts +403 -0
  172. package/src/server/ws-router-project.ts +256 -0
  173. package/src/server/ws-router-push.test.ts +193 -0
  174. package/src/server/ws-router-push.ts +120 -0
  175. package/src/server/ws-router-settings.test.ts +173 -0
  176. package/src/server/ws-router-settings.ts +416 -0
  177. package/src/server/ws-router-skills.test.ts +194 -0
  178. package/src/server/ws-router-skills.ts +204 -0
  179. package/src/server/ws-router-utils.ts +224 -0
  180. package/src/server/ws-router.ts +339 -2254
  181. package/src/shared/app-settings-types.ts +365 -0
  182. package/src/shared/core-types.ts +27 -0
  183. package/src/shared/git-diff-types.ts +165 -0
  184. package/src/shared/loop-progress.test.ts +106 -0
  185. package/src/shared/loop-progress.ts +88 -0
  186. package/src/shared/mcp-types.ts +105 -0
  187. package/src/shared/provider-model-types.ts +437 -0
  188. package/src/shared/subagent-types.ts +198 -0
  189. package/src/shared/tool-call-types.ts +314 -0
  190. package/src/shared/transcript-types.ts +270 -0
  191. package/src/shared/types.ts +27 -1668
  192. package/dist/client/assets/channel-BMkhjiLI.js +0 -1
  193. package/dist/client/assets/classDiagram-4FO5ZUOK-C5Kg1XOD.js +0 -1
  194. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-C5Kg1XOD.js +0 -1
  195. package/dist/client/assets/index-H6C-f2Vp.css +0 -32
  196. package/dist/client/assets/stateDiagram-v2-BHNVJYJU-Ca2U11af.js +0 -1
@@ -0,0 +1,88 @@
1
+ import type {
2
+ LoopProgressSnapshot,
3
+ LoopRateLimitInfo,
4
+ LoopRow,
5
+ LoopRowStatus,
6
+ SubagentRunSnapshot,
7
+ } from "./types"
8
+
9
+ /**
10
+ * Loop Progress — a per-chat, read-only view of an armed autonomous loop's
11
+ * work, surfaced as a checklist panel (mockup: a "Progress" card with one row
12
+ * per chunk). Event-sourced per round: each background subagent delegation the
13
+ * loop fires becomes one row, its status driven by the existing subagent-run
14
+ * lifecycle events. A rate-limited loop (see the rate-limit-resilience fix)
15
+ * surfaces a distinct "resume" affordance instead of looking silently stuck.
16
+ *
17
+ * These helpers are pure so the server read-model and the client share one
18
+ * shape; the DTO types live in ./types to avoid a runtime import cycle.
19
+ */
20
+
21
+ const MAX_LABEL = 80
22
+
23
+ /**
24
+ * First non-blank line of a spawn prompt, stripped of a single leading markdown
25
+ * marker (heading / list / quote / ordinal) and capped to a chip-friendly
26
+ * length. Deterministic and side-effect free so it can run at delegate time on
27
+ * the server and be asserted in unit tests.
28
+ */
29
+ export function deriveChunkLabel(prompt: string): string {
30
+ const firstLine =
31
+ prompt
32
+ .split("\n")
33
+ .map((line) => line.trim())
34
+ .find((line) => line.length > 0) ?? ""
35
+ const stripped = firstLine.replace(/^(#{1,6}\s+|[-*+]\s+|\d+[.)]\s+|>\s+)/, "").trim()
36
+ const cleaned = stripped.length > 0 ? stripped : firstLine
37
+ if (cleaned.length <= MAX_LABEL) return cleaned
38
+ return `${cleaned.slice(0, MAX_LABEL - 1).trimEnd()}…`
39
+ }
40
+
41
+ function rowStatusFor(run: SubagentRunSnapshot): LoopRowStatus {
42
+ switch (run.status) {
43
+ case "running":
44
+ return "running"
45
+ case "completed":
46
+ return "done"
47
+ default:
48
+ // failed | cancelled | any future terminal-failure state
49
+ return "failed"
50
+ }
51
+ }
52
+
53
+ export interface BuildLoopProgressInput {
54
+ chatId: string
55
+ armed: boolean
56
+ /** Timestamp of the current `loop_armed` event; rows before it are excluded. */
57
+ loopArmedAt: number | null
58
+ /** All subagent runs for the chat (any order). */
59
+ runs: readonly SubagentRunSnapshot[]
60
+ rateLimit: LoopRateLimitInfo | null
61
+ }
62
+
63
+ /**
64
+ * Fold the chat's subagent runs + loop/rate-limit state into the panel DTO.
65
+ * Only top-level (depth 0) runs started since the loop was armed count as loop
66
+ * rows — nested sub-spawns and pre-loop delegations are excluded. Rows are
67
+ * sorted newest-first.
68
+ */
69
+ export function buildLoopProgress(input: BuildLoopProgressInput): LoopProgressSnapshot {
70
+ const since = input.loopArmedAt ?? 0
71
+ const rows: LoopRow[] = input.runs
72
+ .filter((run) => run.depth === 0 && run.startedAt >= since)
73
+ .map((run) => ({
74
+ runId: run.runId,
75
+ label: run.label ?? run.subagentName,
76
+ status: rowStatusFor(run),
77
+ startedAt: run.startedAt,
78
+ finishedAt: run.finishedAt,
79
+ }))
80
+ .sort((a, b) => b.startedAt - a.startedAt)
81
+
82
+ return {
83
+ chatId: input.chatId,
84
+ armed: input.armed,
85
+ rows,
86
+ rateLimit: input.rateLimit,
87
+ }
88
+ }
@@ -0,0 +1,105 @@
1
+ // MCP (Model Context Protocol) server types.
2
+ // Extracted from types.ts to keep the barrel lean.
3
+ // All external consumers importing from "../shared/types" continue to work unchanged.
4
+
5
+ import type {
6
+ OAuthTokens,
7
+ OAuthClientInformationFull,
8
+ AuthorizationServerMetadata,
9
+ } from "@modelcontextprotocol/sdk/shared/auth.js"
10
+
11
+ export type McpServerTransport = "stdio" | "http" | "sse" | "ws"
12
+
13
+ export type McpServerTestResult =
14
+ | { status: "untested" }
15
+ | { status: "pending"; startedAt: string }
16
+ | { status: "ok"; testedAt: string; toolCount: number }
17
+ | { status: "error"; testedAt: string; message: string }
18
+
19
+ export interface McpOAuthFlowState {
20
+ codeVerifier: string
21
+ state: string
22
+ issuer: string
23
+ authorizationUrl: string
24
+ // AS metadata cached between start and complete (avoids re-discovery)
25
+ metadata: AuthorizationServerMetadata
26
+ }
27
+
28
+ export interface McpOAuthState {
29
+ enabled: boolean
30
+ status: "unauthenticated" | "authenticated" | "error"
31
+ errorMessage?: string
32
+ // resolved AS issuer (set on complete; used by refresh without re-discovery)
33
+ issuer?: string
34
+ // cached AS metadata (token_endpoint, etc.) persisted at complete so refresh
35
+ // uses it directly instead of re-discovering from issuer (which may be a
36
+ // non-resolvable resource URL, e.g. claude.ai design MCP)
37
+ metadata?: AuthorizationServerMetadata
38
+ // DCR result keyed by AS issuer (SEP-2352)
39
+ clientByIssuer?: Record<string, OAuthClientInformationFull>
40
+ tokens?: OAuthTokens
41
+ obtainedAt?: number
42
+ // present only mid-flow; cleared on complete/cancel
43
+ flow?: McpOAuthFlowState
44
+ }
45
+
46
+ interface McpServerBase {
47
+ id: string
48
+ name: string
49
+ enabled: boolean
50
+ createdAt: string
51
+ updatedAt: string
52
+ lastTest: McpServerTestResult
53
+ }
54
+
55
+ export interface McpServerStdioFields {
56
+ transport: "stdio"
57
+ command: string
58
+ args: string[]
59
+ env: Record<string, string>
60
+ cwd?: string
61
+ }
62
+
63
+ export interface McpServerNetworkFields {
64
+ transport: "http" | "sse" | "ws"
65
+ url: string
66
+ headers: Record<string, string>
67
+ oauth?: McpOAuthState
68
+ }
69
+
70
+ export type McpServerConfig =
71
+ | (McpServerBase & McpServerStdioFields)
72
+ | (McpServerBase & McpServerNetworkFields)
73
+
74
+ export type McpServerInput =
75
+ | (McpServerStdioFields & { name: string; enabled?: boolean })
76
+ | (McpServerNetworkFields & { name: string; enabled?: boolean })
77
+
78
+ export type McpServerPatch = Partial<{
79
+ name: string
80
+ enabled: boolean
81
+ transport: McpServerTransport
82
+ command: string
83
+ args: string[]
84
+ env: Record<string, string>
85
+ cwd: string | undefined
86
+ url: string
87
+ headers: Record<string, string>
88
+ oauth: McpOAuthState
89
+ }>
90
+
91
+ export interface McpValidationError {
92
+ code:
93
+ | "INVALID_NAME"
94
+ | "DUPLICATE_NAME"
95
+ | "RESERVED_NAME"
96
+ | "INVALID_TRANSPORT"
97
+ | "MISSING_COMMAND"
98
+ | "INVALID_URL"
99
+ | "INVALID_HEADER_KEY"
100
+ | "INVALID_ENV_KEY"
101
+ | "NOT_FOUND"
102
+ | "INVALID_OAUTH_TRANSPORT"
103
+ field?: string
104
+ message: string
105
+ }
@@ -0,0 +1,437 @@
1
+ /**
2
+ * Provider and model catalog types — extracted from shared/types.ts.
3
+ * Imported via the re-export barrel in types.ts; all external consumers
4
+ * continue to import from "../shared/types" unchanged.
5
+ */
6
+
7
+ // AgentProvider lives in types.ts and is only needed as a type here.
8
+ // import type = erased at compile time → no circular runtime dependency.
9
+ import type { AgentProvider } from "./types"
10
+
11
+ export const DEFAULT_OPENROUTER_SDK_MODEL = "moonshotai/kimi-k2.5:nitro"
12
+
13
+ export const OPENROUTER_BASE_URL = "https://openrouter.ai/api"
14
+ export const OPENROUTER_MODELS_URL = "https://openrouter.ai/api/v1/models"
15
+
16
+ export interface OpenRouterModel {
17
+ id: string
18
+ label: string
19
+ contextLength: number
20
+ pricing?: { promptPerTok: number; completionPerTok: number }
21
+ }
22
+
23
+ // ---------------------------------------------------------------------------
24
+ // Provider model option primitives
25
+ // ---------------------------------------------------------------------------
26
+
27
+ export interface ProviderModelOption {
28
+ id: string
29
+ label: string
30
+ supportsEffort: boolean
31
+ aliases?: readonly string[]
32
+ contextWindowOptions?: readonly ProviderContextWindowOption[]
33
+ supportsMaxReasoningEffort?: boolean
34
+ }
35
+
36
+ export interface ProviderEffortOption {
37
+ id: string
38
+ label: string
39
+ }
40
+
41
+ export interface ProviderContextWindowOption {
42
+ id: ClaudeContextWindow
43
+ label: string
44
+ }
45
+
46
+ export const CLAUDE_REASONING_OPTIONS = [
47
+ { id: "low", label: "Low" },
48
+ { id: "medium", label: "Medium" },
49
+ { id: "high", label: "High" },
50
+ { id: "max", label: "Max" },
51
+ ] as const satisfies readonly ProviderEffortOption[]
52
+
53
+ export const CODEX_REASONING_OPTIONS = [
54
+ { id: "minimal", label: "Minimal" },
55
+ { id: "low", label: "Low" },
56
+ { id: "medium", label: "Medium" },
57
+ { id: "high", label: "High" },
58
+ { id: "xhigh", label: "XHigh" },
59
+ ] as const satisfies readonly ProviderEffortOption[]
60
+
61
+ export type ClaudeReasoningEffort = (typeof CLAUDE_REASONING_OPTIONS)[number]["id"]
62
+ export type CodexReasoningEffort = (typeof CODEX_REASONING_OPTIONS)[number]["id"]
63
+ export type ClaudeContextWindow = "200k" | "1m"
64
+ export type ServiceTier = "fast"
65
+
66
+ export interface ClaudeModelOptions {
67
+ reasoningEffort: ClaudeReasoningEffort
68
+ contextWindow: ClaudeContextWindow
69
+ }
70
+
71
+ export interface CodexModelOptions {
72
+ reasoningEffort: CodexReasoningEffort
73
+ fastMode: boolean
74
+ }
75
+
76
+ export type OpenRouterModelOptions = Record<string, never>
77
+
78
+ export interface ProviderModelOptionsByProvider {
79
+ claude: ClaudeModelOptions
80
+ codex: CodexModelOptions
81
+ openrouter: OpenRouterModelOptions
82
+ }
83
+
84
+ export interface ProviderPreference<TModelOptions> {
85
+ model: string
86
+ modelOptions: TModelOptions
87
+ planMode: boolean
88
+ }
89
+
90
+ export type ChatProviderPreferences = {
91
+ claude: ProviderPreference<ClaudeModelOptions>
92
+ codex: ProviderPreference<CodexModelOptions>
93
+ openrouter: ProviderPreference<OpenRouterModelOptions>
94
+ }
95
+
96
+ // ---------------------------------------------------------------------------
97
+ // Model option defaults and type-guards
98
+ // ---------------------------------------------------------------------------
99
+
100
+ export type ModelOptions = Partial<{
101
+ [K in AgentProvider]: Partial<ProviderModelOptionsByProvider[K]>
102
+ }>
103
+
104
+ export const DEFAULT_CLAUDE_MODEL_OPTIONS = {
105
+ reasoningEffort: "high",
106
+ contextWindow: "200k",
107
+ } as const satisfies ClaudeModelOptions
108
+
109
+ export const DEFAULT_CODEX_MODEL_OPTIONS = {
110
+ reasoningEffort: "high",
111
+ fastMode: false,
112
+ } as const satisfies CodexModelOptions
113
+
114
+ export function isClaudeReasoningEffort(value: string | null | undefined): value is ClaudeReasoningEffort {
115
+ return CLAUDE_REASONING_OPTIONS.some((option) => option.id === value)
116
+ }
117
+
118
+ export function isCodexReasoningEffort(value: string | null | undefined): value is CodexReasoningEffort {
119
+ return CODEX_REASONING_OPTIONS.some((option) => option.id === value)
120
+ }
121
+
122
+ export const CLAUDE_CONTEXT_WINDOW_OPTIONS = [
123
+ { id: "200k", label: "200k" },
124
+ { id: "1m", label: "1M" },
125
+ ] as const satisfies readonly ProviderContextWindowOption[]
126
+
127
+ export function isClaudeContextWindow(value: string | null | undefined): value is ClaudeContextWindow {
128
+ return CLAUDE_CONTEXT_WINDOW_OPTIONS.some((option) => option.id === value)
129
+ }
130
+
131
+ // ---------------------------------------------------------------------------
132
+ // Provider catalog
133
+ // ---------------------------------------------------------------------------
134
+
135
+ export interface ProviderCatalogEntry {
136
+ id: AgentProvider
137
+ label: string
138
+ defaultModel: string
139
+ defaultEffort?: string
140
+ supportsPlanMode: boolean
141
+ models: ProviderModelOption[]
142
+ efforts: ProviderEffortOption[]
143
+ }
144
+
145
+ export const PROVIDERS: ProviderCatalogEntry[] = [
146
+ {
147
+ id: "claude",
148
+ label: "Claude",
149
+ defaultModel: "claude-sonnet-4-6",
150
+ defaultEffort: "high",
151
+ supportsPlanMode: true,
152
+ models: [
153
+ {
154
+ id: "claude-fable-5",
155
+ label: "Fable 5",
156
+ supportsEffort: true,
157
+ aliases: ["fable"],
158
+ contextWindowOptions: [...CLAUDE_CONTEXT_WINDOW_OPTIONS],
159
+ supportsMaxReasoningEffort: true,
160
+ },
161
+ {
162
+ id: "claude-opus-4-7",
163
+ label: "Opus 4.7",
164
+ supportsEffort: true,
165
+ aliases: ["opus"],
166
+ contextWindowOptions: [...CLAUDE_CONTEXT_WINDOW_OPTIONS],
167
+ supportsMaxReasoningEffort: true,
168
+ },
169
+ {
170
+ id: "claude-sonnet-4-6",
171
+ label: "Sonnet 4.6",
172
+ supportsEffort: true,
173
+ aliases: ["sonnet"],
174
+ contextWindowOptions: [...CLAUDE_CONTEXT_WINDOW_OPTIONS],
175
+ },
176
+ {
177
+ id: "claude-haiku-4-5-20251001",
178
+ label: "Haiku 4.5",
179
+ supportsEffort: true,
180
+ aliases: ["haiku"],
181
+ },
182
+ {
183
+ id: "claude-opus-4-8",
184
+ label: "Opus 4.8",
185
+ supportsEffort: true,
186
+ aliases: ["opus-4-8"],
187
+ contextWindowOptions: [...CLAUDE_CONTEXT_WINDOW_OPTIONS],
188
+ supportsMaxReasoningEffort: true,
189
+ },
190
+ ],
191
+ efforts: [...CLAUDE_REASONING_OPTIONS],
192
+ },
193
+ {
194
+ id: "codex",
195
+ label: "Codex",
196
+ defaultModel: "gpt-5.5",
197
+ supportsPlanMode: true,
198
+ models: [
199
+ { id: "gpt-5.5", label: "GPT-5.5", supportsEffort: false },
200
+ { id: "gpt-5.4", label: "GPT-5.4", supportsEffort: false },
201
+ { id: "gpt-5.3-codex", label: "GPT-5.3 Codex", supportsEffort: false, aliases: ["gpt-5-codex"] },
202
+ { id: "gpt-5.3-codex-spark", label: "GPT-5.3 Codex Spark", supportsEffort: false },
203
+ ],
204
+ efforts: [],
205
+ },
206
+ {
207
+ id: "openrouter",
208
+ label: "OpenRouter",
209
+ defaultModel: DEFAULT_OPENROUTER_SDK_MODEL,
210
+ supportsPlanMode: true,
211
+ models: [],
212
+ efforts: [],
213
+ },
214
+ ]
215
+
216
+ export function getProviderCatalog(provider: AgentProvider): ProviderCatalogEntry {
217
+ const entry = PROVIDERS.find((candidate) => candidate.id === provider)
218
+ if (!entry) {
219
+ throw new Error(`Unknown provider: ${provider}`)
220
+ }
221
+ return entry
222
+ }
223
+
224
+ // ---------------------------------------------------------------------------
225
+ // Custom model entries (user-configurable overrides over the catalog)
226
+ // ---------------------------------------------------------------------------
227
+
228
+ export interface CustomModelEntry {
229
+ id: string
230
+ label: string
231
+ provider: "claude" | "codex"
232
+ supportsEffort: boolean
233
+ aliases?: readonly string[]
234
+ contextWindowOptions?: readonly ProviderContextWindowOption[]
235
+ supportsMaxReasoningEffort?: boolean
236
+ createdAt: number
237
+ updatedAt: number
238
+ }
239
+
240
+ export interface CustomModelInput {
241
+ id: string
242
+ label: string
243
+ provider: "claude" | "codex"
244
+ supportsEffort: boolean
245
+ aliases?: readonly string[]
246
+ contextWindowOptions?: readonly ProviderContextWindowOption[]
247
+ supportsMaxReasoningEffort?: boolean
248
+ }
249
+
250
+ export interface CustomModelPatch {
251
+ label?: string
252
+ supportsEffort?: boolean
253
+ aliases?: readonly string[] | null
254
+ contextWindowOptions?: readonly ProviderContextWindowOption[] | null
255
+ supportsMaxReasoningEffort?: boolean
256
+ }
257
+
258
+ // ---------------------------------------------------------------------------
259
+ // Text snippets (keyboard-shortcut text expansions)
260
+ // ---------------------------------------------------------------------------
261
+
262
+ export interface TextSnippet {
263
+ id: string
264
+ shortcut: string
265
+ expansion: string
266
+ createdAt: number
267
+ updatedAt: number
268
+ }
269
+
270
+ export interface TextSnippetInput {
271
+ shortcut: string
272
+ expansion: string
273
+ }
274
+
275
+ export interface TextSnippetPatch {
276
+ shortcut?: string
277
+ expansion?: string
278
+ }
279
+
280
+ // ---------------------------------------------------------------------------
281
+ // mergeCustomModels — folds user overrides into the provider catalog
282
+ // ---------------------------------------------------------------------------
283
+
284
+ function customEntryToModelOption(entry: CustomModelEntry): ProviderModelOption {
285
+ return {
286
+ id: entry.id,
287
+ label: entry.label,
288
+ supportsEffort: entry.supportsEffort,
289
+ ...(entry.aliases ? { aliases: entry.aliases } : {}),
290
+ ...(entry.contextWindowOptions ? { contextWindowOptions: entry.contextWindowOptions } : {}),
291
+ ...(entry.supportsMaxReasoningEffort !== undefined ? { supportsMaxReasoningEffort: entry.supportsMaxReasoningEffort } : {}),
292
+ }
293
+ }
294
+
295
+ export function mergeCustomModels(
296
+ base: ProviderCatalogEntry[],
297
+ customModels: readonly CustomModelEntry[],
298
+ ): ProviderCatalogEntry[] {
299
+ return base.map((entry) => {
300
+ if (entry.id !== "claude" && entry.id !== "codex") return { ...entry, models: [...entry.models] }
301
+ const forProvider = customModels.filter((m) => m.provider === entry.id)
302
+ if (forProvider.length === 0) return { ...entry, models: [...entry.models] }
303
+ const models = [...entry.models]
304
+ for (const custom of forProvider) {
305
+ const option = customEntryToModelOption(custom)
306
+ const idx = models.findIndex((m) => m.id === option.id)
307
+ if (idx >= 0) models[idx] = option
308
+ else models.push(option)
309
+ }
310
+ return { ...entry, models }
311
+ })
312
+ }
313
+
314
+ // ---------------------------------------------------------------------------
315
+ // Provider session helpers and model-id normalization
316
+ // ---------------------------------------------------------------------------
317
+
318
+ /**
319
+ * True when the provider's turns run through the Claude SDK session transport
320
+ * (a live `claudeSessions` entry consumed by `runClaudeSession`, prompts
321
+ * delivered via `session.sendPrompt`) rather than the generic harness-turn
322
+ * transport (`runTurn` over `active.turn.stream`).
323
+ *
324
+ * `claude` and `openrouter` both ride the SDK session — openrouter just points
325
+ * the SDK at OpenRouter's Anthropic-compatible endpoint. Branching on
326
+ * `provider === "claude"` where the real intent is "uses the SDK session" is
327
+ * what silently dropped openrouter's prompt delivery; use this predicate so a
328
+ * new SDK-backed provider can never be forgotten by an `if`-chain again.
329
+ */
330
+ export function providerUsesSdkSession(provider: AgentProvider): boolean {
331
+ return provider === "claude" || provider === "openrouter"
332
+ }
333
+
334
+ function catalogModelsFor(
335
+ provider: AgentProvider,
336
+ customModels?: readonly CustomModelEntry[],
337
+ ): readonly ProviderModelOption[] {
338
+ const catalog = getProviderCatalog(provider)
339
+ if (!customModels || customModels.length === 0) return catalog.models
340
+ const [merged] = mergeCustomModels([{ ...catalog, models: [...catalog.models] }], customModels)
341
+ return merged.models
342
+ }
343
+
344
+ function getProviderModelMatch(
345
+ provider: AgentProvider,
346
+ modelId?: string,
347
+ customModels?: readonly CustomModelEntry[],
348
+ ): ProviderModelOption | undefined {
349
+ if (!modelId) return undefined
350
+
351
+ return catalogModelsFor(provider, customModels).find((candidate) =>
352
+ candidate.id === modelId || candidate.aliases?.includes(modelId)
353
+ )
354
+ }
355
+
356
+ export function normalizeProviderModelId(
357
+ provider: AgentProvider,
358
+ modelId?: string,
359
+ fallbackModelId?: string,
360
+ customModels?: readonly CustomModelEntry[],
361
+ ): string {
362
+ return getProviderModelMatch(provider, modelId, customModels)?.id
363
+ ?? fallbackModelId
364
+ ?? getProviderCatalog(provider).defaultModel
365
+ }
366
+
367
+ export function normalizeClaudeModelId(
368
+ modelId?: string,
369
+ fallbackModelId = "claude-opus-4-7",
370
+ customModels?: readonly CustomModelEntry[],
371
+ ): string {
372
+ return normalizeProviderModelId("claude", modelId, fallbackModelId, customModels)
373
+ }
374
+
375
+ export function normalizeCodexModelId(
376
+ modelId?: string,
377
+ fallbackModelId = "gpt-5.5",
378
+ customModels?: readonly CustomModelEntry[],
379
+ ): string {
380
+ return normalizeProviderModelId("codex", modelId, fallbackModelId, customModels)
381
+ }
382
+
383
+ export function getProviderModelOption(
384
+ provider: AgentProvider,
385
+ modelId: string,
386
+ customModels?: readonly CustomModelEntry[],
387
+ ): ProviderModelOption | undefined {
388
+ const normalizedModelId = normalizeProviderModelId(provider, modelId, undefined, customModels)
389
+ return catalogModelsFor(provider, customModels).find((candidate) => candidate.id === normalizedModelId)
390
+ }
391
+
392
+ export function getClaudeModelOption(
393
+ modelId: string,
394
+ customModels?: readonly CustomModelEntry[],
395
+ ): ProviderModelOption | undefined {
396
+ return getProviderModelOption("claude", modelId, customModels)
397
+ }
398
+
399
+ export function supportsClaudeMaxReasoningEffort(
400
+ modelId: string,
401
+ customModels?: readonly CustomModelEntry[],
402
+ ): boolean {
403
+ return Boolean(getClaudeModelOption(modelId, customModels)?.supportsMaxReasoningEffort)
404
+ }
405
+
406
+ export function getClaudeContextWindowOptions(
407
+ modelId: string,
408
+ customModels?: readonly CustomModelEntry[],
409
+ ): readonly ProviderContextWindowOption[] {
410
+ return getClaudeModelOption(modelId, customModels)?.contextWindowOptions ?? []
411
+ }
412
+
413
+ export function normalizeClaudeContextWindow(
414
+ modelId: string,
415
+ contextWindow?: string,
416
+ customModels?: readonly CustomModelEntry[],
417
+ ): ClaudeContextWindow {
418
+ const options = getClaudeContextWindowOptions(modelId, customModels)
419
+ if (options.length === 0) return DEFAULT_CLAUDE_MODEL_OPTIONS.contextWindow
420
+ return isClaudeContextWindow(contextWindow) && options.some((option) => option.id === contextWindow)
421
+ ? contextWindow
422
+ : DEFAULT_CLAUDE_MODEL_OPTIONS.contextWindow
423
+ }
424
+
425
+ export function resolveClaudeApiModelId(modelId: string, contextWindow?: ClaudeContextWindow): string {
426
+ return contextWindow === "1m" ? `${modelId}[1m]` : modelId
427
+ }
428
+
429
+ export function resolveClaudeContextWindowTokens(contextWindow: ClaudeContextWindow): number {
430
+ switch (contextWindow) {
431
+ case "1m":
432
+ return 1_000_000
433
+ case "200k":
434
+ default:
435
+ return 200_000
436
+ }
437
+ }