@oscharko-dev/keiko-server 0.2.7 → 0.2.9

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 (302) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/chat-handlers.d.ts +18 -2
  3. package/dist/chat-handlers.d.ts.map +1 -1
  4. package/dist/chat-handlers.js +185 -3
  5. package/dist/command-runner-errors.d.ts +17 -0
  6. package/dist/command-runner-errors.d.ts.map +1 -0
  7. package/dist/command-runner-errors.js +37 -0
  8. package/dist/command-runner-evidence.d.ts +23 -0
  9. package/dist/command-runner-evidence.d.ts.map +1 -0
  10. package/dist/command-runner-evidence.js +69 -0
  11. package/dist/command-runner-routes.d.ts +7 -0
  12. package/dist/command-runner-routes.d.ts.map +1 -0
  13. package/dist/command-runner-routes.js +175 -0
  14. package/dist/command-runner.d.ts +29 -0
  15. package/dist/command-runner.d.ts.map +1 -0
  16. package/dist/command-runner.js +348 -0
  17. package/dist/conversation-prompt.d.ts +2 -2
  18. package/dist/conversation-prompt.d.ts.map +1 -1
  19. package/dist/conversation-prompt.js +17 -1
  20. package/dist/csp.d.ts.map +1 -1
  21. package/dist/csp.js +3 -0
  22. package/dist/deps.d.ts +28 -1
  23. package/dist/deps.d.ts.map +1 -1
  24. package/dist/deps.js +288 -13
  25. package/dist/discussion-prompt.d.ts +4 -0
  26. package/dist/discussion-prompt.d.ts.map +1 -0
  27. package/dist/discussion-prompt.js +19 -0
  28. package/dist/editor/agentActionAudit.d.ts +18 -0
  29. package/dist/editor/agentActionAudit.d.ts.map +1 -0
  30. package/dist/editor/agentActionAudit.js +80 -0
  31. package/dist/editor/agentRoutes.d.ts +1 -0
  32. package/dist/editor/agentRoutes.d.ts.map +1 -1
  33. package/dist/editor/agentRoutes.js +292 -55
  34. package/dist/editor/agentSessionRegistry.d.ts +35 -0
  35. package/dist/editor/agentSessionRegistry.d.ts.map +1 -0
  36. package/dist/editor/agentSessionRegistry.js +243 -0
  37. package/dist/editor/completionRoutes.d.ts.map +1 -1
  38. package/dist/editor/completionRoutes.js +5 -10
  39. package/dist/editor/languageRoutes.d.ts +12 -1
  40. package/dist/editor/languageRoutes.d.ts.map +1 -1
  41. package/dist/editor/languageRoutes.js +71 -8
  42. package/dist/editor/languageService.d.ts +3 -2
  43. package/dist/editor/languageService.d.ts.map +1 -1
  44. package/dist/editor/languageService.js +41 -3
  45. package/dist/editor/languageServiceHost.d.ts.map +1 -1
  46. package/dist/editor/languageServiceHost.js +2 -2
  47. package/dist/editor/lsp/hostLanguageOperation.d.ts +17 -0
  48. package/dist/editor/lsp/hostLanguageOperation.d.ts.map +1 -0
  49. package/dist/editor/lsp/hostLanguageOperation.js +436 -0
  50. package/dist/editor/lsp/hostLanguageProviders.d.ts +26 -0
  51. package/dist/editor/lsp/hostLanguageProviders.d.ts.map +1 -0
  52. package/dist/editor/lsp/hostLanguageProviders.js +161 -0
  53. package/dist/editor/lsp/lspFrameCodec.d.ts +13 -0
  54. package/dist/editor/lsp/lspFrameCodec.d.ts.map +1 -0
  55. package/dist/editor/lsp/lspFrameCodec.js +164 -0
  56. package/dist/editor/lsp/lspJsonRpcClient.d.ts +34 -0
  57. package/dist/editor/lsp/lspJsonRpcClient.d.ts.map +1 -0
  58. package/dist/editor/lsp/lspJsonRpcClient.js +173 -0
  59. package/dist/editor/lsp/lspLanguageProvider.d.ts +7 -0
  60. package/dist/editor/lsp/lspLanguageProvider.d.ts.map +1 -0
  61. package/dist/editor/lsp/lspLanguageProvider.js +29 -0
  62. package/dist/editor/lsp/lspLifecycleLedger.d.ts +5 -0
  63. package/dist/editor/lsp/lspLifecycleLedger.d.ts.map +1 -0
  64. package/dist/editor/lsp/lspLifecycleLedger.js +37 -0
  65. package/dist/editor/lsp/lspNodeAdapter.d.ts +31 -0
  66. package/dist/editor/lsp/lspNodeAdapter.d.ts.map +1 -0
  67. package/dist/editor/lsp/lspNodeAdapter.js +230 -0
  68. package/dist/editor/lsp/lspProcessManager.d.ts +24 -0
  69. package/dist/editor/lsp/lspProcessManager.d.ts.map +1 -0
  70. package/dist/editor/lsp/lspProcessManager.js +255 -0
  71. package/dist/editor/lsp/lspRestartThrottle.d.ts +6 -0
  72. package/dist/editor/lsp/lspRestartThrottle.d.ts.map +1 -0
  73. package/dist/editor/lsp/lspRestartThrottle.js +24 -0
  74. package/dist/editor/lsp/lspStatusRoute.d.ts +8 -0
  75. package/dist/editor/lsp/lspStatusRoute.d.ts.map +1 -0
  76. package/dist/editor/lsp/lspStatusRoute.js +22 -0
  77. package/dist/editor/lsp/lspTransport.d.ts +19 -0
  78. package/dist/editor/lsp/lspTransport.d.ts.map +1 -0
  79. package/dist/editor/lsp/lspTransport.js +55 -0
  80. package/dist/editor/lsp/testing/fakeLspProcess.d.ts +23 -0
  81. package/dist/editor/lsp/testing/fakeLspProcess.d.ts.map +1 -0
  82. package/dist/editor/lsp/testing/fakeLspProcess.js +132 -0
  83. package/dist/files.d.ts +63 -0
  84. package/dist/files.d.ts.map +1 -1
  85. package/dist/files.js +799 -1
  86. package/dist/gateway-readiness.d.ts +6 -0
  87. package/dist/gateway-readiness.d.ts.map +1 -0
  88. package/dist/gateway-readiness.js +624 -0
  89. package/dist/gateway-setup.d.ts +2 -0
  90. package/dist/gateway-setup.d.ts.map +1 -1
  91. package/dist/gateway-setup.js +275 -11
  92. package/dist/gitDelivery/actionSheetProjection.d.ts +30 -0
  93. package/dist/gitDelivery/actionSheetProjection.d.ts.map +1 -0
  94. package/dist/gitDelivery/actionSheetProjection.js +206 -0
  95. package/dist/gitDelivery/actionSheetRoutes.d.ts +29 -0
  96. package/dist/gitDelivery/actionSheetRoutes.d.ts.map +1 -0
  97. package/dist/gitDelivery/actionSheetRoutes.js +293 -0
  98. package/dist/gitDelivery/agentOperationsRoutes.d.ts +33 -0
  99. package/dist/gitDelivery/agentOperationsRoutes.d.ts.map +1 -0
  100. package/dist/gitDelivery/agentOperationsRoutes.js +405 -0
  101. package/dist/gitDelivery/commitRoutes.d.ts +23 -0
  102. package/dist/gitDelivery/commitRoutes.d.ts.map +1 -0
  103. package/dist/gitDelivery/commitRoutes.js +204 -0
  104. package/dist/gitDelivery/evidenceRoutes.d.ts +9 -0
  105. package/dist/gitDelivery/evidenceRoutes.d.ts.map +1 -0
  106. package/dist/gitDelivery/evidenceRoutes.js +101 -0
  107. package/dist/gitDelivery/execution.d.ts +38 -0
  108. package/dist/gitDelivery/execution.d.ts.map +1 -0
  109. package/dist/gitDelivery/execution.js +117 -0
  110. package/dist/gitDelivery/localMutationRoutes.d.ts +30 -0
  111. package/dist/gitDelivery/localMutationRoutes.d.ts.map +1 -0
  112. package/dist/gitDelivery/localMutationRoutes.js +165 -0
  113. package/dist/gitDelivery/mergeExecution.d.ts +63 -0
  114. package/dist/gitDelivery/mergeExecution.d.ts.map +1 -0
  115. package/dist/gitDelivery/mergeExecution.js +168 -0
  116. package/dist/gitDelivery/mergeRoutes.d.ts +12 -0
  117. package/dist/gitDelivery/mergeRoutes.d.ts.map +1 -0
  118. package/dist/gitDelivery/mergeRoutes.js +218 -0
  119. package/dist/gitDelivery/mutationEvidenceLedger.d.ts +23 -0
  120. package/dist/gitDelivery/mutationEvidenceLedger.d.ts.map +1 -0
  121. package/dist/gitDelivery/mutationEvidenceLedger.js +87 -0
  122. package/dist/gitDelivery/prExecution.d.ts +54 -0
  123. package/dist/gitDelivery/prExecution.d.ts.map +1 -0
  124. package/dist/gitDelivery/prExecution.js +192 -0
  125. package/dist/gitDelivery/prRoutes.d.ts +12 -0
  126. package/dist/gitDelivery/prRoutes.d.ts.map +1 -0
  127. package/dist/gitDelivery/prRoutes.js +256 -0
  128. package/dist/gitDelivery/pushExecution.d.ts +43 -0
  129. package/dist/gitDelivery/pushExecution.d.ts.map +1 -0
  130. package/dist/gitDelivery/pushExecution.js +124 -0
  131. package/dist/gitDelivery/pushRoutes.d.ts +12 -0
  132. package/dist/gitDelivery/pushRoutes.d.ts.map +1 -0
  133. package/dist/gitDelivery/pushRoutes.js +200 -0
  134. package/dist/gitDelivery/requestGuards.d.ts +15 -0
  135. package/dist/gitDelivery/requestGuards.d.ts.map +1 -0
  136. package/dist/gitDelivery/requestGuards.js +97 -0
  137. package/dist/gitDelivery/syncEvidence.d.ts +37 -0
  138. package/dist/gitDelivery/syncEvidence.d.ts.map +1 -0
  139. package/dist/gitDelivery/syncEvidence.js +85 -0
  140. package/dist/gitDelivery/syncExecution.d.ts +30 -0
  141. package/dist/gitDelivery/syncExecution.d.ts.map +1 -0
  142. package/dist/gitDelivery/syncExecution.js +266 -0
  143. package/dist/gitDelivery/syncRoutes.d.ts +13 -0
  144. package/dist/gitDelivery/syncRoutes.d.ts.map +1 -0
  145. package/dist/gitDelivery/syncRoutes.js +200 -0
  146. package/dist/gitPorcelainStatus.d.ts +15 -0
  147. package/dist/gitPorcelainStatus.d.ts.map +1 -0
  148. package/dist/gitPorcelainStatus.js +104 -0
  149. package/dist/gitRepositoryReads.d.ts +10 -0
  150. package/dist/gitRepositoryReads.d.ts.map +1 -0
  151. package/dist/gitRepositoryReads.js +314 -0
  152. package/dist/gitRepositoryRoutes.d.ts +7 -0
  153. package/dist/gitRepositoryRoutes.d.ts.map +1 -0
  154. package/dist/gitRepositoryRoutes.js +221 -0
  155. package/dist/gitRoutes.d.ts +66 -0
  156. package/dist/gitRoutes.d.ts.map +1 -0
  157. package/dist/gitRoutes.js +543 -0
  158. package/dist/governed-workflow.d.ts +2 -0
  159. package/dist/governed-workflow.d.ts.map +1 -1
  160. package/dist/governed-workflow.js +4 -0
  161. package/dist/grounded-qa-hybrid.d.ts.map +1 -1
  162. package/dist/grounded-qa-hybrid.js +2 -0
  163. package/dist/grounded-qa-multi-source.d.ts.map +1 -1
  164. package/dist/grounded-qa-multi-source.js +1 -0
  165. package/dist/grounded-qa.d.ts +11 -0
  166. package/dist/grounded-qa.d.ts.map +1 -1
  167. package/dist/grounded-qa.js +14 -4
  168. package/dist/headers.d.ts +4 -1
  169. package/dist/headers.d.ts.map +1 -1
  170. package/dist/headers.js +11 -4
  171. package/dist/index.d.ts +8 -1
  172. package/dist/index.d.ts.map +1 -1
  173. package/dist/index.js +9 -1
  174. package/dist/local-knowledge-grounded-qa.d.ts.map +1 -1
  175. package/dist/local-knowledge-grounded-qa.js +11 -2
  176. package/dist/qualityIntelligence/figmaSnapshotRoutes.d.ts +1 -1
  177. package/dist/qualityIntelligence/figmaSnapshotRoutes.d.ts.map +1 -1
  178. package/dist/qualityIntelligence/figmaSnapshotRoutes.js +1 -1
  179. package/dist/read-handlers.d.ts +5 -0
  180. package/dist/read-handlers.d.ts.map +1 -1
  181. package/dist/read-handlers.js +57 -1
  182. package/dist/routes.d.ts.map +1 -1
  183. package/dist/routes.js +260 -12
  184. package/dist/run-engine.d.ts.map +1 -1
  185. package/dist/run-engine.js +3 -0
  186. package/dist/run-handlers.d.ts +0 -1
  187. package/dist/run-handlers.d.ts.map +1 -1
  188. package/dist/run-handlers.js +64 -211
  189. package/dist/run-request.d.ts +11 -0
  190. package/dist/run-request.d.ts.map +1 -1
  191. package/dist/run-request.js +158 -10
  192. package/dist/runtime/capabilityDetector.d.ts +38 -0
  193. package/dist/runtime/capabilityDetector.d.ts.map +1 -0
  194. package/dist/runtime/capabilityDetector.js +443 -0
  195. package/dist/runtime/capabilityRoutes.d.ts +9 -0
  196. package/dist/runtime/capabilityRoutes.d.ts.map +1 -0
  197. package/dist/runtime/capabilityRoutes.js +45 -0
  198. package/dist/runtime/containerEngineDetector.d.ts +17 -0
  199. package/dist/runtime/containerEngineDetector.d.ts.map +1 -0
  200. package/dist/runtime/containerEngineDetector.js +222 -0
  201. package/dist/runtime/containerRoutes.d.ts +8 -0
  202. package/dist/runtime/containerRoutes.d.ts.map +1 -0
  203. package/dist/runtime/containerRoutes.js +207 -0
  204. package/dist/runtime/containerRunner-errors.d.ts +18 -0
  205. package/dist/runtime/containerRunner-errors.d.ts.map +1 -0
  206. package/dist/runtime/containerRunner-errors.js +42 -0
  207. package/dist/runtime/containerRunner-evidence.d.ts +24 -0
  208. package/dist/runtime/containerRunner-evidence.d.ts.map +1 -0
  209. package/dist/runtime/containerRunner-evidence.js +74 -0
  210. package/dist/runtime/containerRunner.d.ts +37 -0
  211. package/dist/runtime/containerRunner.d.ts.map +1 -0
  212. package/dist/runtime/containerRunner.js +443 -0
  213. package/dist/server.d.ts.map +1 -1
  214. package/dist/server.js +24 -4
  215. package/dist/store/db.d.ts.map +1 -1
  216. package/dist/store/db.js +2 -1
  217. package/dist/store/index.d.ts +1 -1
  218. package/dist/store/index.d.ts.map +1 -1
  219. package/dist/store/messages.d.ts +2 -1
  220. package/dist/store/messages.d.ts.map +1 -1
  221. package/dist/store/messages.js +46 -4
  222. package/dist/store/schema.d.ts +1 -1
  223. package/dist/store/schema.d.ts.map +1 -1
  224. package/dist/store/schema.js +68 -1
  225. package/dist/store/types.d.ts +3 -2
  226. package/dist/store/types.d.ts.map +1 -1
  227. package/dist/task-workspace/active-store.d.ts +21 -0
  228. package/dist/task-workspace/active-store.d.ts.map +1 -0
  229. package/dist/task-workspace/active-store.js +55 -0
  230. package/dist/task-workspace/authorization.d.ts +7 -0
  231. package/dist/task-workspace/authorization.d.ts.map +1 -0
  232. package/dist/task-workspace/authorization.js +54 -0
  233. package/dist/task-workspace/binding.d.ts +3 -0
  234. package/dist/task-workspace/binding.d.ts.map +1 -0
  235. package/dist/task-workspace/binding.js +22 -0
  236. package/dist/task-workspace/cleanup.d.ts +4 -0
  237. package/dist/task-workspace/cleanup.d.ts.map +1 -0
  238. package/dist/task-workspace/cleanup.js +428 -0
  239. package/dist/task-workspace/errors.d.ts +14 -0
  240. package/dist/task-workspace/errors.d.ts.map +1 -0
  241. package/dist/task-workspace/errors.js +81 -0
  242. package/dist/task-workspace/evidence.d.ts +32 -0
  243. package/dist/task-workspace/evidence.d.ts.map +1 -0
  244. package/dist/task-workspace/evidence.js +52 -0
  245. package/dist/task-workspace/field-safety.d.ts +3 -0
  246. package/dist/task-workspace/field-safety.d.ts.map +1 -0
  247. package/dist/task-workspace/field-safety.js +42 -0
  248. package/dist/task-workspace/health.d.ts +4 -0
  249. package/dist/task-workspace/health.d.ts.map +1 -0
  250. package/dist/task-workspace/health.js +163 -0
  251. package/dist/task-workspace/lifecycle.d.ts +3 -0
  252. package/dist/task-workspace/lifecycle.d.ts.map +1 -0
  253. package/dist/task-workspace/lifecycle.js +248 -0
  254. package/dist/task-workspace/locks.d.ts +13 -0
  255. package/dist/task-workspace/locks.d.ts.map +1 -0
  256. package/dist/task-workspace/locks.js +44 -0
  257. package/dist/task-workspace/managed-root.d.ts +7 -0
  258. package/dist/task-workspace/managed-root.d.ts.map +1 -0
  259. package/dist/task-workspace/managed-root.js +98 -0
  260. package/dist/task-workspace/mutex.d.ts +8 -0
  261. package/dist/task-workspace/mutex.d.ts.map +1 -0
  262. package/dist/task-workspace/mutex.js +82 -0
  263. package/dist/task-workspace/naming.d.ts +15 -0
  264. package/dist/task-workspace/naming.d.ts.map +1 -0
  265. package/dist/task-workspace/naming.js +0 -0
  266. package/dist/task-workspace/provisioning.d.ts +3 -0
  267. package/dist/task-workspace/provisioning.d.ts.map +1 -0
  268. package/dist/task-workspace/provisioning.js +528 -0
  269. package/dist/task-workspace/reconciliation.d.ts +15 -0
  270. package/dist/task-workspace/reconciliation.d.ts.map +1 -0
  271. package/dist/task-workspace/reconciliation.js +274 -0
  272. package/dist/task-workspace/repair.d.ts +3 -0
  273. package/dist/task-workspace/repair.d.ts.map +1 -0
  274. package/dist/task-workspace/repair.js +286 -0
  275. package/dist/task-workspace/routes.d.ts +19 -0
  276. package/dist/task-workspace/routes.d.ts.map +1 -0
  277. package/dist/task-workspace/routes.js +481 -0
  278. package/dist/task-workspace/store.d.ts +12 -0
  279. package/dist/task-workspace/store.d.ts.map +1 -0
  280. package/dist/task-workspace/store.js +128 -0
  281. package/dist/task-workspace/types.d.ts +170 -0
  282. package/dist/task-workspace/types.d.ts.map +1 -0
  283. package/dist/task-workspace/types.js +5 -0
  284. package/dist/voice-action-governance.d.ts +23 -0
  285. package/dist/voice-action-governance.d.ts.map +1 -0
  286. package/dist/voice-action-governance.js +126 -0
  287. package/dist/voice-handlers.d.ts +6 -0
  288. package/dist/voice-handlers.d.ts.map +1 -0
  289. package/dist/voice-handlers.js +570 -0
  290. package/dist/voice-realtime-grounded-tool.d.ts +31 -0
  291. package/dist/voice-realtime-grounded-tool.d.ts.map +1 -0
  292. package/dist/voice-realtime-grounded-tool.js +322 -0
  293. package/dist/voice-realtime.d.ts +69 -0
  294. package/dist/voice-realtime.d.ts.map +1 -0
  295. package/dist/voice-realtime.js +787 -0
  296. package/dist/workspace-state-handlers.d.ts +5 -0
  297. package/dist/workspace-state-handlers.d.ts.map +1 -0
  298. package/dist/workspace-state-handlers.js +106 -0
  299. package/package.json +20 -19
  300. package/dist/grounded-handoff.d.ts +0 -4
  301. package/dist/grounded-handoff.d.ts.map +0 -1
  302. package/dist/grounded-handoff.js +0 -445
