@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
@@ -5,17 +5,19 @@
5
5
  // redacted final report projection (or status:"running"); apply is the ONLY write path, re-invoking
6
6
  // the same workflow with apply:true through the existing gated path. No model is ever called
7
7
  // directly; no guard is reimplemented; no secret reaches any response (live payloads are redacted).
8
- import { randomUUID } from "node:crypto";
9
8
  import { parseRunRequest } from "./run-request.js";
10
9
  import { startRun, applyRun } from "./run-engine.js";
11
10
  import { ActiveRunLimitError } from "./runs.js";
12
11
  import { SSE_HEADERS, writeEvent, readyMessage } from "./sse.js";
13
12
  import { errorBody, STREAMING } from "./routes.js";
14
13
  import { currentRedactionSecrets } from "./deps.js";
15
- import { createAuditRedactor } from "@oscharko-dev/keiko-evidence";
14
+ import { VOICE_TRANSCRIPT_SCHEMA_VERSION, } from "@oscharko-dev/keiko-contracts";
15
+ import { validateWorkflowHandoffRequest } from "@oscharko-dev/keiko-contracts/workflow-handoff";
16
16
  import { WorkspaceError } from "@oscharko-dev/keiko-workspace";
17
- import { UiStoreError } from "./store/index.js";
18
- import { memoryCaptureCustomerMatchers } from "./memory-capture-policy.js";
17
+ import { approvalTokenInputFor, createApprovalToken } from "./governed-workflow.js";
18
+ import { isVoiceDictationCapable, isVoiceRealtimeCapable } from "./read-handlers.js";
19
+ import { evaluateSpokenActionGovernance } from "./voice-action-governance.js";
20
+ import { resolveRegisteredOrManagedWorkspaceRoot } from "./task-workspace/authorization.js";
19
21
  const MAX_BODY_BYTES = 1_000_000;