@@ -3,7 +3,7 @@
3
3
  // chat-completions smoke call, stores the resulting config on disk with private permissions, and
4
4
  // updates the in-memory runtime config without exposing credentials back to the browser.
5
5
  import { resolveEvidenceDir } from "@oscharko-dev/keiko-evidence";
6
- import { apiKeyHeaderValue, ConfigInvalidError, DEFAULT_API_KEY_HEADER_NAME, Gateway, createDefaultChatCapability, listConfiguredCapabilities, normalizeApiKeyHeaderName, parseGatewayConfig, toSafeObject, validateBaseUrl, } from "@oscharko-dev/keiko-model-gateway";
6
+ import { apiKeyHeaderValue, ConfigInvalidError, DEFAULT_API_KEY_HEADER_NAME, Gateway, createDefaultChatCapability, listConfiguredCapabilities, modelSupportsSpeechInput, normalizeApiKeyHeaderName, parseGatewayConfig, selectSpeechToTextModel, toSafeObject, validateBaseUrl, VOICE_PROVIDER_LOCALITIES, } from "@oscharko-dev/keiko-model-gateway";
7
7
  import { gatewayFetch, readJsonCapped } from "@oscharko-dev/keiko-model-gateway/internal/http";
8
8
  import { redact } from "@oscharko-dev/keiko-security";
9
9
  import { errorBody } from "./routes.js";
@@ -108,10 +108,25 @@ function isAzureFoundryBaseUrl(baseUrl) {
108
108
  return false;
109
109
  }
110
110
  }
111
+ function createDefaultSetupCapability(modelId, embeddingModelIds) {
112
+ if (embeddingModelIds?.includes(modelId) === true) {
113
+ return createDefaultEmbeddingCapabilityForSetup(modelId);
114
+ }
115
+ const capability = createDefaultChatCapability(modelId);
116
+ if (modelId.toLowerCase().includes("mistral")) {
117
+ return {
118
+ ...capability,
119
+ toolCalling: false,
120
+ knownLimitations: [
121
+ ...capability.knownLimitations,
122
+ "Tool calling is disabled by default for Mistral deployments until endpoint readiness verifies it",
123
+ ],
124
+ };
125
+ }
126
+ return capability;
127
+ }
111
128
  function providerRaw(modelId, baseUrl, apiKey, options = {}) {
112
- const defaultCapability = options.embeddingModelIds?.includes(modelId) === true
113
- ? createDefaultEmbeddingCapabilityForSetup(modelId)
114
- : createDefaultChatCapability(modelId);
129
+ const defaultCapability = createDefaultSetupCapability(modelId, options.embeddingModelIds);
115
130
  return {
116
131
  modelId,
117
132
  baseUrl,
@@ -125,6 +140,41 @@ function providerRaw(modelId, baseUrl, apiKey, options = {}) {
125
140
  retryBaseDelayMs: options.retryBaseDelayMs ?? 500,
126
141
  };
127
142
  }
143
+ function createDefaultVoiceDictationCapabilityForSetup(modelId, providerLocality) {
144
+ return {
145
+ id: modelId,
146
+ kind: "voice",
147
+ contextWindow: 0,
148
+ maxOutputTokens: 0,
149
+ toolCalling: false,
150
+ structuredOutput: false,
151
+ streaming: false,
152
+ supportsImageInput: false,
153
+ supportsDocumentInput: false,
154
+ workflowEligible: false,
155
+ supportsSpeechInput: true,
156
+ voiceProviderLocality: providerLocality,
157
+ costClass: "low",
158
+ latencyClass: "fast",
159
+ throughputHint: "runtime-configured speech-to-text endpoint",
160
+ preferredUseCases: ["Dictation"],
161
+ knownLimitations: [
162
+ "Speech-to-text only; dictation is verified when the first audio clip is transcribed",
163
+ ],
164
+ };
165
+ }
166
+ function voiceProviderRaw(modelId, baseUrl, apiKey, options = {}) {
167
+ return {
168
+ modelId,
169
+ baseUrl,
170
+ apiKey,
171
+ apiKeyHeaderName: options.apiKeyHeaderName ?? DEFAULT_API_KEY_HEADER_NAME,
172
+ capability: createDefaultVoiceDictationCapabilityForSetup(modelId, options.providerLocality ?? "azure-foundry"),
173
+ timeoutMs: options.timeoutMs ?? 30_000,
174
+ maxRetries: 1,
175
+ retryBaseDelayMs: 500,
176
+ };
177
+ }
128
178
  function isLikelyEmbeddingModelId(modelId) {
129
179
  return EMBEDDING_ID_PATTERN.test(modelId);
130
180
  }
@@ -223,7 +273,19 @@ function currentImageInputModelIds(config) {
223
273
  ?.filter((capability) => capability.kind === "chat" && capability.supportsImageInput)
224
274
  .map((capability) => capability.id) ?? []);
225
275
  }