20
22
  const VERIFY_NOOP_MODEL = {
21
23
  call: () => Promise.reject(new Error("verify runs must not call the model")),
@@ -64,7 +66,7 @@ function readBody(req) {
64
66
  // directories. Returns a RouteResult to return, or null when the check passes.
65
67
  function rejectUnregisteredWorkspace(parsed, deps) {
66
68
  const root = typeof parsed.input.workspaceRoot === "string" ? parsed.input.workspaceRoot : "";
67
- const registered = deps.store.listProjects().some((p) => p.path === root);
69
+ const registered = resolveRegisteredOrManagedWorkspaceRoot(deps, root) !== undefined;
68
70
  return registered
69
71
  ? null
70
72
  : {
@@ -75,111 +77,68 @@ function rejectUnregisteredWorkspace(parsed, deps) {
75
77
  function resolveRunModel(parsed, deps) {
76
78
  return parsed.kind === "verify" ? VERIFY_NOOP_MODEL : deps.modelPortFactory(parsed.modelId);
77
79
  }
78
- function isRecord(value) {
79
- return typeof value === "object" && value !== null && !Array.isArray(value);
80
- }
81
- function requireBodyString(body, name) {
82
- const value = body[name];
83
- if (typeof value !== "string" || value.length === 0) {
84
- return { status: 400, body: errorBody("BAD_REQUEST", `Field "${name}" is required.`) };
80
+ function validateTextGovernedHandoff(parsed) {
81
+ if (parsed.governedHandoff === undefined || parsed.governedHandoffVoiceOrigin !== undefined) {
82
+ return null;
85
83
  }
86
- return value;
87
- }
88
- function requireBodyNumber(body, name) {
89
- const value = body[name];
90
- if (typeof value !== "number" || !Number.isFinite(value)) {
84
+ const validation = validateWorkflowHandoffRequest(parsed.governedHandoff);
85
+ if (!validation.ok) {
86
+ return { status: 400, body: errorBody("BAD_REQUEST", validation.reasons.join("; ")) };
87
+ }
88
+ const expectedToken = createApprovalToken(approvalTokenInputFor(parsed.governedHandoff));
89
+ if (parsed.governedHandoff.userApprovalToken !== expectedToken) {
91
90
  return {
92
91
  status: 400,
93
- body: errorBody("BAD_REQUEST", `Field "${name}" must be a finite number.`),
92
+ body: errorBody("BAD_REQUEST", "governedHandoff approval token does not match the request."),
94
93
  };
95
94
  }
96
- return value;
95
+ return null;
97
96
  }
98
- function requireBodyRecord(body, name) {
99
- const value = body[name];
100
- if (!isRecord(value)) {
101
- return { status: 400, body: errorBody("BAD_REQUEST", `Field "${name}" must be an object.`) };
102
- }
103
- return value;
104
- }
105
- function parseJsonRecord(raw) {
106
- let parsed;
107
- try {
108
- parsed = JSON.parse(raw);
109
- }
110
- catch {
111
- return { status: 400, body: errorBody("BAD_REQUEST", "Request body is not valid JSON.") };
97
+ function serverTrustedVoiceProfile(deps) {
98
+ if (isVoiceRealtimeCapable(deps)) {
99
+ return "full-realtime";
112
100
  }
113
- if (!isRecord(parsed)) {
114
- return { status: 400, body: errorBody("BAD_REQUEST", "Request body must be a JSON object.") };
101
+ if (isVoiceDictationCapable(deps)) {
102
+ return "speech-to-text";
115
103
  }
116
- return parsed;
104
+ return "none";
117
105
  }
118
- function isRouteResult(value) {
119
- return isRecord(value) && typeof value.status === "number" && "body" in value;
120
- }
121
- function chatBelongsToProject(deps, projectPath, chatId) {
122
- return deps.store.listChats(projectPath).some((chat) => chat.id === chatId);
106
+ function committedProjectionFor(origin) {
107
+ return {
108
+ schemaVersion: VOICE_TRANSCRIPT_SCHEMA_VERSION,
109
+ segments: [],
110
+ text: origin.committedText,
111
+ segmentCount: origin.committedSegments,
112
+ };
123
113
  }
124
- function runSummaryDiscriminator(request) {
125
- if (request.kind === "unit-tests") {
126
- return { workflowId: "unit-test-generation", taskType: undefined };
127
- }
128
- if (request.kind === "bug-investigation") {
129
- return { workflowId: "bug-investigation", taskType: undefined };
130
- }
131
- if (request.kind === "explain-plan") {
132
- return { workflowId: undefined, taskType: "explain-plan" };
114
+ function applyVoiceGovernance(parsed, deps) {
115
+ if (parsed.governedHandoffVoiceOrigin === undefined) {
116
+ const invalidTextHandoff = validateTextGovernedHandoff(parsed);
117
+ return invalidTextHandoff ?? parsed;
118
+ }
119
+ if (parsed.governedHandoff === undefined) {
120
+ return { status: 400, body: errorBody("BAD_REQUEST", "voiceOrigin requires governedHandoff.") };
121
+ }
122
+ const trustedProfile = serverTrustedVoiceProfile(deps);
123
+ const decision = evaluateSpokenActionGovernance({
124
+ projection: committedProjectionFor(parsed.governedHandoffVoiceOrigin),
125
+ profile: trustedProfile,
126
+ turnIndex: parsed.governedHandoffVoiceOrigin.turnIndex,
127
+ source: parsed.governedHandoffVoiceOrigin.source,
128
+ request: parsed.governedHandoff,
129
+ providedConfirmationDigest: parsed.governedHandoffVoiceOrigin.confirmationDigest,
130
+ });
131
+ if (!decision.allowed) {
132
+ return { status: 403, body: errorBody("VOICE_ACTION_DENIED", decision.reason) };
133
133
  }
134
- return { workflowId: undefined, taskType: "verify" };
135
- }
136
- function buildChatRunMessages(body, request, chatId, runId) {
137
- const user = requireBodyRecord(body, "user");
138
- if (isRouteResult(user))
139
- return user;
140
- const summary = requireBodyRecord(body, "summary");
141
- if (isRouteResult(summary))
142
- return summary;
143
- const userContent = requireBodyString(user, "content");
144
- if (typeof userContent !== "string")
145
- return userContent;
146
- const userTimestamp = requireBodyNumber(user, "timestamp");
147
- if (typeof userTimestamp !== "number")
148
- return userTimestamp;
149
- const summaryContent = requireBodyString(summary, "content");
150
- if (typeof summaryContent !== "string")
151
- return summaryContent;
152
- const summaryTimestamp = requireBodyNumber(summary, "timestamp");
153
- if (typeof summaryTimestamp !== "number")
154
- return summaryTimestamp;
155
- const discriminator = runSummaryDiscriminator(request);
156
- return [
157
- {
158
- chatId,
159
- role: "user",
160
- content: userContent,
161
- timestamp: userTimestamp,
162
- runId: undefined,
163
- workflowId: undefined,
164
- workflowStatus: undefined,
165
- shortResult: undefined,
166
- taskType: undefined,
167
- },
168
- {
169
- chatId,
170
- role: "system",
171
- content: summaryContent,
172
- timestamp: summaryTimestamp,
173
- runId,
174
- workflowId: discriminator.workflowId,
175
- workflowStatus: "running",
176
- shortResult: undefined,
177
- taskType: discriminator.taskType,
178
- },
179
- ];
134
+ return {
135
+ ...parsed,
136
+ governedHandoffVoiceOrigin: undefined,
137
+ governedHandoffVoiceAction: decision.audit,
138
+ };
180
139
  }
181
- function storeErrorResult(error) {
182
- return { status: error.status, body: errorBody(error.code, error.message) };
140
+ function isRecord(value) {
141
+ return typeof value === "object" && value !== null && !Array.isArray(value);
183
142
  }
184
143
  // Static, path-safe message for workspace errors surfaced during run launch. The underlying
185
144
  // WorkspaceError messages may carry absolute paths — we never echo them (ADR-0005, CWE-209).
@@ -190,86 +149,6 @@ function workspaceRunErrorResult() {
190
149
  body: errorBody("WORKSPACE_UNAVAILABLE", WORKSPACE_RUN_ERROR_MESSAGE),
191
150
  };
192
151
  }
193
- function markSummaryFailed(deps, message, shortResult) {
194
- try {
195
- deps.store.updateMessage(message.id, { workflowStatus: "failed", shortResult });
196
- }
197
- catch {
198
- // Best-effort compensation only. The original start error remains the response source.
199
- }
200
- }
201
- function parseChatRunEnvelope(raw, deps) {
202
- const body = parseJsonRecord(raw);
203
- if (isRouteResult(body))
204
- return body;
205
- const chatId = requireBodyString(body, "chatId");
206
- if (isRouteResult(chatId))
207
- return chatId;
208
- const projectPath = requireBodyString(body, "projectPath");
209
- if (isRouteResult(projectPath))
210
- return projectPath;
211
- if (!chatBelongsToProject(deps, projectPath, chatId)) {
212
- return { status: 404, body: errorBody("NOT_FOUND", "Chat not found.") };
213
- }
214
- const runBody = requireBodyRecord(body, "run");
215
- if (isRouteResult(runBody))
216
- return runBody;
217
- return { body, chatId, projectPath, runBody };
218
- }
219
- function validateChatRunRequest(runBody, deps) {
220
- const parsed = parseRunRequest(JSON.stringify(runBody));
221
- if ("code" in parsed) {
222
- return { status: 400, body: errorBody(parsed.code, parsed.message) };
223
- }
224
- const unregistered = rejectUnregisteredWorkspace(parsed, deps);
225
- if (unregistered !== null)
226
- return unregistered;
227
- const model = resolveRunModel(parsed, deps);
228
- return model === undefined
229
- ? { status: 400, body: errorBody("NO_MODEL", "No model provider is configured.") }
230
- : { request: parsed, model };
231
- }
232
- function engineContextFor(deps, request, model) {
233
- return {
234
- request,
235
- model,
236
- registry: deps.registry,
237
- evidence: {
238
- store: deps.evidenceStore,
239
- env: deps.env,
240
- additionalSecrets: currentRedactionSecrets(deps),
241
- },
242
- memoryVault: deps.memoryVault,
243
- memoryAuditRedactString: createAuditRedactor({ additionalSecrets: currentRedactionSecrets(deps) }, deps.env),
244
- memoryCustomerIdentifierMatchers: memoryCaptureCustomerMatchers(deps),
245
- };
246
- }
247
- function persistChatRunMessages(deps, envelope, request, runId) {
248
- const messagesInput = buildChatRunMessages(envelope.body, request, envelope.chatId, runId);
249
- if (isRouteResult(messagesInput))
250
- return messagesInput;
251
- try {
252
- return deps.store.createMessages(messagesInput);
253
- }
254
- catch (error) {
255
- if (error instanceof UiStoreError)
256
- return storeErrorResult(error);
257
- throw error;
258
- }
259
- }
260
- function startPersistedChatRun(deps, request, model, runId, messages) {
261
- try {
262
- const run = startRun(engineContextFor(deps, request, model), deps.redactor, { runId });
263
- return { status: 202, body: { run, messages } };
264
- }
265
- catch (error) {
266
- const summary = messages[1];
267
- if (summary !== undefined) {
268
- markSummaryFailed(deps, summary, "Run could not be started.");
269
- }
270
- return mapRunStartError(error);
271
- }
272
- }
273
152
  function mapRunStartError(error) {
274
153
  if (error instanceof ActiveRunLimitError) {
275
154
  return { status: 429, body: errorBody("TOO_MANY_RUNS", "The active run limit is reached.") };
@@ -298,16 +177,20 @@ export async function handleCreateRun(ctx, deps) {
298
177
  if ("code" in parsed) {
299
178
  return { status: 400, body: errorBody(parsed.code, parsed.message) };
300
179
  }
301
- const unregistered = rejectUnregisteredWorkspace(parsed, deps);
180
+ const governed = applyVoiceGovernance(parsed, deps);
181
+ if ("status" in governed) {
182
+ return governed;
183
+ }
184
+ const unregistered = rejectUnregisteredWorkspace(governed, deps);
302
185
  if (unregistered !== null) {
303
186
  return unregistered;
304
187
  }
305
- const model = resolveRunModel(parsed, deps);
188
+ const model = resolveRunModel(governed, deps);
306
189
  if (model === undefined) {
307
190
  return { status: 400, body: errorBody("NO_MODEL", "No model provider is configured.") };
308
191
  }
309
192
  const engineCtx = {
310
- request: parsed,
193
+ request: governed,
311
194
  model,
312
195
  registry: deps.registry,
313
196
  evidence: {
@@ -324,36 +207,6 @@ export async function handleCreateRun(ctx, deps) {
324
207
  return mapRunStartError(error);
325
208
  }
326
209
  }
327
- // Route — POST /api/chats/runs. Composer-specific path that makes Issue #66's chat invariant
328
- // explicit: a successful workflow launch first reserves a runId and persists exactly one user
329
- // message plus one system run summary, then starts the run with that reserved runId. If persistence
330
- // fails, no run is started; if the start is refused, the summary is terminalized as failed.
331
- export async function handleCreateChatRun(ctx, deps) {
332
- let raw;
333
- try {
334
- raw = await readBody(ctx.req);
335
- }
336
- catch (error) {
337
- if (error instanceof BodyTooLargeError) {
338
- return {
339
- status: 413,
340
- body: errorBody("PAYLOAD_TOO_LARGE", "Request body exceeds the size limit."),
341
- };
342
- }
343
- throw error;
344
- }
345
- const envelope = parseChatRunEnvelope(raw, deps);
346
- if (isRouteResult(envelope))
347
- return envelope;
348
- const validated = validateChatRunRequest(envelope.runBody, deps);
349
- if (isRouteResult(validated))
350
- return validated;
351
- const runId = randomUUID();
352
- const messages = persistChatRunMessages(deps, envelope, validated.request, runId);
353
- if (isRouteResult(messages))
354
- return messages;
355
- return startPersistedChatRun(deps, validated.request, validated.model, runId, messages);
356
- }
357
210
  function lastEventId(req) {
358
211
  const header = req.headers["last-event-id"];
359
212
  const value = Array.isArray(header) ? header[0] : header;
@@ -1,5 +1,14 @@
1
+ import { type SpokenActionAuditRecord, type VoiceProfile, type VoiceTranscriptSource } from "@oscharko-dev/keiko-contracts";
1
2
  import type { WorkflowHandoffRequest } from "@oscharko-dev/keiko-contracts/workflow-handoff";
2
3
  export type RunKind = "unit-tests" | "bug-investigation" | "explain-plan" | "verify";
4
+ export interface RunVoiceOrigin {
5
+ readonly profile: VoiceProfile;
6
+ readonly turnIndex: number;
7
+ readonly source: VoiceTranscriptSource;
8
+ readonly committedSegments: number;
9
+ readonly committedText: string;
10
+ readonly confirmationDigest: string | undefined;
11
+ }
3
12
  export interface RunRequest {
4
13
  readonly kind: RunKind;
5
14
  readonly modelId: string;
@@ -8,6 +17,8 @@ export interface RunRequest {
8
17
  readonly limits: Record<string, unknown> | undefined;
9
18
  readonly governedHandoff?: WorkflowHandoffRequest | undefined;
10
19
  readonly governedHandoffSourceGroundedRunId?: string | undefined;
20
+ readonly governedHandoffVoiceOrigin?: RunVoiceOrigin | undefined;
21
+ readonly governedHandoffVoiceAction?: SpokenActionAuditRecord | undefined;
11
22
  }
12
23
  export interface RunRequestError {
13
24
  readonly code: "BAD_REQUEST";
@@ -1 +1 @@
1
- {"version":3,"file":"run-request.d.ts","sourceRoot":"","sources":["../src/run-request.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,gDAAgD,CAAC;AAE7F,MAAM,MAAM,OAAO,GAAG,YAAY,GAAG,mBAAmB,GAAG,cAAc,GAAG,QAAQ,CAAC;AAErF,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IAExB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAExC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;IAErD,QAAQ,CAAC,eAAe,CAAC,EAAE,sBAAsB,GAAG,SAAS,CAAC;IAC9D,QAAQ,CAAC,kCAAkC,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAClE;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AA4ND,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,GAAG,eAAe,CAsCzE"}
1
+ {"version":3,"file":"run-request.d.ts","sourceRoot":"","sources":["../src/run-request.ts"],"names":[],"mappings":"AASA,OAAO,EACL,KAAK,uBAAuB,EAC5B,KAAK,YAAY,EACjB,KAAK,qBAAqB,EAC3B,MAAM,+BAA+B,CAAC;AACvC,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,gDAAgD,CAAC;AAE7F,MAAM,MAAM,OAAO,GAAG,YAAY,GAAG,mBAAmB,GAAG,cAAc,GAAG,QAAQ,CAAC;AAErF,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAC;IAC/B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,qBAAqB,CAAC;IACvC,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,kBAAkB,EAAE,MAAM,GAAG,SAAS,CAAC;CACjD;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IAExB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAExC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;IAErD,QAAQ,CAAC,eAAe,CAAC,EAAE,sBAAsB,GAAG,SAAS,CAAC;IAC9D,QAAQ,CAAC,kCAAkC,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAGjE,QAAQ,CAAC,0BAA0B,CAAC,EAAE,cAAc,GAAG,SAAS,CAAC;IAGjE,QAAQ,CAAC,0BAA0B,CAAC,EAAE,uBAAuB,GAAG,SAAS,CAAC;CAC3E;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAubD,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,GAAG,eAAe,CAczE"}
@@ -6,9 +6,107 @@
6
6
  // the gated apply route (D8). The deeper guards (`isSensitivePath`, patch limits, target validation)
7
7
  // are enforced by the workflow/harness entry points the engine calls; the BFF never reimplements
8
8
  // them.
9
+ import {} from "@oscharko-dev/keiko-contracts";
9
10
  function isRecord(value) {
10
11
  return typeof value === "object" && value !== null && !Array.isArray(value);
11
12
  }
13
+ const VOICE_PROFILES = [
14
+ "none",
15
+ "speech-to-text",
16
+ "speech-output",
17
+ "full-realtime",
18
+ ];
19
+ const VOICE_TRANSCRIPT_SOURCES = ["dictation", "realtime"];
20
+ const MAX_VOICE_COMMITTED_TEXT_CHARS = 8192;
21
+ const MAX_VOICE_TURN_INDEX = 1_000_000;
22
+ const MAX_VOICE_COMMITTED_SEGMENTS = 10_000;
23
+ const CONFIRMATION_DIGEST_PATTERN = /^[0-9a-f]{64}$/;
24
+ function badRequest(message) {
25
+ return { code: "BAD_REQUEST", message };
26
+ }
27
+ function isRunRequestError(value) {
28
+ return isRecord(value) && value.code === "BAD_REQUEST" && typeof value.message === "string";
29
+ }
30
+ function isVoiceProfile(value) {
31
+ return typeof value === "string" && VOICE_PROFILES.includes(value);
32
+ }
33
+ function isRunVoiceTranscriptSource(value) {
34
+ return (typeof value === "string" && VOICE_TRANSCRIPT_SOURCES.includes(value));
35
+ }
36
+ function isBoundedNonNegativeInteger(value, max) {
37
+ return typeof value === "number" && Number.isInteger(value) && value >= 0 && value <= max;
38
+ }
39
+ function parseOptionalString(body, name) {
40
+ const value = body[name];
41
+ if (value === undefined) {
42
+ return undefined;
43
+ }
44
+ return typeof value === "string"
45
+ ? value
46
+ : badRequest(`${name} must be a string when provided.`);
47
+ }
48
+ function parseConfirmationDigest(value) {
49
+ if (value === undefined) {
50
+ return undefined;
51
+ }
52
+ if (typeof value !== "string" || !CONFIRMATION_DIGEST_PATTERN.test(value)) {
53
+ return badRequest("voiceOrigin.confirmationDigest must be a 64-char lowercase sha256 hex.");
54
+ }
55
+ return value;
56
+ }
57
+ function parseVoiceOriginFields(value) {
58
+ if (!isVoiceProfile(value.profile)) {
59
+ return badRequest("voiceOrigin.profile is invalid.");
60
+ }
61
+ if (!isBoundedNonNegativeInteger(value.turnIndex, MAX_VOICE_TURN_INDEX)) {
62
+ return badRequest("voiceOrigin.turnIndex must be a bounded non-negative integer.");
63
+ }
64
+ if (!isRunVoiceTranscriptSource(value.source)) {
65
+ return badRequest("voiceOrigin.source is invalid.");
66
+ }
67
+ if (!isBoundedNonNegativeInteger(value.committedSegments, MAX_VOICE_COMMITTED_SEGMENTS)) {
68
+ return badRequest("voiceOrigin.committedSegments must be a bounded non-negative integer.");
69
+ }
70
+ if (typeof value.committedText !== "string" ||
71
+ value.committedText.length > MAX_VOICE_COMMITTED_TEXT_CHARS) {
72
+ return badRequest("voiceOrigin.committedText must be a string within the size limit.");
73
+ }
74
+ const confirmationDigest = parseConfirmationDigest(value.confirmationDigest);
75
+ if (typeof confirmationDigest === "object") {
76
+ return confirmationDigest;
77
+ }
78
+ return {
79
+ profile: value.profile,
80
+ turnIndex: value.turnIndex,
81
+ source: value.source,
82
+ committedSegments: value.committedSegments,
83
+ committedText: value.committedText,
84
+ confirmationDigest,
85
+ };
86
+ }
87
+ function parseGovernedHandoff(body) {
88
+ const value = body.governedHandoff;
89
+ if (value === undefined) {
90
+ return undefined;
91
+ }
92
+ if (!isRecord(value)) {
93
+ return badRequest("governedHandoff must be an object when provided.");
94
+ }
95
+ return value;
96
+ }
97
+ function parseVoiceOrigin(body, governedHandoff) {
98
+ const value = body.voiceOrigin;
99
+ if (value === undefined) {
100
+ return undefined;
101
+ }
102
+ if (governedHandoff === undefined) {
103
+ return badRequest("voiceOrigin requires governedHandoff.");
104
+ }
105
+ if (!isRecord(value)) {
106
+ return badRequest("voiceOrigin must be an object when provided.");
107
+ }
108
+ return parseVoiceOriginFields(value);
109
+ }
12
110
  function resolveKind(body) {
13
111
  const workflowId = body.workflowId;
14
112
  const taskType = body.taskType;
@@ -176,18 +274,16 @@ function validateRunInput(kind, input) {
176
274
  }
177
275
  return validateBugInvestigationInput(input);
178
276
  }
179
- // Parses the raw JSON text into a validated RunRequest, or a typed BAD_REQUEST error.
180
- export function parseRunRequest(raw) {
181
- let parsed;
277
+ function parseJsonObject(raw) {
182
278
  try {
183
- parsed = JSON.parse(raw);
279
+ const parsed = JSON.parse(raw);
280
+ return isRecord(parsed) ? parsed : badRequest("Request body must be a JSON object.");
184
281
  }
185
282
  catch {
186
- return { code: "BAD_REQUEST", message: "Request body is not valid JSON." };
187
- }
188
- if (!isRecord(parsed)) {
189
- return { code: "BAD_REQUEST", message: "Request body must be a JSON object." };
283
+ return badRequest("Request body is not valid JSON.");
190
284
  }
285
+ }
286
+ function parseRunRequestCore(parsed) {
191
287
  const kind = resolveKind(parsed);
192
288
  if (typeof kind !== "string") {
193
289
  return kind;
@@ -208,12 +304,64 @@ export function parseRunRequest(raw) {
208
304
  return {
209
305
  kind,
210
306
  modelId,
307
+ input,
308
+ limits: isRecord(limits) ? limits : undefined,
309
+ };
310
+ }
311
+ function parseGovernedHandoffMetadata(parsed) {
312
+ if (parsed.governedHandoffVoiceAction !== undefined) {
313
+ return badRequest("governedHandoffVoiceAction is server-generated and must not be provided.");
314
+ }
315
+ const governedHandoff = parseGovernedHandoff(parsed);
316
+ if (isRunRequestError(governedHandoff)) {
317
+ return governedHandoff;
318
+ }
319
+ const governedHandoffSourceGroundedRunId = parseOptionalString(parsed, "governedHandoffSourceGroundedRunId");
320
+ if (isRunRequestError(governedHandoffSourceGroundedRunId)) {
321
+ return governedHandoffSourceGroundedRunId;
322
+ }
323
+ if (governedHandoffSourceGroundedRunId !== undefined && governedHandoff === undefined) {
324
+ return badRequest("governedHandoffSourceGroundedRunId requires governedHandoff.");
325
+ }
326
+ const governedHandoffVoiceOrigin = parseVoiceOrigin(parsed, governedHandoff);
327
+ if (isRunRequestError(governedHandoffVoiceOrigin)) {
328
+ return governedHandoffVoiceOrigin;
329
+ }
330
+ return { governedHandoff, governedHandoffSourceGroundedRunId, governedHandoffVoiceOrigin };
331
+ }
332
+ function buildRunRequest(core, metadata) {
333
+ return {
334
+ kind: core.kind,
335
+ modelId: core.modelId,
211
336
  // Dry-run-first (ADR-0011 D8 / security M1): the create route NEVER applies, even if the client
212
337
  // body carries `apply:true`. Applying is the sole responsibility of POST /api/runs/:runId/apply
213
338
  // (route 9), which re-invokes the workflow through the gated path. A one-shot create-with-apply
214
339
  // would bypass the explicit review→apply step, so the body flag is deliberately ignored here.
215
340
  apply: false,
216
- input,
217
- ...(isRecord(limits) ? { limits } : { limits: undefined }),
341
+ input: core.input,
342
+ limits: core.limits,
343
+ ...(metadata.governedHandoff === undefined ? {} : { governedHandoff: metadata.governedHandoff }),
344
+ ...(metadata.governedHandoffSourceGroundedRunId === undefined
345
+ ? {}
346
+ : { governedHandoffSourceGroundedRunId: metadata.governedHandoffSourceGroundedRunId }),
347
+ ...(metadata.governedHandoffVoiceOrigin === undefined
348
+ ? {}
349
+ : { governedHandoffVoiceOrigin: metadata.governedHandoffVoiceOrigin }),
218
350
  };
219
351
  }
352
+ // Parses the raw JSON text into a validated RunRequest, or a typed BAD_REQUEST error.
353
+ export function parseRunRequest(raw) {
354
+ const parsed = parseJsonObject(raw);
355
+ if (isRunRequestError(parsed)) {
356
+ return parsed;
357
+ }
358
+ const core = parseRunRequestCore(parsed);
359
+ if (isRunRequestError(core)) {
360
+ return core;
361
+ }
362
+ const metadata = parseGovernedHandoffMetadata(parsed);
363
+ if (isRunRequestError(metadata)) {
364
+ return metadata;
365
+ }
366
+ return buildRunRequest(core, metadata);
367
+ }
@@ -0,0 +1,38 @@
1
+ import { type RuntimeCapabilitiesResponse, type RuntimeCapabilityKind, type RuntimeCapabilityState, type RuntimeCapabilityUnavailableReason } from "@oscharko-dev/keiko-contracts";
2
+ import { type WorkspaceInfo } from "@oscharko-dev/keiko-workspace";
3
+ import type { WorkspaceFs } from "@oscharko-dev/keiko-workspace";
4
+ export declare const DEFAULT_RUNTIME_CAPABILITY_DEADLINE_MS: 250;
5
+ export interface HostExecutableSpec {
6
+ readonly id: string;
7
+ readonly kind: Exclude<RuntimeCapabilityKind, "command-source">;
8
+ readonly executable: string;
9
+ readonly label: string;
10
+ readonly remediationHint: string;
11
+ }
12
+ export interface HostExecutableProbeResult {
13
+ readonly state: RuntimeCapabilityState;
14
+ readonly unavailableReason?: RuntimeCapabilityUnavailableReason | undefined;
15
+ readonly version?: string | undefined;
16
+ }
17
+ export interface HostExecutableProbe {
18
+ probe(executable: string, workspaceRoot: string | undefined): HostExecutableProbeResult;
19
+ }
20
+ export interface RuntimeCapabilityDetectorOptions {
21
+ readonly workspace?: WorkspaceInfo | undefined;
22
+ readonly fs?: WorkspaceFs | undefined;
23
+ readonly env?: NodeJS.ProcessEnv | undefined;
24
+ readonly platform?: NodeJS.Platform | undefined;
25
+ readonly now?: (() => number) | undefined;
26
+ readonly deadlineMs?: number | undefined;
27
+ readonly probe?: HostExecutableProbe | undefined;
28
+ readonly specs?: readonly HostExecutableSpec[] | undefined;
29
+ }
30
+ export declare const RUNTIME_HOST_EXECUTABLE_SPECS: readonly HostExecutableSpec[];
31
+ export declare class PathHostExecutableProbe implements HostExecutableProbe {
32
+ private readonly env;
33
+ private readonly platform;
34
+ constructor(env?: NodeJS.ProcessEnv, platform?: NodeJS.Platform);
35
+ probe(executable: string, workspaceRoot: string | undefined): HostExecutableProbeResult;
36
+ }
37
+ export declare function detectRuntimeCapabilities(options?: RuntimeCapabilityDetectorOptions): RuntimeCapabilitiesResponse;
38
+ //# sourceMappingURL=capabilityDetector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capabilityDetector.d.ts","sourceRoot":"","sources":["../../src/runtime/capabilityDetector.ts"],"names":[],"mappings":"AAOA,OAAO,EAEL,KAAK,2BAA2B,EAEhC,KAAK,qBAAqB,EAC1B,KAAK,sBAAsB,EAC3B,KAAK,kCAAkC,EAGxC,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAAqB,KAAK,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAGtF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAEjE,eAAO,MAAM,sCAAsC,EAAG,GAAY,CAAC;AAKnE,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC,qBAAqB,EAAE,gBAAgB,CAAC,CAAC;IAChE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;CAClC;AAED,MAAM,WAAW,yBAAyB;IACxC,QAAQ,CAAC,KAAK,EAAE,sBAAsB,CAAC;IACvC,QAAQ,CAAC,iBAAiB,CAAC,EAAE,kCAAkC,GAAG,SAAS,CAAC;IAC5E,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACvC;AAED,MAAM,WAAW,mBAAmB;IAClC,KAAK,CAAC,UAAU,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,SAAS,GAAG,yBAAyB,CAAC;CACzF;AAED,MAAM,WAAW,gCAAgC;IAC/C,QAAQ,CAAC,SAAS,CAAC,EAAE,aAAa,GAAG,SAAS,CAAC;IAC/C,QAAQ,CAAC,EAAE,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;IACtC,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,GAAG,SAAS,CAAC;IAC7C,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,GAAG,SAAS,CAAC;IAChD,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,MAAM,MAAM,CAAC,GAAG,SAAS,CAAC;IAC1C,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzC,QAAQ,CAAC,KAAK,CAAC,EAAE,mBAAmB,GAAG,SAAS,CAAC;IACjD,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,kBAAkB,EAAE,GAAG,SAAS,CAAC;CAC5D;AAED,eAAO,MAAM,6BAA6B,EAAE,SAAS,kBAAkB,EA4FrE,CAAC;AA6GH,qBAAa,uBAAwB,YAAW,mBAAmB;IACjE,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAoB;IACxC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAkB;gBAGzC,GAAG,GAAE,MAAM,CAAC,UAAwB,EACpC,QAAQ,GAAE,MAAM,CAAC,QAA2B;IAMvC,KAAK,CAAC,UAAU,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,SAAS,GAAG,yBAAyB;CAqB/F;AAwRD,wBAAgB,yBAAyB,CACvC,OAAO,GAAE,gCAAqC,GAC7C,2BAA2B,CAsB7B"}