226
- function rawConfigFromCurrent(config, figmaAccessToken, timeoutMs) {
276
+ // `supportedVoicePersonas` is DERIVED at parse time from a provider's `voiceProfiles` (Issue #1557,
277
+ // ADR-0094 D2 / HAZARD-3). It must NOT be persisted: the strict top-level `capabilities` parser
278
+ // rejects it as an unrecognised input key, and re-deriving it on reload keeps a single source of
279
+ // truth (the credential-tier `voiceProfiles`). Strip it so a save → reload round-trip re-derives.
280
+ function stripDerivedVoicePersonas(capability) {
281
+ const { supportedVoicePersonas, ...rest } = capability;
282
+ return supportedVoicePersonas === undefined ? capability : rest;
283
+ }
284
+ // Exported as a test seam (mirroring `smokeTestCandidates` / the discovery-normalization exports):
285
+ // the preserve-existing save path round-trips a parsed config back to raw for persistence, and the
286
+ // Issue #1557 voice-persona round-trip (voiceProfiles preserved, derived supportedVoicePersonas
287
+ // stripped and re-derived on reload — ADR-0094 D2) is pinned directly against this function.
288
+ export function rawConfigFromCurrent(config, figmaAccessToken, timeoutMs) {
227
289
  return {
228
290
  providers: config.providers.map((provider) => {
229
291
  const capability = config.capabilities?.find((item) => item.id === provider.modelId);
@@ -235,15 +297,67 @@ function rawConfigFromCurrent(config, figmaAccessToken, timeoutMs) {
235
297
  timeoutMs: timeoutMs ?? provider.timeoutMs,
236
298
  maxRetries: provider.maxRetries,
237
299
  retryBaseDelayMs: provider.retryBaseDelayMs,
238
- ...(capability === undefined ? {} : { capability }),
300
+ // Persist the credential-tier persona voice-id mapping so personas survive a save; the
301
+ // derived content-free `supportedVoicePersonas` is stripped and re-derived on reload.
302
+ ...(provider.voiceProfiles === undefined ? {} : { voiceProfiles: provider.voiceProfiles }),
303
+ ...(capability === undefined ? {} : { capability: stripDerivedVoicePersonas(capability) }),
239
304
  };
240
305
  }),
241
306
  circuitBreaker: config.circuitBreaker,
242
- ...(config.capabilities === undefined ? {} : { capabilities: config.capabilities }),
307
+ ...(config.capabilities === undefined
308
+ ? {}
309
+ : { capabilities: config.capabilities.map(stripDerivedVoicePersonas) }),
243
310
  ...(config.grounding === undefined ? {} : { grounding: config.grounding }),
244
311
  ...(figmaAccessToken === undefined ? {} : { figma: { accessToken: figmaAccessToken } }),
245
312
  };
246
313
  }
314
+ function rawCapabilityIsSpeechInputVoice(value) {
315
+ return (isRecord(value) &&
316
+ value.kind === "voice" &&
317
+ (value.supportsSpeechInput === true || value.supportsRealtimeVoice === true));
318
+ }
319
+ function rawProviderIsSpeechInputVoice(value) {
320
+ return isRecord(value) && rawCapabilityIsSpeechInputVoice(value.capability);
321
+ }
322
+ function setupVoiceProviderFromCurrent(current) {
323
+ const provider = currentSpeechInputProvider(current);
324
+ if (provider === undefined) {
325
+ return undefined;
326
+ }
327
+ const capability = currentSpeechInputCapability(current, provider.modelId);
328
+ return {
329
+ modelId: provider.modelId,
330
+ baseUrl: provider.baseUrl,
331
+ apiKey: provider.apiKey,
332
+ apiKeyHeaderName: provider.apiKeyHeaderName ?? DEFAULT_API_KEY_HEADER_NAME,
333
+ timeoutMs: provider.timeoutMs,
334
+ providerLocality: capability?.voiceProviderLocality ?? "azure-foundry",
335
+ };
336
+ }
337
+ function applyVoiceProvider(rawConfig, voiceProvider) {
338
+ if (voiceProvider === undefined) {
339
+ return rawConfig;
340
+ }
341
+ const providers = Array.isArray(rawConfig.providers) ? rawConfig.providers : [];
342
+ const nextProviders = providers.filter((provider) => !rawProviderIsSpeechInputVoice(provider) &&
343
+ (!isRecord(provider) || provider.modelId !== voiceProvider.modelId));
344
+ const nextConfig = {
345
+ ...rawConfig,
346
+ providers: [
347
+ ...nextProviders,
348
+ voiceProviderRaw(voiceProvider.modelId, voiceProvider.baseUrl, voiceProvider.apiKey, {
349
+ apiKeyHeaderName: voiceProvider.apiKeyHeaderName,
350
+ timeoutMs: voiceProvider.timeoutMs,
351
+ providerLocality: voiceProvider.providerLocality,
352
+ }),
353
+ ],
354
+ };
355
+ if (Array.isArray(rawConfig.capabilities)) {
356
+ nextConfig.capabilities = rawConfig.capabilities.filter((capability) => !rawCapabilityIsSpeechInputVoice(capability) &&
357
+ (!isRecord(capability) || capability.id !== voiceProvider.modelId));
358
+ }
359
+ return nextConfig;
360
+ }
247
361
  function withInheritedEgress(rawConfig, egress) {
248
362
  if (egress === undefined || Object.hasOwn(rawConfig, "egress")) {
249
363
  return rawConfig;
@@ -640,6 +754,19 @@ function optionalSetupPositiveInt(value, path) {
640
754
  }
641
755
  return value;
642
756
  }
757
+ function parseVoiceProviderLocality(value, fallback) {
758
+ if (value === undefined) {
759
+ return fallback;
760
+ }
761
+ if (typeof value !== "string" ||
762
+ !VOICE_PROVIDER_LOCALITIES.includes(value)) {
763
+ return {
764
+ status: 400,
765
+ body: errorBody("BAD_REQUEST", "voiceProviderLocality is not supported."),
766
+ };
767
+ }
768
+ return value;
769
+ }
643
770
  function hasNonBlankStringField(raw, key) {
644
771
  const value = raw[key];
645
772
  return typeof value === "string" && value.trim().length > 0;
@@ -651,12 +778,36 @@ function hasNonEmptyListField(raw, key) {
651
778
  }
652
779
  return Array.isArray(value) && value.some((item) => typeof item === "string" && item.trim());
653
780
  }
781
+ function hasVoiceProviderInput(raw) {
782
+ return (hasNonBlankStringField(raw, "voiceBaseUrl") ||
783
+ hasNonBlankStringField(raw, "voiceApiKey") ||
784
+ hasNonBlankStringField(raw, "voiceApiKeyHeaderName") ||
785
+ hasNonBlankStringField(raw, "voiceModelId") ||
786
+ hasNonBlankStringField(raw, "voiceProviderLocality") ||
787
+ raw.voiceTimeoutMs !== undefined);
788
+ }
654
789
  function shouldPreserveExisting(raw, current) {
655
790
  return raw.preserveExisting === true && current !== undefined;
656
791
  }
657
792
  function firstProvider(current) {
658
793
  return current?.providers[0];
659
794
  }
795
+ function currentSpeechInputProvider(current) {
796
+ if (current === undefined) {
797
+ return undefined;
798
+ }
799
+ const modelId = selectSpeechToTextModel(current);
800
+ if (modelId === undefined) {
801
+ return undefined;
802
+ }
803
+ return current.providers.find((provider) => provider.modelId === modelId);
804
+ }
805
+ function currentSpeechInputCapability(current, modelId) {
806
+ if (current === undefined || modelId === undefined) {
807
+ return undefined;
808
+ }
809
+ return current.capabilities?.find((capability) => capability.id === modelId && modelSupportsSpeechInput(capability));
810
+ }
660
811
  function trimmedSubmittedString(raw, key) {
661
812
  const value = raw[key];
662
813
  if (typeof value !== "string")
@@ -673,6 +824,12 @@ function setupApiKeyHeaderSource(raw, provider, preserveExisting) {
673
824
  }
674
825
  return provider?.apiKeyHeaderName ?? DEFAULT_API_KEY_HEADER_NAME;
675
826
  }
827
+ function setupVoiceApiKeyHeaderSource(raw, provider, preserveExisting) {
828
+ if (raw.voiceApiKeyHeaderName !== undefined || !preserveExisting) {
829
+ return raw.voiceApiKeyHeaderName;
830
+ }
831
+ return provider?.apiKeyHeaderName ?? DEFAULT_API_KEY_HEADER_NAME;
832
+ }
676
833
  function readSetupGatewayCredentials(raw, env, current, preserveExisting) {
677
834
  const provider = firstProvider(current);
678
835
  const baseUrl = submittedOrInheritedString(raw, "baseUrl", provider?.baseUrl, preserveExisting);
@@ -691,6 +848,93 @@ function readSetupGatewayCredentials(raw, env, current, preserveExisting) {
691
848
  }
692
849
  return { baseUrl, apiKey, apiKeyHeaderName };
693
850
  }
851
+ function validateVoiceProviderConnection(provider, env) {
852
+ try {
853
+ parseGatewayConfig({
854
+ providers: [
855
+ voiceProviderRaw(provider.modelId, provider.baseUrl, provider.apiKey, {
856
+ apiKeyHeaderName: provider.apiKeyHeaderName,
857
+ timeoutMs: provider.timeoutMs,
858
+ providerLocality: provider.providerLocality,
859
+ }),
860
+ ],
861
+ circuitBreaker: { failureThreshold: 5, cooldownMs: 30_000, halfOpenProbes: 2 },
862
+ }, env);
863
+ return undefined;
864
+ }
865
+ catch (error) {
866
+ if (error instanceof ConfigInvalidError) {
867
+ return { status: 400, body: errorBody("BAD_REQUEST", error.message) };
868
+ }
869
+ throw error;
870
+ }
871
+ }
872
+ function setupVoiceModelId(raw, existing, preserveExisting) {
873
+ const modelId = trimmedSubmittedString(raw, "voiceModelId") ??
874
+ (preserveExisting ? existing?.modelId : undefined) ??
875
+ "keiko-stt";
876
+ if (!isUsableModelId(modelId)) {
877
+ return { status: 400, body: errorBody("BAD_REQUEST", "voiceModelId is invalid.") };
878
+ }
879
+ return modelId;
880
+ }
881
+ function setupVoiceConnection(raw, existing, preserveExisting) {
882
+ const baseUrl = submittedOrInheritedString(raw, "voiceBaseUrl", existing?.baseUrl, preserveExisting);
883
+ const apiKey = submittedOrInheritedString(raw, "voiceApiKey", existing?.apiKey, preserveExisting);
884
+ if (baseUrl.length === 0 || apiKey.length === 0) {
885
+ return {
886
+ status: 400,
887
+ body: errorBody("BAD_REQUEST", "voiceBaseUrl and voiceApiKey are required for dictation."),
888
+ };
889
+ }
890
+ return { baseUrl, apiKey };
891
+ }
892
+ function setupVoiceApiKeyHeaderName(raw, existing, preserveExisting) {
893
+ return normalizeSetupApiKeyHeaderName(setupVoiceApiKeyHeaderSource(raw, existing, preserveExisting));
894
+ }
895
+ function setupVoiceProviderLocality(raw, existingCapability) {
896
+ return parseVoiceProviderLocality(raw.voiceProviderLocality, existingCapability?.voiceProviderLocality ?? "azure-foundry");
897
+ }
898
+ function firstRouteResult(values) {
899
+ return values.find(isRouteResult);
900
+ }
901
+ function readSetupVoiceProvider(raw, env, current, preserveExisting) {
902
+ if (!hasVoiceProviderInput(raw)) {
903
+ return undefined;
904
+ }
905
+ const existing = preserveExisting ? currentSpeechInputProvider(current) : undefined;
906
+ const existingCapability = currentSpeechInputCapability(current, existing?.modelId);
907
+ const modelId = setupVoiceModelId(raw, existing, preserveExisting);
908
+ const connection = setupVoiceConnection(raw, existing, preserveExisting);
909
+ const apiKeyHeaderName = setupVoiceApiKeyHeaderName(raw, existing, preserveExisting);
910
+ const timeoutMs = optionalSetupPositiveInt(raw.voiceTimeoutMs, "voiceTimeoutMs");
911
+ const providerLocality = setupVoiceProviderLocality(raw, existingCapability);
912
+ const routeError = firstRouteResult([
913
+ modelId,
914
+ connection,
915
+ apiKeyHeaderName,
916
+ timeoutMs,
917
+ providerLocality,
918
+ ]);
919
+ if (routeError !== undefined) {
920
+ return routeError;
921
+ }
922
+ const resolvedConnection = connection;
923
+ const resolvedTimeoutMs = timeoutMs;
924
+ const provider = {
925
+ modelId: modelId,
926
+ baseUrl: resolvedConnection.baseUrl,
927
+ apiKey: resolvedConnection.apiKey,
928
+ apiKeyHeaderName: apiKeyHeaderName,
929
+ timeoutMs: resolvedTimeoutMs ?? existing?.timeoutMs,
930
+ providerLocality: providerLocality,
931
+ };
932
+ const invalidConnection = validateVoiceProviderConnection(provider, env);
933
+ if (invalidConnection !== undefined) {
934
+ return invalidConnection;
935
+ }
936
+ return provider;
937
+ }
694
938
  function resolveSetupModelLists(modelLists, current, preserveExisting) {
695
939
  const existing = preserveExisting ? current : undefined;
696
940
  return {
@@ -731,12 +975,17 @@ function readSetupRequest(raw, env, current) {
731
975
  if (isRouteResult(figmaAccessToken)) {
732
976
  return figmaAccessToken;
733
977
  }
978
+ const voiceProvider = readSetupVoiceProvider(raw, env, current, preserveExisting);
979
+ if (isRouteResult(voiceProvider)) {
980
+ return voiceProvider;
981
+ }
734
982
  const resolvedModelLists = resolveSetupModelLists(modelLists, current, preserveExisting);
735
983
  return {
736
984
  ...credentials,
737
985
  timeoutMs,
738
986
  deploymentNames: resolvedModelLists.deploymentNames,
739
987
  imageInputModelIds: resolvedModelLists.imageInputModelIds,
988
+ voiceProvider,
740
989
  figmaAccessToken: figmaAccessToken ?? current?.figma?.accessToken,
741
990
  verifyGateway: setupRequiresGatewayVerification(raw, preserveExisting),
742
991
  verifyFigmaCredential: figmaAccessToken !== undefined,
@@ -832,13 +1081,14 @@ function finalRawConfigForSetup(input, testedModelIds, embeddingModelIds, imageI
832
1081
  embeddingModelIds,
833
1082
  timeoutMs: input.timeoutMs,
834
1083
  });
835
- return {
1084
+ const rawConfigWithOptionalBlocks = {
836
1085
  ...rawConfig,
837
1086
  ...(input.current?.grounding === undefined ? {} : { grounding: input.current.grounding }),
838
1087
  ...(input.figmaAccessToken === undefined
839
1088
  ? {}
840
1089
  : { figma: { accessToken: input.figmaAccessToken } }),
841
1090
  };
1091
+ return applyVoiceProvider(rawConfigWithOptionalBlocks, input.voiceProvider ?? setupVoiceProviderFromCurrent(input.current));
842
1092
  }
843
1093
  function skippedModelIdsForSetup(candidateModelIds, testedModelIds, embeddingModelIds) {
844
1094
  const acceptedModelIds = new Set([...testedModelIds, ...embeddingModelIds]);
@@ -922,7 +1172,13 @@ function figmaCredentialFailureResult(error, request) {
922
1172
  }
923
1173
  return {
924
1174
  status: 502,
925
- body: errorBody("FIGMA_EGRESS_FAILED", safeError(error, [request.figmaAccessToken, request.apiKey, request.baseUrl])),
1175
+ body: errorBody("FIGMA_EGRESS_FAILED", safeError(error, [
1176
+ request.figmaAccessToken,
1177
+ request.apiKey,
1178
+ request.baseUrl,
1179
+ request.voiceProvider?.apiKey,
1180
+ request.voiceProvider?.baseUrl,
1181
+ ])),
926
1182
  };
927
1183
  }
928
1184
  async function verifySubmittedFigmaCredential(request, deps) {
@@ -979,6 +1235,7 @@ async function trySetupCandidate(baseUrl, request, deps, gatewayConfig, tester,
979
1235
  timeoutMs: request.timeoutMs,
980
1236
  deploymentNames: request.deploymentNames,
981
1237
  imageInputModelIds: request.imageInputModelIds,
1238
+ voiceProvider: request.voiceProvider,
982
1239
  tester,
983
1240
  discovery,
984
1241
  env: deps.env,
@@ -991,10 +1248,17 @@ async function trySetupCandidate(baseUrl, request, deps, gatewayConfig, tester,
991
1248
  return setupSuccessResult(verified.config, verified.testedModelIds, verified.skippedModelIds);
992
1249
  }
993
1250
  function setupCandidateError(error, request, baseUrl) {
994
- return safeError(error, [request.apiKey, request.baseUrl, baseUrl, request.figmaAccessToken]);
1251
+ return safeError(error, [
1252
+ request.apiKey,
1253
+ request.baseUrl,
1254
+ baseUrl,
1255
+ request.figmaAccessToken,
1256
+ request.voiceProvider?.apiKey,
1257
+ request.voiceProvider?.baseUrl,
1258
+ ]);
995
1259
  }
996
1260
  function saveExistingConfigUpdate(request, current, deps, gatewayConfig) {
997
- const rawConfig = rawConfigFromCurrent(current, request.figmaAccessToken, request.timeoutMs);
1261
+ const rawConfig = applyVoiceProvider(rawConfigFromCurrent(current, request.figmaAccessToken, request.timeoutMs), request.voiceProvider);
998
1262
  const config = parseGatewayConfig(withInheritedEgress(rawConfig, currentGatewayEgressConfig(deps)), deps.env);
999
1263
  persistGatewayConfig(rawConfig, gatewayConfig.storagePath, deps);
1000
1264
  gatewayConfig.set(config, true);
@@ -0,0 +1,30 @@
1
+ import { type GitDeliveryActionSheet, type GitDeliveryApprovalRequirement, type GitDeliveryBranchProtection, type GitDeliveryChecksState, type GitDeliveryMergeReadiness, type GitDeliveryOrgPolicyPack, type GitDeliveryProviderCapability, type GitDeliveryPullRequestState, type GitDeliveryRemediationClass, type GitDeliveryRepoPolicyPack, type GitDeliveryResolvedInputs } from "@oscharko-dev/keiko-contracts";
2
+ import { type GitWorktreeSnapshot } from "@oscharko-dev/keiko-tools";
3
+ export interface GitDeliveryProviderStateFacts {
4
+ readonly pullRequest?: GitDeliveryPullRequestState | undefined;
5
+ readonly mergeReadiness?: GitDeliveryMergeReadiness | undefined;
6
+ readonly branchProtection?: GitDeliveryBranchProtection | undefined;
7
+ readonly checks?: GitDeliveryChecksState | undefined;
8
+ }
9
+ export interface GitDeliveryTrustedPolicyPacks {
10
+ readonly orgPack?: GitDeliveryOrgPolicyPack | undefined;
11
+ readonly repoPack?: GitDeliveryRepoPolicyPack | undefined;
12
+ }
13
+ export interface BuildActionSheetFacts {
14
+ readonly actionId: string;
15
+ readonly resolvedInputs: GitDeliveryResolvedInputs;
16
+ readonly worktreeSnapshot: GitWorktreeSnapshot;
17
+ readonly approvalRequirement: GitDeliveryApprovalRequirement;
18
+ readonly policyPacks: GitDeliveryTrustedPolicyPacks;
19
+ readonly activeProviderCapabilities: readonly GitDeliveryProviderCapability[];
20
+ readonly providerState: GitDeliveryProviderStateFacts;
21
+ readonly providerReady: boolean;
22
+ readonly nowMs: number;
23
+ }
24
+ /**
25
+ * The pure projection. Runs preflight + policy over the supplied content-free facts and assembles the
26
+ * UI-safe action sheet. Deterministic: identical facts always yield an identical sheet.
27
+ */
28
+ export declare function buildActionSheetFromFacts(facts: BuildActionSheetFacts): GitDeliveryActionSheet;
29
+ export type { GitDeliveryRemediationClass };
30
+ //# sourceMappingURL=actionSheetProjection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"actionSheetProjection.d.ts","sourceRoot":"","sources":["../../src/gitDelivery/actionSheetProjection.ts"],"names":[],"mappings":"AAgBA,OAAO,EAIL,KAAK,sBAAsB,EAC3B,KAAK,8BAA8B,EACnC,KAAK,2BAA2B,EAChC,KAAK,sBAAsB,EAE3B,KAAK,yBAAyB,EAC9B,KAAK,wBAAwB,EAE7B,KAAK,6BAA6B,EAClC,KAAK,2BAA2B,EAGhC,KAAK,2BAA2B,EAChC,KAAK,yBAAyB,EAC9B,KAAK,yBAAyB,EAE/B,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAIL,KAAK,mBAAmB,EACzB,MAAM,2BAA2B,CAAC;AAInC,MAAM,WAAW,6BAA6B;IAC5C,QAAQ,CAAC,WAAW,CAAC,EAAE,2BAA2B,GAAG,SAAS,CAAC;IAC/D,QAAQ,CAAC,cAAc,CAAC,EAAE,yBAAyB,GAAG,SAAS,CAAC;IAChE,QAAQ,CAAC,gBAAgB,CAAC,EAAE,2BAA2B,GAAG,SAAS,CAAC;IACpE,QAAQ,CAAC,MAAM,CAAC,EAAE,sBAAsB,GAAG,SAAS,CAAC;CACtD;AAED,MAAM,WAAW,6BAA6B;IAC5C,QAAQ,CAAC,OAAO,CAAC,EAAE,wBAAwB,GAAG,SAAS,CAAC;IACxD,QAAQ,CAAC,QAAQ,CAAC,EAAE,yBAAyB,GAAG,SAAS,CAAC;CAC3D;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,cAAc,EAAE,yBAAyB,CAAC;IACnD,QAAQ,CAAC,gBAAgB,EAAE,mBAAmB,CAAC;IAC/C,QAAQ,CAAC,mBAAmB,EAAE,8BAA8B,CAAC;IAC7D,QAAQ,CAAC,WAAW,EAAE,6BAA6B,CAAC;IACpD,QAAQ,CAAC,0BAA0B,EAAE,SAAS,6BAA6B,EAAE,CAAC;IAC9E,QAAQ,CAAC,aAAa,EAAE,6BAA6B,CAAC;IACtD,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;IAIhC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAkOD;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,qBAAqB,GAAG,sBAAsB,CA+B9F;AAID,YAAY,EAAE,2BAA2B,EAAE,CAAC"}
@@ -0,0 +1,206 @@
1
+ // Pure projection: content-free kernel facts → UI-safe GitDeliveryActionSheet (Issue #473, Epic #470).
2
+ //
3
+ // This module is a PURE function of (resolvedInputs, worktreeSnapshot, trusted policy packs, approval
4
+ // shape, provider state, providerReady). It runs the kernel preflight (keiko-tools) and the policy
5
+ // evaluator (keiko-contracts), maps their content-free outputs into the action-sheet's typed
6
+ // expected-blocker and recovery-hint vocabularies, and hands the assembled facts to the contract's
7
+ // buildGitDeliveryActionSheet. No IO, no clock, no randomness, no request access — the caller (the
8
+ // route handler) gathers the facts and supplies the actionId and trusted packs.
9
+ //
10
+ // AUTHORITY (AC1): the policy decision, approval necessity, and required approvers are derived ONLY
11
+ // from evaluateGitPolicy over the server's TRUSTED packs. The approval payload is consumed for its
12
+ // SHAPE alone (whether a granted approval is attached); it never asserts the policy decision.
13
+ //
14
+ // Content-free: every value produced here is a count, flag, branch name, or typed/closed-vocabulary
15
+ // code. Never diff content, file paths, secrets, command strings, or raw subprocess output.
16
+ import { buildGitDeliveryActionSheet, gitDeliverySuggestedRecoveryStrategy, evaluateGitPolicy, } from "@oscharko-dev/keiko-contracts";
17
+ import { evaluateGitPreflight, } from "@oscharko-dev/keiko-tools";
18
+ // Demotes an expired granted approval to "not attached" so the action-sheet projection never shows an
19
+ // expired grant as satisfied/ready. The #472 execution kernel re-validates the token and expiry
20
+ // independently before any mutation; this is the preview-time mirror of that check.
21
+ function effectiveApproval(approval, nowMs) {
22
+ if (approval.required && approval.expiresAtMs !== undefined && approval.expiresAtMs <= nowMs) {
23
+ return { required: false };
24
+ }
25
+ return approval;
26
+ }
27
+ // ─── Target-branch derivation (for policy evaluation) ───────────────────────────────
28
+ // The affected branch name the policy evaluator matches branch-pattern rules against. Mirrors the
29
+ // contract's affectedBranchOf but is local so this module owns its own policy-context derivation.
30
+ function targetBranchName(inputs) {
31
+ switch (inputs.kind) {
32
+ case "branch-create":
33
+ return inputs.branchName;
34
+ case "push":
35
+ return inputs.sourceBranchName;
36
+ case "pr-create":
37
+ case "pr-update":
38
+ return inputs.headBranchName;
39
+ default:
40
+ return undefined;
41
+ }
42
+ }
43
+ // ─── Expected-blocker mapping (AC2/AC3) ─────────────────────────────────────────────
44
+ function preflightBlocker(finding) {
45
+ return {
46
+ source: "preflight",
47
+ severity: finding.severity,
48
+ remediation: finding.remediation,
49
+ reasonCode: finding.code,
50
+ };
51
+ }
52
+ function policyBlocker(decision) {
53
+ if (decision.outcome !== "blocked") {
54
+ return undefined;
55
+ }
56
+ return {
57
+ source: "policy",
58
+ severity: "blocking",
59
+ remediation: "user-actionable",
60
+ reasonCode: decision.reason,
61
+ };
62
+ }
63
+ const PROVIDER_BLOCKER_KINDS = [
64
+ "merge",
65
+ "pr-create",
66
+ "pr-update",
67
+ ];
68
+ function providerBlocker(inputs, mergeReadiness) {
69
+ if (!PROVIDER_BLOCKER_KINDS.includes(inputs.kind)) {
70
+ return undefined;
71
+ }
72
+ if (mergeReadiness?.ready !== false || mergeReadiness.blockingReason === undefined) {
73
+ return undefined;
74
+ }
75
+ return {
76
+ source: "provider",
77
+ severity: "advisory",
78
+ remediation: "user-actionable",
79
+ reasonCode: mergeReadiness.blockingReason,
80
+ };
81
+ }
82
+ function collectExpectedBlockers(findings, decision, inputs, mergeReadiness) {
83
+ const blockers = findings.map(preflightBlocker);
84
+ const policy = policyBlocker(decision);
85
+ if (policy !== undefined) {
86
+ blockers.push(policy);
87
+ }
88
+ const provider = providerBlocker(inputs, mergeReadiness);
89
+ if (provider !== undefined) {
90
+ blockers.push(provider);
91
+ }
92
+ return blockers;
93
+ }
94
+ // ─── Recovery-hint mapping (AC4 — common failure classes, same surface) ─────────────
95
+ // A deterministic, exhaustive table from each preflight finding code to a recovery action hint. The
96
+ // "recover-via-strategy" hints are produced separately so they can carry a suggestedRecoveryStrategy.
97
+ const STRATEGY_RECOVERY_CODES = new Set([
98
+ "dirty-worktree-impacts-recovery",
99
+ "recovery-target-unset",
100
+ ]);
101
+ const FINDING_RECOVERY_HINT = {
102
+ "detached-head": "configure-upstream",
103
+ "branch-already-exists": "adjust-policy-target",
104
+ "base-branch-missing": "adjust-policy-target",
105
+ "switch-target-missing": "adjust-policy-target",
106
+ "no-changes-to-stage": "stage-changes",
107
+ "nothing-staged-to-unstage": "stage-changes",
108
+ "nothing-staged-to-commit": "stage-changes",
109
+ "untracked-files-impacted": "stage-changes",
110
+ "no-upstream-configured": "configure-upstream",
111
+ "nothing-to-push": "retry",
112
+ "non-fast-forward": "resolve-conflicts",
113
+ "remote-alias-missing": "configure-upstream",
114
+ "remote-unreachable": "retry",
115
+ "operation-in-progress": "abort-in-progress-operation",
116
+ "no-operation-to-abort": "retry",
117
+ "recovery-target-unset": "recover-via-strategy",
118
+ "dirty-worktree-impacts-recovery": "recover-via-strategy",
119
+ };
120
+ function worktreeIsDirty(snapshot) {
121
+ return (snapshot.stagedFileCount > 0 ||
122
+ snapshot.unstagedFileCount > 0 ||
123
+ snapshot.untrackedFileCount > 0);
124
+ }
125
+ function recoveryHintForFinding(finding, inputs, snapshot) {
126
+ if (STRATEGY_RECOVERY_CODES.has(finding.code)) {
127
+ return {
128
+ actionHint: "recover-via-strategy",
129
+ remediation: finding.remediation,
130
+ suggestedRecoveryStrategy: gitDeliverySuggestedRecoveryStrategy(inputs.kind, worktreeIsDirty(snapshot)),
131
+ };
132
+ }
133
+ return { actionHint: FINDING_RECOVERY_HINT[finding.code], remediation: finding.remediation };
134
+ }
135
+ function policyRecoveryHint(decision) {
136
+ if (decision.outcome === "approval-gated") {
137
+ return { actionHint: "request-approval", remediation: "user-actionable" };
138
+ }
139
+ if (decision.outcome === "blocked") {
140
+ const actionHint = decision.reason === "approval-expired" ? "request-approval" : "adjust-policy-target";
141
+ return { actionHint, remediation: "user-actionable" };
142
+ }
143
+ return undefined;
144
+ }
145
+ // De-duplicates recovery hints on a stable signature (action + strategy) so the same class of fix is
146
+ // surfaced once even when several findings map to it. First occurrence wins (order-preserving).
147
+ function dedupeRecoveryHints(hints) {
148
+ const seen = new Set();
149
+ const out = [];
150
+ for (const hint of hints) {
151
+ const signature = `${hint.actionHint}:${hint.suggestedRecoveryStrategy ?? ""}`;
152
+ if (!seen.has(signature)) {
153
+ seen.add(signature);
154
+ out.push(hint);
155
+ }
156
+ }
157
+ return out;
158
+ }
159
+ function buildRecoveryHints(findings, decision, inputs, snapshot, providerReady) {
160
+ const hints = findings.map((finding) => recoveryHintForFinding(finding, inputs, snapshot));
161
+ const policy = policyRecoveryHint(decision);
162
+ if (policy !== undefined) {
163
+ hints.push(policy);
164
+ }
165
+ if (!providerReady) {
166
+ hints.push({ actionHint: "wait-for-provider", remediation: "internal" });
167
+ }
168
+ return dedupeRecoveryHints(hints);
169
+ }
170
+ function definedProviderState(state) {
171
+ const out = {};
172
+ if (state.pullRequest !== undefined)
173
+ out.pullRequest = state.pullRequest;
174
+ if (state.mergeReadiness !== undefined)
175
+ out.mergeReadiness = state.mergeReadiness;
176
+ if (state.branchProtection !== undefined)
177
+ out.branchProtection = state.branchProtection;
178
+ if (state.checks !== undefined)
179
+ out.checks = state.checks;
180
+ return out;
181
+ }
182
+ /**
183
+ * The pure projection. Runs preflight + policy over the supplied content-free facts and assembles the
184
+ * UI-safe action sheet. Deterministic: identical facts always yield an identical sheet.
185
+ */
186
+ export function buildActionSheetFromFacts(facts) {
187
+ const { resolvedInputs, worktreeSnapshot, providerState } = facts;
188
+ const preflight = evaluateGitPreflight(resolvedInputs, worktreeSnapshot);
189
+ const policyDecision = evaluateGitPolicy(facts.policyPacks.orgPack, facts.policyPacks.repoPack, {
190
+ actionKind: resolvedInputs.kind,
191
+ targetBranchName: targetBranchName(resolvedInputs),
192
+ activeProviderCapabilities: facts.activeProviderCapabilities,
193
+ });
194
+ const expectedBlockers = collectExpectedBlockers(preflight.findings, policyDecision, resolvedInputs, providerState.mergeReadiness);
195
+ const recovery = buildRecoveryHints(preflight.findings, policyDecision, resolvedInputs, worktreeSnapshot, facts.providerReady);
196
+ return buildGitDeliveryActionSheet({
197
+ actionId: facts.actionId,
198
+ resolvedInputs,
199
+ policyDecision,
200
+ approvalRequirement: effectiveApproval(facts.approvalRequirement, facts.nowMs),
201
+ providerReady: facts.providerReady,
202
+ expectedBlockers,
203
+ recovery,
204
+ ...definedProviderState(providerState),
205
+ });
206
+ }
@@ -0,0 +1,29 @@
1
+ import { type GitDeliveryApprovalRequirement, type GitDeliveryProviderCapability, type GitDeliveryResolvedInputs } from "@oscharko-dev/keiko-contracts";
2
+ import type { GitWorktreeSnapshot } from "@oscharko-dev/keiko-tools";
3
+ import type { RouteContext, RouteDefinition, RouteResult } from "../routes.js";
4
+ import type { UiHandlerDeps } from "../deps.js";
5
+ import { type GitDeliveryProviderStateFacts, type GitDeliveryTrustedPolicyPacks } from "./actionSheetProjection.js";
6
+ export type GitDeliveryActionSheetErrorCode = "GIT_DELIVERY_ACTION_SHEET_BAD_REQUEST" | "GIT_DELIVERY_ACTION_SHEET_PAYLOAD_TOO_LARGE" | "GIT_DELIVERY_ACTION_SHEET_FORBIDDEN_PAYLOAD";
7
+ export interface GitDeliveryActionSheetErrorBody {
8
+ readonly error: {
9
+ readonly code: GitDeliveryActionSheetErrorCode;
10
+ readonly message: string;
11
+ };
12
+ }
13
+ interface ValidatedRequest {
14
+ readonly resolvedInputs: GitDeliveryResolvedInputs;
15
+ readonly worktreeSnapshot: GitWorktreeSnapshot;
16
+ readonly approvalRequirement: GitDeliveryApprovalRequirement;
17
+ readonly providerState: GitDeliveryProviderStateFacts;
18
+ readonly activeProviderCapabilities: readonly GitDeliveryProviderCapability[];
19
+ }
20
+ export interface GitDeliveryActionSheetRouteOptions {
21
+ readonly idGenerator?: (request: ValidatedRequest) => string;
22
+ readonly policyPacks?: (deps: UiHandlerDeps) => GitDeliveryTrustedPolicyPacks;
23
+ readonly now?: () => number;
24
+ }
25
+ export declare const createHandleGitDeliveryActionSheet: (options?: GitDeliveryActionSheetRouteOptions) => ((ctx: RouteContext, deps: UiHandlerDeps) => Promise<RouteResult>);
26
+ export declare const handleGitDeliveryActionSheet: (ctx: RouteContext, deps: UiHandlerDeps) => Promise<RouteResult>;
27
+ export declare const GIT_DELIVERY_ACTION_SHEET_ROUTE_GROUP: readonly RouteDefinition[];
28
+ export {};
29
+ //# sourceMappingURL=actionSheetRoutes.d.ts.map